@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/index.cjs
CHANGED
|
@@ -10229,6 +10229,8 @@ var MultiProviderFeed = class {
|
|
|
10229
10229
|
this.liveBars = [];
|
|
10230
10230
|
/** Sync-accessible symbol metadata, warmed by load()/symbolInfoFor (the engine reads it synchronously). */
|
|
10231
10231
|
this.symInfoCache = /* @__PURE__ */ new Map();
|
|
10232
|
+
/** Requests in flight, keyed like the cache — every concurrent asker shares one round trip. */
|
|
10233
|
+
this.symInfoInflight = /* @__PURE__ */ new Map();
|
|
10232
10234
|
this.cache = new CachingDataFeed(new RegistryFetchFeed(this.registry), store);
|
|
10233
10235
|
}
|
|
10234
10236
|
// ── Registry surface (driven by chart.data / DataControl) ──────────────
|
|
@@ -10289,9 +10291,36 @@ var MultiProviderFeed = class {
|
|
|
10289
10291
|
async symbolInfoFor(raw) {
|
|
10290
10292
|
const resolved = this.registry.resolve(raw, { default: this.primaryProvider });
|
|
10291
10293
|
if (!resolved) return void 0;
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10294
|
+
return this.fetchSymbolInfo(resolved);
|
|
10295
|
+
}
|
|
10296
|
+
/**
|
|
10297
|
+
* One round trip per symbol, shared. A market switch probes this metadata from five
|
|
10298
|
+
* places at once (the bar load's prefetch, the renderer's tick size, the market-status
|
|
10299
|
+
* badge, the session shading, the session toggle) — without the in-flight map each
|
|
10300
|
+
* would open its own request for the same answer, and the last one to land decided how
|
|
10301
|
+
* late the chrome settled.
|
|
10302
|
+
*
|
|
10303
|
+
* Cached for {@link SYMBOL_INFO_TTL_MS}: this metadata is stable within a session
|
|
10304
|
+
* (tick size, session vocabulary, listing prefix), but not immutable — a continuous
|
|
10305
|
+
* futures row rolls its current contract — so the entry expires rather than pinning the
|
|
10306
|
+
* session's first answer forever. A failed probe is NOT remembered: the next asker retries.
|
|
10307
|
+
*/
|
|
10308
|
+
fetchSymbolInfo(resolved) {
|
|
10309
|
+
const key = symKey(resolved);
|
|
10310
|
+
const hit = this.symInfoCache.get(key);
|
|
10311
|
+
if (hit && Date.now() - hit.at < SYMBOL_INFO_TTL_MS) return Promise.resolve(hit.info);
|
|
10312
|
+
const inflight = this.symInfoInflight.get(key);
|
|
10313
|
+
if (inflight) return inflight;
|
|
10314
|
+
const getSymbolInfo = this.registry.get(resolved.provider)?.getSymbolInfo;
|
|
10315
|
+
if (!getSymbolInfo) return Promise.resolve(void 0);
|
|
10316
|
+
const p = Promise.resolve(getSymbolInfo.call(this.registry.get(resolved.provider), resolved.ticker)).then((info) => {
|
|
10317
|
+
if (info) this.symInfoCache.set(key, { info, at: Date.now() });
|
|
10318
|
+
return info;
|
|
10319
|
+
}).finally(() => {
|
|
10320
|
+
this.symInfoInflight.delete(key);
|
|
10321
|
+
});
|
|
10322
|
+
this.symInfoInflight.set(key, p);
|
|
10323
|
+
return p;
|
|
10295
10324
|
}
|
|
10296
10325
|
/**
|
|
10297
10326
|
* Per-symbol capabilities, resolved through the owning provider's `capabilitiesFor`
|
|
@@ -10335,16 +10364,10 @@ var MultiProviderFeed = class {
|
|
|
10335
10364
|
symbolInfo(cfg) {
|
|
10336
10365
|
if (cfg.data && cfg.data.length > 0) return void 0;
|
|
10337
10366
|
const resolved = this.registry.resolve(rawSymbol(cfg), { default: this.primaryProvider });
|
|
10338
|
-
return resolved ? this.symInfoCache.get(symKey(resolved)) : void 0;
|
|
10367
|
+
return resolved ? this.symInfoCache.get(symKey(resolved))?.info : void 0;
|
|
10339
10368
|
}
|
|
10340
10369
|
prefetchSymbolInfo(resolved) {
|
|
10341
|
-
|
|
10342
|
-
if (this.symInfoCache.has(key)) return;
|
|
10343
|
-
const provider = this.registry.get(resolved.provider);
|
|
10344
|
-
if (!provider?.getSymbolInfo) return;
|
|
10345
|
-
void provider.getSymbolInfo(resolved.ticker).then((info) => {
|
|
10346
|
-
if (info) this.symInfoCache.set(key, info);
|
|
10347
|
-
}).catch(() => {
|
|
10370
|
+
void this.fetchSymbolInfo(resolved).catch(() => {
|
|
10348
10371
|
});
|
|
10349
10372
|
}
|
|
10350
10373
|
async loadRange(cfg, range) {
|
|
@@ -10394,8 +10417,9 @@ function canonical(cfg, resolved) {
|
|
|
10394
10417
|
return { ...cfg, symbol: `${resolved.provider}:${resolved.ticker}` };
|
|
10395
10418
|
}
|
|
10396
10419
|
function symKey(resolved) {
|
|
10397
|
-
return `${resolved.provider}|${resolved.ticker}`;
|
|
10420
|
+
return `${resolved.provider}|${resolved.ticker.trim().toUpperCase()}`;
|
|
10398
10421
|
}
|
|
10422
|
+
var SYMBOL_INFO_TTL_MS = 10 * 6e4;
|
|
10399
10423
|
async function safeBars(provider, ticker, tf, range) {
|
|
10400
10424
|
try {
|
|
10401
10425
|
return await provider.getBars(ticker, tf, range);
|
|
@@ -22181,7 +22205,7 @@ var SettingsDialog = class {
|
|
|
22181
22205
|
if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
|
|
22182
22206
|
else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
22183
22207
|
else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
22184
|
-
else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options
|
|
22208
|
+
else body.append(this.selectRowLabeled(hr.label, hr.get(), normalizeSelectOptions(hr.options), (v) => hr.set(v)));
|
|
22185
22209
|
}
|
|
22186
22210
|
}
|
|
22187
22211
|
};
|
|
@@ -33450,7 +33474,8 @@ var ClassicIndicator = class {
|
|
|
33450
33474
|
lineStyle: plot.lineStyle ?? "solid",
|
|
33451
33475
|
...plot.base != null ? { base: plot.base } : {}
|
|
33452
33476
|
},
|
|
33453
|
-
...plot.overlay ? { overlay: true } : {}
|
|
33477
|
+
...plot.overlay ? { overlay: true } : {},
|
|
33478
|
+
...plot.display ? { display: plot.display } : {}
|
|
33454
33479
|
});
|
|
33455
33480
|
});
|
|
33456
33481
|
if (out.markers && out.markers.length > 0) {
|
|
@@ -33472,7 +33497,9 @@ var ClassicIndicator = class {
|
|
|
33472
33497
|
paneId: "",
|
|
33473
33498
|
fromSeriesId: from,
|
|
33474
33499
|
toSeriesId: to,
|
|
33475
|
-
color: band.color
|
|
33500
|
+
color: band.color,
|
|
33501
|
+
...band.colors ? { colors: [...band.colors] } : {},
|
|
33502
|
+
...band.gradient ? { gradient: [...band.gradient] } : {}
|
|
33476
33503
|
}
|
|
33477
33504
|
];
|
|
33478
33505
|
});
|
|
@@ -33630,6 +33657,16 @@ function stdev(v, len) {
|
|
|
33630
33657
|
return Math.sqrt(sq / len);
|
|
33631
33658
|
});
|
|
33632
33659
|
}
|
|
33660
|
+
function meanDev(v, len) {
|
|
33661
|
+
return windowed(v, len, (w, from, to) => {
|
|
33662
|
+
let s = 0;
|
|
33663
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
33664
|
+
const mean = s / len;
|
|
33665
|
+
let dev = 0;
|
|
33666
|
+
for (let k = from; k <= to; k++) dev += Math.abs(w[k] - mean);
|
|
33667
|
+
return dev / len;
|
|
33668
|
+
});
|
|
33669
|
+
}
|
|
33633
33670
|
function highest(v, len) {
|
|
33634
33671
|
return windowed(v, len, (w, from, to) => {
|
|
33635
33672
|
let m = -Infinity;
|
|
@@ -33761,7 +33798,7 @@ function stoch(v, hi, lo, len) {
|
|
|
33761
33798
|
}
|
|
33762
33799
|
return out;
|
|
33763
33800
|
}
|
|
33764
|
-
function linreg(v, len) {
|
|
33801
|
+
function linreg(v, len, offset = 0) {
|
|
33765
33802
|
const sx = (len - 1) * len / 2;
|
|
33766
33803
|
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
33767
33804
|
const denom = len * sxx - sx * sx;
|
|
@@ -33775,9 +33812,101 @@ function linreg(v, len) {
|
|
|
33775
33812
|
}
|
|
33776
33813
|
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
33777
33814
|
const intercept = (sy - slope * sx) / len;
|
|
33778
|
-
return intercept + slope * (len - 1);
|
|
33815
|
+
return intercept + slope * (len - 1 - offset);
|
|
33816
|
+
});
|
|
33817
|
+
}
|
|
33818
|
+
function hma(v, len) {
|
|
33819
|
+
const n = Math.max(2, Math.trunc(len));
|
|
33820
|
+
const raw = sub(map(wma(v, Math.max(1, Math.trunc(n / 2))), (x) => 2 * x), wma(v, n));
|
|
33821
|
+
return wma(raw, Math.max(1, Math.round(Math.sqrt(n))));
|
|
33822
|
+
}
|
|
33823
|
+
function alma(v, len, offset, sigma) {
|
|
33824
|
+
const m = offset * (len - 1);
|
|
33825
|
+
const s = len / Math.max(sigma, 1e-9);
|
|
33826
|
+
const weights = new Array(len);
|
|
33827
|
+
let norm = 0;
|
|
33828
|
+
for (let i = 0; i < len; i++) {
|
|
33829
|
+
const d = i - m;
|
|
33830
|
+
const w = Math.exp(-(d * d) / (2 * s * s));
|
|
33831
|
+
weights[i] = w;
|
|
33832
|
+
norm += w;
|
|
33833
|
+
}
|
|
33834
|
+
return windowed(v, len, (win, from, to) => {
|
|
33835
|
+
let acc = 0;
|
|
33836
|
+
for (let k = from; k <= to; k++) acc += win[k] * weights[k - from];
|
|
33837
|
+
return norm === 0 ? Number.NaN : acc / norm;
|
|
33838
|
+
});
|
|
33839
|
+
}
|
|
33840
|
+
function dema(v, len) {
|
|
33841
|
+
const e1 = ema(v, len);
|
|
33842
|
+
return sub(map(e1, (x) => 2 * x), ema(e1, len));
|
|
33843
|
+
}
|
|
33844
|
+
function tema(v, len) {
|
|
33845
|
+
const e1 = ema(v, len);
|
|
33846
|
+
const e2 = ema(e1, len);
|
|
33847
|
+
const e3 = ema(e2, len);
|
|
33848
|
+
return zip(zip(map(e1, (x) => 3 * x), map(e2, (x) => 3 * x), (a, b) => a - b), e3, (a, b) => a + b);
|
|
33849
|
+
}
|
|
33850
|
+
function kama(v, len, fastAlpha = 2 / 3, slowAlpha = 2 / 31) {
|
|
33851
|
+
const direction = map(change(v, len), Math.abs);
|
|
33852
|
+
const volatility = sum(map(change(v), Math.abs), len);
|
|
33853
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
33854
|
+
let prev2 = Number.NaN;
|
|
33855
|
+
for (let i = 0; i < v.length; i++) {
|
|
33856
|
+
const x = v[i];
|
|
33857
|
+
if (!Number.isFinite(x)) continue;
|
|
33858
|
+
const d = direction[i];
|
|
33859
|
+
const w = volatility[i];
|
|
33860
|
+
if (!Number.isFinite(d) || !Number.isFinite(w)) {
|
|
33861
|
+
prev2 = Number.isFinite(prev2) ? prev2 : x;
|
|
33862
|
+
out[i] = prev2;
|
|
33863
|
+
continue;
|
|
33864
|
+
}
|
|
33865
|
+
const er = w === 0 ? 0 : d / w;
|
|
33866
|
+
const sc = (er * (fastAlpha - slowAlpha) + slowAlpha) ** 2;
|
|
33867
|
+
prev2 = Number.isFinite(prev2) ? prev2 + sc * (x - prev2) : x;
|
|
33868
|
+
out[i] = prev2;
|
|
33869
|
+
}
|
|
33870
|
+
return out;
|
|
33871
|
+
}
|
|
33872
|
+
function mcginley(v, len) {
|
|
33873
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
33874
|
+
let prev2 = Number.NaN;
|
|
33875
|
+
for (let i = 0; i < v.length; i++) {
|
|
33876
|
+
const x = v[i];
|
|
33877
|
+
if (!Number.isFinite(x)) continue;
|
|
33878
|
+
if (!Number.isFinite(prev2)) {
|
|
33879
|
+
prev2 = x;
|
|
33880
|
+
} else {
|
|
33881
|
+
const ratio = prev2 === 0 ? 0 : x / prev2;
|
|
33882
|
+
const denom = len * ratio ** 4;
|
|
33883
|
+
prev2 += denom === 0 ? 0 : (x - prev2) / denom;
|
|
33884
|
+
}
|
|
33885
|
+
out[i] = prev2;
|
|
33886
|
+
}
|
|
33887
|
+
return out;
|
|
33888
|
+
}
|
|
33889
|
+
function hamming(v, len, a0 = 0.54, a1 = 0.46) {
|
|
33890
|
+
if (len <= 1) return v.map((x) => Number.isFinite(x) ? x : Number.NaN);
|
|
33891
|
+
const weights = new Array(len);
|
|
33892
|
+
let norm = 0;
|
|
33893
|
+
for (let i = 0; i < len; i++) {
|
|
33894
|
+
const w = a0 - a1 * Math.cos(2 * Math.PI * i / (len - 1));
|
|
33895
|
+
weights[i] = w;
|
|
33896
|
+
norm += w;
|
|
33897
|
+
}
|
|
33898
|
+
return windowed(v, len, (win, from, to) => {
|
|
33899
|
+
let acc = 0;
|
|
33900
|
+
for (let k = from; k <= to; k++) acc += win[k] * weights[to - k];
|
|
33901
|
+
return norm === 0 ? Number.NaN : acc / norm;
|
|
33779
33902
|
});
|
|
33780
33903
|
}
|
|
33904
|
+
function cmo(v, len) {
|
|
33905
|
+
const d = change(v);
|
|
33906
|
+
const up = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
33907
|
+
const dn = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
33908
|
+
return zip(up, dn, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
33909
|
+
}
|
|
33781
33910
|
function swma(v) {
|
|
33782
33911
|
const out = new Array(v.length).fill(Number.NaN);
|
|
33783
33912
|
for (let i = 3; i < v.length; i++) {
|
|
@@ -33827,38 +33956,253 @@ function cumSum(v) {
|
|
|
33827
33956
|
}
|
|
33828
33957
|
|
|
33829
33958
|
// src/core/native-indicators/classics/shared.ts
|
|
33830
|
-
|
|
33831
|
-
|
|
33959
|
+
var SETTINGS = "Settings";
|
|
33960
|
+
var STYLE = "Style";
|
|
33961
|
+
function lengthInput(defval, key = "length", title = "Length", max = 5e3, tooltip) {
|
|
33962
|
+
return { key, title, type: "int", defval, min: 1, max, step: 1, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
33963
|
+
}
|
|
33964
|
+
function intInput(key, title, defval, min = 1, max = 5e3, tooltip) {
|
|
33965
|
+
return { key, title, type: "int", defval, min, max, step: 1, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
33966
|
+
}
|
|
33967
|
+
function floatInput(key, title, defval, min = 0, max = 1e3, step = 0.1, tooltip) {
|
|
33968
|
+
return { key, title, type: "float", defval, min, max, step, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
33832
33969
|
}
|
|
33833
|
-
function
|
|
33834
|
-
return { key
|
|
33970
|
+
function boolInput(key, title, defval, group = SETTINGS, tooltip) {
|
|
33971
|
+
return { key, title, type: "bool", defval, group, ...tooltip != null ? { tooltip } : {} };
|
|
33835
33972
|
}
|
|
33836
|
-
function
|
|
33837
|
-
return { key, title, type: "
|
|
33973
|
+
function optionInput(key, title, defval, options, tooltip) {
|
|
33974
|
+
return { key, title, type: "string", defval, options, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
33975
|
+
}
|
|
33976
|
+
function sourceInput(defval = "Close", key = "source", title = "Source", tooltip) {
|
|
33977
|
+
return { key, title, type: "string", defval, options: SOURCES2, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
33978
|
+
}
|
|
33979
|
+
function colorInput(defval = INFO, title = "Color", key = "color", tooltip) {
|
|
33980
|
+
return { key, title, type: "color", defval, group: STYLE, ...tooltip != null ? { tooltip } : {} };
|
|
33981
|
+
}
|
|
33982
|
+
function widthInput(defval = 2, key = "lineWidth", title = "Line Width") {
|
|
33983
|
+
return { key, title, type: "int", defval, min: 1, max: 5, step: 1, group: STYLE, tooltip: "Width of the plotted line, in pixels." };
|
|
33984
|
+
}
|
|
33985
|
+
function transp(hex, transparency) {
|
|
33986
|
+
return withAlpha2(hex, (100 - transparency) / 100);
|
|
33838
33987
|
}
|
|
33839
33988
|
function withAlpha2(hex, alpha = 0.08) {
|
|
33840
33989
|
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
33841
33990
|
if (!m) return hex;
|
|
33842
|
-
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
33991
|
+
const a = Math.round(Math.max(0, Math.min(1, alpha)) * 255).toString(16).padStart(2, "0");
|
|
33843
33992
|
return `#${m[1]}${a}`;
|
|
33844
33993
|
}
|
|
33994
|
+
function channelStyleInputs(basisTitle = "Basis") {
|
|
33995
|
+
return [
|
|
33996
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the lower band and of the basis while price holds above it."),
|
|
33997
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the upper band and of the basis while price sits below it."),
|
|
33998
|
+
colorInput(NEUTRAL, basisTitle, "basisColor", "Color of the basis line while price sits exactly on it.")
|
|
33999
|
+
];
|
|
34000
|
+
}
|
|
34001
|
+
function directionalChannel(bars, basis, upper, lower, ink, key) {
|
|
34002
|
+
const basisColors = basis.map((b, i) => {
|
|
34003
|
+
if (!Number.isFinite(b)) return null;
|
|
34004
|
+
const c = bars[i].close;
|
|
34005
|
+
return c > b ? ink.bull : c < b ? ink.bear : ink.neutral;
|
|
34006
|
+
});
|
|
34007
|
+
const wash = upper.map((u, i) => {
|
|
34008
|
+
const l = lower[i];
|
|
34009
|
+
if (!Number.isFinite(u) || !Number.isFinite(l)) return null;
|
|
34010
|
+
return { topValue: u, bottomValue: l, topColor: transp(ink.bear, 90), bottomColor: transp(ink.bull, 90) };
|
|
34011
|
+
});
|
|
34012
|
+
return {
|
|
34013
|
+
plots: [
|
|
34014
|
+
{ key: "basis", title: "Basis", values: [...basis], color: ink.neutral, colors: basisColors },
|
|
34015
|
+
{ key: "upper", title: "Upper", values: [...upper], color: transp(ink.bear, 30) },
|
|
34016
|
+
{ key: "lower", title: "Lower", values: [...lower], color: transp(ink.bull, 30) }
|
|
34017
|
+
],
|
|
34018
|
+
bands: [{ key, from: "upper", to: "lower", color: transp(NEUTRAL, 90), gradient: wash }]
|
|
34019
|
+
};
|
|
34020
|
+
}
|
|
34021
|
+
function anchorPlot(key, title, length, level) {
|
|
34022
|
+
return {
|
|
34023
|
+
key,
|
|
34024
|
+
title,
|
|
34025
|
+
values: new Array(length).fill(level),
|
|
34026
|
+
color: transp(NEUTRAL, 100),
|
|
34027
|
+
display: { pane: false, legend: false, dataWindow: false }
|
|
34028
|
+
};
|
|
34029
|
+
}
|
|
34030
|
+
function thresholdZones(length, levels, ink, transparency = 92) {
|
|
34031
|
+
return {
|
|
34032
|
+
plots: [
|
|
34033
|
+
anchorPlot("obAnchor", "Overbought Anchor", length, levels.overbought),
|
|
34034
|
+
anchorPlot("osAnchor", "Oversold Anchor", length, levels.oversold),
|
|
34035
|
+
anchorPlot("topAnchor", "Top Anchor", length, levels.top),
|
|
34036
|
+
anchorPlot("bottomAnchor", "Bottom Anchor", length, levels.bottom)
|
|
34037
|
+
],
|
|
34038
|
+
bands: [
|
|
34039
|
+
{ key: "obZone", from: "obAnchor", to: "topAnchor", color: transp(ink.overbought, transparency) },
|
|
34040
|
+
{ key: "osZone", from: "osAnchor", to: "bottomAnchor", color: transp(ink.oversold, transparency) }
|
|
34041
|
+
]
|
|
34042
|
+
};
|
|
34043
|
+
}
|
|
34044
|
+
function rampColors(values, levels, ink) {
|
|
34045
|
+
return values.map((x) => {
|
|
34046
|
+
if (!Number.isFinite(x)) return null;
|
|
34047
|
+
return x <= levels.mid ? gradient(x, levels.low, levels.mid, ink.low, ink.mid) : gradient(x, levels.mid, levels.high, ink.mid, ink.high);
|
|
34048
|
+
});
|
|
34049
|
+
}
|
|
34050
|
+
function baselineGradient(values, baseline, inkOf, valueTransparency = 50, baseTransparency = 100) {
|
|
34051
|
+
return values.map((x, i) => {
|
|
34052
|
+
if (!Number.isFinite(x)) return null;
|
|
34053
|
+
const ink = inkOf(x, i);
|
|
34054
|
+
const above = x > baseline;
|
|
34055
|
+
return {
|
|
34056
|
+
topValue: Math.max(x, baseline),
|
|
34057
|
+
bottomValue: Math.min(x, baseline),
|
|
34058
|
+
topColor: transp(ink, above ? valueTransparency : baseTransparency),
|
|
34059
|
+
bottomColor: transp(ink, above ? baseTransparency : valueTransparency)
|
|
34060
|
+
};
|
|
34061
|
+
});
|
|
34062
|
+
}
|
|
34063
|
+
function momentumColumnColors(values, ink) {
|
|
34064
|
+
return values.map((x, i) => {
|
|
34065
|
+
if (!Number.isFinite(x)) return null;
|
|
34066
|
+
const prev2 = i > 0 && Number.isFinite(values[i - 1]) ? values[i - 1] : x;
|
|
34067
|
+
const rising = x > prev2;
|
|
34068
|
+
if (x >= 0) return rising ? ink.growAbove : ink.fallAbove;
|
|
34069
|
+
return rising ? ink.growBelow : ink.fallBelow;
|
|
34070
|
+
});
|
|
34071
|
+
}
|
|
34072
|
+
function boundedOscillator(opts) {
|
|
34073
|
+
const mid = (opts.overbought + opts.oversold) / 2;
|
|
34074
|
+
const ramp = rampColors(opts.values, { low: opts.oversold, mid, high: opts.overbought }, opts.ink);
|
|
34075
|
+
const zones = thresholdZones(
|
|
34076
|
+
opts.values.length,
|
|
34077
|
+
{ overbought: opts.overbought, oversold: opts.oversold, top: opts.scale.top, bottom: opts.scale.bottom },
|
|
34078
|
+
{ overbought: opts.ink.high, oversold: opts.ink.low },
|
|
34079
|
+
opts.zoneTransparency
|
|
34080
|
+
);
|
|
34081
|
+
const plots = [{ key: opts.key, title: opts.title, values: opts.values, color: opts.ink.mid, width: opts.width ?? 1, colors: ramp }, ...zones.plots];
|
|
34082
|
+
const bands = [...zones.bands];
|
|
34083
|
+
if (opts.bandFill != null) bands.push({ key: "band", from: "obAnchor", to: "osAnchor", color: opts.bandFill });
|
|
34084
|
+
if (opts.midpointWash) {
|
|
34085
|
+
plots.push(anchorPlot("midAnchor", "Midpoint Anchor", opts.values.length, mid));
|
|
34086
|
+
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) });
|
|
34087
|
+
}
|
|
34088
|
+
const levels = [
|
|
34089
|
+
{ key: "overbought", price: opts.overbought, color: opts.levelInk, lineStyle: "dashed", title: opts.levelTitles?.overbought ?? "Overbought" },
|
|
34090
|
+
{ key: "oversold", price: opts.oversold, color: opts.levelInk, lineStyle: "dashed", title: opts.levelTitles?.oversold ?? "Oversold" }
|
|
34091
|
+
];
|
|
34092
|
+
if (opts.midline != null) levels.splice(1, 0, { key: "midline", price: opts.midline, color: opts.levelInk, lineStyle: "dotted", title: "Midline" });
|
|
34093
|
+
return { plots, bands, levels };
|
|
34094
|
+
}
|
|
34095
|
+
function centeredOscillator(opts) {
|
|
34096
|
+
const baseline = opts.baseline ?? 0;
|
|
34097
|
+
const inkOf = (x) => x > baseline ? opts.ink.bull : x < baseline ? opts.ink.bear : opts.ink.neutral;
|
|
34098
|
+
const plots = [
|
|
34099
|
+
{ 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) },
|
|
34100
|
+
...opts.extraPlots ?? []
|
|
34101
|
+
];
|
|
34102
|
+
const bands = [];
|
|
34103
|
+
if (opts.fill !== false) {
|
|
34104
|
+
plots.push(anchorPlot("baseline", "Baseline Anchor", opts.values.length, baseline));
|
|
34105
|
+
bands.push({ key: "wash", from: opts.key, to: "baseline", color: transp(NEUTRAL, 100), gradient: baselineGradient(opts.values, baseline, inkOf, opts.valueTransparency) });
|
|
34106
|
+
}
|
|
34107
|
+
return {
|
|
34108
|
+
plots,
|
|
34109
|
+
bands,
|
|
34110
|
+
levels: [{ key: "zero", price: baseline, color: opts.zeroInk, lineStyle: opts.zeroLineStyle ?? "dashed", title: opts.zeroTitle ?? "Zero Line" }, ...opts.extraLevels ?? []]
|
|
34111
|
+
};
|
|
34112
|
+
}
|
|
34113
|
+
function channels(hex) {
|
|
34114
|
+
const m = /^#([0-9a-f]{6})/i.exec(hex.trim());
|
|
34115
|
+
if (!m) return null;
|
|
34116
|
+
const n = parseInt(m[1], 16);
|
|
34117
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
34118
|
+
}
|
|
34119
|
+
function gradient(value, low, high, from, to) {
|
|
34120
|
+
const a = channels(from);
|
|
34121
|
+
const b = channels(to);
|
|
34122
|
+
if (!a || !b || !Number.isFinite(value)) return from;
|
|
34123
|
+
const span = high - low;
|
|
34124
|
+
const t = span === 0 ? 0 : Math.max(0, Math.min(1, (value - low) / span));
|
|
34125
|
+
const mix2 = (i) => Math.round(a[i] + (b[i] - a[i]) * t).toString(16).padStart(2, "0");
|
|
34126
|
+
return `#${mix2(0)}${mix2(1)}${mix2(2)}`;
|
|
34127
|
+
}
|
|
33845
34128
|
|
|
33846
34129
|
// src/core/native-indicators/classics/averages.ts
|
|
33847
|
-
var
|
|
33848
|
-
var
|
|
34130
|
+
var MA_TYPES = ["SMA", "EMA", "WMA", "RMA (SMMA)", "HMA", "ALMA", "VWMA", "DEMA", "TEMA", "KAMA", "LSMA", "McGinley", "Hamming"];
|
|
34131
|
+
var ENVELOPE_MA_TYPES = ["SMA", "EMA", "WMA", "RMA (SMMA)", "HMA", "VWMA"];
|
|
34132
|
+
function movingAverageOf(kind, src, bars, len, opts = {}) {
|
|
34133
|
+
switch (kind) {
|
|
34134
|
+
case "EMA":
|
|
34135
|
+
return ema(src, len);
|
|
34136
|
+
case "WMA":
|
|
34137
|
+
return wma(src, len);
|
|
34138
|
+
case "RMA":
|
|
34139
|
+
case "RMA (SMMA)":
|
|
34140
|
+
return rma(src, len);
|
|
34141
|
+
case "HMA":
|
|
34142
|
+
return hma(src, len);
|
|
34143
|
+
case "ALMA":
|
|
34144
|
+
return alma(src, len, opts.almaOffset ?? 0.85, opts.almaSigma ?? 6);
|
|
34145
|
+
case "VWMA":
|
|
34146
|
+
return vwma(src, volumes(bars), len);
|
|
34147
|
+
case "DEMA":
|
|
34148
|
+
return dema(src, len);
|
|
34149
|
+
case "TEMA":
|
|
34150
|
+
return tema(src, len);
|
|
34151
|
+
case "KAMA":
|
|
34152
|
+
return kama(src, len);
|
|
34153
|
+
case "LSMA":
|
|
34154
|
+
return linreg(src, len, opts.lsmaOffset ?? 0);
|
|
34155
|
+
case "McGinley":
|
|
34156
|
+
return mcginley(src, len);
|
|
34157
|
+
case "Hamming":
|
|
34158
|
+
return hamming(src, len);
|
|
34159
|
+
default:
|
|
34160
|
+
return sma(src, len);
|
|
34161
|
+
}
|
|
34162
|
+
}
|
|
34163
|
+
function slopeColors(values, bull, bear, flat) {
|
|
34164
|
+
return values.map((x, i) => {
|
|
34165
|
+
if (!Number.isFinite(x)) return null;
|
|
34166
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
34167
|
+
if (!Number.isFinite(prev2)) return flat;
|
|
34168
|
+
return x > prev2 ? bull : x < prev2 ? bear : flat;
|
|
34169
|
+
});
|
|
34170
|
+
}
|
|
34171
|
+
function directionInputs() {
|
|
34172
|
+
return [
|
|
34173
|
+
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."),
|
|
34174
|
+
colorInput(BULLISH, "Bullish", "bullColor"),
|
|
34175
|
+
colorInput(BEARISH, "Bearish", "bearColor"),
|
|
34176
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor"),
|
|
34177
|
+
widthInput()
|
|
34178
|
+
];
|
|
34179
|
+
}
|
|
34180
|
+
function slopeColoredPlot(key, title, values, inputs) {
|
|
34181
|
+
const neutral = str3(inputs, "neutralColor", NEUTRAL);
|
|
34182
|
+
const colored = bool(inputs, "trendColor", true);
|
|
34183
|
+
return {
|
|
34184
|
+
key,
|
|
34185
|
+
title,
|
|
34186
|
+
values,
|
|
34187
|
+
color: neutral,
|
|
34188
|
+
width: num3(inputs, "lineWidth", 2),
|
|
34189
|
+
...colored ? { colors: slopeColors(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), neutral) } : {}
|
|
34190
|
+
};
|
|
34191
|
+
}
|
|
34192
|
+
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." };
|
|
33849
34193
|
function fixedAverage(type, title, shortTitle, smooth) {
|
|
33850
34194
|
return {
|
|
33851
34195
|
type,
|
|
33852
34196
|
title,
|
|
33853
34197
|
shortTitle,
|
|
33854
34198
|
overlay: true,
|
|
33855
|
-
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
34199
|
+
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()],
|
|
33856
34200
|
compute: (bars, inputs) => {
|
|
33857
34201
|
const len = num3(inputs, "length", 20);
|
|
33858
34202
|
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
33859
34203
|
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
33860
34204
|
if (offset !== 0) values = shift(values, offset);
|
|
33861
|
-
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color",
|
|
34205
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
33862
34206
|
}
|
|
33863
34207
|
};
|
|
33864
34208
|
}
|
|
@@ -33870,36 +34214,37 @@ var movingAverage = {
|
|
|
33870
34214
|
shortTitle: "MA",
|
|
33871
34215
|
overlay: true,
|
|
33872
34216
|
inputs: [
|
|
33873
|
-
|
|
33874
|
-
lengthInput(20),
|
|
33875
|
-
sourceInput(),
|
|
34217
|
+
optionInput("maType", "MA 1 Type", "SMA", MA_TYPES, "Moving average calculation used for MA 1."),
|
|
34218
|
+
lengthInput(20, "length", "MA 1 Length", 5e3, "Number of bars used in the MA 1 calculation."),
|
|
34219
|
+
sourceInput("Close", "source", "MA 1 Source", "Price series MA 1 is calculated on."),
|
|
34220
|
+
boolInput("showMa2", "Show MA 2", false, SETTINGS, "Displays the optional second moving average."),
|
|
34221
|
+
optionInput("ma2Type", "MA 2 Type", "SMA", MA_TYPES, "Moving average calculation used for MA 2."),
|
|
34222
|
+
lengthInput(50, "ma2Length", "MA 2 Length", 5e3, "Number of bars used in the MA 2 calculation."),
|
|
34223
|
+
sourceInput("Close", "ma2Source", "MA 2 Source", "Price series MA 2 is calculated on."),
|
|
34224
|
+
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."),
|
|
34225
|
+
floatInput("almaSigma", "ALMA Sigma", 6, 0.1, 100, 0.5, "ALMA type only. Larger values widen the Gaussian window for a smoother average."),
|
|
34226
|
+
intInput("lsmaOffset", "LSMA Offset", 0, 0, 500, "LSMA type only. Bar offset applied to the linear regression value."),
|
|
33876
34227
|
offsetInput,
|
|
33877
|
-
colorInput()
|
|
34228
|
+
colorInput(INFO, "MA 1", "color", "MA 1 line color."),
|
|
34229
|
+
colorInput(WARNING, "MA 2", "ma2Color", "MA 2 line color.")
|
|
33878
34230
|
],
|
|
33879
34231
|
compute: (bars, inputs) => {
|
|
33880
|
-
const
|
|
33881
|
-
|
|
34232
|
+
const opts = {
|
|
34233
|
+
almaOffset: num3(inputs, "almaOffset", 0.85),
|
|
34234
|
+
almaSigma: num3(inputs, "almaSigma", 6),
|
|
34235
|
+
lsmaOffset: Math.trunc(num3(inputs, "lsmaOffset", 0))
|
|
34236
|
+
};
|
|
34237
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
34238
|
+
const displace = (v) => offset === 0 ? v : shift(v, offset);
|
|
33882
34239
|
const kind = str3(inputs, "maType", "SMA");
|
|
33883
|
-
|
|
33884
|
-
|
|
33885
|
-
|
|
33886
|
-
|
|
33887
|
-
|
|
33888
|
-
|
|
33889
|
-
values = wma(src, len);
|
|
33890
|
-
break;
|
|
33891
|
-
case "RMA":
|
|
33892
|
-
values = rma(src, len);
|
|
33893
|
-
break;
|
|
33894
|
-
case "VWMA":
|
|
33895
|
-
values = vwma(src, volumes(bars), len);
|
|
33896
|
-
break;
|
|
33897
|
-
default:
|
|
33898
|
-
values = sma(src, len);
|
|
34240
|
+
const ma1 = displace(movingAverageOf(kind, sourceValues(bars, str3(inputs, "source", "Close")), bars, num3(inputs, "length", 20), opts));
|
|
34241
|
+
const plots = [{ key: "ma", title: kind, values: ma1, color: str3(inputs, "color", INFO), width: 2 }];
|
|
34242
|
+
if (bool(inputs, "showMa2", false)) {
|
|
34243
|
+
const kind2 = str3(inputs, "ma2Type", "SMA");
|
|
34244
|
+
const ma2 = displace(movingAverageOf(kind2, sourceValues(bars, str3(inputs, "ma2Source", "Close")), bars, num3(inputs, "ma2Length", 50), opts));
|
|
34245
|
+
plots.push({ key: "ma2", title: `${kind2} 2`, values: ma2, color: str3(inputs, "ma2Color", WARNING), width: 2 });
|
|
33899
34246
|
}
|
|
33900
|
-
|
|
33901
|
-
if (offset !== 0) values = shift(values, offset);
|
|
33902
|
-
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
34247
|
+
return { plots };
|
|
33903
34248
|
}
|
|
33904
34249
|
};
|
|
33905
34250
|
var smoothedMa = {
|
|
@@ -33907,17 +34252,13 @@ var smoothedMa = {
|
|
|
33907
34252
|
title: "Smoothed Moving Average",
|
|
33908
34253
|
shortTitle: "RMA",
|
|
33909
34254
|
overlay: true,
|
|
33910
|
-
inputs: [
|
|
34255
|
+
inputs: [
|
|
34256
|
+
sourceInput("Close", "source", "Source", "Price series the average smooths. Wilder computed his 1978 indicators on the close, but the recursion accepts any source."),
|
|
34257
|
+
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."),
|
|
34258
|
+
...directionInputs()
|
|
34259
|
+
],
|
|
33911
34260
|
compute: (bars, inputs) => ({
|
|
33912
|
-
plots: [
|
|
33913
|
-
{
|
|
33914
|
-
key: "rma",
|
|
33915
|
-
title: "RMA",
|
|
33916
|
-
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
33917
|
-
color: str3(inputs, "color", SERIES_LINE),
|
|
33918
|
-
width: 2
|
|
33919
|
-
}
|
|
33920
|
-
]
|
|
34261
|
+
plots: [slopeColoredPlot("rma", "RMA", rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)), inputs)]
|
|
33921
34262
|
})
|
|
33922
34263
|
};
|
|
33923
34264
|
var zlema = {
|
|
@@ -33925,61 +34266,86 @@ var zlema = {
|
|
|
33925
34266
|
title: "Zero-Lag Exponential Moving Average",
|
|
33926
34267
|
shortTitle: "ZLEMA",
|
|
33927
34268
|
overlay: true,
|
|
33928
|
-
inputs: [
|
|
34269
|
+
inputs: [
|
|
34270
|
+
sourceInput("Close", "source", "Source", "Price series the average de-lags and then smooths."),
|
|
34271
|
+
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."),
|
|
34272
|
+
...directionInputs()
|
|
34273
|
+
],
|
|
33929
34274
|
compute: (bars, inputs) => {
|
|
33930
|
-
const len = num3(inputs, "length",
|
|
34275
|
+
const len = num3(inputs, "length", 21);
|
|
33931
34276
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
33932
|
-
const lag = Math.
|
|
33933
|
-
const
|
|
33934
|
-
|
|
33935
|
-
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
34277
|
+
const lag = Math.round((len - 1) / 2);
|
|
34278
|
+
const delagged = zip(src, shift(src, lag), (a, b) => 2 * a - b);
|
|
34279
|
+
return { plots: [slopeColoredPlot("zlema", "ZLEMA", ema(delagged, len), inputs)] };
|
|
33936
34280
|
}
|
|
33937
34281
|
};
|
|
34282
|
+
var MOMENTUM_SCALER = "Momentum (|CMO|)";
|
|
34283
|
+
var VOLATILITY_SCALER = "Volatility (StDev Ratio)";
|
|
34284
|
+
var REGIME = "Trend Regime";
|
|
33938
34285
|
var vidya = {
|
|
33939
34286
|
type: "vidya",
|
|
33940
34287
|
title: "Variable Index Dynamic Average",
|
|
33941
34288
|
shortTitle: "VIDYA",
|
|
33942
34289
|
overlay: true,
|
|
33943
|
-
inputs: [
|
|
34290
|
+
inputs: [
|
|
34291
|
+
sourceInput("Close", "source", "Source", "Price series the average smooths. VIDYA is classically computed on the close."),
|
|
34292
|
+
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."),
|
|
34293
|
+
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."),
|
|
34294
|
+
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."),
|
|
34295
|
+
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."),
|
|
34296
|
+
{ ...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 },
|
|
34297
|
+
{ ...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 },
|
|
34298
|
+
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."),
|
|
34299
|
+
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."),
|
|
34300
|
+
colorInput(BULLISH, "Bullish", "bullColor"),
|
|
34301
|
+
colorInput(BEARISH, "Bearish", "bearColor"),
|
|
34302
|
+
colorInput(NEUTRAL, "Flat", "flatColor"),
|
|
34303
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor"),
|
|
34304
|
+
widthInput()
|
|
34305
|
+
],
|
|
33944
34306
|
compute: (bars, inputs) => {
|
|
33945
|
-
const len = num3(inputs, "length",
|
|
33946
|
-
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
34307
|
+
const len = num3(inputs, "length", 12);
|
|
33947
34308
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
33948
|
-
const
|
|
33949
|
-
const
|
|
33950
|
-
const
|
|
33951
|
-
|
|
33952
|
-
|
|
33953
|
-
|
|
33954
|
-
|
|
33955
|
-
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
33956
|
-
const x = d[k];
|
|
33957
|
-
if (!Number.isFinite(x)) {
|
|
33958
|
-
ok = false;
|
|
33959
|
-
break;
|
|
33960
|
-
}
|
|
33961
|
-
if (x > 0) u += x;
|
|
33962
|
-
else w -= x;
|
|
33963
|
-
}
|
|
33964
|
-
if (ok) {
|
|
33965
|
-
su[i] = u;
|
|
33966
|
-
sd[i] = w;
|
|
33967
|
-
}
|
|
33968
|
-
}
|
|
33969
|
-
const alpha = 2 / (len + 1);
|
|
34309
|
+
const scalerLen = num3(inputs, "scalerLength", 9);
|
|
34310
|
+
const momentum = map(cmo(src, scalerLen), (x) => Math.abs(x) / 100);
|
|
34311
|
+
const shortDev = stdev(src, scalerLen);
|
|
34312
|
+
const refDev = stdev(src, num3(inputs, "refLength", 30));
|
|
34313
|
+
const volatility = zip(shortDev, refDev, (s, r) => r > 0 ? s / r : 0);
|
|
34314
|
+
const scale = str3(inputs, "scaler", MOMENTUM_SCALER) === VOLATILITY_SCALER ? volatility : momentum;
|
|
34315
|
+
const alphaBase = 2 / (len + 1);
|
|
33970
34316
|
const out = new Array(src.length).fill(Number.NaN);
|
|
33971
34317
|
let prev2 = Number.NaN;
|
|
33972
34318
|
for (let i = 0; i < src.length; i++) {
|
|
33973
34319
|
const x = src[i];
|
|
33974
|
-
|
|
33975
|
-
const
|
|
33976
|
-
if (!Number.isFinite(
|
|
33977
|
-
|
|
33978
|
-
|
|
33979
|
-
|
|
34320
|
+
if (!Number.isFinite(x)) continue;
|
|
34321
|
+
const k = scale[i];
|
|
34322
|
+
if (!Number.isFinite(prev2) || !Number.isFinite(k)) prev2 = x;
|
|
34323
|
+
else {
|
|
34324
|
+
const alpha = Math.min(alphaBase * k, 1);
|
|
34325
|
+
prev2 = alpha * x + (1 - alpha) * prev2;
|
|
34326
|
+
}
|
|
33980
34327
|
out[i] = prev2;
|
|
33981
34328
|
}
|
|
33982
|
-
|
|
34329
|
+
const neutral = str3(inputs, "neutralColor", NEUTRAL);
|
|
34330
|
+
if (!bool(inputs, "trendColor", true)) {
|
|
34331
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: neutral, width: num3(inputs, "lineWidth", 2) }] };
|
|
34332
|
+
}
|
|
34333
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
34334
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
34335
|
+
const flat = str3(inputs, "flatColor", NEUTRAL);
|
|
34336
|
+
const threshold = num3(inputs, "flatThreshold", 0.05);
|
|
34337
|
+
const range = atr(bars, num3(inputs, "atrLength", 14));
|
|
34338
|
+
const fade = bool(inputs, "adaptiveGradient", true);
|
|
34339
|
+
const colors = out.map((x, i) => {
|
|
34340
|
+
if (!Number.isFinite(x)) return null;
|
|
34341
|
+
const r = range[i];
|
|
34342
|
+
const slope = i > 0 && Number.isFinite(out[i - 1]) ? x - out[i - 1] : 0;
|
|
34343
|
+
const normalized = Number.isFinite(r) && r !== 0 ? slope / r : 0;
|
|
34344
|
+
const ink = normalized > threshold ? bull : normalized < -threshold ? bear : null;
|
|
34345
|
+
if (ink == null) return flat;
|
|
34346
|
+
return fade ? gradient(Math.min(scale[i] ?? 0, 1), 0, 1, flat, ink) : ink;
|
|
34347
|
+
});
|
|
34348
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: flat, width: num3(inputs, "lineWidth", 2), colors }] };
|
|
33983
34349
|
}
|
|
33984
34350
|
};
|
|
33985
34351
|
var maEnvelope = {
|
|
@@ -33988,115 +34354,218 @@ var maEnvelope = {
|
|
|
33988
34354
|
shortTitle: "MA Env",
|
|
33989
34355
|
overlay: true,
|
|
33990
34356
|
inputs: [
|
|
33991
|
-
|
|
33992
|
-
lengthInput(20),
|
|
33993
|
-
sourceInput(),
|
|
33994
|
-
|
|
33995
|
-
|
|
34357
|
+
optionInput("maType", "MA Type", "SMA", ENVELOPE_MA_TYPES, "Moving average type used as the envelope basis."),
|
|
34358
|
+
{ ...lengthInput(20, "length", "Length", 5e3, "Number of bars in the basis moving average."), min: 2 },
|
|
34359
|
+
sourceInput("Close", "source", "Source", "Price series the basis moving average is calculated on."),
|
|
34360
|
+
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)."),
|
|
34361
|
+
...channelStyleInputs()
|
|
33996
34362
|
],
|
|
33997
34363
|
compute: (bars, inputs) => {
|
|
33998
|
-
const len = num3(inputs, "length", 20);
|
|
33999
34364
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
34000
|
-
const basis = str3(inputs, "maType", "SMA")
|
|
34365
|
+
const basis = movingAverageOf(str3(inputs, "maType", "SMA"), src, bars, num3(inputs, "length", 20));
|
|
34001
34366
|
const k = num3(inputs, "percent", 2.5) / 100;
|
|
34002
|
-
const ink = str3(inputs, "
|
|
34003
|
-
return
|
|
34004
|
-
plots: [
|
|
34005
|
-
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
34006
|
-
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
34007
|
-
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
34008
|
-
],
|
|
34009
|
-
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
34010
|
-
};
|
|
34367
|
+
const ink = { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "basisColor", NEUTRAL) };
|
|
34368
|
+
return directionalChannel(bars, basis, map(basis, (x) => x * (1 + k)), map(basis, (x) => x * (1 - k)), ink, "envelope");
|
|
34011
34369
|
}
|
|
34012
34370
|
};
|
|
34371
|
+
var LINREG_CURVE = "Curve";
|
|
34372
|
+
var LINREG_SLOPE = "Slope";
|
|
34013
34373
|
var linearRegression = {
|
|
34014
34374
|
type: "linear-regression",
|
|
34015
34375
|
title: "Linear Regression Curve",
|
|
34016
34376
|
shortTitle: "LinReg",
|
|
34017
|
-
|
|
34018
|
-
|
|
34019
|
-
|
|
34020
|
-
plots
|
|
34021
|
-
|
|
34022
|
-
|
|
34023
|
-
|
|
34024
|
-
|
|
34025
|
-
|
|
34026
|
-
|
|
34027
|
-
|
|
34028
|
-
|
|
34029
|
-
|
|
34377
|
+
// Slope lives in its own pane; Curve force-overlays itself onto the price candles.
|
|
34378
|
+
overlay: false,
|
|
34379
|
+
inputs: [
|
|
34380
|
+
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."),
|
|
34381
|
+
{ ...lengthInput(100, "length", "Length", 5e3, "Number of bars used to fit the linear regression."), min: 2 },
|
|
34382
|
+
sourceInput("Close", "source", "Source", "Price series the regression is fitted to."),
|
|
34383
|
+
colorInput(BULLISH, "Rising", "bullColor", "Color of the regression curve and slope line while the fit slopes upward."),
|
|
34384
|
+
colorInput(BEARISH, "Falling", "bearColor", "Color of the regression curve and slope line while the fit slopes downward."),
|
|
34385
|
+
colorInput(NEUTRAL, "Regression Line", "lineColor", "Color of the regression curve and slope line while the fit is flat.")
|
|
34386
|
+
],
|
|
34387
|
+
compute: (bars, inputs) => {
|
|
34388
|
+
const len = num3(inputs, "length", 100);
|
|
34389
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
34390
|
+
const curve = linreg(src, len, 0);
|
|
34391
|
+
const slope = zip(curve, linreg(src, len, 1), (a, b) => a - b);
|
|
34392
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
34393
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
34394
|
+
const flat = str3(inputs, "lineColor", NEUTRAL);
|
|
34395
|
+
const colors = slope.map((s) => Number.isFinite(s) ? s > 0 ? bull : s < 0 ? bear : flat : null);
|
|
34396
|
+
if (str3(inputs, "mode", LINREG_CURVE) === LINREG_SLOPE) {
|
|
34397
|
+
return {
|
|
34398
|
+
plots: [{ key: "slope", title: "Slope", values: slope, color: flat, width: 2, colors }],
|
|
34399
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed" }]
|
|
34400
|
+
};
|
|
34401
|
+
}
|
|
34402
|
+
return { plots: [{ key: "curve", title: "Curve", values: curve, color: flat, width: 2, colors, overlay: true }] };
|
|
34403
|
+
}
|
|
34030
34404
|
};
|
|
34031
34405
|
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
34032
34406
|
|
|
34033
34407
|
// src/core/native-indicators/classics/bands.ts
|
|
34408
|
+
var JAW_COLOR = "#ab47bc";
|
|
34409
|
+
var TEETH_COLOR = WARNING;
|
|
34410
|
+
var LIPS_COLOR = "#ffd700";
|
|
34034
34411
|
function bollinger(bars, inputs) {
|
|
34035
34412
|
const len = num3(inputs, "length", 20);
|
|
34036
34413
|
const mult = num3(inputs, "mult", 2);
|
|
34037
34414
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
34038
34415
|
const basis = sma(src, len);
|
|
34039
34416
|
const dev = map(stdev(src, len), (x) => x * mult);
|
|
34040
|
-
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
34417
|
+
return { src, basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
34418
|
+
}
|
|
34419
|
+
function bollingerInputs(multMin, multStep) {
|
|
34420
|
+
return [
|
|
34421
|
+
{ ...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 },
|
|
34422
|
+
sourceInput("Close", "source", "Source", "Price series the bands are computed from."),
|
|
34423
|
+
floatInput("mult", "Multiplier", 2, multMin, 50, multStep, "Standard deviation multiplier for the upper and lower bands.")
|
|
34424
|
+
];
|
|
34041
34425
|
}
|
|
34042
|
-
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
34043
34426
|
var bollingerBands = {
|
|
34044
34427
|
type: "bollinger-bands",
|
|
34045
34428
|
title: "Bollinger Bands",
|
|
34046
34429
|
shortTitle: "BB",
|
|
34047
34430
|
overlay: true,
|
|
34048
|
-
inputs: [
|
|
34431
|
+
inputs: [
|
|
34432
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used to calculate the basis SMA and the standard deviation."),
|
|
34433
|
+
sourceInput("Close", "source", "Source", "Price series the bands are calculated on."),
|
|
34434
|
+
floatInput("mult", "Multiplier", 2, 0, 50, 0.1, "Number of standard deviations added above and below the basis to form the bands."),
|
|
34435
|
+
colorInput(WARNING, "Basis", "basisColor", "Color of the basis (middle band) moving average."),
|
|
34436
|
+
colorInput(INFO, "Bands", "color", "Color of the upper and lower bands."),
|
|
34437
|
+
colorInput(transp(INFO, 92), "Fill", "fillColor", "Fill color of the area between the upper and lower bands.")
|
|
34438
|
+
],
|
|
34049
34439
|
compute: (bars, inputs) => {
|
|
34050
34440
|
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
34051
|
-
const ink = str3(inputs, "color",
|
|
34441
|
+
const ink = str3(inputs, "color", INFO);
|
|
34052
34442
|
return {
|
|
34053
34443
|
plots: [
|
|
34054
|
-
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
34444
|
+
{ key: "basis", title: "Basis", values: basis, color: str3(inputs, "basisColor", WARNING) },
|
|
34055
34445
|
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
34056
34446
|
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
34057
34447
|
],
|
|
34058
|
-
bands: [{ key: "bb", from: "upper", to: "lower", color:
|
|
34448
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: str3(inputs, "fillColor", transp(INFO, 92)) }]
|
|
34059
34449
|
};
|
|
34060
34450
|
}
|
|
34061
34451
|
};
|
|
34452
|
+
var PERCENT_B_MID = 0.5;
|
|
34453
|
+
var THRESHOLDS = "Thresholds";
|
|
34062
34454
|
var percentB = {
|
|
34063
34455
|
type: "percent-b",
|
|
34064
34456
|
title: "Bollinger Bands %B",
|
|
34065
34457
|
shortTitle: "%B",
|
|
34066
34458
|
overlay: false,
|
|
34067
|
-
inputs: [
|
|
34459
|
+
inputs: [
|
|
34460
|
+
...bollingerInputs(1e-3, 0.25),
|
|
34461
|
+
{ ...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 },
|
|
34462
|
+
{ ...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 },
|
|
34463
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of %B above the 0.5 midline, where price trades in the upper half of the bands."),
|
|
34464
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of %B below the 0.5 midline, where price trades in the lower half of the bands."),
|
|
34465
|
+
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."),
|
|
34466
|
+
colorInput(NEUTRAL, "Levels Color", "levelsColor", "Color of the dashed overbought/oversold levels and of the dotted midline.")
|
|
34467
|
+
],
|
|
34068
34468
|
compute: (bars, inputs) => {
|
|
34069
|
-
const { upper, lower } = bollinger(bars, inputs);
|
|
34070
|
-
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
34469
|
+
const { src, upper, lower } = bollinger(bars, inputs);
|
|
34071
34470
|
const values = src.map((x, i) => {
|
|
34072
34471
|
const u = upper[i];
|
|
34073
34472
|
const l = lower[i];
|
|
34074
34473
|
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
34075
34474
|
});
|
|
34475
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
34476
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
34477
|
+
const levels = str3(inputs, "levelsColor", NEUTRAL);
|
|
34478
|
+
const colors = values.map((x) => Number.isFinite(x) ? x > PERCENT_B_MID ? bull : bear : null);
|
|
34479
|
+
const plots = [
|
|
34480
|
+
{ key: "pb", title: "%B", values, color: bear, colors },
|
|
34481
|
+
{ 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 } }
|
|
34482
|
+
];
|
|
34483
|
+
const bands = bool(inputs, "gradientFill", true) ? [
|
|
34484
|
+
{
|
|
34485
|
+
key: "pbFill",
|
|
34486
|
+
from: "pb",
|
|
34487
|
+
to: "mid",
|
|
34488
|
+
color: transp(NEUTRAL, 100),
|
|
34489
|
+
// Opaque at the oscillator, invisible where it meets the midline.
|
|
34490
|
+
gradient: values.map((x) => {
|
|
34491
|
+
if (!Number.isFinite(x)) return null;
|
|
34492
|
+
const up = x > PERCENT_B_MID;
|
|
34493
|
+
const ink = up ? bull : bear;
|
|
34494
|
+
return {
|
|
34495
|
+
topValue: Math.max(x, PERCENT_B_MID),
|
|
34496
|
+
bottomValue: Math.min(x, PERCENT_B_MID),
|
|
34497
|
+
topColor: up ? transp(ink, 50) : transp(ink, 100),
|
|
34498
|
+
bottomColor: up ? transp(ink, 100) : transp(ink, 50)
|
|
34499
|
+
};
|
|
34500
|
+
})
|
|
34501
|
+
}
|
|
34502
|
+
] : [];
|
|
34076
34503
|
return {
|
|
34077
|
-
plots
|
|
34504
|
+
plots,
|
|
34505
|
+
bands,
|
|
34078
34506
|
levels: [
|
|
34079
|
-
{ key: "
|
|
34080
|
-
{ key: "
|
|
34081
|
-
{ key: "
|
|
34507
|
+
{ key: "overbought", price: num3(inputs, "overbought", 1), color: levels, lineStyle: "dashed", title: "Overbought" },
|
|
34508
|
+
{ key: "oversold", price: num3(inputs, "oversold", 0), color: levels, lineStyle: "dashed", title: "Oversold" },
|
|
34509
|
+
{ key: "midline", price: PERCENT_B_MID, color: transp(levels, 50), lineStyle: "dotted", title: "Midline" }
|
|
34082
34510
|
]
|
|
34083
34511
|
};
|
|
34084
34512
|
}
|
|
34085
34513
|
};
|
|
34514
|
+
var SQUEEZE_BULGE = "Squeeze & Bulge";
|
|
34086
34515
|
var bandwidth = {
|
|
34087
34516
|
type: "bandwidth",
|
|
34088
34517
|
title: "Bollinger Bands Width",
|
|
34089
34518
|
shortTitle: "BandWidth",
|
|
34090
34519
|
overlay: false,
|
|
34091
|
-
inputs: [
|
|
34520
|
+
inputs: [
|
|
34521
|
+
...bollingerInputs(1e-3, 0.25),
|
|
34522
|
+
{ ...intInput("lookback", "Reference Lookback", 125, 2, 5e3, "Window used to locate the lowest (Squeeze) and highest (Bulge) BandWidth references."), group: SQUEEZE_BULGE },
|
|
34523
|
+
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."),
|
|
34524
|
+
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."),
|
|
34525
|
+
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."),
|
|
34526
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between zero and BandWidth with a vertical gradient, fully transparent at zero."),
|
|
34527
|
+
colorInput(WARNING, "Squeeze Level", "squeezeColor", "Color of the lowest-BandWidth reference line."),
|
|
34528
|
+
colorInput(BEARISH, "Bulge Level", "bulgeColor", "Color of the highest-BandWidth reference line.")
|
|
34529
|
+
],
|
|
34092
34530
|
compute: (bars, inputs) => {
|
|
34093
34531
|
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
34094
34532
|
const values = basis.map((b, i) => {
|
|
34095
34533
|
const u = upper[i];
|
|
34096
34534
|
const l = lower[i];
|
|
34097
|
-
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b
|
|
34535
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b : Number.NaN;
|
|
34536
|
+
});
|
|
34537
|
+
const lookback = num3(inputs, "lookback", 125);
|
|
34538
|
+
const squeezeLevel = lowest(values, lookback);
|
|
34539
|
+
const bulgeLevel = highest(values, lookback);
|
|
34540
|
+
const base = str3(inputs, "color", NEUTRAL);
|
|
34541
|
+
const squeezeInk = str3(inputs, "squeezeColor", WARNING);
|
|
34542
|
+
const bulgeInk = str3(inputs, "bulgeColor", BEARISH);
|
|
34543
|
+
const colors = values.map((x, i) => {
|
|
34544
|
+
if (!Number.isFinite(x)) return null;
|
|
34545
|
+
if (x <= squeezeLevel[i]) return squeezeInk;
|
|
34546
|
+
if (x >= bulgeLevel[i]) return bulgeInk;
|
|
34547
|
+
return base;
|
|
34098
34548
|
});
|
|
34099
|
-
|
|
34549
|
+
const plots = [
|
|
34550
|
+
{ key: "bbw", title: "BandWidth", values, color: base, colors },
|
|
34551
|
+
{ key: "zero", title: "Zero Line", values: values.map((x) => Number.isFinite(x) ? 0 : Number.NaN), color: transp(NEUTRAL, 90) }
|
|
34552
|
+
];
|
|
34553
|
+
if (bool(inputs, "showSqueeze", true)) plots.push({ key: "squeeze", title: "Squeeze Level", values: squeezeLevel, color: squeezeInk });
|
|
34554
|
+
if (bool(inputs, "showBulge", true)) plots.push({ key: "bulge", title: "Bulge Level", values: bulgeLevel, color: bulgeInk });
|
|
34555
|
+
const bands = bool(inputs, "gradientFill", true) ? [
|
|
34556
|
+
{
|
|
34557
|
+
key: "bbwFill",
|
|
34558
|
+
from: "bbw",
|
|
34559
|
+
to: "zero",
|
|
34560
|
+
color: transp(NEUTRAL, 100),
|
|
34561
|
+
gradient: values.map((x, i) => {
|
|
34562
|
+
if (!Number.isFinite(x)) return null;
|
|
34563
|
+
const ink = colors[i] ?? base;
|
|
34564
|
+
return { topValue: x, bottomValue: 0, topColor: transp(ink, 50), bottomColor: transp(ink, 100) };
|
|
34565
|
+
})
|
|
34566
|
+
}
|
|
34567
|
+
] : [];
|
|
34568
|
+
return { plots, bands };
|
|
34100
34569
|
}
|
|
34101
34570
|
};
|
|
34102
34571
|
var keltner = {
|
|
@@ -34105,26 +34574,17 @@ var keltner = {
|
|
|
34105
34574
|
shortTitle: "KC",
|
|
34106
34575
|
overlay: true,
|
|
34107
34576
|
inputs: [
|
|
34108
|
-
lengthInput(20),
|
|
34109
|
-
|
|
34110
|
-
|
|
34111
|
-
sourceInput(),
|
|
34112
|
-
|
|
34577
|
+
lengthInput(20, "length", "EMA Length", 5e3, "Number of bars used to compute the exponential moving average basis line."),
|
|
34578
|
+
intInput("atrLength", "ATR Length", 10, 1, 5e3, "Number of bars used to compute the Average True Range (Wilder smoothing)."),
|
|
34579
|
+
floatInput("mult", "Multiplier", 2, 0, 50, 0.1, "ATR multiple used to offset the upper and lower bands from the basis line."),
|
|
34580
|
+
sourceInput("Close", "source", "Source", "Price series used to compute the basis line."),
|
|
34581
|
+
...channelStyleInputs("Basis Color")
|
|
34113
34582
|
],
|
|
34114
34583
|
compute: (bars, inputs) => {
|
|
34115
|
-
const
|
|
34116
|
-
const
|
|
34117
|
-
const
|
|
34118
|
-
|
|
34119
|
-
const ink = str3(inputs, "color", SERIES_LINE);
|
|
34120
|
-
return {
|
|
34121
|
-
plots: [
|
|
34122
|
-
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
34123
|
-
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
34124
|
-
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
34125
|
-
],
|
|
34126
|
-
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
34127
|
-
};
|
|
34584
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20));
|
|
34585
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * num3(inputs, "mult", 2));
|
|
34586
|
+
const ink = { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "basisColor", NEUTRAL) };
|
|
34587
|
+
return directionalChannel(bars, basis, zip(basis, range, (b, r) => b + r), zip(basis, range, (b, r) => b - r), ink, "kc");
|
|
34128
34588
|
}
|
|
34129
34589
|
};
|
|
34130
34590
|
var donchian = {
|
|
@@ -34132,19 +34592,24 @@ var donchian = {
|
|
|
34132
34592
|
title: "Donchian Channels",
|
|
34133
34593
|
shortTitle: "DC",
|
|
34134
34594
|
overlay: true,
|
|
34135
|
-
inputs: [
|
|
34595
|
+
inputs: [
|
|
34596
|
+
lengthInput(20, "length", "Length", 5e3, "Lookback length. The upper and lower channels are the highest high and lowest low over this many bars."),
|
|
34597
|
+
colorInput(BEARISH, "Upper", "upperColor", "Color of the upper channel line."),
|
|
34598
|
+
colorInput(NEUTRAL, "Middle", "middleColor", "Color of the channel midline."),
|
|
34599
|
+
colorInput(BULLISH, "Lower", "lowerColor", "Color of the lower channel line."),
|
|
34600
|
+
colorInput(transp(NEUTRAL, 92), "Fill", "fillColor", "Color of the channel interior fill.")
|
|
34601
|
+
],
|
|
34136
34602
|
compute: (bars, inputs) => {
|
|
34137
34603
|
const len = num3(inputs, "length", 20);
|
|
34138
34604
|
const upper = highest(highs(bars), len);
|
|
34139
34605
|
const lower = lowest(lows(bars), len);
|
|
34140
|
-
const ink = str3(inputs, "color", SERIES_LINE);
|
|
34141
34606
|
return {
|
|
34142
34607
|
plots: [
|
|
34143
|
-
{ key: "
|
|
34144
|
-
{ key: "
|
|
34145
|
-
{ key: "lower", title: "Lower", values: lower, color:
|
|
34608
|
+
{ key: "upper", title: "Upper", values: upper, color: str3(inputs, "upperColor", BEARISH) },
|
|
34609
|
+
{ key: "middle", title: "Middle", kind: "circles", values: zip(upper, lower, (u, l) => (u + l) / 2), color: str3(inputs, "middleColor", NEUTRAL) },
|
|
34610
|
+
{ key: "lower", title: "Lower", values: lower, color: str3(inputs, "lowerColor", BULLISH) }
|
|
34146
34611
|
],
|
|
34147
|
-
bands: [{ key: "dc", from: "upper", to: "lower", color:
|
|
34612
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: str3(inputs, "fillColor", transp(NEUTRAL, 92)) }]
|
|
34148
34613
|
};
|
|
34149
34614
|
}
|
|
34150
34615
|
};
|
|
@@ -34153,16 +34618,37 @@ var chandelier = {
|
|
|
34153
34618
|
title: "Chandelier Exit",
|
|
34154
34619
|
shortTitle: "CE",
|
|
34155
34620
|
overlay: true,
|
|
34156
|
-
inputs: [
|
|
34621
|
+
inputs: [
|
|
34622
|
+
lengthInput(22, "length", "Length", 5e3, "Lookback used for both the highest high / lowest low extremes and the ATR."),
|
|
34623
|
+
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)."),
|
|
34624
|
+
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."),
|
|
34625
|
+
colorInput(BULLISH, "Long Stop", "longColor", "Color of the long trailing stop line."),
|
|
34626
|
+
colorInput(BEARISH, "Short Stop", "shortColor", "Color of the short trailing stop line.")
|
|
34627
|
+
],
|
|
34157
34628
|
compute: (bars, inputs) => {
|
|
34158
34629
|
const len = num3(inputs, "length", 22);
|
|
34159
34630
|
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
34160
|
-
const
|
|
34161
|
-
const
|
|
34631
|
+
const basicLong = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
34632
|
+
const basicShort = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
34633
|
+
let long = basicLong;
|
|
34634
|
+
let short = basicShort;
|
|
34635
|
+
if (bool(inputs, "ratchet", false)) {
|
|
34636
|
+
long = new Array(bars.length).fill(Number.NaN);
|
|
34637
|
+
short = new Array(bars.length).fill(Number.NaN);
|
|
34638
|
+
for (let i = 0; i < bars.length; i++) {
|
|
34639
|
+
const bl = basicLong[i];
|
|
34640
|
+
const bs = basicShort[i];
|
|
34641
|
+
const prevClose = i > 0 ? bars[i - 1].close : bars[i].close;
|
|
34642
|
+
const prevLong = i > 0 && Number.isFinite(long[i - 1]) ? long[i - 1] : bl;
|
|
34643
|
+
const prevShort = i > 0 && Number.isFinite(short[i - 1]) ? short[i - 1] : bs;
|
|
34644
|
+
long[i] = Number.isFinite(bl) ? prevClose > prevLong ? Math.max(bl, prevLong) : bl : Number.NaN;
|
|
34645
|
+
short[i] = Number.isFinite(bs) ? prevClose < prevShort ? Math.min(bs, prevShort) : bs : Number.NaN;
|
|
34646
|
+
}
|
|
34647
|
+
}
|
|
34162
34648
|
return {
|
|
34163
34649
|
plots: [
|
|
34164
|
-
{ key: "long", title: "Long
|
|
34165
|
-
{ key: "short", title: "Short
|
|
34650
|
+
{ key: "long", title: "Long Stop", values: long, color: str3(inputs, "longColor", BULLISH) },
|
|
34651
|
+
{ key: "short", title: "Short Stop", values: short, color: str3(inputs, "shortColor", BEARISH) }
|
|
34166
34652
|
]
|
|
34167
34653
|
};
|
|
34168
34654
|
}
|
|
@@ -34173,21 +34659,22 @@ var chandeKroll = {
|
|
|
34173
34659
|
shortTitle: "CKS",
|
|
34174
34660
|
overlay: true,
|
|
34175
34661
|
inputs: [
|
|
34176
|
-
|
|
34177
|
-
|
|
34178
|
-
|
|
34662
|
+
intInput("p", "ATR Length (P)", 10, 1, 5e3, "Lookback used for both the ATR and the highest high / lowest low price extremes."),
|
|
34663
|
+
floatInput("x", "ATR Multiplier (X)", 1, 0, 50, 0.1, "Multiplier applied to the ATR to offset the preliminary stops from the price extremes."),
|
|
34664
|
+
intInput("q", "Stop Length (Q)", 9, 1, 5e3, "Lookback over which the preliminary stops are extended into the final stop levels."),
|
|
34665
|
+
colorInput(BEARISH, "Stop Short", "shortColor", "Color of the protective stop line for short positions."),
|
|
34666
|
+
colorInput(BULLISH, "Stop Long", "longColor", "Color of the protective stop line for long positions.")
|
|
34179
34667
|
],
|
|
34180
34668
|
compute: (bars, inputs) => {
|
|
34181
34669
|
const p = num3(inputs, "p", 10);
|
|
34182
|
-
const x = num3(inputs, "x", 1);
|
|
34183
34670
|
const q = num3(inputs, "q", 9);
|
|
34184
|
-
const range = map(atr(bars, p), (v) => v * x);
|
|
34671
|
+
const range = map(atr(bars, p), (v) => v * num3(inputs, "x", 1));
|
|
34185
34672
|
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
34186
34673
|
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
34187
34674
|
return {
|
|
34188
34675
|
plots: [
|
|
34189
|
-
{ key: "stopShort", title: "Stop
|
|
34190
|
-
{ key: "stopLong", title: "Stop
|
|
34676
|
+
{ key: "stopShort", title: "Stop Short", values: highest(firstHigh, q), color: str3(inputs, "shortColor", BEARISH) },
|
|
34677
|
+
{ key: "stopLong", title: "Stop Long", values: lowest(firstLow, q), color: str3(inputs, "longColor", BULLISH) }
|
|
34191
34678
|
]
|
|
34192
34679
|
};
|
|
34193
34680
|
}
|
|
@@ -34198,36 +34685,42 @@ var supertrend = {
|
|
|
34198
34685
|
shortTitle: "SuperTrend",
|
|
34199
34686
|
overlay: true,
|
|
34200
34687
|
inputs: [
|
|
34201
|
-
|
|
34202
|
-
|
|
34688
|
+
intInput("atrLength", "ATR Length", 10, 1, 5e3, "Number of bars used to compute the Wilder ATR."),
|
|
34689
|
+
floatInput("mult", "Factor", 3, 0, 50, 0.1, "ATR multiplier setting how far the trailing stop sits from the bar midpoint."),
|
|
34690
|
+
colorInput(BULLISH, "Uptrend Color", "upColor", "Line color while the trend is up."),
|
|
34691
|
+
colorInput(BEARISH, "Downtrend Color", "downColor", "Line color while the trend is down.")
|
|
34203
34692
|
],
|
|
34204
34693
|
compute: (bars, inputs) => {
|
|
34205
|
-
const len = num3(inputs, "atrLength", 10);
|
|
34206
34694
|
const mult = num3(inputs, "mult", 3);
|
|
34207
|
-
const range = atr(bars,
|
|
34695
|
+
const range = atr(bars, num3(inputs, "atrLength", 10));
|
|
34208
34696
|
const n = bars.length;
|
|
34209
|
-
const
|
|
34210
|
-
const
|
|
34211
|
-
let
|
|
34212
|
-
let
|
|
34213
|
-
let trend = 1;
|
|
34697
|
+
const up = new Array(n).fill(Number.NaN);
|
|
34698
|
+
const down = new Array(n).fill(Number.NaN);
|
|
34699
|
+
let finalUpper = Number.NaN;
|
|
34700
|
+
let finalLower = Number.NaN;
|
|
34701
|
+
let trend = -1;
|
|
34214
34702
|
for (let i = 0; i < n; i++) {
|
|
34215
34703
|
const r = range[i];
|
|
34216
34704
|
if (!Number.isFinite(r)) continue;
|
|
34217
34705
|
const b = bars[i];
|
|
34218
34706
|
const mid = (b.high + b.low) / 2;
|
|
34219
|
-
const
|
|
34220
|
-
const
|
|
34707
|
+
const basicUpper = mid + mult * r;
|
|
34708
|
+
const basicLower = mid - mult * r;
|
|
34709
|
+
const prevUpper = Number.isFinite(finalUpper) ? finalUpper : basicUpper;
|
|
34710
|
+
const prevLower = Number.isFinite(finalLower) ? finalLower : basicLower;
|
|
34221
34711
|
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
34222
|
-
|
|
34223
|
-
|
|
34224
|
-
|
|
34225
|
-
|
|
34226
|
-
|
|
34227
|
-
st[i] = trend === 1 ? up : dn;
|
|
34712
|
+
finalUpper = prevClose <= prevUpper ? Math.min(basicUpper, prevUpper) : basicUpper;
|
|
34713
|
+
finalLower = prevClose >= prevLower ? Math.max(basicLower, prevLower) : basicLower;
|
|
34714
|
+
trend = b.close > finalUpper ? 1 : b.close < finalLower ? -1 : trend;
|
|
34715
|
+
if (trend === 1) up[i] = finalLower;
|
|
34716
|
+
else down[i] = finalUpper;
|
|
34228
34717
|
}
|
|
34229
|
-
|
|
34230
|
-
|
|
34718
|
+
return {
|
|
34719
|
+
plots: [
|
|
34720
|
+
{ key: "up", title: "Uptrend", values: up, color: str3(inputs, "upColor", BULLISH) },
|
|
34721
|
+
{ key: "down", title: "Downtrend", values: down, color: str3(inputs, "downColor", BEARISH) }
|
|
34722
|
+
]
|
|
34723
|
+
};
|
|
34231
34724
|
}
|
|
34232
34725
|
};
|
|
34233
34726
|
var alligator = {
|
|
@@ -34236,12 +34729,15 @@ var alligator = {
|
|
|
34236
34729
|
shortTitle: "Alligator",
|
|
34237
34730
|
overlay: true,
|
|
34238
34731
|
inputs: [
|
|
34239
|
-
|
|
34240
|
-
|
|
34241
|
-
|
|
34242
|
-
|
|
34243
|
-
|
|
34244
|
-
|
|
34732
|
+
intInput("jawLength", "Jaw Length", 13, 1, 5e3, "Smoothing length of the Jaw SMMA of median price (hl2), 13 in the classic setup."),
|
|
34733
|
+
intInput("teethLength", "Teeth Length", 8, 1, 5e3, "Smoothing length of the Teeth SMMA of median price (hl2), 8 in the classic setup."),
|
|
34734
|
+
intInput("lipsLength", "Lips Length", 5, 1, 5e3, "Smoothing length of the Lips SMMA of median price (hl2), 5 in the classic setup."),
|
|
34735
|
+
intInput("jawOffset", "Jaw Offset", 8, 0, 100, "Number of bars the Jaw plot is shifted into the future, 8 in the classic setup."),
|
|
34736
|
+
intInput("teethOffset", "Teeth Offset", 5, 0, 100, "Number of bars the Teeth plot is shifted into the future, 5 in the classic setup."),
|
|
34737
|
+
intInput("lipsOffset", "Lips Offset", 3, 0, 100, "Number of bars the Lips plot is shifted into the future, 3 in the classic setup."),
|
|
34738
|
+
colorInput(JAW_COLOR, "Jaw", "jawColor", "Color of the Jaw line."),
|
|
34739
|
+
colorInput(TEETH_COLOR, "Teeth", "teethColor", "Color of the Teeth line."),
|
|
34740
|
+
colorInput(LIPS_COLOR, "Lips", "lipsColor", "Color of the Lips line.")
|
|
34245
34741
|
],
|
|
34246
34742
|
compute: (bars, inputs) => {
|
|
34247
34743
|
const hl2 = sourceValues(bars, "HL2");
|
|
@@ -34250,99 +34746,134 @@ var alligator = {
|
|
|
34250
34746
|
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
34251
34747
|
return {
|
|
34252
34748
|
plots: [
|
|
34253
|
-
{ key: "jaw", title: "Jaw", values: jaw, color:
|
|
34254
|
-
{ key: "teeth", title: "Teeth", values: teeth, color:
|
|
34255
|
-
{ key: "lips", title: "Lips", values: lips, color:
|
|
34749
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: str3(inputs, "jawColor", JAW_COLOR) },
|
|
34750
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: str3(inputs, "teethColor", TEETH_COLOR) },
|
|
34751
|
+
{ key: "lips", title: "Lips", values: lips, color: str3(inputs, "lipsColor", LIPS_COLOR) }
|
|
34256
34752
|
]
|
|
34257
34753
|
};
|
|
34258
34754
|
}
|
|
34259
34755
|
};
|
|
34756
|
+
var GATOR_LINES = "Alligator Lines";
|
|
34757
|
+
var SMMA_TYPE = "SMMA (Wilder)";
|
|
34260
34758
|
var gator = {
|
|
34261
34759
|
type: "gator-oscillator",
|
|
34262
34760
|
title: "Gator Oscillator",
|
|
34263
34761
|
shortTitle: "Gator Oscillator",
|
|
34264
34762
|
overlay: false,
|
|
34265
|
-
inputs: [
|
|
34266
|
-
|
|
34267
|
-
|
|
34268
|
-
|
|
34269
|
-
|
|
34270
|
-
|
|
34763
|
+
inputs: [
|
|
34764
|
+
{ ...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 },
|
|
34765
|
+
{ ...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 },
|
|
34766
|
+
{ ...intInput("jawLength", "Jaw Length", 13, 1, 500, "Averaging length of the jaw line."), group: GATOR_LINES },
|
|
34767
|
+
{ ...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 },
|
|
34768
|
+
{ ...intInput("teethLength", "Teeth Length", 8, 1, 500, "Averaging length of the teeth line."), group: GATOR_LINES },
|
|
34769
|
+
{ ...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 },
|
|
34770
|
+
{ ...intInput("lipsLength", "Lips Length", 5, 1, 500, "Averaging length of the lips line."), group: GATOR_LINES },
|
|
34771
|
+
{ ...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 },
|
|
34772
|
+
colorInput(BULLISH, "Expanding", "growColor", "Column color while a spread grew against its own previous bar \u2014 the averages are spreading apart."),
|
|
34773
|
+
colorInput(BEARISH, "Contracting", "shrinkColor", "Column color while a spread shrank \u2014 the averages are converging. Color encodes expansion, not trade direction."),
|
|
34774
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero line separating the two histograms.")
|
|
34775
|
+
],
|
|
34776
|
+
compute: (bars, inputs) => {
|
|
34777
|
+
const src = sourceValues(bars, str3(inputs, "source", "HL2"));
|
|
34778
|
+
const kind = str3(inputs, "maType", SMMA_TYPE);
|
|
34779
|
+
const smooth = (len) => kind === "SMA" ? sma(src, len) : kind === "EMA" ? ema(src, len) : rma(src, len);
|
|
34780
|
+
const jaw = shift(smooth(num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
34781
|
+
const teeth = shift(smooth(num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
34782
|
+
const lips = shift(smooth(num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
34271
34783
|
const upper = map(sub(jaw, teeth), Math.abs);
|
|
34272
|
-
const lower = map(sub(teeth, lips),
|
|
34273
|
-
const
|
|
34274
|
-
|
|
34275
|
-
|
|
34276
|
-
|
|
34277
|
-
|
|
34784
|
+
const lower = map(sub(teeth, lips), Math.abs);
|
|
34785
|
+
const grow = str3(inputs, "growColor", BULLISH);
|
|
34786
|
+
const shrink = str3(inputs, "shrinkColor", BEARISH);
|
|
34787
|
+
const expansionColors2 = (v) => {
|
|
34788
|
+
let growing = false;
|
|
34789
|
+
return v.map((x, i) => {
|
|
34790
|
+
if (!Number.isFinite(x)) return null;
|
|
34791
|
+
const prev2 = i > 0 ? v[i - 1] : Number.NaN;
|
|
34792
|
+
if (Number.isFinite(prev2)) growing = x > prev2 ? true : x < prev2 ? false : growing;
|
|
34793
|
+
return growing ? grow : shrink;
|
|
34794
|
+
});
|
|
34795
|
+
};
|
|
34278
34796
|
return {
|
|
34279
34797
|
plots: [
|
|
34280
|
-
{ key: "upper", title: "Upper", values: upper, kind: "
|
|
34281
|
-
{ key: "lower", title: "Lower", values: lower, kind: "
|
|
34282
|
-
]
|
|
34798
|
+
{ key: "upper", title: "Upper Histogram (Jaw - Teeth)", values: upper, kind: "columns", color: grow, colors: expansionColors2(upper), base: 0 },
|
|
34799
|
+
{ key: "lower", title: "Lower Histogram (Teeth - Lips)", values: map(lower, (x) => -x), kind: "columns", color: shrink, colors: expansionColors2(lower), base: 0 }
|
|
34800
|
+
],
|
|
34801
|
+
levels: [{ key: "zero", price: 0, color: str3(inputs, "zeroColor", NEUTRAL), lineStyle: "solid", title: "Zero Line" }]
|
|
34283
34802
|
};
|
|
34284
34803
|
}
|
|
34285
34804
|
};
|
|
34286
34805
|
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
34287
34806
|
|
|
34288
34807
|
// src/core/native-indicators/classics/oscillators.ts
|
|
34289
|
-
function
|
|
34290
|
-
|
|
34291
|
-
|
|
34292
|
-
|
|
34808
|
+
function thresholdInputs(obDefault, osDefault, min = 0, max = 100, obTitle = "Overbought Level", osTitle = "Oversold Level") {
|
|
34809
|
+
return [
|
|
34810
|
+
floatInput("overbought", obTitle, obDefault, min, max, 1, "Level above which the oscillator is considered overbought."),
|
|
34811
|
+
floatInput("oversold", osTitle, osDefault, min, max, 1, "Level below which the oscillator is considered oversold.")
|
|
34293
34812
|
];
|
|
34294
|
-
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
34295
|
-
return levels;
|
|
34296
|
-
}
|
|
34297
|
-
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
34298
|
-
function histColors(values) {
|
|
34299
|
-
return values.map((x, i) => {
|
|
34300
|
-
if (!Number.isFinite(x)) return null;
|
|
34301
|
-
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
34302
|
-
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
34303
|
-
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
34304
|
-
return rising ? `${BEARISH}80` : BEARISH;
|
|
34305
|
-
});
|
|
34306
34813
|
}
|
|
34307
34814
|
var rsiSpec = {
|
|
34308
34815
|
type: "rsi",
|
|
34309
34816
|
title: "Relative Strength Index",
|
|
34310
34817
|
shortTitle: "RSI",
|
|
34311
34818
|
overlay: false,
|
|
34312
|
-
inputs: [
|
|
34313
|
-
|
|
34314
|
-
|
|
34315
|
-
|
|
34316
|
-
|
|
34317
|
-
|
|
34318
|
-
|
|
34319
|
-
|
|
34320
|
-
|
|
34321
|
-
|
|
34322
|
-
|
|
34323
|
-
|
|
34819
|
+
inputs: [
|
|
34820
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used in the Wilder smoothing of average gains and losses."),
|
|
34821
|
+
sourceInput("Close", "source", "Source", "Price series the RSI is calculated on."),
|
|
34822
|
+
...thresholdInputs(70, 30, 0, 100, "Overbought", "Oversold"),
|
|
34823
|
+
colorInput(NEUTRAL, "RSI", "color", "Color of the RSI line at the midline. Toward either threshold it blends into that threshold\u2019s color."),
|
|
34824
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level."),
|
|
34825
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
34826
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought, midline and oversold levels."),
|
|
34827
|
+
colorInput(transp(NEUTRAL, 90), "Band Fill", "fillColor", "Color of the fill between the overbought and oversold levels.")
|
|
34828
|
+
],
|
|
34829
|
+
compute: (bars, inputs) => boundedOscillator({
|
|
34830
|
+
key: "rsi",
|
|
34831
|
+
title: "RSI",
|
|
34832
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
34833
|
+
scale: { top: 100, bottom: 0 },
|
|
34834
|
+
overbought: num3(inputs, "overbought", 70),
|
|
34835
|
+
oversold: num3(inputs, "oversold", 30),
|
|
34836
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
34837
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
34838
|
+
width: 2,
|
|
34839
|
+
midline: 50,
|
|
34840
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 90))
|
|
34324
34841
|
})
|
|
34325
34842
|
};
|
|
34843
|
+
function stochasticOutput(k, d, inputs) {
|
|
34844
|
+
const overbought = num3(inputs, "overbought", 80);
|
|
34845
|
+
const oversold = num3(inputs, "oversold", 20);
|
|
34846
|
+
return {
|
|
34847
|
+
plots: [
|
|
34848
|
+
{ key: "k", title: "%K", values: k, color: str3(inputs, "kColor", INFO) },
|
|
34849
|
+
{ key: "d", title: "%D", values: d, color: str3(inputs, "dColor", WARNING) },
|
|
34850
|
+
anchorPlot("obAnchor", "Overbought Anchor", k.length, overbought),
|
|
34851
|
+
anchorPlot("osAnchor", "Oversold Anchor", k.length, oversold)
|
|
34852
|
+
],
|
|
34853
|
+
bands: [{ key: "band", from: "obAnchor", to: "osAnchor", color: str3(inputs, "fillColor", transp(INFO, 90)) }],
|
|
34854
|
+
levels: [
|
|
34855
|
+
{ key: "overbought", price: overbought, color: NEUTRAL, lineStyle: "dashed", title: "Overbought" },
|
|
34856
|
+
{ key: "oversold", price: oversold, color: NEUTRAL, lineStyle: "dashed", title: "Oversold" }
|
|
34857
|
+
]
|
|
34858
|
+
};
|
|
34859
|
+
}
|
|
34326
34860
|
var stochastic = {
|
|
34327
34861
|
type: "stochastic",
|
|
34328
34862
|
title: "Stochastic",
|
|
34329
34863
|
shortTitle: "Stoch",
|
|
34330
34864
|
overlay: false,
|
|
34331
34865
|
inputs: [
|
|
34332
|
-
lengthInput(14, "kLength", "%K
|
|
34333
|
-
lengthInput(
|
|
34334
|
-
lengthInput(3, "dLength", "%D
|
|
34866
|
+
lengthInput(14, "kLength", "%K Length", 5e3, "Number of bars used for the highest high / lowest low range of the raw %K."),
|
|
34867
|
+
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."),
|
|
34868
|
+
lengthInput(3, "dLength", "%D Smoothing", 500, "Simple moving average length of the %D signal line, applied to the smoothed %K."),
|
|
34869
|
+
...thresholdInputs(80, 20),
|
|
34870
|
+
colorInput(INFO, "%K", "kColor", "Color of the %K line."),
|
|
34871
|
+
colorInput(WARNING, "%D", "dColor", "Color of the %D signal line."),
|
|
34872
|
+
colorInput(transp(INFO, 90), "Band Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
34335
34873
|
],
|
|
34336
34874
|
compute: (bars, inputs) => {
|
|
34337
|
-
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing",
|
|
34338
|
-
|
|
34339
|
-
return {
|
|
34340
|
-
plots: [
|
|
34341
|
-
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
34342
|
-
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
34343
|
-
],
|
|
34344
|
-
levels: guideLevels(20, 80)
|
|
34345
|
-
};
|
|
34875
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
34876
|
+
return stochasticOutput(k, sma(k, num3(inputs, "dLength", 3)), inputs);
|
|
34346
34877
|
}
|
|
34347
34878
|
};
|
|
34348
34879
|
var stochasticRsi = {
|
|
@@ -34351,23 +34882,20 @@ var stochasticRsi = {
|
|
|
34351
34882
|
shortTitle: "Stoch RSI",
|
|
34352
34883
|
overlay: false,
|
|
34353
34884
|
inputs: [
|
|
34354
|
-
|
|
34355
|
-
lengthInput(14, "
|
|
34356
|
-
lengthInput(
|
|
34357
|
-
lengthInput(3, "
|
|
34358
|
-
|
|
34885
|
+
sourceInput("Close", "source", "RSI Source", "Price series the RSI is computed on."),
|
|
34886
|
+
lengthInput(14, "rsiLength", "RSI Length", 5e3, "Lookback length of the Wilder RSI."),
|
|
34887
|
+
lengthInput(14, "stochLength", "Stochastic Length", 5e3, "Lookback over which the highest and lowest RSI values are taken."),
|
|
34888
|
+
lengthInput(3, "kSmoothing", "%K Smoothing", 500, "Simple moving average length applied to the stochastic of RSI to form %K."),
|
|
34889
|
+
lengthInput(3, "dLength", "%D Smoothing", 500, "Simple moving average length applied to %K to form the %D signal line."),
|
|
34890
|
+
...thresholdInputs(80, 20),
|
|
34891
|
+
colorInput(INFO, "%K Color", "kColor", "Color of the %K line."),
|
|
34892
|
+
colorInput(WARNING, "%D Color", "dColor", "Color of the %D signal line."),
|
|
34893
|
+
colorInput(transp(INFO, 90), "Zone Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
34359
34894
|
],
|
|
34360
34895
|
compute: (bars, inputs) => {
|
|
34361
34896
|
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
34362
34897
|
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
34363
|
-
|
|
34364
|
-
return {
|
|
34365
|
-
plots: [
|
|
34366
|
-
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
34367
|
-
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
34368
|
-
],
|
|
34369
|
-
levels: guideLevels(20, 80)
|
|
34370
|
-
};
|
|
34898
|
+
return stochasticOutput(k, sma(k, num3(inputs, "dLength", 3)), inputs);
|
|
34371
34899
|
}
|
|
34372
34900
|
};
|
|
34373
34901
|
var macd = {
|
|
@@ -34375,23 +34903,35 @@ var macd = {
|
|
|
34375
34903
|
title: "MACD",
|
|
34376
34904
|
overlay: false,
|
|
34377
34905
|
inputs: [
|
|
34378
|
-
lengthInput(12, "fastLength", "Fast
|
|
34379
|
-
lengthInput(26, "slowLength", "Slow
|
|
34380
|
-
lengthInput(9, "signalLength", "Signal
|
|
34381
|
-
sourceInput()
|
|
34906
|
+
lengthInput(12, "fastLength", "Fast Length", 5e3, "Length of the fast EMA."),
|
|
34907
|
+
lengthInput(26, "slowLength", "Slow Length", 5e3, "Length of the slow EMA."),
|
|
34908
|
+
lengthInput(9, "signalLength", "Signal Smoothing", 500, "Length of the EMA applied to the MACD line to obtain the signal line."),
|
|
34909
|
+
sourceInput("Close", "source", "Source", "Price series used to compute the fast and slow EMAs."),
|
|
34910
|
+
colorInput(INFO, "MACD Line", "color", "Color of the MACD line."),
|
|
34911
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
34912
|
+
colorInput(BULLISH, "Grow Above", "growAboveColor", "Histogram color when above zero and rising."),
|
|
34913
|
+
colorInput(transp(BULLISH, 50), "Fall Above", "fallAboveColor", "Histogram color when above zero and falling."),
|
|
34914
|
+
colorInput(transp(BEARISH, 50), "Grow Below", "growBelowColor", "Histogram color when below zero and rising."),
|
|
34915
|
+
colorInput(BEARISH, "Fall Below", "fallBelowColor", "Histogram color when below zero and falling.")
|
|
34382
34916
|
],
|
|
34383
34917
|
compute: (bars, inputs) => {
|
|
34384
34918
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
34385
34919
|
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
34386
34920
|
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
34387
34921
|
const hist = sub(line, signal);
|
|
34922
|
+
const colors = momentumColumnColors(hist, {
|
|
34923
|
+
growAbove: str3(inputs, "growAboveColor", BULLISH),
|
|
34924
|
+
fallAbove: str3(inputs, "fallAboveColor", transp(BULLISH, 50)),
|
|
34925
|
+
growBelow: str3(inputs, "growBelowColor", transp(BEARISH, 50)),
|
|
34926
|
+
fallBelow: str3(inputs, "fallBelowColor", BEARISH)
|
|
34927
|
+
});
|
|
34388
34928
|
return {
|
|
34389
34929
|
plots: [
|
|
34390
|
-
{ key: "hist", title: "Histogram", values: hist, kind: "
|
|
34391
|
-
{ key: "macd", title: "MACD", values: line, color:
|
|
34392
|
-
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
34930
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "columns", color: BULLISH, colors, base: 0 },
|
|
34931
|
+
{ key: "macd", title: "MACD", values: line, color: str3(inputs, "color", INFO) },
|
|
34932
|
+
{ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) }
|
|
34393
34933
|
],
|
|
34394
|
-
levels:
|
|
34934
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL, lineStyle: "dashed", title: "Zero Line" }]
|
|
34395
34935
|
};
|
|
34396
34936
|
}
|
|
34397
34937
|
};
|
|
@@ -34401,26 +34941,49 @@ var ppo = {
|
|
|
34401
34941
|
shortTitle: "PPO",
|
|
34402
34942
|
overlay: false,
|
|
34403
34943
|
inputs: [
|
|
34404
|
-
|
|
34405
|
-
lengthInput(
|
|
34406
|
-
lengthInput(
|
|
34407
|
-
|
|
34944
|
+
sourceInput("Close", "source", "Source", "Price series fed into the fast and slow averages."),
|
|
34945
|
+
lengthInput(12, "fastLength", "Fast Length", 5e3, "Length of the fast moving average."),
|
|
34946
|
+
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."),
|
|
34947
|
+
optionInput("maType", "Average Type", "EMA", ["EMA", "SMA"], "Moving average type used for the fast and slow averages."),
|
|
34948
|
+
lengthInput(9, "signalLength", "Signal Smoothing", 500, "Length of the signal line that smooths the PPO line."),
|
|
34949
|
+
optionInput("signalType", "Signal Type", "EMA", ["EMA", "SMA"], "Moving average type used for the signal line."),
|
|
34950
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the PPO line, gradient fill and histogram above the zero line."),
|
|
34951
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the PPO line, gradient fill and histogram below the zero line."),
|
|
34952
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
34953
|
+
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."),
|
|
34954
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the zero line and the PPO line with a vertical gradient.")
|
|
34408
34955
|
],
|
|
34409
34956
|
compute: (bars, inputs) => {
|
|
34410
34957
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
34411
|
-
const
|
|
34412
|
-
const
|
|
34958
|
+
const average = (kind2, v, len) => kind2 === "SMA" ? sma(v, len) : ema(v, len);
|
|
34959
|
+
const kind = str3(inputs, "maType", "EMA");
|
|
34960
|
+
const fast = average(kind, src, num3(inputs, "fastLength", 12));
|
|
34961
|
+
const slow = average(kind, src, num3(inputs, "slowLength", 26));
|
|
34413
34962
|
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
34414
|
-
const signal =
|
|
34963
|
+
const signal = average(str3(inputs, "signalType", "EMA"), line, num3(inputs, "signalLength", 9));
|
|
34415
34964
|
const hist = sub(line, signal);
|
|
34416
|
-
|
|
34417
|
-
|
|
34418
|
-
|
|
34419
|
-
|
|
34420
|
-
|
|
34421
|
-
|
|
34422
|
-
|
|
34423
|
-
|
|
34965
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
34966
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
34967
|
+
const out = centeredOscillator({
|
|
34968
|
+
key: "ppo",
|
|
34969
|
+
title: "PPO Line",
|
|
34970
|
+
values: line,
|
|
34971
|
+
ink: { bull, bear, neutral: bear },
|
|
34972
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
34973
|
+
fill: bool(inputs, "gradientFill", true),
|
|
34974
|
+
extraPlots: [{ key: "signal", title: "Signal Line", values: signal, color: str3(inputs, "signalColor", WARNING) }]
|
|
34975
|
+
});
|
|
34976
|
+
if (bool(inputs, "showHistogram", true)) {
|
|
34977
|
+
const colors = hist.map((x, i) => {
|
|
34978
|
+
if (!Number.isFinite(x)) return null;
|
|
34979
|
+
const prev2 = i > 0 && Number.isFinite(hist[i - 1]) ? hist[i - 1] : x;
|
|
34980
|
+
const ink = x >= 0 ? bull : bear;
|
|
34981
|
+
const expanding = x >= 0 ? x > prev2 : x < prev2;
|
|
34982
|
+
return transp(ink, expanding ? 40 : 70);
|
|
34983
|
+
});
|
|
34984
|
+
out.plots.unshift({ key: "hist", title: "Histogram", values: hist, kind: "columns", color: bull, colors, base: 0 });
|
|
34985
|
+
}
|
|
34986
|
+
return out;
|
|
34424
34987
|
}
|
|
34425
34988
|
};
|
|
34426
34989
|
var awesome = {
|
|
@@ -34428,48 +34991,67 @@ var awesome = {
|
|
|
34428
34991
|
title: "Awesome Oscillator",
|
|
34429
34992
|
shortTitle: "AO",
|
|
34430
34993
|
overlay: false,
|
|
34431
|
-
inputs: [
|
|
34432
|
-
|
|
34994
|
+
inputs: [
|
|
34995
|
+
colorInput(BULLISH, "Rising", "risingColor", "Histogram color when the oscillator is higher than on the previous bar."),
|
|
34996
|
+
colorInput(BEARISH, "Falling", "fallingColor", "Histogram color when the oscillator is lower than or equal to the previous bar."),
|
|
34997
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
34998
|
+
],
|
|
34999
|
+
compute: (bars, inputs) => {
|
|
34433
35000
|
const hl2 = sourceValues(bars, "HL2");
|
|
34434
35001
|
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
35002
|
+
const rising = str3(inputs, "risingColor", BULLISH);
|
|
35003
|
+
const falling = str3(inputs, "fallingColor", BEARISH);
|
|
34435
35004
|
const colors = ao.map((x, i) => {
|
|
34436
35005
|
if (!Number.isFinite(x)) return null;
|
|
34437
35006
|
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
34438
|
-
return Number.isFinite(prev2) && x
|
|
35007
|
+
return Number.isFinite(prev2) && x > prev2 ? rising : falling;
|
|
34439
35008
|
});
|
|
34440
35009
|
return {
|
|
34441
|
-
plots: [{ key: "ao", title: "AO", values: ao, kind: "
|
|
34442
|
-
levels:
|
|
35010
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "columns", color: rising, colors, base: 0 }],
|
|
35011
|
+
levels: [{ key: "zero", price: 0, color: str3(inputs, "zeroColor", NEUTRAL), lineStyle: "dashed", title: "Zero Line" }]
|
|
34443
35012
|
};
|
|
34444
35013
|
}
|
|
34445
35014
|
};
|
|
35015
|
+
var CCI_SCALING = 0.015;
|
|
34446
35016
|
var cci = {
|
|
34447
35017
|
type: "commodity-channel-index",
|
|
34448
35018
|
title: "Commodity Channel Index",
|
|
34449
35019
|
shortTitle: "CCI",
|
|
34450
35020
|
overlay: false,
|
|
34451
|
-
inputs: [
|
|
35021
|
+
inputs: [
|
|
35022
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used for the moving average and the mean absolute deviation of the source."),
|
|
35023
|
+
sourceInput("HLC3", "source", "Source", "Price series measured by the CCI; the typical price hlc3 is the classic choice."),
|
|
35024
|
+
floatInput("upper", "Upper Level", 100, -1e3, 1e3, 10, "Overbought reference level, +100 classically."),
|
|
35025
|
+
floatInput("lower", "Lower Level", -100, -1e3, 1e3, 10, "Oversold reference level, \u2212100 classically."),
|
|
35026
|
+
colorInput(NEUTRAL, "CCI Line", "color", "Color of the CCI line."),
|
|
35027
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the CCI reads above zero."),
|
|
35028
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the CCI reads below zero."),
|
|
35029
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the upper, lower and zero reference lines.")
|
|
35030
|
+
],
|
|
34452
35031
|
compute: (bars, inputs) => {
|
|
34453
35032
|
const len = num3(inputs, "length", 20);
|
|
34454
35033
|
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
34455
35034
|
const basis = sma(src, len);
|
|
34456
|
-
const dev =
|
|
34457
|
-
for (let i = len - 1; i < src.length; i++) {
|
|
34458
|
-
const mean = basis[i];
|
|
34459
|
-
if (!Number.isFinite(mean)) continue;
|
|
34460
|
-
let d = 0;
|
|
34461
|
-
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
34462
|
-
dev[i] = d / len;
|
|
34463
|
-
}
|
|
35035
|
+
const dev = meanDev(src, len);
|
|
34464
35036
|
const values = src.map((x, i) => {
|
|
34465
35037
|
const b = basis[i];
|
|
34466
35038
|
const d = dev[i];
|
|
34467
|
-
|
|
35039
|
+
if (!Number.isFinite(b) || !Number.isFinite(d)) return Number.NaN;
|
|
35040
|
+
return d === 0 ? 0 : (x - b) / (CCI_SCALING * d);
|
|
35041
|
+
});
|
|
35042
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
35043
|
+
return centeredOscillator({
|
|
35044
|
+
key: "cci",
|
|
35045
|
+
title: "CCI",
|
|
35046
|
+
values,
|
|
35047
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
35048
|
+
zeroInk: levelInk,
|
|
35049
|
+
zeroLineStyle: "dotted",
|
|
35050
|
+
extraLevels: [
|
|
35051
|
+
{ key: "upper", price: num3(inputs, "upper", 100), color: levelInk, lineStyle: "dashed", title: "Upper Level" },
|
|
35052
|
+
{ key: "lower", price: num3(inputs, "lower", -100), color: levelInk, lineStyle: "dashed", title: "Lower Level" }
|
|
35053
|
+
]
|
|
34468
35054
|
});
|
|
34469
|
-
return {
|
|
34470
|
-
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
34471
|
-
levels: guideLevels(-100, 100, 0)
|
|
34472
|
-
};
|
|
34473
35055
|
}
|
|
34474
35056
|
};
|
|
34475
35057
|
var williamsR = {
|
|
@@ -34477,31 +35059,78 @@ var williamsR = {
|
|
|
34477
35059
|
title: "Williams %R",
|
|
34478
35060
|
shortTitle: "%R",
|
|
34479
35061
|
overlay: false,
|
|
34480
|
-
inputs: [
|
|
35062
|
+
inputs: [
|
|
35063
|
+
lengthInput(14, "length", "Length", 5e3, "Lookback length of the highest high / lowest low range. 14 is the classic setting."),
|
|
35064
|
+
sourceInput("Close", "source", "Source", "Series located within the lookback range. Close is the classic choice."),
|
|
35065
|
+
...thresholdInputs(-20, -80, -100, 0),
|
|
35066
|
+
colorInput(NEUTRAL, "%R Line", "color", "Color of the %R line between the thresholds. Toward either level it blends into that level\u2019s color."),
|
|
35067
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
35068
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level.")
|
|
35069
|
+
],
|
|
34481
35070
|
compute: (bars, inputs) => {
|
|
34482
|
-
const
|
|
34483
|
-
|
|
34484
|
-
|
|
34485
|
-
|
|
34486
|
-
|
|
35071
|
+
const len = num3(inputs, "length", 14);
|
|
35072
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
35073
|
+
const hh = highest(highs(bars), len);
|
|
35074
|
+
const ll = lowest(lows(bars), len);
|
|
35075
|
+
const values = src.map((x, i) => {
|
|
35076
|
+
const h = hh[i];
|
|
35077
|
+
const l = ll[i];
|
|
35078
|
+
return Number.isFinite(h) && Number.isFinite(l) && h > l ? -100 * (h - x) / (h - l) : Number.NaN;
|
|
35079
|
+
});
|
|
35080
|
+
return boundedOscillator({
|
|
35081
|
+
key: "wr",
|
|
35082
|
+
title: "%R",
|
|
35083
|
+
values,
|
|
35084
|
+
scale: { top: 0, bottom: -100 },
|
|
35085
|
+
overbought: num3(inputs, "overbought", -20),
|
|
35086
|
+
oversold: num3(inputs, "oversold", -80),
|
|
35087
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
35088
|
+
levelInk: NEUTRAL
|
|
35089
|
+
});
|
|
34487
35090
|
}
|
|
34488
35091
|
};
|
|
34489
|
-
var
|
|
35092
|
+
var cmoSpec = {
|
|
34490
35093
|
type: "chande-momentum-oscillator",
|
|
34491
35094
|
title: "Chande Momentum Oscillator",
|
|
34492
35095
|
shortTitle: "CMO",
|
|
34493
35096
|
overlay: false,
|
|
34494
|
-
inputs: [
|
|
35097
|
+
inputs: [
|
|
35098
|
+
lengthInput(9, "length", "Length", 5e3, "Number of bars over which up moves and down moves are summed."),
|
|
35099
|
+
sourceInput("Close", "source", "Source", "Price series used to measure bar-to-bar changes."),
|
|
35100
|
+
floatInput("overbought", "Overbought Level", 50, 0, 100, 1, "Level above which the oscillator is considered overbought."),
|
|
35101
|
+
floatInput("oversold", "Oversold Level", -50, -100, 0, 1, "Level below which the oscillator is considered oversold."),
|
|
35102
|
+
colorInput(NEUTRAL, "CMO Line", "color", "Color of the CMO line."),
|
|
35103
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while momentum is positive, and shading of the oversold zone."),
|
|
35104
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while momentum is negative, and shading of the overbought zone."),
|
|
35105
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought, oversold, and zero lines.")
|
|
35106
|
+
],
|
|
34495
35107
|
compute: (bars, inputs) => {
|
|
34496
35108
|
const len = num3(inputs, "length", 9);
|
|
34497
35109
|
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
34498
|
-
const
|
|
34499
|
-
const
|
|
34500
|
-
const values = zip(
|
|
34501
|
-
|
|
34502
|
-
|
|
34503
|
-
|
|
34504
|
-
|
|
35110
|
+
const up = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
35111
|
+
const down = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
35112
|
+
const values = zip(up, down, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
35113
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
35114
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
35115
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
35116
|
+
const overbought = num3(inputs, "overbought", 50);
|
|
35117
|
+
const oversold = num3(inputs, "oversold", -50);
|
|
35118
|
+
const out = centeredOscillator({
|
|
35119
|
+
key: "cmo",
|
|
35120
|
+
title: "CMO",
|
|
35121
|
+
values,
|
|
35122
|
+
ink: { bull, bear, neutral: str3(inputs, "color", NEUTRAL) },
|
|
35123
|
+
zeroInk: levelInk,
|
|
35124
|
+
zeroLineStyle: "dotted",
|
|
35125
|
+
extraLevels: [
|
|
35126
|
+
{ key: "overbought", price: overbought, color: levelInk, lineStyle: "dashed", title: "Overbought" },
|
|
35127
|
+
{ key: "oversold", price: oversold, color: levelInk, lineStyle: "dashed", title: "Oversold" }
|
|
35128
|
+
]
|
|
35129
|
+
});
|
|
35130
|
+
const zones = thresholdZones(values.length, { overbought, oversold, top: 100, bottom: -100 }, { overbought: bear, oversold: bull });
|
|
35131
|
+
out.plots.push(...zones.plots);
|
|
35132
|
+
out.bands = [...out.bands ?? [], ...zones.bands];
|
|
35133
|
+
return out;
|
|
34505
35134
|
}
|
|
34506
35135
|
};
|
|
34507
35136
|
var connorsRsi = {
|
|
@@ -34510,10 +35139,14 @@ var connorsRsi = {
|
|
|
34510
35139
|
shortTitle: "CRSI",
|
|
34511
35140
|
overlay: false,
|
|
34512
35141
|
inputs: [
|
|
34513
|
-
lengthInput(3, "rsiLength", "RSI
|
|
34514
|
-
lengthInput(2, "streakLength", "Streak RSI
|
|
34515
|
-
lengthInput(100, "rankLength", "Percent
|
|
34516
|
-
|
|
35142
|
+
lengthInput(3, "rsiLength", "RSI Length", 5e3, "Length of the Wilder RSI applied to the closing price."),
|
|
35143
|
+
lengthInput(2, "streakLength", "Streak RSI Length", 5e3, "Length of the Wilder RSI applied to the up/down close streak."),
|
|
35144
|
+
lengthInput(100, "rankLength", "Percent Rank Length", 5e3, "Lookback of the percent rank applied to the 1-bar rate of change of the closing price."),
|
|
35145
|
+
...thresholdInputs(90, 10),
|
|
35146
|
+
colorInput(NEUTRAL, "CRSI Line", "color", "Color of the Connors RSI line."),
|
|
35147
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Line, fill and zone color toward and below the oversold level."),
|
|
35148
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Line, fill and zone color toward and above the overbought level."),
|
|
35149
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought and oversold levels.")
|
|
34517
35150
|
],
|
|
34518
35151
|
compute: (bars, inputs) => {
|
|
34519
35152
|
const c = closes(bars);
|
|
@@ -34531,10 +35164,17 @@ var connorsRsi = {
|
|
|
34531
35164
|
const z = r[i];
|
|
34532
35165
|
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
34533
35166
|
});
|
|
34534
|
-
return {
|
|
34535
|
-
|
|
34536
|
-
|
|
34537
|
-
|
|
35167
|
+
return boundedOscillator({
|
|
35168
|
+
key: "crsi",
|
|
35169
|
+
title: "Connors RSI",
|
|
35170
|
+
values,
|
|
35171
|
+
scale: { top: 100, bottom: 0 },
|
|
35172
|
+
overbought: num3(inputs, "overbought", 90),
|
|
35173
|
+
oversold: num3(inputs, "oversold", 10),
|
|
35174
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
35175
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
35176
|
+
midpointWash: true
|
|
35177
|
+
});
|
|
34538
35178
|
}
|
|
34539
35179
|
};
|
|
34540
35180
|
var fisher = {
|
|
@@ -34542,7 +35182,13 @@ var fisher = {
|
|
|
34542
35182
|
title: "Fisher Transform",
|
|
34543
35183
|
shortTitle: "Fisher",
|
|
34544
35184
|
overlay: false,
|
|
34545
|
-
inputs: [
|
|
35185
|
+
inputs: [
|
|
35186
|
+
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."),
|
|
35187
|
+
colorInput(NEUTRAL, "Fisher Line", "color", "Color of the Fisher line when it sits exactly on its trigger."),
|
|
35188
|
+
colorInput(NEUTRAL, "Trigger Line", "triggerColor", "Color of the trigger line, the Fisher value delayed one bar."),
|
|
35189
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the Fisher line and fill while it is above the trigger."),
|
|
35190
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the Fisher line and fill while it is below the trigger.")
|
|
35191
|
+
],
|
|
34546
35192
|
compute: (bars, inputs) => {
|
|
34547
35193
|
const len = num3(inputs, "length", 9);
|
|
34548
35194
|
const hl2 = sourceValues(bars, "HL2");
|
|
@@ -34550,23 +35196,39 @@ var fisher = {
|
|
|
34550
35196
|
const lo = lowest(hl2, len);
|
|
34551
35197
|
const n = bars.length;
|
|
34552
35198
|
const out = new Array(n).fill(Number.NaN);
|
|
35199
|
+
let position = 0;
|
|
34553
35200
|
let value = 0;
|
|
34554
|
-
let fish = 0;
|
|
34555
35201
|
for (let i = 0; i < n; i++) {
|
|
34556
35202
|
const h = hi[i];
|
|
34557
35203
|
const l = lo[i];
|
|
34558
35204
|
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
34559
|
-
const
|
|
34560
|
-
|
|
34561
|
-
|
|
34562
|
-
|
|
34563
|
-
|
|
35205
|
+
const raw = h > l ? (hl2[i] - l) / (h - l) - 0.5 : 0;
|
|
35206
|
+
position = 0.66 * raw + 0.67 * position;
|
|
35207
|
+
position = position > 0.99 ? 0.999 : position < -0.99 ? -0.999 : position;
|
|
35208
|
+
value = 0.5 * Math.log((1 + position) / (1 - position)) + 0.5 * value;
|
|
35209
|
+
out[i] = value;
|
|
35210
|
+
}
|
|
35211
|
+
const trigger = shift(out, 1);
|
|
35212
|
+
const neutral = str3(inputs, "color", NEUTRAL);
|
|
35213
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
35214
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
35215
|
+
const inkOf = (i) => {
|
|
35216
|
+
const f = out[i];
|
|
35217
|
+
const t = trigger[i];
|
|
35218
|
+
if (!Number.isFinite(t)) return neutral;
|
|
35219
|
+
return f > t ? bull : f < t ? bear : neutral;
|
|
35220
|
+
};
|
|
34564
35221
|
return {
|
|
34565
35222
|
plots: [
|
|
34566
|
-
{ key: "fisher", title: "Fisher", values: out, color:
|
|
34567
|
-
{ key: "trigger", title: "Trigger", values:
|
|
35223
|
+
{ key: "fisher", title: "Fisher", values: out, color: neutral, width: 2, colors: out.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) },
|
|
35224
|
+
{ key: "trigger", title: "Trigger", values: trigger, color: str3(inputs, "triggerColor", NEUTRAL) }
|
|
34568
35225
|
],
|
|
34569
|
-
|
|
35226
|
+
bands: [{ key: "spread", from: "fisher", to: "trigger", color: transp(neutral, 80), colors: out.map((x, i) => Number.isFinite(x) ? transp(inkOf(i), 80) : null) }],
|
|
35227
|
+
levels: [
|
|
35228
|
+
{ key: "upper", price: 1.5, color: NEUTRAL, lineStyle: "dashed", title: "Upper Level" },
|
|
35229
|
+
{ key: "lower", price: -1.5, color: NEUTRAL, lineStyle: "dashed", title: "Lower Level" },
|
|
35230
|
+
{ key: "zero", price: 0, color: NEUTRAL, lineStyle: "dotted", title: "Zero Line" }
|
|
35231
|
+
]
|
|
34570
35232
|
};
|
|
34571
35233
|
}
|
|
34572
35234
|
};
|
|
@@ -34574,93 +35236,180 @@ var trix = {
|
|
|
34574
35236
|
type: "trix",
|
|
34575
35237
|
title: "TRIX",
|
|
34576
35238
|
overlay: false,
|
|
34577
|
-
inputs: [
|
|
35239
|
+
inputs: [
|
|
35240
|
+
lengthInput(18, "length", "Length", 5e3, "Length of each of the three EMA smoothing passes applied to the close price."),
|
|
35241
|
+
lengthInput(9, "signalLength", "Signal Length", 500, "Length of the EMA applied to TRIX to form the signal line."),
|
|
35242
|
+
colorInput(NEUTRAL, "TRIX Color", "color", "Color of the TRIX line."),
|
|
35243
|
+
colorInput(WARNING, "Signal Color", "signalColor", "Color of the signal line."),
|
|
35244
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the TRIX line and its gradient fill while TRIX holds above zero."),
|
|
35245
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the TRIX line and its gradient fill while TRIX holds below zero.")
|
|
35246
|
+
],
|
|
34578
35247
|
compute: (bars, inputs) => {
|
|
34579
35248
|
const len = num3(inputs, "length", 18);
|
|
34580
|
-
const
|
|
34581
|
-
const
|
|
34582
|
-
|
|
34583
|
-
|
|
34584
|
-
|
|
34585
|
-
|
|
34586
|
-
|
|
35249
|
+
const smooth = ema(ema(ema(closes(bars), len), len), len);
|
|
35250
|
+
const values = roc(smooth, 1);
|
|
35251
|
+
return centeredOscillator({
|
|
35252
|
+
key: "trix",
|
|
35253
|
+
title: "TRIX",
|
|
35254
|
+
values,
|
|
35255
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
35256
|
+
zeroInk: NEUTRAL,
|
|
35257
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(values, num3(inputs, "signalLength", 9)), color: str3(inputs, "signalColor", WARNING) }]
|
|
35258
|
+
});
|
|
34587
35259
|
}
|
|
34588
35260
|
};
|
|
34589
|
-
function
|
|
35261
|
+
function tsiRatio(src, long, short) {
|
|
34590
35262
|
const mom = change(src);
|
|
34591
35263
|
const numer = ema(ema(mom, long), short);
|
|
34592
35264
|
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
34593
|
-
return zip(numer, denom, (a, b) => b === 0 ? 0 :
|
|
35265
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : a / b);
|
|
34594
35266
|
}
|
|
34595
35267
|
var tsi = {
|
|
34596
35268
|
type: "true-strength-index",
|
|
34597
35269
|
title: "True Strength Index",
|
|
34598
35270
|
shortTitle: "TSI",
|
|
34599
35271
|
overlay: false,
|
|
34600
|
-
inputs: [
|
|
35272
|
+
inputs: [
|
|
35273
|
+
lengthInput(25, "longLength", "Long Length", 5e3, "Length of the first (long) EMA applied to the 1-bar price change."),
|
|
35274
|
+
lengthInput(13, "shortLength", "Short Length", 5e3, "Length of the second (short) EMA applied to the long-smoothed price change."),
|
|
35275
|
+
lengthInput(13, "signalLength", "Signal Length", 500, "Length of the EMA of TSI used as the signal line."),
|
|
35276
|
+
colorInput(NEUTRAL, "TSI", "color", "Color of the TSI line."),
|
|
35277
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line."),
|
|
35278
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the TSI line and its gradient fill while TSI holds above zero."),
|
|
35279
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the TSI line and its gradient fill while TSI holds below zero.")
|
|
35280
|
+
],
|
|
34601
35281
|
compute: (bars, inputs) => {
|
|
34602
|
-
const
|
|
34603
|
-
return {
|
|
34604
|
-
|
|
34605
|
-
|
|
34606
|
-
|
|
34607
|
-
|
|
34608
|
-
|
|
34609
|
-
|
|
35282
|
+
const values = map(tsiRatio(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13)), (x) => 100 * x);
|
|
35283
|
+
return centeredOscillator({
|
|
35284
|
+
key: "tsi",
|
|
35285
|
+
title: "TSI",
|
|
35286
|
+
values,
|
|
35287
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
35288
|
+
zeroInk: NEUTRAL,
|
|
35289
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(values, num3(inputs, "signalLength", 13)), color: str3(inputs, "signalColor", WARNING) }]
|
|
35290
|
+
});
|
|
34610
35291
|
}
|
|
34611
35292
|
};
|
|
35293
|
+
var SMI_INDICATOR = "Indicator";
|
|
35294
|
+
var SMI_OSCILLATOR = "Oscillator";
|
|
34612
35295
|
var smiErgodic = {
|
|
34613
35296
|
type: "smi-ergodic",
|
|
34614
35297
|
title: "SMI Ergodic Oscillator",
|
|
34615
35298
|
shortTitle: "SMIE",
|
|
34616
35299
|
overlay: false,
|
|
34617
|
-
inputs: [
|
|
35300
|
+
inputs: [
|
|
35301
|
+
lengthInput(20, "longLength", "Long Length", 5e3, "Length of the first EMA smoothing applied to the momentum of the closing price."),
|
|
35302
|
+
lengthInput(5, "shortLength", "Short Length", 5e3, "Length of the second EMA smoothing applied to the momentum of the closing price."),
|
|
35303
|
+
lengthInput(5, "signalLength", "Signal Length", 500, "Length of the EMA of the SMI that forms the signal line."),
|
|
35304
|
+
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."),
|
|
35305
|
+
colorInput(NEUTRAL, "SMI", "color", "Color of the SMI line while it sits exactly on its signal line."),
|
|
35306
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line."),
|
|
35307
|
+
colorInput(BULLISH, "Oscillator Grow", "growColor", "Color of the rising oscillator histogram, and of the SMI line while it leads its signal."),
|
|
35308
|
+
colorInput(BEARISH, "Oscillator Fall", "fallColor", "Color of the falling oscillator histogram, and of the SMI line while it lags its signal.")
|
|
35309
|
+
],
|
|
34618
35310
|
compute: (bars, inputs) => {
|
|
34619
|
-
const
|
|
34620
|
-
const signal = ema(
|
|
34621
|
-
const
|
|
35311
|
+
const smi = tsiRatio(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
35312
|
+
const signal = ema(smi, num3(inputs, "signalLength", 5));
|
|
35313
|
+
const grow = str3(inputs, "growColor", BULLISH);
|
|
35314
|
+
const fall = str3(inputs, "fallColor", BEARISH);
|
|
35315
|
+
const zero = [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed", title: "Zero" }];
|
|
35316
|
+
if (str3(inputs, "display", SMI_INDICATOR) === SMI_OSCILLATOR) {
|
|
35317
|
+
const osc = sub(smi, signal);
|
|
35318
|
+
const colors = osc.map((x, i) => {
|
|
35319
|
+
if (!Number.isFinite(x)) return null;
|
|
35320
|
+
const prev2 = i > 0 && Number.isFinite(osc[i - 1]) ? osc[i - 1] : x;
|
|
35321
|
+
return x > prev2 ? grow : fall;
|
|
35322
|
+
});
|
|
35323
|
+
return { plots: [{ key: "osc", title: "Oscillator", values: osc, kind: "columns", color: grow, colors, base: 0 }], levels: zero };
|
|
35324
|
+
}
|
|
35325
|
+
const neutral = str3(inputs, "color", NEUTRAL);
|
|
35326
|
+
const inkOf = (i) => {
|
|
35327
|
+
const a = smi[i];
|
|
35328
|
+
const b = signal[i];
|
|
35329
|
+
if (!Number.isFinite(b)) return neutral;
|
|
35330
|
+
return a > b ? grow : a < b ? fall : neutral;
|
|
35331
|
+
};
|
|
34622
35332
|
return {
|
|
34623
35333
|
plots: [
|
|
34624
|
-
{ key: "
|
|
34625
|
-
{ key: "
|
|
34626
|
-
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
35334
|
+
{ key: "smi", title: "SMI", values: smi, color: neutral, colors: smi.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) },
|
|
35335
|
+
{ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) }
|
|
34627
35336
|
],
|
|
34628
|
-
|
|
35337
|
+
bands: [{ key: "spread", from: "smi", to: "signal", color: transp(neutral, 75), colors: smi.map((x, i) => Number.isFinite(x) ? transp(inkOf(i), 75) : null) }],
|
|
35338
|
+
levels: zero
|
|
34629
35339
|
};
|
|
34630
35340
|
}
|
|
34631
35341
|
};
|
|
35342
|
+
var STC_MID = 50;
|
|
35343
|
+
var TRIGGERS = "Trigger Levels";
|
|
34632
35344
|
var stc = {
|
|
34633
35345
|
type: "schaff-trend-cycle",
|
|
34634
35346
|
title: "Schaff Trend Cycle",
|
|
34635
35347
|
shortTitle: "Schaff Trend Cycle",
|
|
34636
35348
|
overlay: false,
|
|
34637
|
-
inputs: [
|
|
35349
|
+
inputs: [
|
|
35350
|
+
sourceInput("Close", "source", "Source", "Price series used to build the MACD that the trend cycle is measured on."),
|
|
35351
|
+
lengthInput(23, "fastLength", "Fast Length", 5e3, "Fast EMA length of the underlying MACD."),
|
|
35352
|
+
lengthInput(50, "slowLength", "Slow Length", 5e3, "Slow EMA length of the underlying MACD."),
|
|
35353
|
+
{ ...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 },
|
|
35354
|
+
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."),
|
|
35355
|
+
{ ...floatInput("upper", "Upper Trigger", 75, 0, 100, 1, "Upper trigger line. A cross down through it flags an emerging downtrend."), group: TRIGGERS },
|
|
35356
|
+
{ ...floatInput("lower", "Lower Trigger", 25, 0, 100, 1, "Lower trigger line. A cross up through it flags an emerging uptrend."), group: TRIGGERS },
|
|
35357
|
+
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."),
|
|
35358
|
+
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."),
|
|
35359
|
+
colorInput(NEUTRAL, "Levels Color", "levelsColor", "Color of the dashed trigger lines and the dotted midline."),
|
|
35360
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Vertical gradient between the midline and the trend cycle, fading out toward the midline.")
|
|
35361
|
+
],
|
|
34638
35362
|
compute: (bars, inputs) => {
|
|
34639
35363
|
const len = num3(inputs, "cycleLength", 10);
|
|
34640
|
-
const
|
|
35364
|
+
const factor = num3(inputs, "factor", 0.5);
|
|
35365
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
34641
35366
|
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
34642
|
-
const
|
|
35367
|
+
const stochNorm = (v) => {
|
|
34643
35368
|
const lo = lowest(v, len);
|
|
34644
35369
|
const hi = highest(v, len);
|
|
34645
35370
|
const out = new Array(v.length).fill(Number.NaN);
|
|
34646
|
-
let
|
|
34647
|
-
let smoothed = Number.NaN;
|
|
35371
|
+
let pct = Number.NaN;
|
|
34648
35372
|
for (let i = 0; i < v.length; i++) {
|
|
34649
35373
|
const x = v[i];
|
|
34650
35374
|
const l = lo[i];
|
|
34651
35375
|
const h = hi[i];
|
|
34652
35376
|
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
34653
|
-
|
|
34654
|
-
|
|
34655
|
-
|
|
35377
|
+
if (h > l) pct = 100 * (x - l) / (h - l);
|
|
35378
|
+
out[i] = pct;
|
|
35379
|
+
}
|
|
35380
|
+
return out;
|
|
35381
|
+
};
|
|
35382
|
+
const smoothStep = (v) => {
|
|
35383
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
35384
|
+
let smoothed = Number.NaN;
|
|
35385
|
+
for (let i = 0; i < v.length; i++) {
|
|
35386
|
+
const x = v[i];
|
|
35387
|
+
if (!Number.isFinite(x)) continue;
|
|
35388
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + factor * (x - smoothed) : x;
|
|
34656
35389
|
out[i] = smoothed;
|
|
34657
35390
|
}
|
|
34658
35391
|
return out;
|
|
34659
35392
|
};
|
|
34660
|
-
const values =
|
|
35393
|
+
const values = smoothStep(stochNorm(smoothStep(stochNorm(macdLine))));
|
|
35394
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
35395
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
35396
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
35397
|
+
const plots = [
|
|
35398
|
+
{ key: "stc", title: "Schaff Trend Cycle", values, color: bear, width: 2, colors: values.map((x) => Number.isFinite(x) ? x > STC_MID ? bull : bear : null) }
|
|
35399
|
+
];
|
|
35400
|
+
const bands = [];
|
|
35401
|
+
if (bool(inputs, "gradientFill", true)) {
|
|
35402
|
+
plots.push(anchorPlot("midAnchor", "Midline Anchor", values.length, STC_MID));
|
|
35403
|
+
bands.push({ key: "wash", from: "stc", to: "midAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, STC_MID, (x) => x > STC_MID ? bull : bear) });
|
|
35404
|
+
}
|
|
34661
35405
|
return {
|
|
34662
|
-
plots
|
|
34663
|
-
|
|
35406
|
+
plots,
|
|
35407
|
+
bands,
|
|
35408
|
+
levels: [
|
|
35409
|
+
{ key: "upper", price: num3(inputs, "upper", 75), color: transp(levelInk, 25), lineStyle: "dashed", title: "Upper Trigger" },
|
|
35410
|
+
{ key: "lower", price: num3(inputs, "lower", 25), color: transp(levelInk, 25), lineStyle: "dashed", title: "Lower Trigger" },
|
|
35411
|
+
{ key: "midline", price: STC_MID, color: transp(levelInk, 60), lineStyle: "dotted", title: "Midline" }
|
|
35412
|
+
]
|
|
34664
35413
|
};
|
|
34665
35414
|
}
|
|
34666
35415
|
};
|
|
@@ -34669,22 +35418,37 @@ var kst = {
|
|
|
34669
35418
|
title: "Know Sure Thing",
|
|
34670
35419
|
shortTitle: "KST",
|
|
34671
35420
|
overlay: false,
|
|
34672
|
-
inputs: [
|
|
35421
|
+
inputs: [
|
|
35422
|
+
lengthInput(10, "rocLength1", "ROC Length #1", 5e3, "Lookback of the first (shortest) rate of change."),
|
|
35423
|
+
lengthInput(15, "rocLength2", "ROC Length #2", 5e3, "Lookback of the second rate of change."),
|
|
35424
|
+
lengthInput(20, "rocLength3", "ROC Length #3", 5e3, "Lookback of the third rate of change."),
|
|
35425
|
+
lengthInput(30, "rocLength4", "ROC Length #4", 5e3, "Lookback of the fourth (longest) rate of change."),
|
|
35426
|
+
lengthInput(10, "smaLength1", "SMA Length #1", 5e3, "Smoothing length applied to the first rate of change (RCMA #1)."),
|
|
35427
|
+
lengthInput(10, "smaLength2", "SMA Length #2", 5e3, "Smoothing length applied to the second rate of change (RCMA #2)."),
|
|
35428
|
+
lengthInput(10, "smaLength3", "SMA Length #3", 5e3, "Smoothing length applied to the third rate of change (RCMA #3)."),
|
|
35429
|
+
lengthInput(15, "smaLength4", "SMA Length #4", 5e3, "Smoothing length applied to the fourth rate of change (RCMA #4)."),
|
|
35430
|
+
lengthInput(9, "signalLength", "Signal Length", 500, "Simple moving average length of the signal line."),
|
|
35431
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the KST line while above zero."),
|
|
35432
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the KST line while below zero."),
|
|
35433
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line.")
|
|
35434
|
+
],
|
|
34673
35435
|
compute: (bars, inputs) => {
|
|
34674
|
-
const
|
|
34675
|
-
const
|
|
34676
|
-
|
|
34677
|
-
|
|
34678
|
-
|
|
34679
|
-
|
|
34680
|
-
|
|
34681
|
-
return {
|
|
34682
|
-
|
|
34683
|
-
|
|
34684
|
-
|
|
34685
|
-
|
|
34686
|
-
|
|
34687
|
-
|
|
35436
|
+
const c = closes(bars);
|
|
35437
|
+
const rcma = (rocKey, rocDefault, smaKey, smaDefault) => sma(roc(c, num3(inputs, rocKey, rocDefault)), num3(inputs, smaKey, smaDefault));
|
|
35438
|
+
const values = [rcma("rocLength1", 10, "smaLength1", 10), rcma("rocLength2", 15, "smaLength2", 10), rcma("rocLength3", 20, "smaLength3", 10), rcma("rocLength4", 30, "smaLength4", 15)].reduce(
|
|
35439
|
+
(acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)),
|
|
35440
|
+
new Array(c.length).fill(0)
|
|
35441
|
+
);
|
|
35442
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
35443
|
+
return centeredOscillator({
|
|
35444
|
+
key: "kst",
|
|
35445
|
+
title: "KST",
|
|
35446
|
+
values,
|
|
35447
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
35448
|
+
zeroInk: NEUTRAL,
|
|
35449
|
+
valueTransparency: 60,
|
|
35450
|
+
extraPlots: [{ key: "signal", title: "Signal", values: sma(values, num3(inputs, "signalLength", 9)), color: str3(inputs, "signalColor", WARNING) }]
|
|
35451
|
+
});
|
|
34688
35452
|
}
|
|
34689
35453
|
};
|
|
34690
35454
|
var coppock = {
|
|
@@ -34693,19 +35457,25 @@ var coppock = {
|
|
|
34693
35457
|
shortTitle: "Coppock",
|
|
34694
35458
|
overlay: false,
|
|
34695
35459
|
inputs: [
|
|
34696
|
-
lengthInput(10, "wmaLength", "WMA
|
|
34697
|
-
lengthInput(14, "longRoc", "Long RoC
|
|
34698
|
-
lengthInput(11, "shortRoc", "Short RoC
|
|
34699
|
-
sourceInput(),
|
|
34700
|
-
colorInput()
|
|
35460
|
+
lengthInput(10, "wmaLength", "WMA Length", 5e3, "Length of the weighted moving average applied to the sum of the two rates of change."),
|
|
35461
|
+
lengthInput(14, "longRoc", "Long RoC Length", 5e3, "Lookback of the long rate of change, in bars. The original definition uses 14 monthly closes."),
|
|
35462
|
+
lengthInput(11, "shortRoc", "Short RoC Length", 5e3, "Lookback of the short rate of change, in bars. The original definition uses 11 monthly closes."),
|
|
35463
|
+
sourceInput("Close", "source", "Source", "Price series used for the rate of change calculations."),
|
|
35464
|
+
colorInput(NEUTRAL, "Coppock Curve", "color", "Color of the Coppock Curve line."),
|
|
35465
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the curve reads above zero."),
|
|
35466
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the curve reads below zero."),
|
|
35467
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
34701
35468
|
],
|
|
34702
35469
|
compute: (bars, inputs) => {
|
|
34703
35470
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
34704
35471
|
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
34705
|
-
return {
|
|
34706
|
-
|
|
34707
|
-
|
|
34708
|
-
|
|
35472
|
+
return centeredOscillator({
|
|
35473
|
+
key: "coppock",
|
|
35474
|
+
title: "Coppock Curve",
|
|
35475
|
+
values,
|
|
35476
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
35477
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
35478
|
+
});
|
|
34709
35479
|
}
|
|
34710
35480
|
};
|
|
34711
35481
|
var dpo = {
|
|
@@ -34713,42 +35483,76 @@ var dpo = {
|
|
|
34713
35483
|
title: "Detrended Price Oscillator",
|
|
34714
35484
|
shortTitle: "DPO",
|
|
34715
35485
|
overlay: false,
|
|
34716
|
-
inputs: [
|
|
35486
|
+
inputs: [
|
|
35487
|
+
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."),
|
|
35488
|
+
sourceInput("Close", "source", "Source", "Price series used in the detrending calculation."),
|
|
35489
|
+
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."),
|
|
35490
|
+
colorInput(NEUTRAL, "DPO", "color", "Color of the line when it sits exactly at zero."),
|
|
35491
|
+
colorInput(BULLISH, "Above Zero", "bullColor", "Color of the line and fill while the oscillator is above zero."),
|
|
35492
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while the oscillator is below zero."),
|
|
35493
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
35494
|
+
],
|
|
34717
35495
|
compute: (bars, inputs) => {
|
|
34718
35496
|
const len = num3(inputs, "length", 21);
|
|
34719
|
-
const
|
|
34720
|
-
const
|
|
34721
|
-
|
|
34722
|
-
|
|
34723
|
-
|
|
34724
|
-
|
|
35497
|
+
const barsBack = Math.trunc(len / 2) + 1;
|
|
35498
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
35499
|
+
const basis = sma(src, len);
|
|
35500
|
+
const centered = bool(inputs, "centered", false);
|
|
35501
|
+
const values = centered ? shift(sub(shift(src, barsBack), basis), -barsBack) : sub(src, shift(basis, barsBack));
|
|
35502
|
+
return centeredOscillator({
|
|
35503
|
+
key: "dpo",
|
|
35504
|
+
title: "DPO",
|
|
35505
|
+
values,
|
|
35506
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
35507
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
35508
|
+
});
|
|
34725
35509
|
}
|
|
34726
35510
|
};
|
|
35511
|
+
var ROC_PERCENT = "ROC %";
|
|
35512
|
+
var ROC_MOMENTUM = "Momentum";
|
|
34727
35513
|
var rocSpec = {
|
|
34728
35514
|
type: "rate-of-change",
|
|
34729
35515
|
title: "Rate of Change",
|
|
34730
35516
|
shortTitle: "ROC",
|
|
34731
35517
|
overlay: false,
|
|
34732
|
-
inputs: [
|
|
34733
|
-
|
|
34734
|
-
|
|
34735
|
-
|
|
34736
|
-
|
|
34737
|
-
|
|
34738
|
-
|
|
34739
|
-
|
|
34740
|
-
|
|
34741
|
-
|
|
34742
|
-
|
|
34743
|
-
|
|
34744
|
-
|
|
35518
|
+
inputs: [
|
|
35519
|
+
lengthInput(9, "length", "Length", 5e3, "Lookback period; the source is compared with its value this many bars ago."),
|
|
35520
|
+
sourceInput("Close", "source", "Source", "Series used as input for the calculation."),
|
|
35521
|
+
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."),
|
|
35522
|
+
colorInput(NEUTRAL, "Line", "color", "Color of the line when the reading sits exactly at zero."),
|
|
35523
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the line and fill while the reading is above zero."),
|
|
35524
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line and fill while the reading is below zero."),
|
|
35525
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the line and zero, following the line color.")
|
|
35526
|
+
],
|
|
35527
|
+
compute: (bars, inputs) => {
|
|
35528
|
+
const len = num3(inputs, "length", 9);
|
|
35529
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
35530
|
+
const values = str3(inputs, "mode", ROC_PERCENT) === ROC_MOMENTUM ? change(src, len) : roc(src, len);
|
|
35531
|
+
return centeredOscillator({
|
|
35532
|
+
key: "roc",
|
|
35533
|
+
title: "ROC",
|
|
35534
|
+
values,
|
|
35535
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
35536
|
+
zeroInk: NEUTRAL,
|
|
35537
|
+
fill: bool(inputs, "gradientFill", true)
|
|
35538
|
+
});
|
|
35539
|
+
}
|
|
34745
35540
|
};
|
|
34746
35541
|
var ultimate = {
|
|
34747
35542
|
type: "ultimate-oscillator",
|
|
34748
35543
|
title: "Ultimate Oscillator",
|
|
34749
35544
|
shortTitle: "UO",
|
|
34750
35545
|
overlay: false,
|
|
34751
|
-
inputs: [
|
|
35546
|
+
inputs: [
|
|
35547
|
+
lengthInput(7, "fastLength", "Fast Length", 5e3, "Bars in the fastest averaging window. Weighted 4 in the final blend, so it dominates the oscillator."),
|
|
35548
|
+
lengthInput(14, "middleLength", "Middle Length", 5e3, "Bars in the intermediate averaging window. Weighted 2 in the final blend."),
|
|
35549
|
+
lengthInput(28, "slowLength", "Slow Length", 5e3, "Bars in the slowest averaging window. Weighted 1 in the final blend."),
|
|
35550
|
+
...thresholdInputs(70, 30, 0, 100, "Overbought", "Oversold"),
|
|
35551
|
+
colorInput(NEUTRAL, "Oscillator", "color", "Color of the line midway between the levels. Toward either level it blends into that level\u2019s color."),
|
|
35552
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought and oversold levels."),
|
|
35553
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line at the oversold level and of the oversold zone shading."),
|
|
35554
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line at the overbought level and of the overbought zone shading.")
|
|
35555
|
+
],
|
|
34752
35556
|
compute: (bars, inputs) => {
|
|
34753
35557
|
const n = bars.length;
|
|
34754
35558
|
const bp = new Array(n).fill(Number.NaN);
|
|
@@ -34756,10 +35560,9 @@ var ultimate = {
|
|
|
34756
35560
|
for (let i = 0; i < n; i++) {
|
|
34757
35561
|
const b = bars[i];
|
|
34758
35562
|
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
34759
|
-
const
|
|
34760
|
-
|
|
34761
|
-
|
|
34762
|
-
tr[i] = hi - lo;
|
|
35563
|
+
const trueLow = Math.min(b.low, pc);
|
|
35564
|
+
bp[i] = b.close - trueLow;
|
|
35565
|
+
tr[i] = Math.max(b.high, pc) - trueLow;
|
|
34763
35566
|
}
|
|
34764
35567
|
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
34765
35568
|
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
@@ -34770,10 +35573,17 @@ var ultimate = {
|
|
|
34770
35573
|
const z = a28[i];
|
|
34771
35574
|
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
34772
35575
|
});
|
|
34773
|
-
return {
|
|
34774
|
-
|
|
34775
|
-
|
|
34776
|
-
|
|
35576
|
+
return boundedOscillator({
|
|
35577
|
+
key: "uo",
|
|
35578
|
+
title: "UO",
|
|
35579
|
+
values,
|
|
35580
|
+
scale: { top: 100, bottom: 0 },
|
|
35581
|
+
overbought: num3(inputs, "overbought", 70),
|
|
35582
|
+
oversold: num3(inputs, "oversold", 30),
|
|
35583
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
35584
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
35585
|
+
zoneTransparency: 91
|
|
35586
|
+
});
|
|
34777
35587
|
}
|
|
34778
35588
|
};
|
|
34779
35589
|
var rvgi = {
|
|
@@ -34781,7 +35591,11 @@ var rvgi = {
|
|
|
34781
35591
|
title: "Relative Vigor Index",
|
|
34782
35592
|
shortTitle: "RVGI",
|
|
34783
35593
|
overlay: false,
|
|
34784
|
-
inputs: [
|
|
35594
|
+
inputs: [
|
|
35595
|
+
lengthInput(10, "length", "Length", 5e3, "Number of bars summed for the smoothed numerator (close \u2212 open) and denominator (high \u2212 low)."),
|
|
35596
|
+
colorInput(BULLISH, "RVGI", "color", "Color of the RVGI line."),
|
|
35597
|
+
colorInput(BEARISH, "Signal", "signalColor", "Color of the signal line.")
|
|
35598
|
+
],
|
|
34785
35599
|
compute: (bars, inputs) => {
|
|
34786
35600
|
const len = num3(inputs, "length", 10);
|
|
34787
35601
|
const co = swma(sub(closes(bars), opens(bars)));
|
|
@@ -34789,31 +35603,49 @@ var rvgi = {
|
|
|
34789
35603
|
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
34790
35604
|
return {
|
|
34791
35605
|
plots: [
|
|
34792
|
-
{ key: "rvgi", title: "RVGI", values: line, color:
|
|
34793
|
-
{ key: "signal", title: "Signal", values: swma(line), color:
|
|
35606
|
+
{ key: "rvgi", title: "RVGI", values: line, color: str3(inputs, "color", BULLISH) },
|
|
35607
|
+
{ key: "signal", title: "Signal", values: swma(line), color: str3(inputs, "signalColor", BEARISH) }
|
|
34794
35608
|
],
|
|
34795
|
-
levels:
|
|
35609
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed", title: "Zero" }]
|
|
34796
35610
|
};
|
|
34797
35611
|
}
|
|
34798
35612
|
};
|
|
35613
|
+
var RVI_EMA = "EMA";
|
|
35614
|
+
var RVI_RMA = "Wilder (RMA)";
|
|
34799
35615
|
var relVolatility = {
|
|
34800
35616
|
type: "relative-volatility-index",
|
|
34801
35617
|
title: "Relative Volatility Index",
|
|
34802
35618
|
shortTitle: "RVI",
|
|
34803
35619
|
overlay: false,
|
|
34804
|
-
inputs: [
|
|
35620
|
+
inputs: [
|
|
35621
|
+
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."),
|
|
35622
|
+
lengthInput(14, "smoothLength", "Smoothing Length", 5e3, "Number of bars used to smooth the up and down volatility streams."),
|
|
35623
|
+
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."),
|
|
35624
|
+
...thresholdInputs(80, 20),
|
|
35625
|
+
colorInput(NEUTRAL, "RVI Line", "color", "Color of the RVI line at the midline. Toward either threshold it blends into that threshold\u2019s color."),
|
|
35626
|
+
colorInput(BULLISH, "Up Volatility", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
35627
|
+
colorInput(BEARISH, "Down Volatility", "oversoldColor", "Color of the line and zone shading at and below the oversold level.")
|
|
35628
|
+
],
|
|
34805
35629
|
compute: (bars, inputs) => {
|
|
35630
|
+
const c = closes(bars);
|
|
35631
|
+
const sigma = stdev(c, num3(inputs, "stdevLength", 10));
|
|
35632
|
+
const d = change(c);
|
|
35633
|
+
const up = zip(sigma, d, (s, x) => x > 0 ? s : 0);
|
|
35634
|
+
const down = zip(sigma, d, (s, x) => x < 0 ? s : 0);
|
|
34806
35635
|
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
34807
|
-
const
|
|
34808
|
-
const
|
|
34809
|
-
|
|
34810
|
-
|
|
34811
|
-
|
|
34812
|
-
|
|
34813
|
-
|
|
34814
|
-
|
|
34815
|
-
|
|
34816
|
-
|
|
35636
|
+
const smooth = str3(inputs, "smoothType", RVI_EMA) === RVI_RMA ? rma : ema;
|
|
35637
|
+
const values = zip(smooth(up, smoothLen), smooth(down, smoothLen), (u, w) => u + w === 0 ? 0 : 100 * u / (u + w));
|
|
35638
|
+
return boundedOscillator({
|
|
35639
|
+
key: "rvi",
|
|
35640
|
+
title: "RVI",
|
|
35641
|
+
values,
|
|
35642
|
+
scale: { top: 100, bottom: 0 },
|
|
35643
|
+
overbought: num3(inputs, "overbought", 80),
|
|
35644
|
+
oversold: num3(inputs, "oversold", 20),
|
|
35645
|
+
ink: { low: str3(inputs, "oversoldColor", BEARISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BULLISH) },
|
|
35646
|
+
levelInk: NEUTRAL,
|
|
35647
|
+
width: 2
|
|
35648
|
+
});
|
|
34817
35649
|
}
|
|
34818
35650
|
};
|
|
34819
35651
|
var bop = {
|
|
@@ -34821,28 +35653,43 @@ var bop = {
|
|
|
34821
35653
|
title: "Balance of Power",
|
|
34822
35654
|
shortTitle: "BOP",
|
|
34823
35655
|
overlay: false,
|
|
34824
|
-
inputs: [
|
|
35656
|
+
inputs: [
|
|
35657
|
+
boolInput("smoothing", "Smoothing", false, void 0, "Smooth the raw Balance of Power with a simple moving average instead of plotting the single-bar value."),
|
|
35658
|
+
lengthInput(14, "smoothLength", "Length", 5e3, "Simple moving average length applied when smoothing is enabled."),
|
|
35659
|
+
colorInput(NEUTRAL, "BOP Color", "color", "Color of the line at exactly zero."),
|
|
35660
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the Balance of Power is above zero \u2014 buyers in control of the bar."),
|
|
35661
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the Balance of Power is below zero \u2014 sellers in control of the bar.")
|
|
35662
|
+
],
|
|
34825
35663
|
compute: (bars, inputs) => {
|
|
34826
|
-
const
|
|
34827
|
-
|
|
34828
|
-
|
|
34829
|
-
|
|
34830
|
-
|
|
35664
|
+
const raw = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
35665
|
+
const values = bool(inputs, "smoothing", false) ? sma(raw, num3(inputs, "smoothLength", 14)) : raw;
|
|
35666
|
+
return centeredOscillator({
|
|
35667
|
+
key: "bop",
|
|
35668
|
+
title: "BOP",
|
|
35669
|
+
values,
|
|
35670
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
35671
|
+
zeroInk: NEUTRAL,
|
|
35672
|
+
valueTransparency: 60
|
|
35673
|
+
});
|
|
34831
35674
|
}
|
|
34832
35675
|
};
|
|
34833
35676
|
var elderRay = {
|
|
34834
35677
|
type: "elder-ray",
|
|
34835
35678
|
title: "Elder Ray",
|
|
34836
35679
|
overlay: false,
|
|
34837
|
-
inputs: [
|
|
35680
|
+
inputs: [
|
|
35681
|
+
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."),
|
|
35682
|
+
colorInput(BULLISH, "Bull Power", "bullColor", "Color of the Bull Power columns."),
|
|
35683
|
+
colorInput(BEARISH, "Bear Power", "bearColor", "Color of the Bear Power columns.")
|
|
35684
|
+
],
|
|
34838
35685
|
compute: (bars, inputs) => {
|
|
34839
35686
|
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
34840
35687
|
return {
|
|
34841
35688
|
plots: [
|
|
34842
|
-
{ key: "bull", title: "Bull
|
|
34843
|
-
{ key: "bear", title: "Bear
|
|
35689
|
+
{ key: "bull", title: "Bull Power", values: sub(highs(bars), basis), kind: "columns", color: str3(inputs, "bullColor", BULLISH), base: 0 },
|
|
35690
|
+
{ key: "bear", title: "Bear Power", values: sub(lows(bars), basis), kind: "columns", color: str3(inputs, "bearColor", BEARISH), base: 0 }
|
|
34844
35691
|
],
|
|
34845
|
-
levels:
|
|
35692
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 30), lineStyle: "dashed", title: "Zero Line" }]
|
|
34846
35693
|
};
|
|
34847
35694
|
}
|
|
34848
35695
|
};
|
|
@@ -34851,31 +35698,66 @@ var ttmSqueeze = {
|
|
|
34851
35698
|
title: "TTM Squeeze",
|
|
34852
35699
|
overlay: false,
|
|
34853
35700
|
inputs: [
|
|
34854
|
-
lengthInput(20),
|
|
34855
|
-
|
|
34856
|
-
|
|
35701
|
+
{ ...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 },
|
|
35702
|
+
sourceInput("Close", "source", "Source", "Price series used for the shared moving average basis, the standard deviation, and the momentum displacement."),
|
|
35703
|
+
floatInput("bbMult", "Bollinger Bands Multiplier", 2, 0.1, 50, 0.1, "Standard deviation multiplier for the Bollinger Bands."),
|
|
35704
|
+
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."),
|
|
35705
|
+
colorInput(BULLISH, "Momentum Up", "bullColor", "Histogram color while momentum is positive. Bars building away from zero print solid, bars fading back print dimmed."),
|
|
35706
|
+
colorInput(BEARISH, "Momentum Down", "bearColor", "Histogram color while momentum is negative. Bars building away from zero print solid, bars fading back print dimmed."),
|
|
35707
|
+
colorInput(WARNING, "Squeeze On", "squeezeOnColor", "Zero-line dot color while both Bollinger Bands sit fully inside the Keltner Channels."),
|
|
35708
|
+
colorInput(NEUTRAL, "Squeeze Off", "squeezeOffColor", "Zero-line dot color while the Bollinger Bands trade outside the Keltner Channels."),
|
|
35709
|
+
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.")
|
|
34857
35710
|
],
|
|
34858
35711
|
compute: (bars, inputs) => {
|
|
34859
35712
|
const len = num3(inputs, "length", 20);
|
|
34860
|
-
const
|
|
34861
|
-
const basis = sma(
|
|
34862
|
-
const dev = map(stdev(
|
|
34863
|
-
const kcRange = map(
|
|
34864
|
-
const
|
|
34865
|
-
const anchor = zip(
|
|
34866
|
-
const mom = linreg(sub(
|
|
34867
|
-
const
|
|
35713
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
35714
|
+
const basis = sma(src, len);
|
|
35715
|
+
const dev = map(stdev(src, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
35716
|
+
const kcRange = map(atr(bars, len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
35717
|
+
const rangeMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
35718
|
+
const anchor = zip(rangeMid, basis, (d, b) => (d + b) / 2);
|
|
35719
|
+
const mom = linreg(sub(src, anchor), len, 0);
|
|
35720
|
+
const squeezeOn = basis.map((b, i) => {
|
|
34868
35721
|
const d = dev[i];
|
|
34869
35722
|
const r = kcRange[i];
|
|
34870
|
-
|
|
35723
|
+
if (!Number.isFinite(b) || !Number.isFinite(d) || !Number.isFinite(r)) return null;
|
|
35724
|
+
return d < r;
|
|
34871
35725
|
});
|
|
34872
|
-
const
|
|
34873
|
-
|
|
34874
|
-
|
|
34875
|
-
|
|
34876
|
-
|
|
34877
|
-
|
|
34878
|
-
|
|
35726
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
35727
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
35728
|
+
const onInk = str3(inputs, "squeezeOnColor", WARNING);
|
|
35729
|
+
const offInk = str3(inputs, "squeezeOffColor", NEUTRAL);
|
|
35730
|
+
const momColors = mom.map((x, i) => {
|
|
35731
|
+
if (!Number.isFinite(x)) return null;
|
|
35732
|
+
const prev2 = i > 0 && Number.isFinite(mom[i - 1]) ? mom[i - 1] : x;
|
|
35733
|
+
const rising = x > prev2;
|
|
35734
|
+
return x >= 0 ? rising ? bull : transp(bull, 60) : rising ? transp(bear, 60) : bear;
|
|
35735
|
+
});
|
|
35736
|
+
const plots = [
|
|
35737
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "columns", color: bull, colors: momColors, base: 0 },
|
|
35738
|
+
{
|
|
35739
|
+
key: "state",
|
|
35740
|
+
title: "Squeeze State",
|
|
35741
|
+
values: squeezeOn.map((s) => s == null ? Number.NaN : 0),
|
|
35742
|
+
kind: "circles",
|
|
35743
|
+
color: offInk,
|
|
35744
|
+
width: 2,
|
|
35745
|
+
colors: squeezeOn.map((s) => s == null ? null : s ? onInk : offInk)
|
|
35746
|
+
}
|
|
35747
|
+
];
|
|
35748
|
+
if (bool(inputs, "markFires", true)) {
|
|
35749
|
+
const fires = squeezeOn.map((s, i) => s === false && squeezeOn[i - 1] === true ? 0 : Number.NaN);
|
|
35750
|
+
plots.push({
|
|
35751
|
+
key: "fire",
|
|
35752
|
+
title: "Squeeze Fire",
|
|
35753
|
+
values: fires,
|
|
35754
|
+
kind: "cross",
|
|
35755
|
+
color: offInk,
|
|
35756
|
+
width: 3,
|
|
35757
|
+
colors: fires.map((x, i) => Number.isFinite(x) ? Number.isFinite(mom[i]) ? mom[i] >= 0 ? bull : bear : offInk : null)
|
|
35758
|
+
});
|
|
35759
|
+
}
|
|
35760
|
+
return { plots };
|
|
34879
35761
|
}
|
|
34880
35762
|
};
|
|
34881
35763
|
var oscillatorSpecs = [
|
|
@@ -34887,7 +35769,7 @@ var oscillatorSpecs = [
|
|
|
34887
35769
|
awesome,
|
|
34888
35770
|
cci,
|
|
34889
35771
|
williamsR,
|
|
34890
|
-
|
|
35772
|
+
cmoSpec,
|
|
34891
35773
|
connorsRsi,
|
|
34892
35774
|
fisher,
|
|
34893
35775
|
trix,
|
|
@@ -34911,15 +35793,19 @@ var aroon = {
|
|
|
34911
35793
|
type: "aroon",
|
|
34912
35794
|
title: "Aroon",
|
|
34913
35795
|
overlay: false,
|
|
34914
|
-
inputs: [
|
|
35796
|
+
inputs: [
|
|
35797
|
+
lengthInput(14, "length", "Length", 5e3, "Lookback length used to locate the highest high and lowest low."),
|
|
35798
|
+
colorInput(BULLISH, "Aroon Up", "upColor", "Color of the Aroon Up line."),
|
|
35799
|
+
colorInput(BEARISH, "Aroon Down", "downColor", "Color of the Aroon Down line.")
|
|
35800
|
+
],
|
|
34915
35801
|
compute: (bars, inputs) => {
|
|
34916
35802
|
const len = num3(inputs, "length", 14);
|
|
34917
|
-
const up = map(barsSinceHighest(highs(bars), len
|
|
34918
|
-
const down = map(barsSinceLowest(lows(bars), len
|
|
35803
|
+
const up = map(barsSinceHighest(highs(bars), len), (x) => 100 * (len - x) / len);
|
|
35804
|
+
const down = map(barsSinceLowest(lows(bars), len), (x) => 100 * (len - x) / len);
|
|
34919
35805
|
return {
|
|
34920
35806
|
plots: [
|
|
34921
|
-
{ key: "up", title: "Aroon
|
|
34922
|
-
{ key: "down", title: "Aroon
|
|
35807
|
+
{ key: "up", title: "Aroon Up", values: up, color: str3(inputs, "upColor", BULLISH) },
|
|
35808
|
+
{ key: "down", title: "Aroon Down", values: down, color: str3(inputs, "downColor", BEARISH) }
|
|
34923
35809
|
]
|
|
34924
35810
|
};
|
|
34925
35811
|
}
|
|
@@ -34929,10 +35815,16 @@ var adx = {
|
|
|
34929
35815
|
title: "Average Directional Index",
|
|
34930
35816
|
shortTitle: "ADX",
|
|
34931
35817
|
overlay: false,
|
|
34932
|
-
inputs: [
|
|
35818
|
+
inputs: [
|
|
35819
|
+
lengthInput(14, "diLength", "DI Length", 5e3, "Wilder RMA smoothing length applied to directional movement and true range when computing +DI and \u2212DI."),
|
|
35820
|
+
lengthInput(14, "adxLength", "ADX Smoothing", 5e3, "Wilder RMA smoothing length applied to DX to obtain the ADX line."),
|
|
35821
|
+
floatInput("keyLevel", "Key Level", 25, 0, 100, 1, "Trend strength reference level; ADX above it is commonly read as a trending market."),
|
|
35822
|
+
colorInput(BULLISH, "+DI Color", "plusDiColor", "Color of the plus directional indicator, and of the ADX line above the key level while +DI leads."),
|
|
35823
|
+
colorInput(BEARISH, "-DI Color", "minusDiColor", "Color of the minus directional indicator, and of the ADX line above the key level while \u2212DI leads."),
|
|
35824
|
+
colorInput(NEUTRAL, "ADX Color", "adxColor", "Color of the ADX line while it sits below the key level \u2014 no trend strong enough to trade.")
|
|
35825
|
+
],
|
|
34933
35826
|
compute: (bars, inputs) => {
|
|
34934
35827
|
const diLen = num3(inputs, "diLength", 14);
|
|
34935
|
-
const adxLen = num3(inputs, "adxLength", 14);
|
|
34936
35828
|
const n = bars.length;
|
|
34937
35829
|
const plusDm = new Array(n).fill(Number.NaN);
|
|
34938
35830
|
const minusDm = new Array(n).fill(Number.NaN);
|
|
@@ -34946,13 +35838,23 @@ var adx = {
|
|
|
34946
35838
|
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
34947
35839
|
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
34948
35840
|
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
35841
|
+
const adxLine = rma(dx, num3(inputs, "adxLength", 14));
|
|
35842
|
+
const plusInk = str3(inputs, "plusDiColor", BULLISH);
|
|
35843
|
+
const minusInk = str3(inputs, "minusDiColor", BEARISH);
|
|
35844
|
+
const neutralInk = str3(inputs, "adxColor", NEUTRAL);
|
|
35845
|
+
const keyLevel = num3(inputs, "keyLevel", 25);
|
|
35846
|
+
const adxColors = adxLine.map((x, i) => {
|
|
35847
|
+
if (!Number.isFinite(x)) return null;
|
|
35848
|
+
if (x <= keyLevel) return neutralInk;
|
|
35849
|
+
return (plusDi[i] ?? 0) > (minusDi[i] ?? 0) ? plusInk : minusInk;
|
|
35850
|
+
});
|
|
34949
35851
|
return {
|
|
34950
35852
|
plots: [
|
|
34951
|
-
{ key: "
|
|
34952
|
-
{ key: "
|
|
34953
|
-
{ key: "
|
|
35853
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: transp(plusInk, 30) },
|
|
35854
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: transp(minusInk, 30) },
|
|
35855
|
+
{ key: "adx", title: "ADX", values: adxLine, color: neutralInk, width: 2, colors: adxColors }
|
|
34954
35856
|
],
|
|
34955
|
-
levels: [{ key: "
|
|
35857
|
+
levels: [{ key: "keyLevel", price: keyLevel, color: NEUTRAL, lineStyle: "dashed", title: "Key Level" }]
|
|
34956
35858
|
};
|
|
34957
35859
|
}
|
|
34958
35860
|
};
|
|
@@ -34961,7 +35863,12 @@ var vortex = {
|
|
|
34961
35863
|
title: "Vortex Indicator",
|
|
34962
35864
|
shortTitle: "VI",
|
|
34963
35865
|
overlay: false,
|
|
34964
|
-
inputs: [
|
|
35866
|
+
inputs: [
|
|
35867
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to sum the vortex movements (VM+ and VM\u2212) and the true range."),
|
|
35868
|
+
colorInput(BULLISH, "VI+", "plusColor", "Color of the positive vortex line (VI+)."),
|
|
35869
|
+
colorInput(BEARISH, "VI-", "minusColor", "Color of the negative vortex line (VI\u2212)."),
|
|
35870
|
+
colorInput(NEUTRAL, "Baseline", "baselineColor", "Color of the dotted baseline drawn at 1.")
|
|
35871
|
+
],
|
|
34965
35872
|
compute: (bars, inputs) => {
|
|
34966
35873
|
const len = num3(inputs, "length", 14);
|
|
34967
35874
|
const n = bars.length;
|
|
@@ -34974,25 +35881,48 @@ var vortex = {
|
|
|
34974
35881
|
const trSum = sum(trueRange(bars), len);
|
|
34975
35882
|
return {
|
|
34976
35883
|
plots: [
|
|
34977
|
-
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color:
|
|
34978
|
-
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color:
|
|
35884
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: str3(inputs, "plusColor", BULLISH) },
|
|
35885
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: str3(inputs, "minusColor", BEARISH) }
|
|
34979
35886
|
],
|
|
34980
|
-
levels: [{ key: "
|
|
35887
|
+
levels: [{ key: "baseline", price: 1, color: str3(inputs, "baselineColor", NEUTRAL), lineStyle: "dotted", title: "Baseline" }]
|
|
34981
35888
|
};
|
|
34982
35889
|
}
|
|
34983
35890
|
};
|
|
34984
35891
|
var trendSpecs = [aroon, adx, vortex];
|
|
34985
35892
|
|
|
34986
35893
|
// src/core/native-indicators/classics/volatility.ts
|
|
35894
|
+
function expansionColors(values, calm, expanding) {
|
|
35895
|
+
return values.map((x, i) => {
|
|
35896
|
+
if (!Number.isFinite(x)) return null;
|
|
35897
|
+
const prev2 = i > 0 && Number.isFinite(values[i - 1]) ? values[i - 1] : x;
|
|
35898
|
+
return x > prev2 ? expanding : calm;
|
|
35899
|
+
});
|
|
35900
|
+
}
|
|
35901
|
+
function washedToZero(key, title, values, colors, fallback, valueTransparency, width = 1) {
|
|
35902
|
+
return {
|
|
35903
|
+
plots: [{ key, title, values, color: fallback, width, colors }, anchorPlot("zeroAnchor", "Zero Anchor", values.length, 0)],
|
|
35904
|
+
bands: [{ key: "wash", from: key, to: "zeroAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, 0, (_x, i) => colors[i] ?? fallback, valueTransparency) }]
|
|
35905
|
+
};
|
|
35906
|
+
}
|
|
34987
35907
|
var atrSpec = {
|
|
34988
35908
|
type: "average-true-range",
|
|
34989
35909
|
title: "Average True Range",
|
|
34990
35910
|
shortTitle: "ATR",
|
|
34991
35911
|
overlay: false,
|
|
34992
|
-
inputs: [
|
|
34993
|
-
|
|
34994
|
-
|
|
34995
|
-
|
|
35912
|
+
inputs: [
|
|
35913
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to average the true range."),
|
|
35914
|
+
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."),
|
|
35915
|
+
colorInput(NEUTRAL, "ATR Line", "color", "Color of the ATR line while volatility contracts or holds flat."),
|
|
35916
|
+
colorInput(WARNING, "Expanding", "expandColor", "Color of the ATR line while it rises \u2014 volatility expanding bar to bar.")
|
|
35917
|
+
],
|
|
35918
|
+
compute: (bars, inputs) => {
|
|
35919
|
+
const len = num3(inputs, "length", 14);
|
|
35920
|
+
const tr = trueRange(bars);
|
|
35921
|
+
const kind = str3(inputs, "smoothing", "RMA");
|
|
35922
|
+
const values = kind === "SMA" ? sma(tr, len) : kind === "EMA" ? ema(tr, len) : kind === "WMA" ? wma(tr, len) : rma(tr, len);
|
|
35923
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
35924
|
+
return washedToZero("atr", "ATR", values, expansionColors(values, calm, str3(inputs, "expandColor", WARNING)), calm, 75);
|
|
35925
|
+
}
|
|
34996
35926
|
};
|
|
34997
35927
|
var historicalVolatility = {
|
|
34998
35928
|
type: "historical-volatility",
|
|
@@ -35000,14 +35930,23 @@ var historicalVolatility = {
|
|
|
35000
35930
|
shortTitle: "HV",
|
|
35001
35931
|
overlay: false,
|
|
35002
35932
|
inputs: [
|
|
35003
|
-
lengthInput(10),
|
|
35004
|
-
|
|
35005
|
-
|
|
35933
|
+
{ ...lengthInput(10, "length", "Length", 5e3, "Lookback window in bars for the standard deviation of close-to-close log returns."), min: 2 },
|
|
35934
|
+
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."),
|
|
35935
|
+
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."),
|
|
35936
|
+
colorInput(WARNING, "Volatility Line", "color", "Color of the line at its recent highs; it fades toward grey as volatility contracts.")
|
|
35006
35937
|
],
|
|
35007
35938
|
compute: (bars, inputs) => {
|
|
35008
|
-
const
|
|
35009
|
-
const
|
|
35010
|
-
|
|
35939
|
+
const len = num3(inputs, "length", 10);
|
|
35940
|
+
const logReturns = sub(map(closes(bars), Math.log), shift(map(closes(bars), Math.log), 1));
|
|
35941
|
+
const meanReturn = map(sum(logReturns, len), (s) => s / len);
|
|
35942
|
+
const squaredSum = sum(map(logReturns, (r) => r * r), len);
|
|
35943
|
+
const factor = bool(inputs, "annualize", true) ? Math.sqrt(num3(inputs, "periodsPerYear", 252)) : 1;
|
|
35944
|
+
const values = zip(squaredSum, meanReturn, (sq, m) => Math.sqrt(Math.max(sq - len * m * m, 0) / (len - 1)) * factor * 100);
|
|
35945
|
+
const low = lowest(values, 100);
|
|
35946
|
+
const high = highest(values, 100);
|
|
35947
|
+
const accent = str3(inputs, "color", WARNING);
|
|
35948
|
+
const colors = values.map((x, i) => Number.isFinite(x) ? gradient(x, low[i] ?? 0, high[i] ?? 0, NEUTRAL, accent) : null);
|
|
35949
|
+
return washedToZero("hv", "Historical Volatility", values, colors, accent, 75, 2);
|
|
35011
35950
|
}
|
|
35012
35951
|
};
|
|
35013
35952
|
var chaikinVolatility = {
|
|
@@ -35015,13 +35954,26 @@ var chaikinVolatility = {
|
|
|
35015
35954
|
title: "Chaikin Volatility",
|
|
35016
35955
|
shortTitle: "CHV",
|
|
35017
35956
|
overlay: false,
|
|
35018
|
-
inputs: [
|
|
35957
|
+
inputs: [
|
|
35958
|
+
lengthInput(10, "emaLength", "EMA Length", 5e3, "Length of the exponential moving average applied to the bar high-low range."),
|
|
35959
|
+
lengthInput(10, "rocLength", "ROC Length", 5e3, "Number of bars used for the rate of change of the smoothed high-low range."),
|
|
35960
|
+
colorInput(NEUTRAL, "CHV Color", "color", "Color of the Chaikin Volatility line."),
|
|
35961
|
+
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.")
|
|
35962
|
+
],
|
|
35019
35963
|
compute: (bars, inputs) => {
|
|
35020
35964
|
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
35021
|
-
|
|
35022
|
-
|
|
35023
|
-
|
|
35024
|
-
|
|
35965
|
+
const prior = shift(range, num3(inputs, "rocLength", 10));
|
|
35966
|
+
const values = zip(range, prior, (r, p) => p === 0 ? Number.NaN : 100 * (r - p) / p);
|
|
35967
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
35968
|
+
const expansion = str3(inputs, "expansionColor", WARNING);
|
|
35969
|
+
return centeredOscillator({
|
|
35970
|
+
key: "chv",
|
|
35971
|
+
title: "Chaikin Volatility",
|
|
35972
|
+
values,
|
|
35973
|
+
// Only expansion is a state worth accenting; contraction stays structural grey.
|
|
35974
|
+
ink: { bull: expansion, bear: calm, neutral: calm },
|
|
35975
|
+
zeroInk: NEUTRAL
|
|
35976
|
+
});
|
|
35025
35977
|
}
|
|
35026
35978
|
};
|
|
35027
35979
|
var massIndex = {
|
|
@@ -35029,14 +35981,42 @@ var massIndex = {
|
|
|
35029
35981
|
title: "Mass Index",
|
|
35030
35982
|
shortTitle: "MI",
|
|
35031
35983
|
overlay: false,
|
|
35032
|
-
inputs: [
|
|
35984
|
+
inputs: [
|
|
35985
|
+
lengthInput(25, "length", "Sum Length", 5e3, "Number of bars over which the single/double EMA ratio is summed."),
|
|
35986
|
+
lengthInput(9, "emaLength", "EMA Length", 5e3, "Length of the single EMA of the high-low range and of its double smoothing."),
|
|
35987
|
+
floatInput("setupLevel", "Setup Level", 27, 0, 1e3, 0.1, "Reversal bulge set-up level; a rise above it arms the pattern."),
|
|
35988
|
+
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."),
|
|
35989
|
+
colorInput(NEUTRAL, "Mass Index", "color", "Line color while no reversal bulge is armed."),
|
|
35990
|
+
colorInput(WARNING, "Armed", "armedColor", "Line color from the rise above the setup level until the decline below the trigger level."),
|
|
35991
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Reversal bulge levels color.")
|
|
35992
|
+
],
|
|
35033
35993
|
compute: (bars, inputs) => {
|
|
35034
|
-
const
|
|
35035
|
-
const single = ema(
|
|
35036
|
-
const ratio = zip(single, ema(single,
|
|
35994
|
+
const emaLen = num3(inputs, "emaLength", 9);
|
|
35995
|
+
const single = ema(sub(highs(bars), lows(bars)), emaLen);
|
|
35996
|
+
const ratio = zip(single, ema(single, emaLen), (a, b) => b === 0 ? 1 : a / b);
|
|
35997
|
+
const values = sum(ratio, num3(inputs, "length", 25));
|
|
35998
|
+
const setup = num3(inputs, "setupLevel", 27);
|
|
35999
|
+
const trigger = num3(inputs, "triggerLevel", 26.5);
|
|
36000
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
36001
|
+
const armedInk = str3(inputs, "armedColor", WARNING);
|
|
36002
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
36003
|
+
let armed = false;
|
|
36004
|
+
const colors = values.map((x) => {
|
|
36005
|
+
if (!Number.isFinite(x)) return null;
|
|
36006
|
+
armed = x > setup ? true : x < trigger ? false : armed;
|
|
36007
|
+
return armed ? armedInk : calm;
|
|
36008
|
+
});
|
|
35037
36009
|
return {
|
|
35038
|
-
plots: [
|
|
35039
|
-
|
|
36010
|
+
plots: [
|
|
36011
|
+
{ key: "mi", title: "Mass Index", values, color: calm, colors },
|
|
36012
|
+
anchorPlot("setupAnchor", "Setup Anchor", values.length, setup),
|
|
36013
|
+
anchorPlot("triggerAnchor", "Trigger Anchor", values.length, trigger)
|
|
36014
|
+
],
|
|
36015
|
+
bands: [{ key: "bulge", from: "setupAnchor", to: "triggerAnchor", color: transp(armedInk, 90) }],
|
|
36016
|
+
levels: [
|
|
36017
|
+
{ key: "setup", price: setup, color: levelInk, lineStyle: "dashed", title: "Setup Level" },
|
|
36018
|
+
{ key: "trigger", price: trigger, color: levelInk, lineStyle: "dashed", title: "Trigger Level" }
|
|
36019
|
+
]
|
|
35040
36020
|
};
|
|
35041
36021
|
}
|
|
35042
36022
|
};
|
|
@@ -35045,30 +36025,83 @@ var standardDeviation = {
|
|
|
35045
36025
|
title: "Standard Deviation",
|
|
35046
36026
|
shortTitle: "StdDev",
|
|
35047
36027
|
overlay: false,
|
|
35048
|
-
inputs: [
|
|
35049
|
-
|
|
35050
|
-
|
|
35051
|
-
|
|
35052
|
-
|
|
35053
|
-
|
|
35054
|
-
|
|
35055
|
-
|
|
35056
|
-
|
|
35057
|
-
|
|
35058
|
-
|
|
35059
|
-
})
|
|
36028
|
+
inputs: [
|
|
36029
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used to compute the standard deviation."),
|
|
36030
|
+
sourceInput("Close", "source", "Source", "Price series the standard deviation is calculated on."),
|
|
36031
|
+
colorInput(NEUTRAL, "Standard Deviation", "color", "Line color while volatility contracts or holds flat."),
|
|
36032
|
+
colorInput(WARNING, "Expanding", "expandColor", "Line and fill color while volatility expands bar to bar.")
|
|
36033
|
+
],
|
|
36034
|
+
compute: (bars, inputs) => {
|
|
36035
|
+
const values = stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20));
|
|
36036
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
36037
|
+
return washedToZero("stdev", "Standard Deviation", values, expansionColors(values, calm, str3(inputs, "expandColor", WARNING)), calm, 80);
|
|
36038
|
+
}
|
|
35060
36039
|
};
|
|
35061
36040
|
var ulcerIndex = {
|
|
35062
36041
|
type: "ulcer-index",
|
|
35063
36042
|
title: "Ulcer Index",
|
|
35064
36043
|
overlay: false,
|
|
35065
|
-
inputs: [
|
|
36044
|
+
inputs: [
|
|
36045
|
+
{ ...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 },
|
|
36046
|
+
sourceInput("Close", "source", "Source", "Price series the Ulcer Index is computed on. Close is the standard choice."),
|
|
36047
|
+
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."),
|
|
36048
|
+
colorInput(NEUTRAL, "Ulcer Index", "color", "Color of the index near zero; it blends toward the stress color as the reading approaches the threshold."),
|
|
36049
|
+
colorInput(BEARISH, "Stress", "stressColor", "Color of the index at and beyond the alert threshold."),
|
|
36050
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Vertical gradient between zero and the Ulcer Index, fully transparent at zero."),
|
|
36051
|
+
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."),
|
|
36052
|
+
colorInput(transp(BEARISH, 50), "Drawdown Color", "drawdownColor", "Color of the drawdown columns."),
|
|
36053
|
+
colorInput(NEUTRAL, "Threshold", "thresholdColor", "Color of the alert threshold line.")
|
|
36054
|
+
],
|
|
35066
36055
|
compute: (bars, inputs) => {
|
|
35067
36056
|
const len = num3(inputs, "length", 14);
|
|
35068
|
-
const
|
|
35069
|
-
const
|
|
35070
|
-
const values =
|
|
35071
|
-
|
|
36057
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
36058
|
+
const n = src.length;
|
|
36059
|
+
const values = new Array(n).fill(Number.NaN);
|
|
36060
|
+
const drawdown = new Array(n).fill(Number.NaN);
|
|
36061
|
+
for (let i = len - 1; i < n; i++) {
|
|
36062
|
+
let runMax = Number.NaN;
|
|
36063
|
+
let sumSq = 0;
|
|
36064
|
+
let valid = true;
|
|
36065
|
+
let last = Number.NaN;
|
|
36066
|
+
for (let k = i - len + 1; k <= i; k++) {
|
|
36067
|
+
const price = src[k];
|
|
36068
|
+
if (!Number.isFinite(price)) {
|
|
36069
|
+
valid = false;
|
|
36070
|
+
break;
|
|
36071
|
+
}
|
|
36072
|
+
runMax = Number.isFinite(runMax) ? Math.max(runMax, price) : price;
|
|
36073
|
+
if (runMax <= 0) {
|
|
36074
|
+
valid = false;
|
|
36075
|
+
break;
|
|
36076
|
+
}
|
|
36077
|
+
last = 100 * (price - runMax) / runMax;
|
|
36078
|
+
sumSq += last * last;
|
|
36079
|
+
}
|
|
36080
|
+
if (!valid) continue;
|
|
36081
|
+
values[i] = Math.sqrt(sumSq / len);
|
|
36082
|
+
drawdown[i] = last;
|
|
36083
|
+
}
|
|
36084
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
36085
|
+
const stress = str3(inputs, "stressColor", BEARISH);
|
|
36086
|
+
const threshold = num3(inputs, "threshold", 5);
|
|
36087
|
+
const colors = values.map((x) => Number.isFinite(x) ? threshold > 0 ? gradient(x, 0, threshold, calm, stress) : stress : null);
|
|
36088
|
+
const plots = [{ key: "ui", title: "Ulcer Index", values, color: calm, colors }];
|
|
36089
|
+
const bands = [];
|
|
36090
|
+
if (bool(inputs, "gradientFill", true)) {
|
|
36091
|
+
plots.push(anchorPlot("zeroAnchor", "Zero Base", n, 0));
|
|
36092
|
+
bands.push({ key: "wash", from: "ui", to: "zeroAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, 0, (_x, i) => colors[i] ?? calm) });
|
|
36093
|
+
}
|
|
36094
|
+
if (bool(inputs, "showDrawdown", false)) {
|
|
36095
|
+
plots.push({ key: "dd", title: "Drawdown %", values: drawdown, kind: "columns", color: str3(inputs, "drawdownColor", transp(BEARISH, 50)), base: 0 });
|
|
36096
|
+
}
|
|
36097
|
+
return {
|
|
36098
|
+
plots,
|
|
36099
|
+
bands,
|
|
36100
|
+
levels: [
|
|
36101
|
+
{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dotted", title: "Zero Level" },
|
|
36102
|
+
{ key: "threshold", price: threshold, color: str3(inputs, "thresholdColor", NEUTRAL), lineStyle: "dashed", title: "Alert Threshold" }
|
|
36103
|
+
]
|
|
36104
|
+
};
|
|
35072
36105
|
}
|
|
35073
36106
|
};
|
|
35074
36107
|
var choppiness = {
|
|
@@ -35076,19 +36109,33 @@ var choppiness = {
|
|
|
35076
36109
|
title: "Choppiness Index",
|
|
35077
36110
|
shortTitle: "CHOP",
|
|
35078
36111
|
overlay: false,
|
|
35079
|
-
inputs: [
|
|
36112
|
+
inputs: [
|
|
36113
|
+
{ ...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 },
|
|
36114
|
+
floatInput("upperBand", "Upper Band", 61.8, 0, 100, 0.1, "Readings above this level indicate a choppy, range-bound market."),
|
|
36115
|
+
floatInput("lowerBand", "Lower Band", 38.2, 0, 100, 0.1, "Readings below this level indicate a strong trend."),
|
|
36116
|
+
colorInput(NEUTRAL, "CHOP Line", "color", "Color of the Choppiness Index line."),
|
|
36117
|
+
colorInput(BULLISH, "Trending", "trendingColor", "Line, fill and zone color toward and below the lower band \u2014 a trending market."),
|
|
36118
|
+
colorInput(BEARISH, "Choppy", "choppyColor", "Line, fill and zone color toward and above the upper band \u2014 a choppy, range-bound market."),
|
|
36119
|
+
colorInput(transp(NEUTRAL, 90), "Band Fill", "fillColor", "Fill color of the zone between the upper and lower bands.")
|
|
36120
|
+
],
|
|
35080
36121
|
compute: (bars, inputs) => {
|
|
35081
36122
|
const len = num3(inputs, "length", 14);
|
|
35082
36123
|
const trSum = sum(trueRange(bars), len);
|
|
35083
36124
|
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
35084
36125
|
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
35085
|
-
return {
|
|
35086
|
-
|
|
35087
|
-
|
|
35088
|
-
|
|
35089
|
-
|
|
35090
|
-
|
|
35091
|
-
|
|
36126
|
+
return boundedOscillator({
|
|
36127
|
+
key: "chop",
|
|
36128
|
+
title: "CHOP",
|
|
36129
|
+
values,
|
|
36130
|
+
scale: { top: 100, bottom: 0 },
|
|
36131
|
+
overbought: num3(inputs, "upperBand", 61.8),
|
|
36132
|
+
oversold: num3(inputs, "lowerBand", 38.2),
|
|
36133
|
+
ink: { low: str3(inputs, "trendingColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "choppyColor", BEARISH) },
|
|
36134
|
+
levelInk: NEUTRAL,
|
|
36135
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 90)),
|
|
36136
|
+
midpointWash: true,
|
|
36137
|
+
levelTitles: { overbought: "Upper Band", oversold: "Lower Band" }
|
|
36138
|
+
});
|
|
35092
36139
|
}
|
|
35093
36140
|
};
|
|
35094
36141
|
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
@@ -35101,17 +36148,85 @@ function moneyFlowVolume(bars) {
|
|
|
35101
36148
|
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
35102
36149
|
});
|
|
35103
36150
|
}
|
|
36151
|
+
function slopeColors2(values, bull, bear, flat) {
|
|
36152
|
+
return values.map((x, i) => {
|
|
36153
|
+
if (!Number.isFinite(x)) return null;
|
|
36154
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
36155
|
+
if (!Number.isFinite(prev2)) return flat;
|
|
36156
|
+
return x > prev2 ? bull : x < prev2 ? bear : flat;
|
|
36157
|
+
});
|
|
36158
|
+
}
|
|
36159
|
+
function volumeIndex(bars, src, base, when) {
|
|
36160
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
36161
|
+
let index = base;
|
|
36162
|
+
for (let i = 0; i < bars.length; i++) {
|
|
36163
|
+
const v = bars[i].volume;
|
|
36164
|
+
const pv = i > 0 ? bars[i - 1].volume : null;
|
|
36165
|
+
const prev2 = i > 0 ? src[i - 1] : Number.NaN;
|
|
36166
|
+
const active = v != null && pv != null && Number.isFinite(v) && Number.isFinite(pv) && when(v, pv);
|
|
36167
|
+
if (active && Number.isFinite(prev2) && prev2 !== 0) index *= 1 + (src[i] - prev2) / prev2;
|
|
36168
|
+
out[i] = index;
|
|
36169
|
+
}
|
|
36170
|
+
return out;
|
|
36171
|
+
}
|
|
36172
|
+
function regimeIndex(key, title, values, signal, ink, showSignal, gradientFill, singleColor = false) {
|
|
36173
|
+
const inkOf = (i) => {
|
|
36174
|
+
const s = signal[i];
|
|
36175
|
+
const x = values[i];
|
|
36176
|
+
if (singleColor || !Number.isFinite(s)) return ink.neutral;
|
|
36177
|
+
return x > s ? ink.bull : x < s ? ink.bear : ink.neutral;
|
|
36178
|
+
};
|
|
36179
|
+
const plots = [
|
|
36180
|
+
{
|
|
36181
|
+
key: "signal",
|
|
36182
|
+
title: "Signal EMA",
|
|
36183
|
+
values: signal,
|
|
36184
|
+
color: ink.signal,
|
|
36185
|
+
...showSignal ? {} : { display: { pane: false } }
|
|
36186
|
+
},
|
|
36187
|
+
{ key, title, values, color: ink.neutral, width: 2, colors: values.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) }
|
|
36188
|
+
];
|
|
36189
|
+
const bands = gradientFill ? [
|
|
36190
|
+
{
|
|
36191
|
+
key: "regime",
|
|
36192
|
+
from: key,
|
|
36193
|
+
to: "signal",
|
|
36194
|
+
color: transp(NEUTRAL, 100),
|
|
36195
|
+
gradient: values.map((x, i) => {
|
|
36196
|
+
const s = signal[i];
|
|
36197
|
+
if (!Number.isFinite(x) || !Number.isFinite(s)) return null;
|
|
36198
|
+
const above = x > s;
|
|
36199
|
+
const c = above ? ink.bull : ink.bear;
|
|
36200
|
+
return { topValue: Math.max(x, s), bottomValue: Math.min(x, s), topColor: transp(c, above ? 50 : 100), bottomColor: transp(c, above ? 100 : 50) };
|
|
36201
|
+
})
|
|
36202
|
+
}
|
|
36203
|
+
] : [];
|
|
36204
|
+
return { plots, bands };
|
|
36205
|
+
}
|
|
35104
36206
|
var obv = {
|
|
35105
36207
|
type: "on-balance-volume",
|
|
35106
36208
|
title: "On Balance Volume",
|
|
35107
36209
|
shortTitle: "OBV",
|
|
35108
36210
|
overlay: false,
|
|
35109
|
-
inputs: [
|
|
36211
|
+
inputs: [
|
|
36212
|
+
boolInput("smoothing", "Smoothing MA", false, SETTINGS, "Display a moving average of OBV to act as a smoothing/signal line."),
|
|
36213
|
+
optionInput("maType", "MA Type", "SMA", ["SMA", "EMA", "WMA", "RMA (SMMA)"], "Moving average type used for the smoothing MA."),
|
|
36214
|
+
lengthInput(20, "maLength", "MA Length", 5e3, "Number of bars used for the smoothing MA."),
|
|
36215
|
+
colorInput(INFO, "OBV", "color", "Color of the OBV line."),
|
|
36216
|
+
colorInput(WARNING, "Smoothing MA", "maColor", "Color of the smoothing MA line.")
|
|
36217
|
+
],
|
|
35110
36218
|
compute: (bars, inputs) => {
|
|
35111
36219
|
const vol = volumes(bars);
|
|
35112
36220
|
const d = change(closes(bars));
|
|
35113
|
-
const
|
|
35114
|
-
|
|
36221
|
+
const values = cumSum(zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0));
|
|
36222
|
+
const plots = [{ key: "obv", title: "OBV", values, color: str3(inputs, "color", INFO) }];
|
|
36223
|
+
if (bool(inputs, "smoothing", false)) {
|
|
36224
|
+
const len = num3(inputs, "maLength", 20);
|
|
36225
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
36226
|
+
const ma = kind === "EMA" ? ema(values, len) : kind === "WMA" ? wma(values, len) : kind === "RMA (SMMA)" ? rma(values, len) : sma(values, len);
|
|
36227
|
+
plots.push({ key: "ma", title: "Smoothing MA", values: ma, color: str3(inputs, "maColor", WARNING) });
|
|
36228
|
+
}
|
|
36229
|
+
return { plots };
|
|
35115
36230
|
}
|
|
35116
36231
|
};
|
|
35117
36232
|
var accDist = {
|
|
@@ -35119,24 +36234,50 @@ var accDist = {
|
|
|
35119
36234
|
title: "Accumulation / Distribution",
|
|
35120
36235
|
shortTitle: "A/D",
|
|
35121
36236
|
overlay: false,
|
|
35122
|
-
inputs: [
|
|
35123
|
-
|
|
35124
|
-
|
|
35125
|
-
|
|
36237
|
+
inputs: [
|
|
36238
|
+
boolInput("slopeColoring", "Slope Coloring", true, STYLE, "Color the line by its slope \u2014 bullish while rising (accumulation), bearish while falling (distribution)."),
|
|
36239
|
+
colorInput(NEUTRAL, "A/D Line", "color", "Color of the line when slope coloring is disabled or the line is flat."),
|
|
36240
|
+
colorInput(BULLISH, "Rising", "bullColor", "Line color while the A/D Line rises."),
|
|
36241
|
+
colorInput(BEARISH, "Falling", "bearColor", "Line color while the A/D Line falls.")
|
|
36242
|
+
],
|
|
36243
|
+
compute: (bars, inputs) => {
|
|
36244
|
+
const values = cumSum(moneyFlowVolume(bars));
|
|
36245
|
+
const flat = str3(inputs, "color", NEUTRAL);
|
|
36246
|
+
return {
|
|
36247
|
+
plots: [
|
|
36248
|
+
{
|
|
36249
|
+
key: "ad",
|
|
36250
|
+
title: "A/D",
|
|
36251
|
+
values,
|
|
36252
|
+
color: flat,
|
|
36253
|
+
...bool(inputs, "slopeColoring", true) ? { colors: slopeColors2(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), flat) } : {}
|
|
36254
|
+
}
|
|
36255
|
+
]
|
|
36256
|
+
};
|
|
36257
|
+
}
|
|
35126
36258
|
};
|
|
35127
36259
|
var cmf = {
|
|
35128
36260
|
type: "chaikin-money-flow",
|
|
35129
36261
|
title: "Chaikin Money Flow",
|
|
35130
36262
|
shortTitle: "CMF",
|
|
35131
36263
|
overlay: false,
|
|
35132
|
-
inputs: [
|
|
36264
|
+
inputs: [
|
|
36265
|
+
lengthInput(20, "length", "Length", 5e3, "Lookback length over which money flow volume and volume are summed."),
|
|
36266
|
+
colorInput(NEUTRAL, "CMF Line", "color", "Color of the Chaikin Money Flow line."),
|
|
36267
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the CMF reads above zero (net money flowing in)."),
|
|
36268
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the CMF reads below zero (net money flowing out)."),
|
|
36269
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
36270
|
+
],
|
|
35133
36271
|
compute: (bars, inputs) => {
|
|
35134
36272
|
const len = num3(inputs, "length", 20);
|
|
35135
36273
|
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
35136
|
-
return {
|
|
35137
|
-
|
|
35138
|
-
|
|
35139
|
-
|
|
36274
|
+
return centeredOscillator({
|
|
36275
|
+
key: "cmf",
|
|
36276
|
+
title: "CMF",
|
|
36277
|
+
values,
|
|
36278
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
36279
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
36280
|
+
});
|
|
35140
36281
|
}
|
|
35141
36282
|
};
|
|
35142
36283
|
var chaikinOscillator = {
|
|
@@ -35144,14 +36285,23 @@ var chaikinOscillator = {
|
|
|
35144
36285
|
title: "Chaikin Oscillator",
|
|
35145
36286
|
shortTitle: "Chaikin Osc",
|
|
35146
36287
|
overlay: false,
|
|
35147
|
-
inputs: [
|
|
36288
|
+
inputs: [
|
|
36289
|
+
lengthInput(3, "fastLength", "Fast Length", 5e3, "Length of the fast EMA applied to the accumulation/distribution line."),
|
|
36290
|
+
lengthInput(10, "slowLength", "Slow Length", 5e3, "Length of the slow EMA applied to the accumulation/distribution line."),
|
|
36291
|
+
colorInput(NEUTRAL, "Oscillator", "color", "Color of the Chaikin Oscillator line."),
|
|
36292
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the oscillator reads above zero (accumulation)."),
|
|
36293
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the oscillator reads below zero (distribution).")
|
|
36294
|
+
],
|
|
35148
36295
|
compute: (bars, inputs) => {
|
|
35149
36296
|
const ad = cumSum(moneyFlowVolume(bars));
|
|
35150
36297
|
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
35151
|
-
return {
|
|
35152
|
-
|
|
35153
|
-
|
|
35154
|
-
|
|
36298
|
+
return centeredOscillator({
|
|
36299
|
+
key: "co",
|
|
36300
|
+
title: "Chaikin Oscillator",
|
|
36301
|
+
values,
|
|
36302
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
36303
|
+
zeroInk: NEUTRAL
|
|
36304
|
+
});
|
|
35155
36305
|
}
|
|
35156
36306
|
};
|
|
35157
36307
|
var eom = {
|
|
@@ -35159,20 +36309,28 @@ var eom = {
|
|
|
35159
36309
|
title: "Ease of Movement",
|
|
35160
36310
|
shortTitle: "EOM",
|
|
35161
36311
|
overlay: false,
|
|
35162
|
-
inputs: [
|
|
36312
|
+
inputs: [
|
|
36313
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars in the simple moving average smoothing the 1-bar Ease of Movement values."),
|
|
36314
|
+
intInput("divisor", "Divisor", 1e4, 1, 1e9, "Volume scaling constant of the box ratio; larger values scale the oscillator up. 10000 is the conventional default."),
|
|
36315
|
+
colorInput(NEUTRAL, "EOM Line", "color", "Color of the line when it sits exactly at zero."),
|
|
36316
|
+
colorInput(BULLISH, "Above Zero", "bullColor", "Color of the line and fill while Ease of Movement is positive."),
|
|
36317
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while Ease of Movement is negative.")
|
|
36318
|
+
],
|
|
35163
36319
|
compute: (bars, inputs) => {
|
|
35164
36320
|
const div = num3(inputs, "divisor", 1e4);
|
|
35165
|
-
const
|
|
35166
|
-
const move = change(hl2);
|
|
36321
|
+
const move = change(sourceValues(bars, "HL2"));
|
|
35167
36322
|
const raw = bars.map((b, i) => {
|
|
35168
36323
|
const m = move[i];
|
|
35169
|
-
if (b.volume == null || !Number.isFinite(b.volume) ||
|
|
35170
|
-
return div * m * (b.high - b.low) / b.volume;
|
|
36324
|
+
if (b.volume == null || !Number.isFinite(b.volume) || !Number.isFinite(m)) return Number.NaN;
|
|
36325
|
+
return b.volume === 0 ? 0 : div * m * (b.high - b.low) / b.volume;
|
|
36326
|
+
});
|
|
36327
|
+
return centeredOscillator({
|
|
36328
|
+
key: "eom",
|
|
36329
|
+
title: "EOM",
|
|
36330
|
+
values: sma(raw, num3(inputs, "length", 14)),
|
|
36331
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
36332
|
+
zeroInk: NEUTRAL
|
|
35171
36333
|
});
|
|
35172
|
-
return {
|
|
35173
|
-
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
35174
|
-
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
35175
|
-
};
|
|
35176
36334
|
}
|
|
35177
36335
|
};
|
|
35178
36336
|
var forceIndex = {
|
|
@@ -35180,13 +36338,24 @@ var forceIndex = {
|
|
|
35180
36338
|
title: "Force Index",
|
|
35181
36339
|
shortTitle: "FI",
|
|
35182
36340
|
overlay: false,
|
|
35183
|
-
inputs: [
|
|
36341
|
+
inputs: [
|
|
36342
|
+
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."),
|
|
36343
|
+
colorInput(BULLISH, "Force Index", "color", "Color of the line while above zero (buying pressure)."),
|
|
36344
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line while below zero (selling pressure).")
|
|
36345
|
+
],
|
|
35184
36346
|
compute: (bars, inputs) => {
|
|
35185
36347
|
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
35186
|
-
|
|
35187
|
-
|
|
35188
|
-
|
|
35189
|
-
|
|
36348
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
36349
|
+
return centeredOscillator({
|
|
36350
|
+
key: "force",
|
|
36351
|
+
title: "Force Index",
|
|
36352
|
+
values: ema(raw, num3(inputs, "length", 13)),
|
|
36353
|
+
// The reference reads an exact zero as buying pressure, so neutral shares the bull ink.
|
|
36354
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
36355
|
+
zeroInk: NEUTRAL,
|
|
36356
|
+
width: 2,
|
|
36357
|
+
valueTransparency: 60
|
|
36358
|
+
});
|
|
35190
36359
|
}
|
|
35191
36360
|
};
|
|
35192
36361
|
var klinger = {
|
|
@@ -35194,19 +36363,44 @@ var klinger = {
|
|
|
35194
36363
|
title: "Klinger Oscillator",
|
|
35195
36364
|
shortTitle: "KVO",
|
|
35196
36365
|
overlay: false,
|
|
35197
|
-
inputs: [
|
|
36366
|
+
inputs: [
|
|
36367
|
+
lengthInput(34, "fastLength", "Fast Length", 5e3, "Length of the fast EMA applied to the volume force."),
|
|
36368
|
+
lengthInput(55, "slowLength", "Slow Length", 5e3, "Length of the slow EMA applied to the volume force."),
|
|
36369
|
+
lengthInput(13, "signalLength", "Signal Length", 500, "Length of the EMA applied to the oscillator to obtain the signal line."),
|
|
36370
|
+
colorInput(BULLISH, "Bullish", "color", "Color of the Klinger oscillator while above zero."),
|
|
36371
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the Klinger oscillator while below zero."),
|
|
36372
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line.")
|
|
36373
|
+
],
|
|
35198
36374
|
compute: (bars, inputs) => {
|
|
35199
|
-
const
|
|
35200
|
-
const
|
|
35201
|
-
|
|
35202
|
-
|
|
35203
|
-
|
|
35204
|
-
|
|
35205
|
-
|
|
35206
|
-
|
|
35207
|
-
|
|
35208
|
-
|
|
35209
|
-
|
|
36375
|
+
const n = bars.length;
|
|
36376
|
+
const force = new Array(n).fill(Number.NaN);
|
|
36377
|
+
let trend = 1;
|
|
36378
|
+
let prevTrend = 1;
|
|
36379
|
+
let cm = 0;
|
|
36380
|
+
let prevDm = 0;
|
|
36381
|
+
for (let i = 0; i < n; i++) {
|
|
36382
|
+
const b = bars[i];
|
|
36383
|
+
const hlc = b.high + b.low + b.close;
|
|
36384
|
+
const prevHlc = i > 0 ? bars[i - 1].high + bars[i - 1].low + bars[i - 1].close : hlc;
|
|
36385
|
+
trend = hlc > prevHlc ? 1 : -1;
|
|
36386
|
+
const dm = b.high - b.low;
|
|
36387
|
+
cm = i > 0 && trend === prevTrend ? cm + dm : prevDm + dm;
|
|
36388
|
+
const v = b.volume;
|
|
36389
|
+
force[i] = v == null || !Number.isFinite(v) ? Number.NaN : cm === 0 ? 0 : v * Math.abs(2 * (dm / cm) - 1) * trend * 100;
|
|
36390
|
+
prevTrend = trend;
|
|
36391
|
+
prevDm = dm;
|
|
36392
|
+
}
|
|
36393
|
+
const line = sub(ema(force, num3(inputs, "fastLength", 34)), ema(force, num3(inputs, "slowLength", 55)));
|
|
36394
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
36395
|
+
return centeredOscillator({
|
|
36396
|
+
key: "kvo",
|
|
36397
|
+
title: "KVO",
|
|
36398
|
+
values: line,
|
|
36399
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
36400
|
+
zeroInk: transp(NEUTRAL, 50),
|
|
36401
|
+
valueTransparency: 60,
|
|
36402
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: str3(inputs, "signalColor", WARNING) }]
|
|
36403
|
+
});
|
|
35210
36404
|
}
|
|
35211
36405
|
};
|
|
35212
36406
|
var mfi = {
|
|
@@ -35214,7 +36408,15 @@ var mfi = {
|
|
|
35214
36408
|
title: "Money Flow Index",
|
|
35215
36409
|
shortTitle: "MFI",
|
|
35216
36410
|
overlay: false,
|
|
35217
|
-
inputs: [
|
|
36411
|
+
inputs: [
|
|
36412
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to sum positive and negative money flow."),
|
|
36413
|
+
floatInput("overbought", "Overbought Level", 80, 0, 100, 1, "Level above which the MFI is considered overbought."),
|
|
36414
|
+
floatInput("oversold", "Oversold Level", 20, 0, 100, 1, "Level below which the MFI is considered oversold."),
|
|
36415
|
+
colorInput(NEUTRAL, "MFI", "color", "Color of the MFI line between the levels. Toward either level it blends into that level\u2019s color."),
|
|
36416
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
36417
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level."),
|
|
36418
|
+
colorInput(transp(NEUTRAL, 94), "OB/OS Zone Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
36419
|
+
],
|
|
35218
36420
|
compute: (bars, inputs) => {
|
|
35219
36421
|
const len = num3(inputs, "length", 14);
|
|
35220
36422
|
const tp = sourceValues(bars, "HLC3");
|
|
@@ -35222,48 +36424,57 @@ var mfi = {
|
|
|
35222
36424
|
const d = change(tp);
|
|
35223
36425
|
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
35224
36426
|
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
35225
|
-
const values = zip(pos, neg, (p,
|
|
35226
|
-
return {
|
|
35227
|
-
|
|
35228
|
-
|
|
35229
|
-
|
|
35230
|
-
|
|
35231
|
-
|
|
35232
|
-
|
|
36427
|
+
const values = zip(pos, neg, (p, w) => p + w > 0 ? 100 * p / (p + w) : Number.NaN);
|
|
36428
|
+
return boundedOscillator({
|
|
36429
|
+
key: "mfi",
|
|
36430
|
+
title: "MFI",
|
|
36431
|
+
values,
|
|
36432
|
+
scale: { top: 100, bottom: 0 },
|
|
36433
|
+
overbought: num3(inputs, "overbought", 80),
|
|
36434
|
+
oversold: num3(inputs, "oversold", 20),
|
|
36435
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
36436
|
+
levelInk: NEUTRAL,
|
|
36437
|
+
zoneTransparency: 91,
|
|
36438
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 94))
|
|
36439
|
+
});
|
|
35233
36440
|
}
|
|
35234
36441
|
};
|
|
35235
|
-
function
|
|
35236
|
-
|
|
35237
|
-
|
|
35238
|
-
|
|
35239
|
-
|
|
35240
|
-
|
|
35241
|
-
|
|
35242
|
-
|
|
35243
|
-
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
35244
|
-
if (started) out[i] = index;
|
|
35245
|
-
continue;
|
|
35246
|
-
}
|
|
35247
|
-
started = true;
|
|
35248
|
-
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
35249
|
-
out[i] = index;
|
|
35250
|
-
}
|
|
35251
|
-
return out;
|
|
36442
|
+
function regimeStyleInputs(signalTitle) {
|
|
36443
|
+
return [
|
|
36444
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the line and gradient fill while it holds above its signal EMA."),
|
|
36445
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line and gradient fill while it holds below its signal EMA."),
|
|
36446
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor", "Color used while the signal EMA is still forming, or the line sits exactly on it."),
|
|
36447
|
+
colorInput(NEUTRAL, signalTitle, "signalColor", "Color of the signal EMA overlay."),
|
|
36448
|
+
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.")
|
|
36449
|
+
];
|
|
35252
36450
|
}
|
|
36451
|
+
var NVI_POSITION = "Position vs Average";
|
|
36452
|
+
var NVI_SINGLE = "Single Color";
|
|
35253
36453
|
var nvi = {
|
|
35254
36454
|
type: "negative-volume-index",
|
|
35255
36455
|
title: "Negative Volume Index",
|
|
35256
36456
|
shortTitle: "Negative Volume Index",
|
|
35257
36457
|
overlay: false,
|
|
35258
|
-
inputs: [
|
|
36458
|
+
inputs: [
|
|
36459
|
+
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."),
|
|
36460
|
+
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."),
|
|
36461
|
+
lengthInput(255, "signalLength", "Signal Length", 5e3, "Length of the signal EMA. The convention is one trading year \u2014 255 sessions on daily charts."),
|
|
36462
|
+
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."),
|
|
36463
|
+
...regimeStyleInputs("Signal EMA")
|
|
36464
|
+
],
|
|
35259
36465
|
compute: (bars, inputs) => {
|
|
35260
|
-
const
|
|
35261
|
-
|
|
35262
|
-
|
|
35263
|
-
|
|
35264
|
-
|
|
35265
|
-
|
|
35266
|
-
|
|
36466
|
+
const values = volumeIndex(bars, closes(bars), num3(inputs, "base", 1e3), (v, pv) => v < pv);
|
|
36467
|
+
const signal = ema(values, num3(inputs, "signalLength", 255));
|
|
36468
|
+
return regimeIndex(
|
|
36469
|
+
"nvi",
|
|
36470
|
+
"NVI",
|
|
36471
|
+
values,
|
|
36472
|
+
signal,
|
|
36473
|
+
{ bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "neutralColor", NEUTRAL), signal: str3(inputs, "signalColor", NEUTRAL) },
|
|
36474
|
+
bool(inputs, "showSignal", true),
|
|
36475
|
+
bool(inputs, "gradientFill", true),
|
|
36476
|
+
str3(inputs, "colorMode", NVI_POSITION) === NVI_SINGLE
|
|
36477
|
+
);
|
|
35267
36478
|
}
|
|
35268
36479
|
};
|
|
35269
36480
|
var pvi = {
|
|
@@ -35271,15 +36482,26 @@ var pvi = {
|
|
|
35271
36482
|
title: "Positive Volume Index",
|
|
35272
36483
|
shortTitle: "Positive Volume Index",
|
|
35273
36484
|
overlay: false,
|
|
35274
|
-
inputs: [
|
|
36485
|
+
inputs: [
|
|
36486
|
+
sourceInput("Close", "source", "Source", "Price series whose one-bar percentage change moves PVI on its active sessions."),
|
|
36487
|
+
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."),
|
|
36488
|
+
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."),
|
|
36489
|
+
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."),
|
|
36490
|
+
...regimeStyleInputs("Signal Line")
|
|
36491
|
+
],
|
|
35275
36492
|
compute: (bars, inputs) => {
|
|
35276
|
-
const
|
|
35277
|
-
|
|
35278
|
-
|
|
35279
|
-
|
|
35280
|
-
|
|
35281
|
-
|
|
35282
|
-
|
|
36493
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
36494
|
+
const values = volumeIndex(bars, src, num3(inputs, "base", 1e3), (v, pv) => v > pv);
|
|
36495
|
+
const signal = ema(values, num3(inputs, "signalLength", 255));
|
|
36496
|
+
return regimeIndex(
|
|
36497
|
+
"pvi",
|
|
36498
|
+
"PVI",
|
|
36499
|
+
values,
|
|
36500
|
+
signal,
|
|
36501
|
+
{ bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "neutralColor", NEUTRAL), signal: str3(inputs, "signalColor", NEUTRAL) },
|
|
36502
|
+
bool(inputs, "showSignal", true),
|
|
36503
|
+
bool(inputs, "gradientFill", true)
|
|
36504
|
+
);
|
|
35283
36505
|
}
|
|
35284
36506
|
};
|
|
35285
36507
|
var pvt = {
|
|
@@ -35287,7 +36509,15 @@ var pvt = {
|
|
|
35287
36509
|
title: "Price Volume Trend",
|
|
35288
36510
|
shortTitle: "PVT",
|
|
35289
36511
|
overlay: false,
|
|
35290
|
-
inputs: [
|
|
36512
|
+
inputs: [
|
|
36513
|
+
boolInput("showSignal", "Signal Line", false, SETTINGS, "Display a moving average of PVT acting as a signal line."),
|
|
36514
|
+
optionInput("signalType", "Signal Type", "SMA", ["SMA", "EMA"], "Moving average type used for the signal line."),
|
|
36515
|
+
lengthInput(21, "signalLength", "Signal Length", 5e3, "Number of bars used to compute the signal line."),
|
|
36516
|
+
colorInput(NEUTRAL, "PVT Color", "color", "Color of the PVT line while it is flat."),
|
|
36517
|
+
colorInput(BULLISH, "Rising", "bullColor", "Color of the PVT line while it rises bar to bar."),
|
|
36518
|
+
colorInput(BEARISH, "Falling", "bearColor", "Color of the PVT line while it falls bar to bar."),
|
|
36519
|
+
colorInput(WARNING, "Signal Color", "signalColor", "Color of the signal line.")
|
|
36520
|
+
],
|
|
35291
36521
|
compute: (bars, inputs) => {
|
|
35292
36522
|
const c = closes(bars);
|
|
35293
36523
|
const raw = bars.map((b, i) => {
|
|
@@ -35295,7 +36525,17 @@ var pvt = {
|
|
|
35295
36525
|
const pc = c[i - 1];
|
|
35296
36526
|
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
35297
36527
|
});
|
|
35298
|
-
|
|
36528
|
+
const values = cumSum(raw);
|
|
36529
|
+
const flat = str3(inputs, "color", NEUTRAL);
|
|
36530
|
+
const plots = [
|
|
36531
|
+
{ key: "pvt", title: "PVT", values, color: flat, width: 2, colors: slopeColors2(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), flat) }
|
|
36532
|
+
];
|
|
36533
|
+
if (bool(inputs, "showSignal", false)) {
|
|
36534
|
+
const len = num3(inputs, "signalLength", 21);
|
|
36535
|
+
const signal = str3(inputs, "signalType", "SMA") === "EMA" ? ema(values, len) : sma(values, len);
|
|
36536
|
+
plots.push({ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) });
|
|
36537
|
+
}
|
|
36538
|
+
return { plots };
|
|
35299
36539
|
}
|
|
35300
36540
|
};
|
|
35301
36541
|
var volumeOscillator = {
|
|
@@ -35303,39 +36543,75 @@ var volumeOscillator = {
|
|
|
35303
36543
|
title: "Volume Oscillator",
|
|
35304
36544
|
shortTitle: "Vol Osc",
|
|
35305
36545
|
overlay: false,
|
|
35306
|
-
inputs: [
|
|
36546
|
+
inputs: [
|
|
36547
|
+
lengthInput(5, "fastLength", "Fast Length", 5e3, "Length of the fast EMA of volume."),
|
|
36548
|
+
lengthInput(10, "slowLength", "Slow Length", 5e3, "Length of the slow EMA of volume."),
|
|
36549
|
+
colorInput(BULLISH, "Above Zero", "color", "Color of the line and fill while it holds above zero \u2014 volume expanding."),
|
|
36550
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while it holds below zero \u2014 volume contracting.")
|
|
36551
|
+
],
|
|
35307
36552
|
compute: (bars, inputs) => {
|
|
35308
36553
|
const vol = volumes(bars);
|
|
35309
36554
|
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
35310
36555
|
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
35311
|
-
const values = zip(fast, slow, (f, s) => s === 0 ?
|
|
35312
|
-
|
|
35313
|
-
|
|
35314
|
-
|
|
35315
|
-
|
|
36556
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? 0 : 100 * (f - s) / s);
|
|
36557
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
36558
|
+
return centeredOscillator({
|
|
36559
|
+
key: "vo",
|
|
36560
|
+
title: "Volume Oscillator",
|
|
36561
|
+
values,
|
|
36562
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
36563
|
+
zeroInk: NEUTRAL,
|
|
36564
|
+
valueTransparency: 60
|
|
36565
|
+
});
|
|
35316
36566
|
}
|
|
35317
36567
|
};
|
|
36568
|
+
var OSCILLATOR = "Oscillator";
|
|
35318
36569
|
var pvo = {
|
|
35319
36570
|
type: "pvo",
|
|
35320
36571
|
title: "Percentage Volume Oscillator",
|
|
35321
36572
|
shortTitle: "PVO",
|
|
35322
36573
|
overlay: false,
|
|
35323
|
-
inputs: [
|
|
36574
|
+
inputs: [
|
|
36575
|
+
{ ...lengthInput(12, "fastLength", "Fast Length", 5e3, "Period of the fast EMA of volume. The conventional setting is 12."), group: OSCILLATOR },
|
|
36576
|
+
{ ...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 },
|
|
36577
|
+
{ ...lengthInput(9, "signalLength", "Signal Length", 500, "Period of the EMA applied to the PVO to form the signal line."), group: OSCILLATOR },
|
|
36578
|
+
colorInput(BULLISH, "Expansion Color", "color", "Color of the oscillator and fill while participation expands."),
|
|
36579
|
+
colorInput(BEARISH, "Contraction Color", "bearColor", "Color of the oscillator and fill while participation contracts."),
|
|
36580
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
36581
|
+
colorInput(transp(NEUTRAL, 50), "Zero Line", "zeroColor", "Color of the zero reference line."),
|
|
36582
|
+
boolInput("showSignal", "Show Signal Line", true, STYLE, "Plot the signal line, an EMA of the PVO."),
|
|
36583
|
+
boolInput("showHistogram", "Show Histogram", true, STYLE, "Plot the difference between the PVO and its signal line as columns behind the oscillator.")
|
|
36584
|
+
],
|
|
35324
36585
|
compute: (bars, inputs) => {
|
|
35325
36586
|
const vol = volumes(bars);
|
|
35326
36587
|
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
35327
36588
|
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
35328
|
-
const
|
|
35329
|
-
const signal = ema(
|
|
35330
|
-
const
|
|
35331
|
-
|
|
35332
|
-
|
|
35333
|
-
|
|
35334
|
-
|
|
35335
|
-
|
|
35336
|
-
|
|
35337
|
-
|
|
35338
|
-
|
|
36589
|
+
const values = zip(fast, slow, (f, s) => s > 0 ? 100 * (f - s) / s : Number.NaN);
|
|
36590
|
+
const signal = ema(values, num3(inputs, "signalLength", 9));
|
|
36591
|
+
const expansion = str3(inputs, "color", BULLISH);
|
|
36592
|
+
const contraction = str3(inputs, "bearColor", BEARISH);
|
|
36593
|
+
const extraPlots = [];
|
|
36594
|
+
if (bool(inputs, "showHistogram", true)) {
|
|
36595
|
+
const hist = sub(values, signal);
|
|
36596
|
+
extraPlots.push({
|
|
36597
|
+
key: "hist",
|
|
36598
|
+
title: "Histogram",
|
|
36599
|
+
values: hist,
|
|
36600
|
+
kind: "columns",
|
|
36601
|
+
color: transp(expansion, 60),
|
|
36602
|
+
colors: hist.map((x) => Number.isFinite(x) ? transp(x > 0 ? expansion : contraction, 60) : null),
|
|
36603
|
+
base: 0
|
|
36604
|
+
});
|
|
36605
|
+
}
|
|
36606
|
+
if (bool(inputs, "showSignal", true)) extraPlots.push({ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) });
|
|
36607
|
+
return centeredOscillator({
|
|
36608
|
+
key: "pvo",
|
|
36609
|
+
title: "PVO",
|
|
36610
|
+
values,
|
|
36611
|
+
ink: { bull: expansion, bear: contraction, neutral: contraction },
|
|
36612
|
+
zeroInk: str3(inputs, "zeroColor", transp(NEUTRAL, 50)),
|
|
36613
|
+
extraPlots
|
|
36614
|
+
});
|
|
35339
36615
|
}
|
|
35340
36616
|
};
|
|
35341
36617
|
var vfi = {
|
|
@@ -35344,55 +36620,86 @@ var vfi = {
|
|
|
35344
36620
|
shortTitle: "Volume Flow Indicator",
|
|
35345
36621
|
overlay: false,
|
|
35346
36622
|
inputs: [
|
|
35347
|
-
lengthInput(130),
|
|
35348
|
-
|
|
35349
|
-
|
|
35350
|
-
lengthInput(
|
|
36623
|
+
{ ...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 },
|
|
36624
|
+
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."),
|
|
36625
|
+
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."),
|
|
36626
|
+
{ ...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 },
|
|
36627
|
+
boolInput("smooth", "Smooth VFI", true, SETTINGS, "Applies the published light smoothing \u2014 an EMA of the summed line \u2014 before plotting."),
|
|
36628
|
+
lengthInput(3, "smoothLength", "Smoothing Length", 500, "Length of the smoothing EMA. The published version uses 3 bars."),
|
|
36629
|
+
boolInput("showSignal", "Signal Line", true, SETTINGS, "Plots an EMA of VFI as a signal line."),
|
|
36630
|
+
lengthInput(5, "signalLength", "Signal Length", 500, "Length of the signal-line EMA."),
|
|
36631
|
+
colorInput(BULLISH, "Bullish", "color", "Color of the VFI line and gradient fill above the zero line \u2014 the net accumulation regime."),
|
|
36632
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the VFI line and gradient fill below the zero line \u2014 the net distribution regime."),
|
|
36633
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
36634
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the zero line and the VFI line with a vertical gradient.")
|
|
35351
36635
|
],
|
|
35352
36636
|
compute: (bars, inputs) => {
|
|
35353
36637
|
const len = num3(inputs, "length", 130);
|
|
36638
|
+
const typical = sourceValues(bars, "HLC3");
|
|
36639
|
+
const logChange = sub(map(typical, Math.log), shift(map(typical, Math.log), 1));
|
|
36640
|
+
const volatility = stdev(logChange, num3(inputs, "volLength", 30));
|
|
35354
36641
|
const coef = num3(inputs, "coef", 0.2);
|
|
35355
36642
|
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
35356
|
-
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
35357
|
-
const inter = change(typical);
|
|
35358
|
-
const vInter = stdev(inter, 30);
|
|
35359
36643
|
const vol = volumes(bars);
|
|
35360
|
-
const
|
|
35361
|
-
const
|
|
35362
|
-
const
|
|
35363
|
-
const
|
|
36644
|
+
const avgVolume = shift(sma(vol, len), 1);
|
|
36645
|
+
const typicalChange = change(typical);
|
|
36646
|
+
const signed3 = bars.map((b, i) => {
|
|
36647
|
+
const sd = volatility[i];
|
|
36648
|
+
const m = typicalChange[i];
|
|
35364
36649
|
const v = vol[i];
|
|
35365
|
-
|
|
35366
|
-
const
|
|
35367
|
-
|
|
35368
|
-
const capped = Math.min(v,
|
|
36650
|
+
if (!Number.isFinite(sd) || !Number.isFinite(m) || !Number.isFinite(v)) return Number.NaN;
|
|
36651
|
+
const cutoff = coef * sd * b.close;
|
|
36652
|
+
const avg = avgVolume[i];
|
|
36653
|
+
const capped = Number.isFinite(avg) ? Math.min(v, avg * volCoef) : v;
|
|
35369
36654
|
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
35370
36655
|
});
|
|
35371
|
-
const
|
|
35372
|
-
|
|
35373
|
-
|
|
36656
|
+
const raw = zip(sum(signed3, len), avgVolume, (f, a) => a > 0 ? f / a : Number.NaN);
|
|
36657
|
+
const values = bool(inputs, "smooth", true) ? ema(raw, num3(inputs, "smoothLength", 3)) : raw;
|
|
36658
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
36659
|
+
const extraPlots = bool(inputs, "showSignal", true) ? [{ key: "signal", title: "Signal Line", values: ema(values, num3(inputs, "signalLength", 5)), color: str3(inputs, "signalColor", WARNING) }] : [];
|
|
36660
|
+
return centeredOscillator({
|
|
36661
|
+
key: "vfi",
|
|
36662
|
+
title: "VFI",
|
|
36663
|
+
values,
|
|
36664
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "bearColor", BEARISH) },
|
|
36665
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
36666
|
+
fill: bool(inputs, "gradientFill", true),
|
|
36667
|
+
extraPlots
|
|
35374
36668
|
});
|
|
35375
|
-
const flowSum = sum(raw, len);
|
|
35376
|
-
const line = zip(flowSum, values, (f, a) => f / a);
|
|
35377
|
-
return {
|
|
35378
|
-
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
35379
|
-
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
35380
|
-
};
|
|
35381
36669
|
}
|
|
35382
36670
|
};
|
|
36671
|
+
var II_OSCILLATOR = "Normalized Oscillator (II%)";
|
|
36672
|
+
var II_CUMULATIVE = "Cumulative Line";
|
|
35383
36673
|
var intradayIntensity = {
|
|
35384
36674
|
type: "intraday-intensity",
|
|
35385
36675
|
title: "Intraday Intensity",
|
|
35386
36676
|
shortTitle: "Intraday Intensity",
|
|
35387
36677
|
overlay: false,
|
|
35388
|
-
inputs: [
|
|
36678
|
+
inputs: [
|
|
36679
|
+
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."),
|
|
36680
|
+
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."),
|
|
36681
|
+
colorInput(BULLISH, "Bullish", "color", "Color of positive readings, the rising cumulative line and the fill above zero \u2014 the accumulation side."),
|
|
36682
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of negative readings, the falling cumulative line and the fill below zero \u2014 the distribution side."),
|
|
36683
|
+
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.")
|
|
36684
|
+
],
|
|
35389
36685
|
compute: (bars, inputs) => {
|
|
36686
|
+
const intensity = moneyFlowVolume(bars);
|
|
36687
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
36688
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
36689
|
+
if (str3(inputs, "mode", II_OSCILLATOR) === II_CUMULATIVE) {
|
|
36690
|
+
const values2 = cumSum(intensity);
|
|
36691
|
+
return { plots: [{ key: "ii", title: "Intraday Intensity", values: values2, color: bull, colors: slopeColors2(values2, bull, bear, bull) }] };
|
|
36692
|
+
}
|
|
35390
36693
|
const len = num3(inputs, "length", 21);
|
|
35391
|
-
const values = zip(sum(
|
|
35392
|
-
return {
|
|
35393
|
-
|
|
35394
|
-
|
|
35395
|
-
|
|
36694
|
+
const values = zip(sum(intensity, len), sum(volumes(bars), len), (m, v) => v > 0 ? 100 * m / v : Number.NaN);
|
|
36695
|
+
return centeredOscillator({
|
|
36696
|
+
key: "ii",
|
|
36697
|
+
title: "Intraday Intensity",
|
|
36698
|
+
values,
|
|
36699
|
+
ink: { bull, bear, neutral: bear },
|
|
36700
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
36701
|
+
fill: bool(inputs, "gradientFill", true)
|
|
36702
|
+
});
|
|
35396
36703
|
}
|
|
35397
36704
|
};
|
|
35398
36705
|
var volumeSpecs = [
|
|
@@ -35425,15 +36732,24 @@ function periodKey(anchor, time) {
|
|
|
35425
36732
|
}
|
|
35426
36733
|
return Math.floor(time / DAY_MS2);
|
|
35427
36734
|
}
|
|
36735
|
+
function barInterval(bars) {
|
|
36736
|
+
if (bars.length < 2) return 0;
|
|
36737
|
+
const gaps = [];
|
|
36738
|
+
for (let i = 1; i < bars.length; i++) gaps.push(bars[i].time - bars[i - 1].time);
|
|
36739
|
+
gaps.sort((a, b) => a - b);
|
|
36740
|
+
return gaps[Math.floor(gaps.length / 2)] ?? 0;
|
|
36741
|
+
}
|
|
35428
36742
|
var parabolicSar = {
|
|
35429
36743
|
type: "parabolic-sar",
|
|
35430
36744
|
title: "Parabolic SAR",
|
|
35431
36745
|
shortTitle: "SAR",
|
|
35432
36746
|
overlay: true,
|
|
35433
36747
|
inputs: [
|
|
35434
|
-
|
|
35435
|
-
|
|
35436
|
-
|
|
36748
|
+
floatInput("start", "Start", 0.02, 0, 1, 0.01, "Initial acceleration factor applied when a new trend begins."),
|
|
36749
|
+
floatInput("increment", "Increment", 0.02, 0, 1, 0.01, "Amount added to the acceleration factor each time the trend makes a new extreme point."),
|
|
36750
|
+
floatInput("maximum", "Maximum", 0.2, 0.01, 1, 0.01, "Upper cap on the acceleration factor."),
|
|
36751
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the SAR crosses while they trail below price \u2014 uptrend, the stop sits under the market."),
|
|
36752
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the SAR crosses while they sit above price \u2014 downtrend, the stop sits over the market.")
|
|
35437
36753
|
],
|
|
35438
36754
|
compute: (bars, inputs) => {
|
|
35439
36755
|
const start = num3(inputs, "start", 0.02);
|
|
@@ -35441,7 +36757,6 @@ var parabolicSar = {
|
|
|
35441
36757
|
const max = num3(inputs, "maximum", 0.2);
|
|
35442
36758
|
const n = bars.length;
|
|
35443
36759
|
const values = new Array(n).fill(Number.NaN);
|
|
35444
|
-
const colors = new Array(n).fill(null);
|
|
35445
36760
|
if (n >= 2) {
|
|
35446
36761
|
let long = bars[1].close >= bars[0].close;
|
|
35447
36762
|
let sar = long ? bars[0].low : bars[0].high;
|
|
@@ -35466,18 +36781,21 @@ var parabolicSar = {
|
|
|
35466
36781
|
af = Math.min(max, af + inc);
|
|
35467
36782
|
}
|
|
35468
36783
|
values[i] = sar;
|
|
35469
|
-
colors[i] = long ? BULLISH : BEARISH;
|
|
35470
36784
|
}
|
|
35471
36785
|
}
|
|
35472
|
-
|
|
36786
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
36787
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
36788
|
+
const colors = values.map((x, i) => Number.isFinite(x) ? x < bars[i].close ? bull : bear : null);
|
|
36789
|
+
return { plots: [{ key: "sar", title: "SAR", values, kind: "cross", color: bull, colors }] };
|
|
35473
36790
|
}
|
|
35474
36791
|
};
|
|
35475
|
-
var
|
|
35476
|
-
var
|
|
36792
|
+
var VWAP_ANCHORS = ["Session", "Week", "Month", "Quarter", "Year"];
|
|
36793
|
+
var BANDS_STDEV = "Standard Deviation";
|
|
36794
|
+
var BANDS_PERCENT = "Percentage";
|
|
35477
36795
|
var VWAP_BANDS = [
|
|
35478
|
-
{
|
|
35479
|
-
{
|
|
35480
|
-
{
|
|
36796
|
+
{ show: "band1", mult: "band1Mult", defMult: 1, defOn: true },
|
|
36797
|
+
{ show: "band2", mult: "band2Mult", defMult: 2, defOn: false },
|
|
36798
|
+
{ show: "band3", mult: "band3Mult", defMult: 3, defOn: false }
|
|
35481
36799
|
];
|
|
35482
36800
|
var vwap = {
|
|
35483
36801
|
type: "vwap",
|
|
@@ -35485,39 +36803,36 @@ var vwap = {
|
|
|
35485
36803
|
shortTitle: "VWAP",
|
|
35486
36804
|
overlay: true,
|
|
35487
36805
|
inputs: [
|
|
35488
|
-
|
|
35489
|
-
sourceInput("HLC3"),
|
|
35490
|
-
|
|
36806
|
+
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)."),
|
|
36807
|
+
sourceInput("HLC3", "source", "Source", "Price used in the volume weighted average. Typical price (HLC3) is the classic choice."),
|
|
36808
|
+
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."),
|
|
35491
36809
|
...VWAP_BANDS.flatMap((b, i) => [
|
|
35492
|
-
|
|
35493
|
-
|
|
36810
|
+
boolInput(b.show, `Band #${i + 1}`, b.defOn, SETTINGS, `Show the ${["first", "second", "third"][i]} band pair.`),
|
|
36811
|
+
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.")
|
|
35494
36812
|
]),
|
|
35495
|
-
|
|
35496
|
-
|
|
35497
|
-
|
|
35498
|
-
|
|
35499
|
-
|
|
35500
|
-
{ ...colorInput(withAlpha2(b.defColor), "", b.fillColor), inline: b.color, group: VWAP_STYLE }
|
|
35501
|
-
])
|
|
36813
|
+
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."),
|
|
36814
|
+
colorInput(BULLISH, "Bullish", "bullColor", "VWAP line color while price closes above it, and the color of the lower (support) bands."),
|
|
36815
|
+
colorInput(BEARISH, "Bearish", "bearColor", "VWAP line color while price closes below it, and the color of the upper (resistance) bands."),
|
|
36816
|
+
colorInput(NEUTRAL, "Bands", "bandsColor", "Deviation bands fill color."),
|
|
36817
|
+
boolInput("fill", "Bands Fill", true, STYLE, "Fill the area between each visible band pair.")
|
|
35502
36818
|
],
|
|
35503
36819
|
compute: (bars, inputs) => {
|
|
35504
|
-
const anchor = str3(inputs, "anchor", "
|
|
36820
|
+
const anchor = str3(inputs, "anchor", "Session");
|
|
35505
36821
|
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
35506
36822
|
const n = bars.length;
|
|
36823
|
+
const hidden = bool(inputs, "hideDwm", true) && barInterval(bars) >= DAY_MS2;
|
|
35507
36824
|
const values = new Array(n).fill(Number.NaN);
|
|
35508
|
-
const bands = VWAP_BANDS.filter((b) => bool(inputs, b.
|
|
36825
|
+
const bands = VWAP_BANDS.filter((b) => bool(inputs, b.show, b.defOn)).map((b) => ({
|
|
35509
36826
|
mult: Math.max(0, num3(inputs, b.mult, b.defMult)),
|
|
35510
|
-
color: str3(inputs, b.color, b.defColor),
|
|
35511
|
-
fill: bool(inputs, b.fill, true),
|
|
35512
|
-
fillColor: str3(inputs, b.fillColor, withAlpha2(b.defColor)),
|
|
35513
36827
|
up: new Array(n).fill(Number.NaN),
|
|
35514
36828
|
down: new Array(n).fill(Number.NaN)
|
|
35515
36829
|
}));
|
|
36830
|
+
const percentMode = str3(inputs, "bandsMode", BANDS_STDEV) === BANDS_PERCENT;
|
|
35516
36831
|
let period = Number.NaN;
|
|
35517
36832
|
let cumPV = 0;
|
|
35518
36833
|
let cumPV2 = 0;
|
|
35519
36834
|
let cumV = 0;
|
|
35520
|
-
for (let i = 0; i < n; i++) {
|
|
36835
|
+
for (let i = 0; i < n && !hidden; i++) {
|
|
35521
36836
|
const b = bars[i];
|
|
35522
36837
|
const key = periodKey(anchor, b.time);
|
|
35523
36838
|
if (key !== period) {
|
|
@@ -35535,38 +36850,58 @@ var vwap = {
|
|
|
35535
36850
|
}
|
|
35536
36851
|
if (cumV <= 0) continue;
|
|
35537
36852
|
const mean = cumPV / cumV;
|
|
35538
|
-
const
|
|
36853
|
+
const offset = percentMode ? mean / 100 : Math.sqrt(Math.max(0, cumPV2 / cumV - mean * mean));
|
|
35539
36854
|
values[i] = mean;
|
|
35540
36855
|
for (const band of bands) {
|
|
35541
|
-
band.up[i] = mean + band.mult *
|
|
35542
|
-
band.down[i] = mean - band.mult *
|
|
36856
|
+
band.up[i] = mean + band.mult * offset;
|
|
36857
|
+
band.down[i] = mean - band.mult * offset;
|
|
35543
36858
|
}
|
|
35544
36859
|
}
|
|
35545
|
-
const
|
|
36860
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
36861
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
36862
|
+
const fillColor = transp(str3(inputs, "bandsColor", NEUTRAL), 95);
|
|
36863
|
+
const plots = [
|
|
36864
|
+
{ key: "vwap", title: "VWAP", values, color: bull, colors: values.map((x, i) => Number.isFinite(x) ? bars[i].close >= x ? bull : bear : null) }
|
|
36865
|
+
];
|
|
35546
36866
|
const fills = [];
|
|
35547
36867
|
bands.forEach((band, i) => {
|
|
35548
36868
|
plots.push(
|
|
35549
|
-
{ key: `up${i}`, title: `Upper
|
|
35550
|
-
{ key: `down${i}`, title: `Lower
|
|
36869
|
+
{ key: `up${i}`, title: `Upper Band #${i + 1}`, values: band.up, color: transp(bear, 40) },
|
|
36870
|
+
{ key: `down${i}`, title: `Lower Band #${i + 1}`, values: band.down, color: transp(bull, 40) }
|
|
35551
36871
|
);
|
|
35552
|
-
if (
|
|
36872
|
+
if (bool(inputs, "fill", true)) fills.push({ key: `band${i}`, from: `up${i}`, to: `down${i}`, color: fillColor });
|
|
35553
36873
|
});
|
|
35554
36874
|
return { plots, bands: fills };
|
|
35555
36875
|
}
|
|
35556
36876
|
};
|
|
36877
|
+
var PIVOT_TRADITIONAL = "Traditional";
|
|
36878
|
+
var PIVOT_FIBONACCI = "Fibonacci";
|
|
36879
|
+
var PIVOT_CAMARILLA = "Camarilla";
|
|
36880
|
+
var PIVOT_WOODIE = "Woodie";
|
|
36881
|
+
var PIVOT_AUTO = "Auto";
|
|
35557
36882
|
var pivotPoints = {
|
|
35558
36883
|
type: "pivot-points",
|
|
35559
|
-
title: "Pivot Points",
|
|
36884
|
+
title: "Pivot Points Standard",
|
|
35560
36885
|
shortTitle: "Pivots",
|
|
35561
36886
|
overlay: true,
|
|
35562
|
-
inputs: [
|
|
36887
|
+
inputs: [
|
|
36888
|
+
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."),
|
|
36889
|
+
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."),
|
|
36890
|
+
colorInput(WARNING, "Pivot", "pivotColor", "Color of the central pivot (P) level."),
|
|
36891
|
+
colorInput(BEARISH, "Resistances", "resistanceColor", "Color of the resistance (R) levels."),
|
|
36892
|
+
colorInput(BULLISH, "Supports", "supportColor", "Color of the support (S) levels.")
|
|
36893
|
+
],
|
|
35563
36894
|
compute: (bars, inputs) => {
|
|
35564
|
-
const
|
|
36895
|
+
const interval = barInterval(bars);
|
|
36896
|
+
const requested = str3(inputs, "anchor", PIVOT_AUTO);
|
|
36897
|
+
const auto = interval <= 15 * 6e4 ? "Session" : interval < DAY_MS2 ? "Week" : interval <= DAY_MS2 ? "Month" : "Year";
|
|
36898
|
+
const anchor = requested === "Daily" ? "Session" : requested === "Weekly" ? "Week" : requested === "Monthly" ? "Month" : requested === "Yearly" ? "Year" : auto;
|
|
36899
|
+
const kind = str3(inputs, "kind", PIVOT_TRADITIONAL);
|
|
35565
36900
|
const aggs = /* @__PURE__ */ new Map();
|
|
35566
36901
|
for (const b of bars) {
|
|
35567
36902
|
const key = periodKey(anchor, b.time);
|
|
35568
36903
|
const a = aggs.get(key);
|
|
35569
|
-
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
36904
|
+
if (!a) aggs.set(key, { open: b.open, high: b.high, low: b.low, close: b.close });
|
|
35570
36905
|
else {
|
|
35571
36906
|
a.high = Math.max(a.high, b.high);
|
|
35572
36907
|
a.low = Math.min(a.low, b.low);
|
|
@@ -35575,31 +36910,61 @@ var pivotPoints = {
|
|
|
35575
36910
|
}
|
|
35576
36911
|
const n = bars.length;
|
|
35577
36912
|
const mk = () => new Array(n).fill(Number.NaN);
|
|
35578
|
-
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
36913
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk(), r4: mk(), s4: mk() };
|
|
36914
|
+
let lastKey = Number.NaN;
|
|
35579
36915
|
for (let i = 0; i < n; i++) {
|
|
35580
|
-
const
|
|
35581
|
-
|
|
35582
|
-
|
|
36916
|
+
const key = periodKey(anchor, bars[i].time);
|
|
36917
|
+
const fresh = key !== lastKey;
|
|
36918
|
+
lastKey = key;
|
|
36919
|
+
if (fresh) continue;
|
|
36920
|
+
const prev2 = aggs.get(key - 1);
|
|
36921
|
+
const curr = aggs.get(key);
|
|
36922
|
+
if (!prev2 || !curr) continue;
|
|
36923
|
+
const range = prev2.high - prev2.low;
|
|
36924
|
+
const traditional = (prev2.high + prev2.low + prev2.close) / 3;
|
|
36925
|
+
const p = kind === PIVOT_WOODIE ? (prev2.high + prev2.low + 2 * curr.open) / 4 : traditional;
|
|
35583
36926
|
levels.p[i] = p;
|
|
35584
|
-
|
|
35585
|
-
|
|
35586
|
-
|
|
35587
|
-
|
|
35588
|
-
|
|
35589
|
-
|
|
35590
|
-
|
|
35591
|
-
|
|
35592
|
-
|
|
35593
|
-
|
|
35594
|
-
|
|
35595
|
-
|
|
35596
|
-
|
|
35597
|
-
|
|
35598
|
-
|
|
35599
|
-
|
|
35600
|
-
|
|
35601
|
-
|
|
35602
|
-
|
|
36927
|
+
if (kind === PIVOT_FIBONACCI) {
|
|
36928
|
+
levels.r1[i] = traditional + 0.382 * range;
|
|
36929
|
+
levels.s1[i] = traditional - 0.382 * range;
|
|
36930
|
+
levels.r2[i] = traditional + 0.618 * range;
|
|
36931
|
+
levels.s2[i] = traditional - 0.618 * range;
|
|
36932
|
+
levels.r3[i] = traditional + range;
|
|
36933
|
+
levels.s3[i] = traditional - range;
|
|
36934
|
+
} else if (kind === PIVOT_CAMARILLA) {
|
|
36935
|
+
const cam = 1.1 * range;
|
|
36936
|
+
levels.r1[i] = prev2.close + cam / 12;
|
|
36937
|
+
levels.s1[i] = prev2.close - cam / 12;
|
|
36938
|
+
levels.r2[i] = prev2.close + cam / 6;
|
|
36939
|
+
levels.s2[i] = prev2.close - cam / 6;
|
|
36940
|
+
levels.r3[i] = prev2.close + cam / 4;
|
|
36941
|
+
levels.s3[i] = prev2.close - cam / 4;
|
|
36942
|
+
levels.r4[i] = prev2.close + cam / 2;
|
|
36943
|
+
levels.s4[i] = prev2.close - cam / 2;
|
|
36944
|
+
} else {
|
|
36945
|
+
levels.r1[i] = 2 * p - prev2.low;
|
|
36946
|
+
levels.s1[i] = 2 * p - prev2.high;
|
|
36947
|
+
levels.r2[i] = p + range;
|
|
36948
|
+
levels.s2[i] = p - range;
|
|
36949
|
+
if (kind !== PIVOT_WOODIE) {
|
|
36950
|
+
levels.r3[i] = prev2.high + 2 * (p - prev2.low);
|
|
36951
|
+
levels.s3[i] = prev2.low - 2 * (prev2.high - p);
|
|
36952
|
+
}
|
|
36953
|
+
}
|
|
36954
|
+
}
|
|
36955
|
+
const resistance = str3(inputs, "resistanceColor", BEARISH);
|
|
36956
|
+
const support = str3(inputs, "supportColor", BULLISH);
|
|
36957
|
+
const plot = (key, title, color) => ({ key, title, values: levels[key], color });
|
|
36958
|
+
const plots = [
|
|
36959
|
+
plot("p", "P", str3(inputs, "pivotColor", WARNING)),
|
|
36960
|
+
plot("r1", "R1", resistance),
|
|
36961
|
+
plot("s1", "S1", support),
|
|
36962
|
+
plot("r2", "R2", resistance),
|
|
36963
|
+
plot("s2", "S2", support)
|
|
36964
|
+
];
|
|
36965
|
+
if (kind !== PIVOT_WOODIE) plots.push(plot("r3", "R3", resistance), plot("s3", "S3", support));
|
|
36966
|
+
if (kind === PIVOT_CAMARILLA) plots.push(plot("r4", "R4", resistance), plot("s4", "S4", support));
|
|
36967
|
+
return { plots };
|
|
35603
36968
|
}
|
|
35604
36969
|
};
|
|
35605
36970
|
var fiftyTwoWeek = {
|
|
@@ -35607,15 +36972,25 @@ var fiftyTwoWeek = {
|
|
|
35607
36972
|
title: "52 Week High/Low",
|
|
35608
36973
|
shortTitle: "52W H/L",
|
|
35609
36974
|
overlay: true,
|
|
35610
|
-
inputs: [
|
|
36975
|
+
inputs: [
|
|
36976
|
+
intInput("weeks", "Weeks", 52, 1, 520, "Length of the rolling window, in weeks."),
|
|
36977
|
+
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."),
|
|
36978
|
+
colorInput(BULLISH, "52 Week High", "highColor", "Color of the 52 week high level."),
|
|
36979
|
+
colorInput(BEARISH, "52 Week Low", "lowColor", "Color of the 52 week low level."),
|
|
36980
|
+
colorInput(NEUTRAL, "All-Time High/Low", "allTimeColor", "Color of the all-time high and all-time low levels.")
|
|
36981
|
+
],
|
|
35611
36982
|
compute: (bars, inputs) => {
|
|
35612
36983
|
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
35613
36984
|
const n = bars.length;
|
|
35614
36985
|
const hi = new Array(n).fill(Number.NaN);
|
|
35615
36986
|
const lo = new Array(n).fill(Number.NaN);
|
|
36987
|
+
const ath = new Array(n).fill(Number.NaN);
|
|
36988
|
+
const atl = new Array(n).fill(Number.NaN);
|
|
35616
36989
|
const maxIdx = [];
|
|
35617
36990
|
const minIdx = [];
|
|
35618
36991
|
let from = 0;
|
|
36992
|
+
let runHigh = -Infinity;
|
|
36993
|
+
let runLow = Infinity;
|
|
35619
36994
|
for (let i = 0; i < n; i++) {
|
|
35620
36995
|
const b = bars[i];
|
|
35621
36996
|
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
@@ -35627,13 +37002,20 @@ var fiftyTwoWeek = {
|
|
|
35627
37002
|
while (minIdx[0] < from) minIdx.shift();
|
|
35628
37003
|
hi[i] = bars[maxIdx[0]].high;
|
|
35629
37004
|
lo[i] = bars[minIdx[0]].low;
|
|
37005
|
+
runHigh = Math.max(runHigh, b.high);
|
|
37006
|
+
runLow = Math.min(runLow, b.low);
|
|
37007
|
+
ath[i] = runHigh;
|
|
37008
|
+
atl[i] = runLow;
|
|
37009
|
+
}
|
|
37010
|
+
const plots = [
|
|
37011
|
+
{ key: "high", title: "52 Week High", values: hi, kind: "step", color: str3(inputs, "highColor", BULLISH), width: 2 },
|
|
37012
|
+
{ key: "low", title: "52 Week Low", values: lo, kind: "step", color: str3(inputs, "lowColor", BEARISH), width: 2 }
|
|
37013
|
+
];
|
|
37014
|
+
if (bool(inputs, "showAllTime", false)) {
|
|
37015
|
+
const ink = str3(inputs, "allTimeColor", NEUTRAL);
|
|
37016
|
+
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 });
|
|
35630
37017
|
}
|
|
35631
|
-
return {
|
|
35632
|
-
plots: [
|
|
35633
|
-
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
35634
|
-
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
35635
|
-
]
|
|
35636
|
-
};
|
|
37018
|
+
return { plots };
|
|
35637
37019
|
}
|
|
35638
37020
|
};
|
|
35639
37021
|
var zigzag = {
|
|
@@ -35641,9 +37023,11 @@ var zigzag = {
|
|
|
35641
37023
|
title: "ZigZag",
|
|
35642
37024
|
overlay: true,
|
|
35643
37025
|
inputs: [
|
|
35644
|
-
|
|
35645
|
-
{
|
|
35646
|
-
colorInput()
|
|
37026
|
+
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."),
|
|
37027
|
+
{ ...intInput("depth", "Depth", 10, 2, 500, "Minimum number of bars required between two consecutive pivots.") },
|
|
37028
|
+
colorInput(BULLISH, "Up Leg", "bullColor", "Color of rising zigzag segments."),
|
|
37029
|
+
colorInput(BEARISH, "Down Leg", "bearColor", "Color of falling zigzag segments."),
|
|
37030
|
+
{ key: "lineWidth", title: "Width", type: "int", defval: 2, min: 1, max: 10, step: 1, group: STYLE, tooltip: "Width of the zigzag segments." }
|
|
35647
37031
|
],
|
|
35648
37032
|
compute: (bars, inputs) => {
|
|
35649
37033
|
const dev = num3(inputs, "deviation", 5) / 100;
|
|
@@ -35656,14 +37040,14 @@ var zigzag = {
|
|
|
35656
37040
|
const b = bars[i];
|
|
35657
37041
|
if (up) {
|
|
35658
37042
|
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
35659
|
-
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
37043
|
+
else if (b.low <= ext.price * (1 - dev) && ext.i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
35660
37044
|
pivots.push(ext);
|
|
35661
37045
|
up = false;
|
|
35662
37046
|
ext = { i, price: b.low };
|
|
35663
37047
|
}
|
|
35664
37048
|
} else if (b.low <= ext.price) {
|
|
35665
37049
|
ext = { i, price: b.low };
|
|
35666
|
-
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
37050
|
+
} else if (b.high >= ext.price * (1 + dev) && ext.i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
35667
37051
|
pivots.push(ext);
|
|
35668
37052
|
up = true;
|
|
35669
37053
|
ext = { i, price: b.high };
|
|
@@ -35671,22 +37055,29 @@ var zigzag = {
|
|
|
35671
37055
|
}
|
|
35672
37056
|
pivots.push(ext);
|
|
35673
37057
|
}
|
|
35674
|
-
|
|
35675
|
-
|
|
35676
|
-
|
|
35677
|
-
|
|
35678
|
-
|
|
35679
|
-
|
|
35680
|
-
|
|
35681
|
-
|
|
35682
|
-
|
|
35683
|
-
|
|
35684
|
-
|
|
35685
|
-
|
|
35686
|
-
|
|
35687
|
-
|
|
35688
|
-
|
|
35689
|
-
|
|
37058
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
37059
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
37060
|
+
const width = num3(inputs, "lineWidth", 2);
|
|
37061
|
+
const polylines = [];
|
|
37062
|
+
for (let k = 1; k < pivots.length; k++) {
|
|
37063
|
+
const a = pivots[k - 1];
|
|
37064
|
+
const b = pivots[k];
|
|
37065
|
+
polylines.push({
|
|
37066
|
+
key: `leg${k}`,
|
|
37067
|
+
points: [
|
|
37068
|
+
{ xloc: "bar_time", x: bars[a.i].time, price: a.price },
|
|
37069
|
+
{ xloc: "bar_time", x: bars[b.i].time, price: b.price }
|
|
37070
|
+
],
|
|
37071
|
+
curved: false,
|
|
37072
|
+
closed: false,
|
|
37073
|
+
lineColor: b.price >= a.price ? bull : bear,
|
|
37074
|
+
lineWidth: width,
|
|
37075
|
+
lineStyle: "solid",
|
|
37076
|
+
arrowLeft: false,
|
|
37077
|
+
arrowRight: false
|
|
37078
|
+
});
|
|
37079
|
+
}
|
|
37080
|
+
return { plots: [], polylines };
|
|
35690
37081
|
}
|
|
35691
37082
|
};
|
|
35692
37083
|
var williamsFractal = {
|
|
@@ -35694,10 +37085,16 @@ var williamsFractal = {
|
|
|
35694
37085
|
title: "Williams Fractal",
|
|
35695
37086
|
shortTitle: "Fractals",
|
|
35696
37087
|
overlay: true,
|
|
35697
|
-
inputs: [
|
|
37088
|
+
inputs: [
|
|
37089
|
+
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."),
|
|
37090
|
+
colorInput(BEARISH, "Up Fractal", "upColor", "Color of the up fractal marker (swing high), drawn above the fractal bar."),
|
|
37091
|
+
colorInput(BULLISH, "Down Fractal", "downColor", "Color of the down fractal marker (swing low), drawn below the fractal bar.")
|
|
37092
|
+
],
|
|
35698
37093
|
compute: (bars, inputs) => {
|
|
35699
37094
|
const p = num3(inputs, "periods", 2);
|
|
35700
37095
|
const markers = [];
|
|
37096
|
+
const up = str3(inputs, "upColor", BEARISH);
|
|
37097
|
+
const down = str3(inputs, "downColor", BULLISH);
|
|
35701
37098
|
const isExtreme = (i, pick, better) => {
|
|
35702
37099
|
const v = pick(bars[i]);
|
|
35703
37100
|
for (let k = i - p; k <= i + p; k++) {
|
|
@@ -35707,12 +37104,8 @@ var williamsFractal = {
|
|
|
35707
37104
|
return true;
|
|
35708
37105
|
};
|
|
35709
37106
|
for (let i = p; i < bars.length - p; i++) {
|
|
35710
|
-
if (isExtreme(i, (b) => b.high, (a, b) => a
|
|
35711
|
-
|
|
35712
|
-
}
|
|
35713
|
-
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
35714
|
-
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
35715
|
-
}
|
|
37107
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a > b)) markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: up });
|
|
37108
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a < b)) markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: down });
|
|
35716
37109
|
}
|
|
35717
37110
|
return { plots: [], markers };
|
|
35718
37111
|
}
|