@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/widget.cjs
CHANGED
|
@@ -700,6 +700,8 @@ var MultiProviderFeed = class {
|
|
|
700
700
|
this.liveBars = [];
|
|
701
701
|
/** Sync-accessible symbol metadata, warmed by load()/symbolInfoFor (the engine reads it synchronously). */
|
|
702
702
|
this.symInfoCache = /* @__PURE__ */ new Map();
|
|
703
|
+
/** Requests in flight, keyed like the cache — every concurrent asker shares one round trip. */
|
|
704
|
+
this.symInfoInflight = /* @__PURE__ */ new Map();
|
|
703
705
|
this.cache = new CachingDataFeed(new RegistryFetchFeed(this.registry), store);
|
|
704
706
|
}
|
|
705
707
|
// ── Registry surface (driven by chart.data / DataControl) ──────────────
|
|
@@ -760,9 +762,36 @@ var MultiProviderFeed = class {
|
|
|
760
762
|
async symbolInfoFor(raw) {
|
|
761
763
|
const resolved = this.registry.resolve(raw, { default: this.primaryProvider });
|
|
762
764
|
if (!resolved) return void 0;
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
765
|
+
return this.fetchSymbolInfo(resolved);
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* One round trip per symbol, shared. A market switch probes this metadata from five
|
|
769
|
+
* places at once (the bar load's prefetch, the renderer's tick size, the market-status
|
|
770
|
+
* badge, the session shading, the session toggle) — without the in-flight map each
|
|
771
|
+
* would open its own request for the same answer, and the last one to land decided how
|
|
772
|
+
* late the chrome settled.
|
|
773
|
+
*
|
|
774
|
+
* Cached for {@link SYMBOL_INFO_TTL_MS}: this metadata is stable within a session
|
|
775
|
+
* (tick size, session vocabulary, listing prefix), but not immutable — a continuous
|
|
776
|
+
* futures row rolls its current contract — so the entry expires rather than pinning the
|
|
777
|
+
* session's first answer forever. A failed probe is NOT remembered: the next asker retries.
|
|
778
|
+
*/
|
|
779
|
+
fetchSymbolInfo(resolved) {
|
|
780
|
+
const key = symKey(resolved);
|
|
781
|
+
const hit = this.symInfoCache.get(key);
|
|
782
|
+
if (hit && Date.now() - hit.at < SYMBOL_INFO_TTL_MS) return Promise.resolve(hit.info);
|
|
783
|
+
const inflight = this.symInfoInflight.get(key);
|
|
784
|
+
if (inflight) return inflight;
|
|
785
|
+
const getSymbolInfo = this.registry.get(resolved.provider)?.getSymbolInfo;
|
|
786
|
+
if (!getSymbolInfo) return Promise.resolve(void 0);
|
|
787
|
+
const p = Promise.resolve(getSymbolInfo.call(this.registry.get(resolved.provider), resolved.ticker)).then((info) => {
|
|
788
|
+
if (info) this.symInfoCache.set(key, { info, at: Date.now() });
|
|
789
|
+
return info;
|
|
790
|
+
}).finally(() => {
|
|
791
|
+
this.symInfoInflight.delete(key);
|
|
792
|
+
});
|
|
793
|
+
this.symInfoInflight.set(key, p);
|
|
794
|
+
return p;
|
|
766
795
|
}
|
|
767
796
|
/**
|
|
768
797
|
* Per-symbol capabilities, resolved through the owning provider's `capabilitiesFor`
|
|
@@ -806,16 +835,10 @@ var MultiProviderFeed = class {
|
|
|
806
835
|
symbolInfo(cfg) {
|
|
807
836
|
if (cfg.data && cfg.data.length > 0) return void 0;
|
|
808
837
|
const resolved = this.registry.resolve(rawSymbol(cfg), { default: this.primaryProvider });
|
|
809
|
-
return resolved ? this.symInfoCache.get(symKey(resolved)) : void 0;
|
|
838
|
+
return resolved ? this.symInfoCache.get(symKey(resolved))?.info : void 0;
|
|
810
839
|
}
|
|
811
840
|
prefetchSymbolInfo(resolved) {
|
|
812
|
-
|
|
813
|
-
if (this.symInfoCache.has(key)) return;
|
|
814
|
-
const provider = this.registry.get(resolved.provider);
|
|
815
|
-
if (!provider?.getSymbolInfo) return;
|
|
816
|
-
void provider.getSymbolInfo(resolved.ticker).then((info) => {
|
|
817
|
-
if (info) this.symInfoCache.set(key, info);
|
|
818
|
-
}).catch(() => {
|
|
841
|
+
void this.fetchSymbolInfo(resolved).catch(() => {
|
|
819
842
|
});
|
|
820
843
|
}
|
|
821
844
|
async loadRange(cfg, range) {
|
|
@@ -865,8 +888,9 @@ function canonical(cfg, resolved) {
|
|
|
865
888
|
return { ...cfg, symbol: `${resolved.provider}:${resolved.ticker}` };
|
|
866
889
|
}
|
|
867
890
|
function symKey(resolved) {
|
|
868
|
-
return `${resolved.provider}|${resolved.ticker}`;
|
|
891
|
+
return `${resolved.provider}|${resolved.ticker.trim().toUpperCase()}`;
|
|
869
892
|
}
|
|
893
|
+
var SYMBOL_INFO_TTL_MS = 10 * 6e4;
|
|
870
894
|
async function safeBars(provider, ticker, tf, range) {
|
|
871
895
|
try {
|
|
872
896
|
return await provider.getBars(ticker, tf, range);
|
|
@@ -29402,7 +29426,7 @@ var SettingsDialog = class {
|
|
|
29402
29426
|
if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
|
|
29403
29427
|
else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
29404
29428
|
else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
29405
|
-
else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options
|
|
29429
|
+
else body.append(this.selectRowLabeled(hr.label, hr.get(), normalizeSelectOptions(hr.options), (v) => hr.set(v)));
|
|
29406
29430
|
}
|
|
29407
29431
|
}
|
|
29408
29432
|
};
|
|
@@ -39861,7 +39885,8 @@ var ClassicIndicator = class {
|
|
|
39861
39885
|
lineStyle: plot.lineStyle ?? "solid",
|
|
39862
39886
|
...plot.base != null ? { base: plot.base } : {}
|
|
39863
39887
|
},
|
|
39864
|
-
...plot.overlay ? { overlay: true } : {}
|
|
39888
|
+
...plot.overlay ? { overlay: true } : {},
|
|
39889
|
+
...plot.display ? { display: plot.display } : {}
|
|
39865
39890
|
});
|
|
39866
39891
|
});
|
|
39867
39892
|
if (out.markers && out.markers.length > 0) {
|
|
@@ -39883,7 +39908,9 @@ var ClassicIndicator = class {
|
|
|
39883
39908
|
paneId: "",
|
|
39884
39909
|
fromSeriesId: from,
|
|
39885
39910
|
toSeriesId: to,
|
|
39886
|
-
color: band.color
|
|
39911
|
+
color: band.color,
|
|
39912
|
+
...band.colors ? { colors: [...band.colors] } : {},
|
|
39913
|
+
...band.gradient ? { gradient: [...band.gradient] } : {}
|
|
39887
39914
|
}
|
|
39888
39915
|
];
|
|
39889
39916
|
});
|
|
@@ -40041,6 +40068,16 @@ function stdev(v, len) {
|
|
|
40041
40068
|
return Math.sqrt(sq / len);
|
|
40042
40069
|
});
|
|
40043
40070
|
}
|
|
40071
|
+
function meanDev(v, len) {
|
|
40072
|
+
return windowed(v, len, (w, from, to) => {
|
|
40073
|
+
let s = 0;
|
|
40074
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
40075
|
+
const mean = s / len;
|
|
40076
|
+
let dev = 0;
|
|
40077
|
+
for (let k = from; k <= to; k++) dev += Math.abs(w[k] - mean);
|
|
40078
|
+
return dev / len;
|
|
40079
|
+
});
|
|
40080
|
+
}
|
|
40044
40081
|
function highest(v, len) {
|
|
40045
40082
|
return windowed(v, len, (w, from, to) => {
|
|
40046
40083
|
let m = -Infinity;
|
|
@@ -40172,7 +40209,7 @@ function stoch(v, hi, lo, len) {
|
|
|
40172
40209
|
}
|
|
40173
40210
|
return out;
|
|
40174
40211
|
}
|
|
40175
|
-
function linreg(v, len) {
|
|
40212
|
+
function linreg(v, len, offset = 0) {
|
|
40176
40213
|
const sx = (len - 1) * len / 2;
|
|
40177
40214
|
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
40178
40215
|
const denom = len * sxx - sx * sx;
|
|
@@ -40186,9 +40223,101 @@ function linreg(v, len) {
|
|
|
40186
40223
|
}
|
|
40187
40224
|
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
40188
40225
|
const intercept = (sy - slope * sx) / len;
|
|
40189
|
-
return intercept + slope * (len - 1);
|
|
40226
|
+
return intercept + slope * (len - 1 - offset);
|
|
40227
|
+
});
|
|
40228
|
+
}
|
|
40229
|
+
function hma(v, len) {
|
|
40230
|
+
const n = Math.max(2, Math.trunc(len));
|
|
40231
|
+
const raw = sub(map(wma(v, Math.max(1, Math.trunc(n / 2))), (x) => 2 * x), wma(v, n));
|
|
40232
|
+
return wma(raw, Math.max(1, Math.round(Math.sqrt(n))));
|
|
40233
|
+
}
|
|
40234
|
+
function alma(v, len, offset, sigma) {
|
|
40235
|
+
const m = offset * (len - 1);
|
|
40236
|
+
const s = len / Math.max(sigma, 1e-9);
|
|
40237
|
+
const weights = new Array(len);
|
|
40238
|
+
let norm = 0;
|
|
40239
|
+
for (let i = 0; i < len; i++) {
|
|
40240
|
+
const d = i - m;
|
|
40241
|
+
const w = Math.exp(-(d * d) / (2 * s * s));
|
|
40242
|
+
weights[i] = w;
|
|
40243
|
+
norm += w;
|
|
40244
|
+
}
|
|
40245
|
+
return windowed(v, len, (win, from, to) => {
|
|
40246
|
+
let acc = 0;
|
|
40247
|
+
for (let k = from; k <= to; k++) acc += win[k] * weights[k - from];
|
|
40248
|
+
return norm === 0 ? Number.NaN : acc / norm;
|
|
40249
|
+
});
|
|
40250
|
+
}
|
|
40251
|
+
function dema(v, len) {
|
|
40252
|
+
const e1 = ema(v, len);
|
|
40253
|
+
return sub(map(e1, (x) => 2 * x), ema(e1, len));
|
|
40254
|
+
}
|
|
40255
|
+
function tema(v, len) {
|
|
40256
|
+
const e1 = ema(v, len);
|
|
40257
|
+
const e2 = ema(e1, len);
|
|
40258
|
+
const e3 = ema(e2, len);
|
|
40259
|
+
return zip(zip(map(e1, (x) => 3 * x), map(e2, (x) => 3 * x), (a, b) => a - b), e3, (a, b) => a + b);
|
|
40260
|
+
}
|
|
40261
|
+
function kama(v, len, fastAlpha = 2 / 3, slowAlpha = 2 / 31) {
|
|
40262
|
+
const direction = map(change(v, len), Math.abs);
|
|
40263
|
+
const volatility = sum(map(change(v), Math.abs), len);
|
|
40264
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
40265
|
+
let prev2 = Number.NaN;
|
|
40266
|
+
for (let i = 0; i < v.length; i++) {
|
|
40267
|
+
const x = v[i];
|
|
40268
|
+
if (!Number.isFinite(x)) continue;
|
|
40269
|
+
const d = direction[i];
|
|
40270
|
+
const w = volatility[i];
|
|
40271
|
+
if (!Number.isFinite(d) || !Number.isFinite(w)) {
|
|
40272
|
+
prev2 = Number.isFinite(prev2) ? prev2 : x;
|
|
40273
|
+
out[i] = prev2;
|
|
40274
|
+
continue;
|
|
40275
|
+
}
|
|
40276
|
+
const er = w === 0 ? 0 : d / w;
|
|
40277
|
+
const sc = (er * (fastAlpha - slowAlpha) + slowAlpha) ** 2;
|
|
40278
|
+
prev2 = Number.isFinite(prev2) ? prev2 + sc * (x - prev2) : x;
|
|
40279
|
+
out[i] = prev2;
|
|
40280
|
+
}
|
|
40281
|
+
return out;
|
|
40282
|
+
}
|
|
40283
|
+
function mcginley(v, len) {
|
|
40284
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
40285
|
+
let prev2 = Number.NaN;
|
|
40286
|
+
for (let i = 0; i < v.length; i++) {
|
|
40287
|
+
const x = v[i];
|
|
40288
|
+
if (!Number.isFinite(x)) continue;
|
|
40289
|
+
if (!Number.isFinite(prev2)) {
|
|
40290
|
+
prev2 = x;
|
|
40291
|
+
} else {
|
|
40292
|
+
const ratio = prev2 === 0 ? 0 : x / prev2;
|
|
40293
|
+
const denom = len * ratio ** 4;
|
|
40294
|
+
prev2 += denom === 0 ? 0 : (x - prev2) / denom;
|
|
40295
|
+
}
|
|
40296
|
+
out[i] = prev2;
|
|
40297
|
+
}
|
|
40298
|
+
return out;
|
|
40299
|
+
}
|
|
40300
|
+
function hamming(v, len, a0 = 0.54, a1 = 0.46) {
|
|
40301
|
+
if (len <= 1) return v.map((x) => Number.isFinite(x) ? x : Number.NaN);
|
|
40302
|
+
const weights = new Array(len);
|
|
40303
|
+
let norm = 0;
|
|
40304
|
+
for (let i = 0; i < len; i++) {
|
|
40305
|
+
const w = a0 - a1 * Math.cos(2 * Math.PI * i / (len - 1));
|
|
40306
|
+
weights[i] = w;
|
|
40307
|
+
norm += w;
|
|
40308
|
+
}
|
|
40309
|
+
return windowed(v, len, (win, from, to) => {
|
|
40310
|
+
let acc = 0;
|
|
40311
|
+
for (let k = from; k <= to; k++) acc += win[k] * weights[to - k];
|
|
40312
|
+
return norm === 0 ? Number.NaN : acc / norm;
|
|
40190
40313
|
});
|
|
40191
40314
|
}
|
|
40315
|
+
function cmo(v, len) {
|
|
40316
|
+
const d = change(v);
|
|
40317
|
+
const up = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
40318
|
+
const dn = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
40319
|
+
return zip(up, dn, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
40320
|
+
}
|
|
40192
40321
|
function swma(v) {
|
|
40193
40322
|
const out = new Array(v.length).fill(Number.NaN);
|
|
40194
40323
|
for (let i = 3; i < v.length; i++) {
|
|
@@ -40238,38 +40367,253 @@ function cumSum(v) {
|
|
|
40238
40367
|
}
|
|
40239
40368
|
|
|
40240
40369
|
// src/core/native-indicators/classics/shared.ts
|
|
40241
|
-
|
|
40242
|
-
|
|
40370
|
+
var SETTINGS = "Settings";
|
|
40371
|
+
var STYLE = "Style";
|
|
40372
|
+
function lengthInput(defval, key = "length", title = "Length", max = 5e3, tooltip2) {
|
|
40373
|
+
return { key, title, type: "int", defval, min: 1, max, step: 1, group: SETTINGS, ...tooltip2 != null ? { tooltip: tooltip2 } : {} };
|
|
40374
|
+
}
|
|
40375
|
+
function intInput(key, title, defval, min = 1, max = 5e3, tooltip2) {
|
|
40376
|
+
return { key, title, type: "int", defval, min, max, step: 1, group: SETTINGS, ...tooltip2 != null ? { tooltip: tooltip2 } : {} };
|
|
40377
|
+
}
|
|
40378
|
+
function floatInput(key, title, defval, min = 0, max = 1e3, step = 0.1, tooltip2) {
|
|
40379
|
+
return { key, title, type: "float", defval, min, max, step, group: SETTINGS, ...tooltip2 != null ? { tooltip: tooltip2 } : {} };
|
|
40380
|
+
}
|
|
40381
|
+
function boolInput(key, title, defval, group = SETTINGS, tooltip2) {
|
|
40382
|
+
return { key, title, type: "bool", defval, group, ...tooltip2 != null ? { tooltip: tooltip2 } : {} };
|
|
40243
40383
|
}
|
|
40244
|
-
function
|
|
40245
|
-
return { key
|
|
40384
|
+
function optionInput(key, title, defval, options, tooltip2) {
|
|
40385
|
+
return { key, title, type: "string", defval, options, group: SETTINGS, ...tooltip2 != null ? { tooltip: tooltip2 } : {} };
|
|
40246
40386
|
}
|
|
40247
|
-
function
|
|
40248
|
-
return { key, title, type: "
|
|
40387
|
+
function sourceInput(defval = "Close", key = "source", title = "Source", tooltip2) {
|
|
40388
|
+
return { key, title, type: "string", defval, options: SOURCES2, group: SETTINGS, ...tooltip2 != null ? { tooltip: tooltip2 } : {} };
|
|
40389
|
+
}
|
|
40390
|
+
function colorInput(defval = INFO, title = "Color", key = "color", tooltip2) {
|
|
40391
|
+
return { key, title, type: "color", defval, group: STYLE, ...tooltip2 != null ? { tooltip: tooltip2 } : {} };
|
|
40392
|
+
}
|
|
40393
|
+
function widthInput(defval = 2, key = "lineWidth", title = "Line Width") {
|
|
40394
|
+
return { key, title, type: "int", defval, min: 1, max: 5, step: 1, group: STYLE, tooltip: "Width of the plotted line, in pixels." };
|
|
40395
|
+
}
|
|
40396
|
+
function transp(hex, transparency) {
|
|
40397
|
+
return withAlpha2(hex, (100 - transparency) / 100);
|
|
40249
40398
|
}
|
|
40250
40399
|
function withAlpha2(hex, alpha = 0.08) {
|
|
40251
40400
|
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
40252
40401
|
if (!m) return hex;
|
|
40253
|
-
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
40402
|
+
const a = Math.round(Math.max(0, Math.min(1, alpha)) * 255).toString(16).padStart(2, "0");
|
|
40254
40403
|
return `#${m[1]}${a}`;
|
|
40255
40404
|
}
|
|
40405
|
+
function channelStyleInputs(basisTitle = "Basis") {
|
|
40406
|
+
return [
|
|
40407
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the lower band and of the basis while price holds above it."),
|
|
40408
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the upper band and of the basis while price sits below it."),
|
|
40409
|
+
colorInput(NEUTRAL, basisTitle, "basisColor", "Color of the basis line while price sits exactly on it.")
|
|
40410
|
+
];
|
|
40411
|
+
}
|
|
40412
|
+
function directionalChannel(bars, basis, upper, lower, ink, key) {
|
|
40413
|
+
const basisColors = basis.map((b, i) => {
|
|
40414
|
+
if (!Number.isFinite(b)) return null;
|
|
40415
|
+
const c = bars[i].close;
|
|
40416
|
+
return c > b ? ink.bull : c < b ? ink.bear : ink.neutral;
|
|
40417
|
+
});
|
|
40418
|
+
const wash = upper.map((u, i) => {
|
|
40419
|
+
const l = lower[i];
|
|
40420
|
+
if (!Number.isFinite(u) || !Number.isFinite(l)) return null;
|
|
40421
|
+
return { topValue: u, bottomValue: l, topColor: transp(ink.bear, 90), bottomColor: transp(ink.bull, 90) };
|
|
40422
|
+
});
|
|
40423
|
+
return {
|
|
40424
|
+
plots: [
|
|
40425
|
+
{ key: "basis", title: "Basis", values: [...basis], color: ink.neutral, colors: basisColors },
|
|
40426
|
+
{ key: "upper", title: "Upper", values: [...upper], color: transp(ink.bear, 30) },
|
|
40427
|
+
{ key: "lower", title: "Lower", values: [...lower], color: transp(ink.bull, 30) }
|
|
40428
|
+
],
|
|
40429
|
+
bands: [{ key, from: "upper", to: "lower", color: transp(NEUTRAL, 90), gradient: wash }]
|
|
40430
|
+
};
|
|
40431
|
+
}
|
|
40432
|
+
function anchorPlot(key, title, length, level) {
|
|
40433
|
+
return {
|
|
40434
|
+
key,
|
|
40435
|
+
title,
|
|
40436
|
+
values: new Array(length).fill(level),
|
|
40437
|
+
color: transp(NEUTRAL, 100),
|
|
40438
|
+
display: { pane: false, legend: false, dataWindow: false }
|
|
40439
|
+
};
|
|
40440
|
+
}
|
|
40441
|
+
function thresholdZones(length, levels, ink, transparency = 92) {
|
|
40442
|
+
return {
|
|
40443
|
+
plots: [
|
|
40444
|
+
anchorPlot("obAnchor", "Overbought Anchor", length, levels.overbought),
|
|
40445
|
+
anchorPlot("osAnchor", "Oversold Anchor", length, levels.oversold),
|
|
40446
|
+
anchorPlot("topAnchor", "Top Anchor", length, levels.top),
|
|
40447
|
+
anchorPlot("bottomAnchor", "Bottom Anchor", length, levels.bottom)
|
|
40448
|
+
],
|
|
40449
|
+
bands: [
|
|
40450
|
+
{ key: "obZone", from: "obAnchor", to: "topAnchor", color: transp(ink.overbought, transparency) },
|
|
40451
|
+
{ key: "osZone", from: "osAnchor", to: "bottomAnchor", color: transp(ink.oversold, transparency) }
|
|
40452
|
+
]
|
|
40453
|
+
};
|
|
40454
|
+
}
|
|
40455
|
+
function rampColors(values, levels, ink) {
|
|
40456
|
+
return values.map((x) => {
|
|
40457
|
+
if (!Number.isFinite(x)) return null;
|
|
40458
|
+
return x <= levels.mid ? gradient(x, levels.low, levels.mid, ink.low, ink.mid) : gradient(x, levels.mid, levels.high, ink.mid, ink.high);
|
|
40459
|
+
});
|
|
40460
|
+
}
|
|
40461
|
+
function baselineGradient(values, baseline, inkOf, valueTransparency = 50, baseTransparency = 100) {
|
|
40462
|
+
return values.map((x, i) => {
|
|
40463
|
+
if (!Number.isFinite(x)) return null;
|
|
40464
|
+
const ink = inkOf(x, i);
|
|
40465
|
+
const above = x > baseline;
|
|
40466
|
+
return {
|
|
40467
|
+
topValue: Math.max(x, baseline),
|
|
40468
|
+
bottomValue: Math.min(x, baseline),
|
|
40469
|
+
topColor: transp(ink, above ? valueTransparency : baseTransparency),
|
|
40470
|
+
bottomColor: transp(ink, above ? baseTransparency : valueTransparency)
|
|
40471
|
+
};
|
|
40472
|
+
});
|
|
40473
|
+
}
|
|
40474
|
+
function momentumColumnColors(values, ink) {
|
|
40475
|
+
return values.map((x, i) => {
|
|
40476
|
+
if (!Number.isFinite(x)) return null;
|
|
40477
|
+
const prev2 = i > 0 && Number.isFinite(values[i - 1]) ? values[i - 1] : x;
|
|
40478
|
+
const rising = x > prev2;
|
|
40479
|
+
if (x >= 0) return rising ? ink.growAbove : ink.fallAbove;
|
|
40480
|
+
return rising ? ink.growBelow : ink.fallBelow;
|
|
40481
|
+
});
|
|
40482
|
+
}
|
|
40483
|
+
function boundedOscillator(opts) {
|
|
40484
|
+
const mid = (opts.overbought + opts.oversold) / 2;
|
|
40485
|
+
const ramp = rampColors(opts.values, { low: opts.oversold, mid, high: opts.overbought }, opts.ink);
|
|
40486
|
+
const zones = thresholdZones(
|
|
40487
|
+
opts.values.length,
|
|
40488
|
+
{ overbought: opts.overbought, oversold: opts.oversold, top: opts.scale.top, bottom: opts.scale.bottom },
|
|
40489
|
+
{ overbought: opts.ink.high, oversold: opts.ink.low },
|
|
40490
|
+
opts.zoneTransparency
|
|
40491
|
+
);
|
|
40492
|
+
const plots = [{ key: opts.key, title: opts.title, values: opts.values, color: opts.ink.mid, width: opts.width ?? 1, colors: ramp }, ...zones.plots];
|
|
40493
|
+
const bands = [...zones.bands];
|
|
40494
|
+
if (opts.bandFill != null) bands.push({ key: "band", from: "obAnchor", to: "osAnchor", color: opts.bandFill });
|
|
40495
|
+
if (opts.midpointWash) {
|
|
40496
|
+
plots.push(anchorPlot("midAnchor", "Midpoint Anchor", opts.values.length, mid));
|
|
40497
|
+
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) });
|
|
40498
|
+
}
|
|
40499
|
+
const levels = [
|
|
40500
|
+
{ key: "overbought", price: opts.overbought, color: opts.levelInk, lineStyle: "dashed", title: opts.levelTitles?.overbought ?? "Overbought" },
|
|
40501
|
+
{ key: "oversold", price: opts.oversold, color: opts.levelInk, lineStyle: "dashed", title: opts.levelTitles?.oversold ?? "Oversold" }
|
|
40502
|
+
];
|
|
40503
|
+
if (opts.midline != null) levels.splice(1, 0, { key: "midline", price: opts.midline, color: opts.levelInk, lineStyle: "dotted", title: "Midline" });
|
|
40504
|
+
return { plots, bands, levels };
|
|
40505
|
+
}
|
|
40506
|
+
function centeredOscillator(opts) {
|
|
40507
|
+
const baseline = opts.baseline ?? 0;
|
|
40508
|
+
const inkOf = (x) => x > baseline ? opts.ink.bull : x < baseline ? opts.ink.bear : opts.ink.neutral;
|
|
40509
|
+
const plots = [
|
|
40510
|
+
{ 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) },
|
|
40511
|
+
...opts.extraPlots ?? []
|
|
40512
|
+
];
|
|
40513
|
+
const bands = [];
|
|
40514
|
+
if (opts.fill !== false) {
|
|
40515
|
+
plots.push(anchorPlot("baseline", "Baseline Anchor", opts.values.length, baseline));
|
|
40516
|
+
bands.push({ key: "wash", from: opts.key, to: "baseline", color: transp(NEUTRAL, 100), gradient: baselineGradient(opts.values, baseline, inkOf, opts.valueTransparency) });
|
|
40517
|
+
}
|
|
40518
|
+
return {
|
|
40519
|
+
plots,
|
|
40520
|
+
bands,
|
|
40521
|
+
levels: [{ key: "zero", price: baseline, color: opts.zeroInk, lineStyle: opts.zeroLineStyle ?? "dashed", title: opts.zeroTitle ?? "Zero Line" }, ...opts.extraLevels ?? []]
|
|
40522
|
+
};
|
|
40523
|
+
}
|
|
40524
|
+
function channels(hex) {
|
|
40525
|
+
const m = /^#([0-9a-f]{6})/i.exec(hex.trim());
|
|
40526
|
+
if (!m) return null;
|
|
40527
|
+
const n = parseInt(m[1], 16);
|
|
40528
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
40529
|
+
}
|
|
40530
|
+
function gradient(value, low, high, from, to) {
|
|
40531
|
+
const a = channels(from);
|
|
40532
|
+
const b = channels(to);
|
|
40533
|
+
if (!a || !b || !Number.isFinite(value)) return from;
|
|
40534
|
+
const span = high - low;
|
|
40535
|
+
const t = span === 0 ? 0 : Math.max(0, Math.min(1, (value - low) / span));
|
|
40536
|
+
const mix2 = (i) => Math.round(a[i] + (b[i] - a[i]) * t).toString(16).padStart(2, "0");
|
|
40537
|
+
return `#${mix2(0)}${mix2(1)}${mix2(2)}`;
|
|
40538
|
+
}
|
|
40256
40539
|
|
|
40257
40540
|
// src/core/native-indicators/classics/averages.ts
|
|
40258
|
-
var
|
|
40259
|
-
var
|
|
40541
|
+
var MA_TYPES = ["SMA", "EMA", "WMA", "RMA (SMMA)", "HMA", "ALMA", "VWMA", "DEMA", "TEMA", "KAMA", "LSMA", "McGinley", "Hamming"];
|
|
40542
|
+
var ENVELOPE_MA_TYPES = ["SMA", "EMA", "WMA", "RMA (SMMA)", "HMA", "VWMA"];
|
|
40543
|
+
function movingAverageOf(kind, src, bars, len, opts = {}) {
|
|
40544
|
+
switch (kind) {
|
|
40545
|
+
case "EMA":
|
|
40546
|
+
return ema(src, len);
|
|
40547
|
+
case "WMA":
|
|
40548
|
+
return wma(src, len);
|
|
40549
|
+
case "RMA":
|
|
40550
|
+
case "RMA (SMMA)":
|
|
40551
|
+
return rma(src, len);
|
|
40552
|
+
case "HMA":
|
|
40553
|
+
return hma(src, len);
|
|
40554
|
+
case "ALMA":
|
|
40555
|
+
return alma(src, len, opts.almaOffset ?? 0.85, opts.almaSigma ?? 6);
|
|
40556
|
+
case "VWMA":
|
|
40557
|
+
return vwma(src, volumes(bars), len);
|
|
40558
|
+
case "DEMA":
|
|
40559
|
+
return dema(src, len);
|
|
40560
|
+
case "TEMA":
|
|
40561
|
+
return tema(src, len);
|
|
40562
|
+
case "KAMA":
|
|
40563
|
+
return kama(src, len);
|
|
40564
|
+
case "LSMA":
|
|
40565
|
+
return linreg(src, len, opts.lsmaOffset ?? 0);
|
|
40566
|
+
case "McGinley":
|
|
40567
|
+
return mcginley(src, len);
|
|
40568
|
+
case "Hamming":
|
|
40569
|
+
return hamming(src, len);
|
|
40570
|
+
default:
|
|
40571
|
+
return sma(src, len);
|
|
40572
|
+
}
|
|
40573
|
+
}
|
|
40574
|
+
function slopeColors(values, bull, bear, flat) {
|
|
40575
|
+
return values.map((x, i) => {
|
|
40576
|
+
if (!Number.isFinite(x)) return null;
|
|
40577
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
40578
|
+
if (!Number.isFinite(prev2)) return flat;
|
|
40579
|
+
return x > prev2 ? bull : x < prev2 ? bear : flat;
|
|
40580
|
+
});
|
|
40581
|
+
}
|
|
40582
|
+
function directionInputs() {
|
|
40583
|
+
return [
|
|
40584
|
+
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."),
|
|
40585
|
+
colorInput(BULLISH, "Bullish", "bullColor"),
|
|
40586
|
+
colorInput(BEARISH, "Bearish", "bearColor"),
|
|
40587
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor"),
|
|
40588
|
+
widthInput()
|
|
40589
|
+
];
|
|
40590
|
+
}
|
|
40591
|
+
function slopeColoredPlot(key, title, values, inputs) {
|
|
40592
|
+
const neutral = str3(inputs, "neutralColor", NEUTRAL);
|
|
40593
|
+
const colored = bool(inputs, "trendColor", true);
|
|
40594
|
+
return {
|
|
40595
|
+
key,
|
|
40596
|
+
title,
|
|
40597
|
+
values,
|
|
40598
|
+
color: neutral,
|
|
40599
|
+
width: num3(inputs, "lineWidth", 2),
|
|
40600
|
+
...colored ? { colors: slopeColors(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), neutral) } : {}
|
|
40601
|
+
};
|
|
40602
|
+
}
|
|
40603
|
+
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." };
|
|
40260
40604
|
function fixedAverage(type, title, shortTitle, smooth) {
|
|
40261
40605
|
return {
|
|
40262
40606
|
type,
|
|
40263
40607
|
title,
|
|
40264
40608
|
shortTitle,
|
|
40265
40609
|
overlay: true,
|
|
40266
|
-
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
40610
|
+
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()],
|
|
40267
40611
|
compute: (bars, inputs) => {
|
|
40268
40612
|
const len = num3(inputs, "length", 20);
|
|
40269
40613
|
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
40270
40614
|
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
40271
40615
|
if (offset !== 0) values = shift(values, offset);
|
|
40272
|
-
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color",
|
|
40616
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
40273
40617
|
}
|
|
40274
40618
|
};
|
|
40275
40619
|
}
|
|
@@ -40281,36 +40625,37 @@ var movingAverage = {
|
|
|
40281
40625
|
shortTitle: "MA",
|
|
40282
40626
|
overlay: true,
|
|
40283
40627
|
inputs: [
|
|
40284
|
-
|
|
40285
|
-
lengthInput(20),
|
|
40286
|
-
sourceInput(),
|
|
40628
|
+
optionInput("maType", "MA 1 Type", "SMA", MA_TYPES, "Moving average calculation used for MA 1."),
|
|
40629
|
+
lengthInput(20, "length", "MA 1 Length", 5e3, "Number of bars used in the MA 1 calculation."),
|
|
40630
|
+
sourceInput("Close", "source", "MA 1 Source", "Price series MA 1 is calculated on."),
|
|
40631
|
+
boolInput("showMa2", "Show MA 2", false, SETTINGS, "Displays the optional second moving average."),
|
|
40632
|
+
optionInput("ma2Type", "MA 2 Type", "SMA", MA_TYPES, "Moving average calculation used for MA 2."),
|
|
40633
|
+
lengthInput(50, "ma2Length", "MA 2 Length", 5e3, "Number of bars used in the MA 2 calculation."),
|
|
40634
|
+
sourceInput("Close", "ma2Source", "MA 2 Source", "Price series MA 2 is calculated on."),
|
|
40635
|
+
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."),
|
|
40636
|
+
floatInput("almaSigma", "ALMA Sigma", 6, 0.1, 100, 0.5, "ALMA type only. Larger values widen the Gaussian window for a smoother average."),
|
|
40637
|
+
intInput("lsmaOffset", "LSMA Offset", 0, 0, 500, "LSMA type only. Bar offset applied to the linear regression value."),
|
|
40287
40638
|
offsetInput,
|
|
40288
|
-
colorInput()
|
|
40639
|
+
colorInput(INFO, "MA 1", "color", "MA 1 line color."),
|
|
40640
|
+
colorInput(WARNING, "MA 2", "ma2Color", "MA 2 line color.")
|
|
40289
40641
|
],
|
|
40290
40642
|
compute: (bars, inputs) => {
|
|
40291
|
-
const
|
|
40292
|
-
|
|
40643
|
+
const opts = {
|
|
40644
|
+
almaOffset: num3(inputs, "almaOffset", 0.85),
|
|
40645
|
+
almaSigma: num3(inputs, "almaSigma", 6),
|
|
40646
|
+
lsmaOffset: Math.trunc(num3(inputs, "lsmaOffset", 0))
|
|
40647
|
+
};
|
|
40648
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
40649
|
+
const displace = (v) => offset === 0 ? v : shift(v, offset);
|
|
40293
40650
|
const kind = str3(inputs, "maType", "SMA");
|
|
40294
|
-
|
|
40295
|
-
|
|
40296
|
-
|
|
40297
|
-
|
|
40298
|
-
|
|
40299
|
-
|
|
40300
|
-
values = wma(src, len);
|
|
40301
|
-
break;
|
|
40302
|
-
case "RMA":
|
|
40303
|
-
values = rma(src, len);
|
|
40304
|
-
break;
|
|
40305
|
-
case "VWMA":
|
|
40306
|
-
values = vwma(src, volumes(bars), len);
|
|
40307
|
-
break;
|
|
40308
|
-
default:
|
|
40309
|
-
values = sma(src, len);
|
|
40651
|
+
const ma1 = displace(movingAverageOf(kind, sourceValues(bars, str3(inputs, "source", "Close")), bars, num3(inputs, "length", 20), opts));
|
|
40652
|
+
const plots = [{ key: "ma", title: kind, values: ma1, color: str3(inputs, "color", INFO), width: 2 }];
|
|
40653
|
+
if (bool(inputs, "showMa2", false)) {
|
|
40654
|
+
const kind2 = str3(inputs, "ma2Type", "SMA");
|
|
40655
|
+
const ma2 = displace(movingAverageOf(kind2, sourceValues(bars, str3(inputs, "ma2Source", "Close")), bars, num3(inputs, "ma2Length", 50), opts));
|
|
40656
|
+
plots.push({ key: "ma2", title: `${kind2} 2`, values: ma2, color: str3(inputs, "ma2Color", WARNING), width: 2 });
|
|
40310
40657
|
}
|
|
40311
|
-
|
|
40312
|
-
if (offset !== 0) values = shift(values, offset);
|
|
40313
|
-
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
40658
|
+
return { plots };
|
|
40314
40659
|
}
|
|
40315
40660
|
};
|
|
40316
40661
|
var smoothedMa = {
|
|
@@ -40318,17 +40663,13 @@ var smoothedMa = {
|
|
|
40318
40663
|
title: "Smoothed Moving Average",
|
|
40319
40664
|
shortTitle: "RMA",
|
|
40320
40665
|
overlay: true,
|
|
40321
|
-
inputs: [
|
|
40666
|
+
inputs: [
|
|
40667
|
+
sourceInput("Close", "source", "Source", "Price series the average smooths. Wilder computed his 1978 indicators on the close, but the recursion accepts any source."),
|
|
40668
|
+
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."),
|
|
40669
|
+
...directionInputs()
|
|
40670
|
+
],
|
|
40322
40671
|
compute: (bars, inputs) => ({
|
|
40323
|
-
plots: [
|
|
40324
|
-
{
|
|
40325
|
-
key: "rma",
|
|
40326
|
-
title: "RMA",
|
|
40327
|
-
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
40328
|
-
color: str3(inputs, "color", SERIES_LINE),
|
|
40329
|
-
width: 2
|
|
40330
|
-
}
|
|
40331
|
-
]
|
|
40672
|
+
plots: [slopeColoredPlot("rma", "RMA", rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)), inputs)]
|
|
40332
40673
|
})
|
|
40333
40674
|
};
|
|
40334
40675
|
var zlema = {
|
|
@@ -40336,61 +40677,86 @@ var zlema = {
|
|
|
40336
40677
|
title: "Zero-Lag Exponential Moving Average",
|
|
40337
40678
|
shortTitle: "ZLEMA",
|
|
40338
40679
|
overlay: true,
|
|
40339
|
-
inputs: [
|
|
40680
|
+
inputs: [
|
|
40681
|
+
sourceInput("Close", "source", "Source", "Price series the average de-lags and then smooths."),
|
|
40682
|
+
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."),
|
|
40683
|
+
...directionInputs()
|
|
40684
|
+
],
|
|
40340
40685
|
compute: (bars, inputs) => {
|
|
40341
|
-
const len = num3(inputs, "length",
|
|
40686
|
+
const len = num3(inputs, "length", 21);
|
|
40342
40687
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
40343
|
-
const lag = Math.
|
|
40344
|
-
const
|
|
40345
|
-
|
|
40346
|
-
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
40688
|
+
const lag = Math.round((len - 1) / 2);
|
|
40689
|
+
const delagged = zip(src, shift(src, lag), (a, b) => 2 * a - b);
|
|
40690
|
+
return { plots: [slopeColoredPlot("zlema", "ZLEMA", ema(delagged, len), inputs)] };
|
|
40347
40691
|
}
|
|
40348
40692
|
};
|
|
40693
|
+
var MOMENTUM_SCALER = "Momentum (|CMO|)";
|
|
40694
|
+
var VOLATILITY_SCALER = "Volatility (StDev Ratio)";
|
|
40695
|
+
var REGIME = "Trend Regime";
|
|
40349
40696
|
var vidya = {
|
|
40350
40697
|
type: "vidya",
|
|
40351
40698
|
title: "Variable Index Dynamic Average",
|
|
40352
40699
|
shortTitle: "VIDYA",
|
|
40353
40700
|
overlay: true,
|
|
40354
|
-
inputs: [
|
|
40701
|
+
inputs: [
|
|
40702
|
+
sourceInput("Close", "source", "Source", "Price series the average smooths. VIDYA is classically computed on the close."),
|
|
40703
|
+
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."),
|
|
40704
|
+
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."),
|
|
40705
|
+
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."),
|
|
40706
|
+
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."),
|
|
40707
|
+
{ ...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 },
|
|
40708
|
+
{ ...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 },
|
|
40709
|
+
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."),
|
|
40710
|
+
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."),
|
|
40711
|
+
colorInput(BULLISH, "Bullish", "bullColor"),
|
|
40712
|
+
colorInput(BEARISH, "Bearish", "bearColor"),
|
|
40713
|
+
colorInput(NEUTRAL, "Flat", "flatColor"),
|
|
40714
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor"),
|
|
40715
|
+
widthInput()
|
|
40716
|
+
],
|
|
40355
40717
|
compute: (bars, inputs) => {
|
|
40356
|
-
const len = num3(inputs, "length",
|
|
40357
|
-
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
40718
|
+
const len = num3(inputs, "length", 12);
|
|
40358
40719
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
40359
|
-
const
|
|
40360
|
-
const
|
|
40361
|
-
const
|
|
40362
|
-
|
|
40363
|
-
|
|
40364
|
-
|
|
40365
|
-
|
|
40366
|
-
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
40367
|
-
const x = d[k];
|
|
40368
|
-
if (!Number.isFinite(x)) {
|
|
40369
|
-
ok = false;
|
|
40370
|
-
break;
|
|
40371
|
-
}
|
|
40372
|
-
if (x > 0) u += x;
|
|
40373
|
-
else w -= x;
|
|
40374
|
-
}
|
|
40375
|
-
if (ok) {
|
|
40376
|
-
su[i] = u;
|
|
40377
|
-
sd[i] = w;
|
|
40378
|
-
}
|
|
40379
|
-
}
|
|
40380
|
-
const alpha = 2 / (len + 1);
|
|
40720
|
+
const scalerLen = num3(inputs, "scalerLength", 9);
|
|
40721
|
+
const momentum = map(cmo(src, scalerLen), (x) => Math.abs(x) / 100);
|
|
40722
|
+
const shortDev = stdev(src, scalerLen);
|
|
40723
|
+
const refDev = stdev(src, num3(inputs, "refLength", 30));
|
|
40724
|
+
const volatility = zip(shortDev, refDev, (s, r) => r > 0 ? s / r : 0);
|
|
40725
|
+
const scale = str3(inputs, "scaler", MOMENTUM_SCALER) === VOLATILITY_SCALER ? volatility : momentum;
|
|
40726
|
+
const alphaBase = 2 / (len + 1);
|
|
40381
40727
|
const out = new Array(src.length).fill(Number.NaN);
|
|
40382
40728
|
let prev2 = Number.NaN;
|
|
40383
40729
|
for (let i = 0; i < src.length; i++) {
|
|
40384
40730
|
const x = src[i];
|
|
40385
|
-
|
|
40386
|
-
const
|
|
40387
|
-
if (!Number.isFinite(
|
|
40388
|
-
|
|
40389
|
-
|
|
40390
|
-
|
|
40731
|
+
if (!Number.isFinite(x)) continue;
|
|
40732
|
+
const k = scale[i];
|
|
40733
|
+
if (!Number.isFinite(prev2) || !Number.isFinite(k)) prev2 = x;
|
|
40734
|
+
else {
|
|
40735
|
+
const alpha = Math.min(alphaBase * k, 1);
|
|
40736
|
+
prev2 = alpha * x + (1 - alpha) * prev2;
|
|
40737
|
+
}
|
|
40391
40738
|
out[i] = prev2;
|
|
40392
40739
|
}
|
|
40393
|
-
|
|
40740
|
+
const neutral = str3(inputs, "neutralColor", NEUTRAL);
|
|
40741
|
+
if (!bool(inputs, "trendColor", true)) {
|
|
40742
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: neutral, width: num3(inputs, "lineWidth", 2) }] };
|
|
40743
|
+
}
|
|
40744
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
40745
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
40746
|
+
const flat = str3(inputs, "flatColor", NEUTRAL);
|
|
40747
|
+
const threshold = num3(inputs, "flatThreshold", 0.05);
|
|
40748
|
+
const range = atr(bars, num3(inputs, "atrLength", 14));
|
|
40749
|
+
const fade = bool(inputs, "adaptiveGradient", true);
|
|
40750
|
+
const colors = out.map((x, i) => {
|
|
40751
|
+
if (!Number.isFinite(x)) return null;
|
|
40752
|
+
const r = range[i];
|
|
40753
|
+
const slope = i > 0 && Number.isFinite(out[i - 1]) ? x - out[i - 1] : 0;
|
|
40754
|
+
const normalized = Number.isFinite(r) && r !== 0 ? slope / r : 0;
|
|
40755
|
+
const ink = normalized > threshold ? bull : normalized < -threshold ? bear : null;
|
|
40756
|
+
if (ink == null) return flat;
|
|
40757
|
+
return fade ? gradient(Math.min(scale[i] ?? 0, 1), 0, 1, flat, ink) : ink;
|
|
40758
|
+
});
|
|
40759
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: flat, width: num3(inputs, "lineWidth", 2), colors }] };
|
|
40394
40760
|
}
|
|
40395
40761
|
};
|
|
40396
40762
|
var maEnvelope = {
|
|
@@ -40399,115 +40765,218 @@ var maEnvelope = {
|
|
|
40399
40765
|
shortTitle: "MA Env",
|
|
40400
40766
|
overlay: true,
|
|
40401
40767
|
inputs: [
|
|
40402
|
-
|
|
40403
|
-
lengthInput(20),
|
|
40404
|
-
sourceInput(),
|
|
40405
|
-
|
|
40406
|
-
|
|
40768
|
+
optionInput("maType", "MA Type", "SMA", ENVELOPE_MA_TYPES, "Moving average type used as the envelope basis."),
|
|
40769
|
+
{ ...lengthInput(20, "length", "Length", 5e3, "Number of bars in the basis moving average."), min: 2 },
|
|
40770
|
+
sourceInput("Close", "source", "Source", "Price series the basis moving average is calculated on."),
|
|
40771
|
+
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)."),
|
|
40772
|
+
...channelStyleInputs()
|
|
40407
40773
|
],
|
|
40408
40774
|
compute: (bars, inputs) => {
|
|
40409
|
-
const len = num3(inputs, "length", 20);
|
|
40410
40775
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
40411
|
-
const basis = str3(inputs, "maType", "SMA")
|
|
40776
|
+
const basis = movingAverageOf(str3(inputs, "maType", "SMA"), src, bars, num3(inputs, "length", 20));
|
|
40412
40777
|
const k = num3(inputs, "percent", 2.5) / 100;
|
|
40413
|
-
const ink = str3(inputs, "
|
|
40414
|
-
return
|
|
40415
|
-
plots: [
|
|
40416
|
-
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
40417
|
-
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
40418
|
-
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
40419
|
-
],
|
|
40420
|
-
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
40421
|
-
};
|
|
40778
|
+
const ink = { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "basisColor", NEUTRAL) };
|
|
40779
|
+
return directionalChannel(bars, basis, map(basis, (x) => x * (1 + k)), map(basis, (x) => x * (1 - k)), ink, "envelope");
|
|
40422
40780
|
}
|
|
40423
40781
|
};
|
|
40782
|
+
var LINREG_CURVE = "Curve";
|
|
40783
|
+
var LINREG_SLOPE = "Slope";
|
|
40424
40784
|
var linearRegression = {
|
|
40425
40785
|
type: "linear-regression",
|
|
40426
40786
|
title: "Linear Regression Curve",
|
|
40427
40787
|
shortTitle: "LinReg",
|
|
40428
|
-
|
|
40429
|
-
|
|
40430
|
-
|
|
40431
|
-
plots
|
|
40432
|
-
|
|
40433
|
-
|
|
40434
|
-
|
|
40435
|
-
|
|
40436
|
-
|
|
40437
|
-
|
|
40438
|
-
|
|
40439
|
-
|
|
40440
|
-
|
|
40788
|
+
// Slope lives in its own pane; Curve force-overlays itself onto the price candles.
|
|
40789
|
+
overlay: false,
|
|
40790
|
+
inputs: [
|
|
40791
|
+
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."),
|
|
40792
|
+
{ ...lengthInput(100, "length", "Length", 5e3, "Number of bars used to fit the linear regression."), min: 2 },
|
|
40793
|
+
sourceInput("Close", "source", "Source", "Price series the regression is fitted to."),
|
|
40794
|
+
colorInput(BULLISH, "Rising", "bullColor", "Color of the regression curve and slope line while the fit slopes upward."),
|
|
40795
|
+
colorInput(BEARISH, "Falling", "bearColor", "Color of the regression curve and slope line while the fit slopes downward."),
|
|
40796
|
+
colorInput(NEUTRAL, "Regression Line", "lineColor", "Color of the regression curve and slope line while the fit is flat.")
|
|
40797
|
+
],
|
|
40798
|
+
compute: (bars, inputs) => {
|
|
40799
|
+
const len = num3(inputs, "length", 100);
|
|
40800
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
40801
|
+
const curve = linreg(src, len, 0);
|
|
40802
|
+
const slope = zip(curve, linreg(src, len, 1), (a, b) => a - b);
|
|
40803
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
40804
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
40805
|
+
const flat = str3(inputs, "lineColor", NEUTRAL);
|
|
40806
|
+
const colors = slope.map((s) => Number.isFinite(s) ? s > 0 ? bull : s < 0 ? bear : flat : null);
|
|
40807
|
+
if (str3(inputs, "mode", LINREG_CURVE) === LINREG_SLOPE) {
|
|
40808
|
+
return {
|
|
40809
|
+
plots: [{ key: "slope", title: "Slope", values: slope, color: flat, width: 2, colors }],
|
|
40810
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed" }]
|
|
40811
|
+
};
|
|
40812
|
+
}
|
|
40813
|
+
return { plots: [{ key: "curve", title: "Curve", values: curve, color: flat, width: 2, colors, overlay: true }] };
|
|
40814
|
+
}
|
|
40441
40815
|
};
|
|
40442
40816
|
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
40443
40817
|
|
|
40444
40818
|
// src/core/native-indicators/classics/bands.ts
|
|
40819
|
+
var JAW_COLOR = "#ab47bc";
|
|
40820
|
+
var TEETH_COLOR = WARNING;
|
|
40821
|
+
var LIPS_COLOR = "#ffd700";
|
|
40445
40822
|
function bollinger(bars, inputs) {
|
|
40446
40823
|
const len = num3(inputs, "length", 20);
|
|
40447
40824
|
const mult = num3(inputs, "mult", 2);
|
|
40448
40825
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
40449
40826
|
const basis = sma(src, len);
|
|
40450
40827
|
const dev = map(stdev(src, len), (x) => x * mult);
|
|
40451
|
-
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
40828
|
+
return { src, basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
40829
|
+
}
|
|
40830
|
+
function bollingerInputs(multMin, multStep) {
|
|
40831
|
+
return [
|
|
40832
|
+
{ ...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 },
|
|
40833
|
+
sourceInput("Close", "source", "Source", "Price series the bands are computed from."),
|
|
40834
|
+
floatInput("mult", "Multiplier", 2, multMin, 50, multStep, "Standard deviation multiplier for the upper and lower bands.")
|
|
40835
|
+
];
|
|
40452
40836
|
}
|
|
40453
|
-
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
40454
40837
|
var bollingerBands = {
|
|
40455
40838
|
type: "bollinger-bands",
|
|
40456
40839
|
title: "Bollinger Bands",
|
|
40457
40840
|
shortTitle: "BB",
|
|
40458
40841
|
overlay: true,
|
|
40459
|
-
inputs: [
|
|
40842
|
+
inputs: [
|
|
40843
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used to calculate the basis SMA and the standard deviation."),
|
|
40844
|
+
sourceInput("Close", "source", "Source", "Price series the bands are calculated on."),
|
|
40845
|
+
floatInput("mult", "Multiplier", 2, 0, 50, 0.1, "Number of standard deviations added above and below the basis to form the bands."),
|
|
40846
|
+
colorInput(WARNING, "Basis", "basisColor", "Color of the basis (middle band) moving average."),
|
|
40847
|
+
colorInput(INFO, "Bands", "color", "Color of the upper and lower bands."),
|
|
40848
|
+
colorInput(transp(INFO, 92), "Fill", "fillColor", "Fill color of the area between the upper and lower bands.")
|
|
40849
|
+
],
|
|
40460
40850
|
compute: (bars, inputs) => {
|
|
40461
40851
|
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
40462
|
-
const ink = str3(inputs, "color",
|
|
40852
|
+
const ink = str3(inputs, "color", INFO);
|
|
40463
40853
|
return {
|
|
40464
40854
|
plots: [
|
|
40465
|
-
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
40855
|
+
{ key: "basis", title: "Basis", values: basis, color: str3(inputs, "basisColor", WARNING) },
|
|
40466
40856
|
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
40467
40857
|
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
40468
40858
|
],
|
|
40469
|
-
bands: [{ key: "bb", from: "upper", to: "lower", color:
|
|
40859
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: str3(inputs, "fillColor", transp(INFO, 92)) }]
|
|
40470
40860
|
};
|
|
40471
40861
|
}
|
|
40472
40862
|
};
|
|
40863
|
+
var PERCENT_B_MID = 0.5;
|
|
40864
|
+
var THRESHOLDS = "Thresholds";
|
|
40473
40865
|
var percentB = {
|
|
40474
40866
|
type: "percent-b",
|
|
40475
40867
|
title: "Bollinger Bands %B",
|
|
40476
40868
|
shortTitle: "%B",
|
|
40477
40869
|
overlay: false,
|
|
40478
|
-
inputs: [
|
|
40870
|
+
inputs: [
|
|
40871
|
+
...bollingerInputs(1e-3, 0.25),
|
|
40872
|
+
{ ...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 },
|
|
40873
|
+
{ ...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 },
|
|
40874
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of %B above the 0.5 midline, where price trades in the upper half of the bands."),
|
|
40875
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of %B below the 0.5 midline, where price trades in the lower half of the bands."),
|
|
40876
|
+
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."),
|
|
40877
|
+
colorInput(NEUTRAL, "Levels Color", "levelsColor", "Color of the dashed overbought/oversold levels and of the dotted midline.")
|
|
40878
|
+
],
|
|
40479
40879
|
compute: (bars, inputs) => {
|
|
40480
|
-
const { upper, lower } = bollinger(bars, inputs);
|
|
40481
|
-
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
40880
|
+
const { src, upper, lower } = bollinger(bars, inputs);
|
|
40482
40881
|
const values = src.map((x, i) => {
|
|
40483
40882
|
const u = upper[i];
|
|
40484
40883
|
const l = lower[i];
|
|
40485
40884
|
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
40486
40885
|
});
|
|
40886
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
40887
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
40888
|
+
const levels = str3(inputs, "levelsColor", NEUTRAL);
|
|
40889
|
+
const colors = values.map((x) => Number.isFinite(x) ? x > PERCENT_B_MID ? bull : bear : null);
|
|
40890
|
+
const plots = [
|
|
40891
|
+
{ key: "pb", title: "%B", values, color: bear, colors },
|
|
40892
|
+
{ 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 } }
|
|
40893
|
+
];
|
|
40894
|
+
const bands = bool(inputs, "gradientFill", true) ? [
|
|
40895
|
+
{
|
|
40896
|
+
key: "pbFill",
|
|
40897
|
+
from: "pb",
|
|
40898
|
+
to: "mid",
|
|
40899
|
+
color: transp(NEUTRAL, 100),
|
|
40900
|
+
// Opaque at the oscillator, invisible where it meets the midline.
|
|
40901
|
+
gradient: values.map((x) => {
|
|
40902
|
+
if (!Number.isFinite(x)) return null;
|
|
40903
|
+
const up = x > PERCENT_B_MID;
|
|
40904
|
+
const ink = up ? bull : bear;
|
|
40905
|
+
return {
|
|
40906
|
+
topValue: Math.max(x, PERCENT_B_MID),
|
|
40907
|
+
bottomValue: Math.min(x, PERCENT_B_MID),
|
|
40908
|
+
topColor: up ? transp(ink, 50) : transp(ink, 100),
|
|
40909
|
+
bottomColor: up ? transp(ink, 100) : transp(ink, 50)
|
|
40910
|
+
};
|
|
40911
|
+
})
|
|
40912
|
+
}
|
|
40913
|
+
] : [];
|
|
40487
40914
|
return {
|
|
40488
|
-
plots
|
|
40915
|
+
plots,
|
|
40916
|
+
bands,
|
|
40489
40917
|
levels: [
|
|
40490
|
-
{ key: "
|
|
40491
|
-
{ key: "
|
|
40492
|
-
{ key: "
|
|
40918
|
+
{ key: "overbought", price: num3(inputs, "overbought", 1), color: levels, lineStyle: "dashed", title: "Overbought" },
|
|
40919
|
+
{ key: "oversold", price: num3(inputs, "oversold", 0), color: levels, lineStyle: "dashed", title: "Oversold" },
|
|
40920
|
+
{ key: "midline", price: PERCENT_B_MID, color: transp(levels, 50), lineStyle: "dotted", title: "Midline" }
|
|
40493
40921
|
]
|
|
40494
40922
|
};
|
|
40495
40923
|
}
|
|
40496
40924
|
};
|
|
40925
|
+
var SQUEEZE_BULGE = "Squeeze & Bulge";
|
|
40497
40926
|
var bandwidth = {
|
|
40498
40927
|
type: "bandwidth",
|
|
40499
40928
|
title: "Bollinger Bands Width",
|
|
40500
40929
|
shortTitle: "BandWidth",
|
|
40501
40930
|
overlay: false,
|
|
40502
|
-
inputs: [
|
|
40931
|
+
inputs: [
|
|
40932
|
+
...bollingerInputs(1e-3, 0.25),
|
|
40933
|
+
{ ...intInput("lookback", "Reference Lookback", 125, 2, 5e3, "Window used to locate the lowest (Squeeze) and highest (Bulge) BandWidth references."), group: SQUEEZE_BULGE },
|
|
40934
|
+
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."),
|
|
40935
|
+
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."),
|
|
40936
|
+
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."),
|
|
40937
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between zero and BandWidth with a vertical gradient, fully transparent at zero."),
|
|
40938
|
+
colorInput(WARNING, "Squeeze Level", "squeezeColor", "Color of the lowest-BandWidth reference line."),
|
|
40939
|
+
colorInput(BEARISH, "Bulge Level", "bulgeColor", "Color of the highest-BandWidth reference line.")
|
|
40940
|
+
],
|
|
40503
40941
|
compute: (bars, inputs) => {
|
|
40504
40942
|
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
40505
40943
|
const values = basis.map((b, i) => {
|
|
40506
40944
|
const u = upper[i];
|
|
40507
40945
|
const l = lower[i];
|
|
40508
|
-
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b
|
|
40946
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b : Number.NaN;
|
|
40947
|
+
});
|
|
40948
|
+
const lookback = num3(inputs, "lookback", 125);
|
|
40949
|
+
const squeezeLevel = lowest(values, lookback);
|
|
40950
|
+
const bulgeLevel = highest(values, lookback);
|
|
40951
|
+
const base = str3(inputs, "color", NEUTRAL);
|
|
40952
|
+
const squeezeInk = str3(inputs, "squeezeColor", WARNING);
|
|
40953
|
+
const bulgeInk = str3(inputs, "bulgeColor", BEARISH);
|
|
40954
|
+
const colors = values.map((x, i) => {
|
|
40955
|
+
if (!Number.isFinite(x)) return null;
|
|
40956
|
+
if (x <= squeezeLevel[i]) return squeezeInk;
|
|
40957
|
+
if (x >= bulgeLevel[i]) return bulgeInk;
|
|
40958
|
+
return base;
|
|
40509
40959
|
});
|
|
40510
|
-
|
|
40960
|
+
const plots = [
|
|
40961
|
+
{ key: "bbw", title: "BandWidth", values, color: base, colors },
|
|
40962
|
+
{ key: "zero", title: "Zero Line", values: values.map((x) => Number.isFinite(x) ? 0 : Number.NaN), color: transp(NEUTRAL, 90) }
|
|
40963
|
+
];
|
|
40964
|
+
if (bool(inputs, "showSqueeze", true)) plots.push({ key: "squeeze", title: "Squeeze Level", values: squeezeLevel, color: squeezeInk });
|
|
40965
|
+
if (bool(inputs, "showBulge", true)) plots.push({ key: "bulge", title: "Bulge Level", values: bulgeLevel, color: bulgeInk });
|
|
40966
|
+
const bands = bool(inputs, "gradientFill", true) ? [
|
|
40967
|
+
{
|
|
40968
|
+
key: "bbwFill",
|
|
40969
|
+
from: "bbw",
|
|
40970
|
+
to: "zero",
|
|
40971
|
+
color: transp(NEUTRAL, 100),
|
|
40972
|
+
gradient: values.map((x, i) => {
|
|
40973
|
+
if (!Number.isFinite(x)) return null;
|
|
40974
|
+
const ink = colors[i] ?? base;
|
|
40975
|
+
return { topValue: x, bottomValue: 0, topColor: transp(ink, 50), bottomColor: transp(ink, 100) };
|
|
40976
|
+
})
|
|
40977
|
+
}
|
|
40978
|
+
] : [];
|
|
40979
|
+
return { plots, bands };
|
|
40511
40980
|
}
|
|
40512
40981
|
};
|
|
40513
40982
|
var keltner = {
|
|
@@ -40516,26 +40985,17 @@ var keltner = {
|
|
|
40516
40985
|
shortTitle: "KC",
|
|
40517
40986
|
overlay: true,
|
|
40518
40987
|
inputs: [
|
|
40519
|
-
lengthInput(20),
|
|
40520
|
-
|
|
40521
|
-
|
|
40522
|
-
sourceInput(),
|
|
40523
|
-
|
|
40988
|
+
lengthInput(20, "length", "EMA Length", 5e3, "Number of bars used to compute the exponential moving average basis line."),
|
|
40989
|
+
intInput("atrLength", "ATR Length", 10, 1, 5e3, "Number of bars used to compute the Average True Range (Wilder smoothing)."),
|
|
40990
|
+
floatInput("mult", "Multiplier", 2, 0, 50, 0.1, "ATR multiple used to offset the upper and lower bands from the basis line."),
|
|
40991
|
+
sourceInput("Close", "source", "Source", "Price series used to compute the basis line."),
|
|
40992
|
+
...channelStyleInputs("Basis Color")
|
|
40524
40993
|
],
|
|
40525
40994
|
compute: (bars, inputs) => {
|
|
40526
|
-
const
|
|
40527
|
-
const
|
|
40528
|
-
const
|
|
40529
|
-
|
|
40530
|
-
const ink = str3(inputs, "color", SERIES_LINE);
|
|
40531
|
-
return {
|
|
40532
|
-
plots: [
|
|
40533
|
-
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
40534
|
-
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
40535
|
-
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
40536
|
-
],
|
|
40537
|
-
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
40538
|
-
};
|
|
40995
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20));
|
|
40996
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * num3(inputs, "mult", 2));
|
|
40997
|
+
const ink = { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "basisColor", NEUTRAL) };
|
|
40998
|
+
return directionalChannel(bars, basis, zip(basis, range, (b, r) => b + r), zip(basis, range, (b, r) => b - r), ink, "kc");
|
|
40539
40999
|
}
|
|
40540
41000
|
};
|
|
40541
41001
|
var donchian = {
|
|
@@ -40543,19 +41003,24 @@ var donchian = {
|
|
|
40543
41003
|
title: "Donchian Channels",
|
|
40544
41004
|
shortTitle: "DC",
|
|
40545
41005
|
overlay: true,
|
|
40546
|
-
inputs: [
|
|
41006
|
+
inputs: [
|
|
41007
|
+
lengthInput(20, "length", "Length", 5e3, "Lookback length. The upper and lower channels are the highest high and lowest low over this many bars."),
|
|
41008
|
+
colorInput(BEARISH, "Upper", "upperColor", "Color of the upper channel line."),
|
|
41009
|
+
colorInput(NEUTRAL, "Middle", "middleColor", "Color of the channel midline."),
|
|
41010
|
+
colorInput(BULLISH, "Lower", "lowerColor", "Color of the lower channel line."),
|
|
41011
|
+
colorInput(transp(NEUTRAL, 92), "Fill", "fillColor", "Color of the channel interior fill.")
|
|
41012
|
+
],
|
|
40547
41013
|
compute: (bars, inputs) => {
|
|
40548
41014
|
const len = num3(inputs, "length", 20);
|
|
40549
41015
|
const upper = highest(highs(bars), len);
|
|
40550
41016
|
const lower = lowest(lows(bars), len);
|
|
40551
|
-
const ink = str3(inputs, "color", SERIES_LINE);
|
|
40552
41017
|
return {
|
|
40553
41018
|
plots: [
|
|
40554
|
-
{ key: "
|
|
40555
|
-
{ key: "
|
|
40556
|
-
{ key: "lower", title: "Lower", values: lower, color:
|
|
41019
|
+
{ key: "upper", title: "Upper", values: upper, color: str3(inputs, "upperColor", BEARISH) },
|
|
41020
|
+
{ key: "middle", title: "Middle", kind: "circles", values: zip(upper, lower, (u, l) => (u + l) / 2), color: str3(inputs, "middleColor", NEUTRAL) },
|
|
41021
|
+
{ key: "lower", title: "Lower", values: lower, color: str3(inputs, "lowerColor", BULLISH) }
|
|
40557
41022
|
],
|
|
40558
|
-
bands: [{ key: "dc", from: "upper", to: "lower", color:
|
|
41023
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: str3(inputs, "fillColor", transp(NEUTRAL, 92)) }]
|
|
40559
41024
|
};
|
|
40560
41025
|
}
|
|
40561
41026
|
};
|
|
@@ -40564,16 +41029,37 @@ var chandelier = {
|
|
|
40564
41029
|
title: "Chandelier Exit",
|
|
40565
41030
|
shortTitle: "CE",
|
|
40566
41031
|
overlay: true,
|
|
40567
|
-
inputs: [
|
|
41032
|
+
inputs: [
|
|
41033
|
+
lengthInput(22, "length", "Length", 5e3, "Lookback used for both the highest high / lowest low extremes and the ATR."),
|
|
41034
|
+
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)."),
|
|
41035
|
+
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."),
|
|
41036
|
+
colorInput(BULLISH, "Long Stop", "longColor", "Color of the long trailing stop line."),
|
|
41037
|
+
colorInput(BEARISH, "Short Stop", "shortColor", "Color of the short trailing stop line.")
|
|
41038
|
+
],
|
|
40568
41039
|
compute: (bars, inputs) => {
|
|
40569
41040
|
const len = num3(inputs, "length", 22);
|
|
40570
41041
|
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
40571
|
-
const
|
|
40572
|
-
const
|
|
41042
|
+
const basicLong = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
41043
|
+
const basicShort = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
41044
|
+
let long = basicLong;
|
|
41045
|
+
let short = basicShort;
|
|
41046
|
+
if (bool(inputs, "ratchet", false)) {
|
|
41047
|
+
long = new Array(bars.length).fill(Number.NaN);
|
|
41048
|
+
short = new Array(bars.length).fill(Number.NaN);
|
|
41049
|
+
for (let i = 0; i < bars.length; i++) {
|
|
41050
|
+
const bl = basicLong[i];
|
|
41051
|
+
const bs = basicShort[i];
|
|
41052
|
+
const prevClose = i > 0 ? bars[i - 1].close : bars[i].close;
|
|
41053
|
+
const prevLong = i > 0 && Number.isFinite(long[i - 1]) ? long[i - 1] : bl;
|
|
41054
|
+
const prevShort = i > 0 && Number.isFinite(short[i - 1]) ? short[i - 1] : bs;
|
|
41055
|
+
long[i] = Number.isFinite(bl) ? prevClose > prevLong ? Math.max(bl, prevLong) : bl : Number.NaN;
|
|
41056
|
+
short[i] = Number.isFinite(bs) ? prevClose < prevShort ? Math.min(bs, prevShort) : bs : Number.NaN;
|
|
41057
|
+
}
|
|
41058
|
+
}
|
|
40573
41059
|
return {
|
|
40574
41060
|
plots: [
|
|
40575
|
-
{ key: "long", title: "Long
|
|
40576
|
-
{ key: "short", title: "Short
|
|
41061
|
+
{ key: "long", title: "Long Stop", values: long, color: str3(inputs, "longColor", BULLISH) },
|
|
41062
|
+
{ key: "short", title: "Short Stop", values: short, color: str3(inputs, "shortColor", BEARISH) }
|
|
40577
41063
|
]
|
|
40578
41064
|
};
|
|
40579
41065
|
}
|
|
@@ -40584,21 +41070,22 @@ var chandeKroll = {
|
|
|
40584
41070
|
shortTitle: "CKS",
|
|
40585
41071
|
overlay: true,
|
|
40586
41072
|
inputs: [
|
|
40587
|
-
|
|
40588
|
-
|
|
40589
|
-
|
|
41073
|
+
intInput("p", "ATR Length (P)", 10, 1, 5e3, "Lookback used for both the ATR and the highest high / lowest low price extremes."),
|
|
41074
|
+
floatInput("x", "ATR Multiplier (X)", 1, 0, 50, 0.1, "Multiplier applied to the ATR to offset the preliminary stops from the price extremes."),
|
|
41075
|
+
intInput("q", "Stop Length (Q)", 9, 1, 5e3, "Lookback over which the preliminary stops are extended into the final stop levels."),
|
|
41076
|
+
colorInput(BEARISH, "Stop Short", "shortColor", "Color of the protective stop line for short positions."),
|
|
41077
|
+
colorInput(BULLISH, "Stop Long", "longColor", "Color of the protective stop line for long positions.")
|
|
40590
41078
|
],
|
|
40591
41079
|
compute: (bars, inputs) => {
|
|
40592
41080
|
const p = num3(inputs, "p", 10);
|
|
40593
|
-
const x = num3(inputs, "x", 1);
|
|
40594
41081
|
const q = num3(inputs, "q", 9);
|
|
40595
|
-
const range = map(atr(bars, p), (v) => v * x);
|
|
41082
|
+
const range = map(atr(bars, p), (v) => v * num3(inputs, "x", 1));
|
|
40596
41083
|
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
40597
41084
|
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
40598
41085
|
return {
|
|
40599
41086
|
plots: [
|
|
40600
|
-
{ key: "stopShort", title: "Stop
|
|
40601
|
-
{ key: "stopLong", title: "Stop
|
|
41087
|
+
{ key: "stopShort", title: "Stop Short", values: highest(firstHigh, q), color: str3(inputs, "shortColor", BEARISH) },
|
|
41088
|
+
{ key: "stopLong", title: "Stop Long", values: lowest(firstLow, q), color: str3(inputs, "longColor", BULLISH) }
|
|
40602
41089
|
]
|
|
40603
41090
|
};
|
|
40604
41091
|
}
|
|
@@ -40609,36 +41096,42 @@ var supertrend = {
|
|
|
40609
41096
|
shortTitle: "SuperTrend",
|
|
40610
41097
|
overlay: true,
|
|
40611
41098
|
inputs: [
|
|
40612
|
-
|
|
40613
|
-
|
|
41099
|
+
intInput("atrLength", "ATR Length", 10, 1, 5e3, "Number of bars used to compute the Wilder ATR."),
|
|
41100
|
+
floatInput("mult", "Factor", 3, 0, 50, 0.1, "ATR multiplier setting how far the trailing stop sits from the bar midpoint."),
|
|
41101
|
+
colorInput(BULLISH, "Uptrend Color", "upColor", "Line color while the trend is up."),
|
|
41102
|
+
colorInput(BEARISH, "Downtrend Color", "downColor", "Line color while the trend is down.")
|
|
40614
41103
|
],
|
|
40615
41104
|
compute: (bars, inputs) => {
|
|
40616
|
-
const len = num3(inputs, "atrLength", 10);
|
|
40617
41105
|
const mult = num3(inputs, "mult", 3);
|
|
40618
|
-
const range = atr(bars,
|
|
41106
|
+
const range = atr(bars, num3(inputs, "atrLength", 10));
|
|
40619
41107
|
const n = bars.length;
|
|
40620
|
-
const
|
|
40621
|
-
const
|
|
40622
|
-
let
|
|
40623
|
-
let
|
|
40624
|
-
let trend = 1;
|
|
41108
|
+
const up = new Array(n).fill(Number.NaN);
|
|
41109
|
+
const down = new Array(n).fill(Number.NaN);
|
|
41110
|
+
let finalUpper = Number.NaN;
|
|
41111
|
+
let finalLower = Number.NaN;
|
|
41112
|
+
let trend = -1;
|
|
40625
41113
|
for (let i = 0; i < n; i++) {
|
|
40626
41114
|
const r = range[i];
|
|
40627
41115
|
if (!Number.isFinite(r)) continue;
|
|
40628
41116
|
const b = bars[i];
|
|
40629
41117
|
const mid = (b.high + b.low) / 2;
|
|
40630
|
-
const
|
|
40631
|
-
const
|
|
41118
|
+
const basicUpper = mid + mult * r;
|
|
41119
|
+
const basicLower = mid - mult * r;
|
|
41120
|
+
const prevUpper = Number.isFinite(finalUpper) ? finalUpper : basicUpper;
|
|
41121
|
+
const prevLower = Number.isFinite(finalLower) ? finalLower : basicLower;
|
|
40632
41122
|
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
40633
|
-
|
|
40634
|
-
|
|
40635
|
-
|
|
40636
|
-
|
|
40637
|
-
|
|
40638
|
-
st[i] = trend === 1 ? up : dn;
|
|
41123
|
+
finalUpper = prevClose <= prevUpper ? Math.min(basicUpper, prevUpper) : basicUpper;
|
|
41124
|
+
finalLower = prevClose >= prevLower ? Math.max(basicLower, prevLower) : basicLower;
|
|
41125
|
+
trend = b.close > finalUpper ? 1 : b.close < finalLower ? -1 : trend;
|
|
41126
|
+
if (trend === 1) up[i] = finalLower;
|
|
41127
|
+
else down[i] = finalUpper;
|
|
40639
41128
|
}
|
|
40640
|
-
|
|
40641
|
-
|
|
41129
|
+
return {
|
|
41130
|
+
plots: [
|
|
41131
|
+
{ key: "up", title: "Uptrend", values: up, color: str3(inputs, "upColor", BULLISH) },
|
|
41132
|
+
{ key: "down", title: "Downtrend", values: down, color: str3(inputs, "downColor", BEARISH) }
|
|
41133
|
+
]
|
|
41134
|
+
};
|
|
40642
41135
|
}
|
|
40643
41136
|
};
|
|
40644
41137
|
var alligator = {
|
|
@@ -40647,12 +41140,15 @@ var alligator = {
|
|
|
40647
41140
|
shortTitle: "Alligator",
|
|
40648
41141
|
overlay: true,
|
|
40649
41142
|
inputs: [
|
|
40650
|
-
|
|
40651
|
-
|
|
40652
|
-
|
|
40653
|
-
|
|
40654
|
-
|
|
40655
|
-
|
|
41143
|
+
intInput("jawLength", "Jaw Length", 13, 1, 5e3, "Smoothing length of the Jaw SMMA of median price (hl2), 13 in the classic setup."),
|
|
41144
|
+
intInput("teethLength", "Teeth Length", 8, 1, 5e3, "Smoothing length of the Teeth SMMA of median price (hl2), 8 in the classic setup."),
|
|
41145
|
+
intInput("lipsLength", "Lips Length", 5, 1, 5e3, "Smoothing length of the Lips SMMA of median price (hl2), 5 in the classic setup."),
|
|
41146
|
+
intInput("jawOffset", "Jaw Offset", 8, 0, 100, "Number of bars the Jaw plot is shifted into the future, 8 in the classic setup."),
|
|
41147
|
+
intInput("teethOffset", "Teeth Offset", 5, 0, 100, "Number of bars the Teeth plot is shifted into the future, 5 in the classic setup."),
|
|
41148
|
+
intInput("lipsOffset", "Lips Offset", 3, 0, 100, "Number of bars the Lips plot is shifted into the future, 3 in the classic setup."),
|
|
41149
|
+
colorInput(JAW_COLOR, "Jaw", "jawColor", "Color of the Jaw line."),
|
|
41150
|
+
colorInput(TEETH_COLOR, "Teeth", "teethColor", "Color of the Teeth line."),
|
|
41151
|
+
colorInput(LIPS_COLOR, "Lips", "lipsColor", "Color of the Lips line.")
|
|
40656
41152
|
],
|
|
40657
41153
|
compute: (bars, inputs) => {
|
|
40658
41154
|
const hl2 = sourceValues(bars, "HL2");
|
|
@@ -40661,99 +41157,134 @@ var alligator = {
|
|
|
40661
41157
|
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
40662
41158
|
return {
|
|
40663
41159
|
plots: [
|
|
40664
|
-
{ key: "jaw", title: "Jaw", values: jaw, color:
|
|
40665
|
-
{ key: "teeth", title: "Teeth", values: teeth, color:
|
|
40666
|
-
{ key: "lips", title: "Lips", values: lips, color:
|
|
41160
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: str3(inputs, "jawColor", JAW_COLOR) },
|
|
41161
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: str3(inputs, "teethColor", TEETH_COLOR) },
|
|
41162
|
+
{ key: "lips", title: "Lips", values: lips, color: str3(inputs, "lipsColor", LIPS_COLOR) }
|
|
40667
41163
|
]
|
|
40668
41164
|
};
|
|
40669
41165
|
}
|
|
40670
41166
|
};
|
|
41167
|
+
var GATOR_LINES = "Alligator Lines";
|
|
41168
|
+
var SMMA_TYPE = "SMMA (Wilder)";
|
|
40671
41169
|
var gator = {
|
|
40672
41170
|
type: "gator-oscillator",
|
|
40673
41171
|
title: "Gator Oscillator",
|
|
40674
41172
|
shortTitle: "Gator Oscillator",
|
|
40675
41173
|
overlay: false,
|
|
40676
|
-
inputs: [
|
|
40677
|
-
|
|
40678
|
-
|
|
40679
|
-
|
|
40680
|
-
|
|
40681
|
-
|
|
41174
|
+
inputs: [
|
|
41175
|
+
{ ...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 },
|
|
41176
|
+
{ ...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 },
|
|
41177
|
+
{ ...intInput("jawLength", "Jaw Length", 13, 1, 500, "Averaging length of the jaw line."), group: GATOR_LINES },
|
|
41178
|
+
{ ...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 },
|
|
41179
|
+
{ ...intInput("teethLength", "Teeth Length", 8, 1, 500, "Averaging length of the teeth line."), group: GATOR_LINES },
|
|
41180
|
+
{ ...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 },
|
|
41181
|
+
{ ...intInput("lipsLength", "Lips Length", 5, 1, 500, "Averaging length of the lips line."), group: GATOR_LINES },
|
|
41182
|
+
{ ...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 },
|
|
41183
|
+
colorInput(BULLISH, "Expanding", "growColor", "Column color while a spread grew against its own previous bar \u2014 the averages are spreading apart."),
|
|
41184
|
+
colorInput(BEARISH, "Contracting", "shrinkColor", "Column color while a spread shrank \u2014 the averages are converging. Color encodes expansion, not trade direction."),
|
|
41185
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero line separating the two histograms.")
|
|
41186
|
+
],
|
|
41187
|
+
compute: (bars, inputs) => {
|
|
41188
|
+
const src = sourceValues(bars, str3(inputs, "source", "HL2"));
|
|
41189
|
+
const kind = str3(inputs, "maType", SMMA_TYPE);
|
|
41190
|
+
const smooth = (len) => kind === "SMA" ? sma(src, len) : kind === "EMA" ? ema(src, len) : rma(src, len);
|
|
41191
|
+
const jaw = shift(smooth(num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
41192
|
+
const teeth = shift(smooth(num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
41193
|
+
const lips = shift(smooth(num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
40682
41194
|
const upper = map(sub(jaw, teeth), Math.abs);
|
|
40683
|
-
const lower = map(sub(teeth, lips),
|
|
40684
|
-
const
|
|
40685
|
-
|
|
40686
|
-
|
|
40687
|
-
|
|
40688
|
-
|
|
41195
|
+
const lower = map(sub(teeth, lips), Math.abs);
|
|
41196
|
+
const grow = str3(inputs, "growColor", BULLISH);
|
|
41197
|
+
const shrink = str3(inputs, "shrinkColor", BEARISH);
|
|
41198
|
+
const expansionColors2 = (v) => {
|
|
41199
|
+
let growing = false;
|
|
41200
|
+
return v.map((x, i) => {
|
|
41201
|
+
if (!Number.isFinite(x)) return null;
|
|
41202
|
+
const prev2 = i > 0 ? v[i - 1] : Number.NaN;
|
|
41203
|
+
if (Number.isFinite(prev2)) growing = x > prev2 ? true : x < prev2 ? false : growing;
|
|
41204
|
+
return growing ? grow : shrink;
|
|
41205
|
+
});
|
|
41206
|
+
};
|
|
40689
41207
|
return {
|
|
40690
41208
|
plots: [
|
|
40691
|
-
{ key: "upper", title: "Upper", values: upper, kind: "
|
|
40692
|
-
{ key: "lower", title: "Lower", values: lower, kind: "
|
|
40693
|
-
]
|
|
41209
|
+
{ key: "upper", title: "Upper Histogram (Jaw - Teeth)", values: upper, kind: "columns", color: grow, colors: expansionColors2(upper), base: 0 },
|
|
41210
|
+
{ key: "lower", title: "Lower Histogram (Teeth - Lips)", values: map(lower, (x) => -x), kind: "columns", color: shrink, colors: expansionColors2(lower), base: 0 }
|
|
41211
|
+
],
|
|
41212
|
+
levels: [{ key: "zero", price: 0, color: str3(inputs, "zeroColor", NEUTRAL), lineStyle: "solid", title: "Zero Line" }]
|
|
40694
41213
|
};
|
|
40695
41214
|
}
|
|
40696
41215
|
};
|
|
40697
41216
|
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
40698
41217
|
|
|
40699
41218
|
// src/core/native-indicators/classics/oscillators.ts
|
|
40700
|
-
function
|
|
40701
|
-
|
|
40702
|
-
|
|
40703
|
-
|
|
41219
|
+
function thresholdInputs(obDefault, osDefault, min = 0, max = 100, obTitle = "Overbought Level", osTitle = "Oversold Level") {
|
|
41220
|
+
return [
|
|
41221
|
+
floatInput("overbought", obTitle, obDefault, min, max, 1, "Level above which the oscillator is considered overbought."),
|
|
41222
|
+
floatInput("oversold", osTitle, osDefault, min, max, 1, "Level below which the oscillator is considered oversold.")
|
|
40704
41223
|
];
|
|
40705
|
-
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
40706
|
-
return levels;
|
|
40707
|
-
}
|
|
40708
|
-
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
40709
|
-
function histColors(values) {
|
|
40710
|
-
return values.map((x, i) => {
|
|
40711
|
-
if (!Number.isFinite(x)) return null;
|
|
40712
|
-
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
40713
|
-
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
40714
|
-
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
40715
|
-
return rising ? `${BEARISH}80` : BEARISH;
|
|
40716
|
-
});
|
|
40717
41224
|
}
|
|
40718
41225
|
var rsiSpec = {
|
|
40719
41226
|
type: "rsi",
|
|
40720
41227
|
title: "Relative Strength Index",
|
|
40721
41228
|
shortTitle: "RSI",
|
|
40722
41229
|
overlay: false,
|
|
40723
|
-
inputs: [
|
|
40724
|
-
|
|
40725
|
-
|
|
40726
|
-
|
|
40727
|
-
|
|
40728
|
-
|
|
40729
|
-
|
|
40730
|
-
|
|
40731
|
-
|
|
40732
|
-
|
|
40733
|
-
|
|
40734
|
-
|
|
41230
|
+
inputs: [
|
|
41231
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used in the Wilder smoothing of average gains and losses."),
|
|
41232
|
+
sourceInput("Close", "source", "Source", "Price series the RSI is calculated on."),
|
|
41233
|
+
...thresholdInputs(70, 30, 0, 100, "Overbought", "Oversold"),
|
|
41234
|
+
colorInput(NEUTRAL, "RSI", "color", "Color of the RSI line at the midline. Toward either threshold it blends into that threshold\u2019s color."),
|
|
41235
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level."),
|
|
41236
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
41237
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought, midline and oversold levels."),
|
|
41238
|
+
colorInput(transp(NEUTRAL, 90), "Band Fill", "fillColor", "Color of the fill between the overbought and oversold levels.")
|
|
41239
|
+
],
|
|
41240
|
+
compute: (bars, inputs) => boundedOscillator({
|
|
41241
|
+
key: "rsi",
|
|
41242
|
+
title: "RSI",
|
|
41243
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
41244
|
+
scale: { top: 100, bottom: 0 },
|
|
41245
|
+
overbought: num3(inputs, "overbought", 70),
|
|
41246
|
+
oversold: num3(inputs, "oversold", 30),
|
|
41247
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
41248
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
41249
|
+
width: 2,
|
|
41250
|
+
midline: 50,
|
|
41251
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 90))
|
|
40735
41252
|
})
|
|
40736
41253
|
};
|
|
41254
|
+
function stochasticOutput(k, d, inputs) {
|
|
41255
|
+
const overbought = num3(inputs, "overbought", 80);
|
|
41256
|
+
const oversold = num3(inputs, "oversold", 20);
|
|
41257
|
+
return {
|
|
41258
|
+
plots: [
|
|
41259
|
+
{ key: "k", title: "%K", values: k, color: str3(inputs, "kColor", INFO) },
|
|
41260
|
+
{ key: "d", title: "%D", values: d, color: str3(inputs, "dColor", WARNING) },
|
|
41261
|
+
anchorPlot("obAnchor", "Overbought Anchor", k.length, overbought),
|
|
41262
|
+
anchorPlot("osAnchor", "Oversold Anchor", k.length, oversold)
|
|
41263
|
+
],
|
|
41264
|
+
bands: [{ key: "band", from: "obAnchor", to: "osAnchor", color: str3(inputs, "fillColor", transp(INFO, 90)) }],
|
|
41265
|
+
levels: [
|
|
41266
|
+
{ key: "overbought", price: overbought, color: NEUTRAL, lineStyle: "dashed", title: "Overbought" },
|
|
41267
|
+
{ key: "oversold", price: oversold, color: NEUTRAL, lineStyle: "dashed", title: "Oversold" }
|
|
41268
|
+
]
|
|
41269
|
+
};
|
|
41270
|
+
}
|
|
40737
41271
|
var stochastic = {
|
|
40738
41272
|
type: "stochastic",
|
|
40739
41273
|
title: "Stochastic",
|
|
40740
41274
|
shortTitle: "Stoch",
|
|
40741
41275
|
overlay: false,
|
|
40742
41276
|
inputs: [
|
|
40743
|
-
lengthInput(14, "kLength", "%K
|
|
40744
|
-
lengthInput(
|
|
40745
|
-
lengthInput(3, "dLength", "%D
|
|
41277
|
+
lengthInput(14, "kLength", "%K Length", 5e3, "Number of bars used for the highest high / lowest low range of the raw %K."),
|
|
41278
|
+
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."),
|
|
41279
|
+
lengthInput(3, "dLength", "%D Smoothing", 500, "Simple moving average length of the %D signal line, applied to the smoothed %K."),
|
|
41280
|
+
...thresholdInputs(80, 20),
|
|
41281
|
+
colorInput(INFO, "%K", "kColor", "Color of the %K line."),
|
|
41282
|
+
colorInput(WARNING, "%D", "dColor", "Color of the %D signal line."),
|
|
41283
|
+
colorInput(transp(INFO, 90), "Band Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
40746
41284
|
],
|
|
40747
41285
|
compute: (bars, inputs) => {
|
|
40748
|
-
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing",
|
|
40749
|
-
|
|
40750
|
-
return {
|
|
40751
|
-
plots: [
|
|
40752
|
-
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
40753
|
-
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
40754
|
-
],
|
|
40755
|
-
levels: guideLevels(20, 80)
|
|
40756
|
-
};
|
|
41286
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
41287
|
+
return stochasticOutput(k, sma(k, num3(inputs, "dLength", 3)), inputs);
|
|
40757
41288
|
}
|
|
40758
41289
|
};
|
|
40759
41290
|
var stochasticRsi = {
|
|
@@ -40762,23 +41293,20 @@ var stochasticRsi = {
|
|
|
40762
41293
|
shortTitle: "Stoch RSI",
|
|
40763
41294
|
overlay: false,
|
|
40764
41295
|
inputs: [
|
|
40765
|
-
|
|
40766
|
-
lengthInput(14, "
|
|
40767
|
-
lengthInput(
|
|
40768
|
-
lengthInput(3, "
|
|
40769
|
-
|
|
41296
|
+
sourceInput("Close", "source", "RSI Source", "Price series the RSI is computed on."),
|
|
41297
|
+
lengthInput(14, "rsiLength", "RSI Length", 5e3, "Lookback length of the Wilder RSI."),
|
|
41298
|
+
lengthInput(14, "stochLength", "Stochastic Length", 5e3, "Lookback over which the highest and lowest RSI values are taken."),
|
|
41299
|
+
lengthInput(3, "kSmoothing", "%K Smoothing", 500, "Simple moving average length applied to the stochastic of RSI to form %K."),
|
|
41300
|
+
lengthInput(3, "dLength", "%D Smoothing", 500, "Simple moving average length applied to %K to form the %D signal line."),
|
|
41301
|
+
...thresholdInputs(80, 20),
|
|
41302
|
+
colorInput(INFO, "%K Color", "kColor", "Color of the %K line."),
|
|
41303
|
+
colorInput(WARNING, "%D Color", "dColor", "Color of the %D signal line."),
|
|
41304
|
+
colorInput(transp(INFO, 90), "Zone Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
40770
41305
|
],
|
|
40771
41306
|
compute: (bars, inputs) => {
|
|
40772
41307
|
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
40773
41308
|
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
40774
|
-
|
|
40775
|
-
return {
|
|
40776
|
-
plots: [
|
|
40777
|
-
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
40778
|
-
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
40779
|
-
],
|
|
40780
|
-
levels: guideLevels(20, 80)
|
|
40781
|
-
};
|
|
41309
|
+
return stochasticOutput(k, sma(k, num3(inputs, "dLength", 3)), inputs);
|
|
40782
41310
|
}
|
|
40783
41311
|
};
|
|
40784
41312
|
var macd = {
|
|
@@ -40786,23 +41314,35 @@ var macd = {
|
|
|
40786
41314
|
title: "MACD",
|
|
40787
41315
|
overlay: false,
|
|
40788
41316
|
inputs: [
|
|
40789
|
-
lengthInput(12, "fastLength", "Fast
|
|
40790
|
-
lengthInput(26, "slowLength", "Slow
|
|
40791
|
-
lengthInput(9, "signalLength", "Signal
|
|
40792
|
-
sourceInput()
|
|
41317
|
+
lengthInput(12, "fastLength", "Fast Length", 5e3, "Length of the fast EMA."),
|
|
41318
|
+
lengthInput(26, "slowLength", "Slow Length", 5e3, "Length of the slow EMA."),
|
|
41319
|
+
lengthInput(9, "signalLength", "Signal Smoothing", 500, "Length of the EMA applied to the MACD line to obtain the signal line."),
|
|
41320
|
+
sourceInput("Close", "source", "Source", "Price series used to compute the fast and slow EMAs."),
|
|
41321
|
+
colorInput(INFO, "MACD Line", "color", "Color of the MACD line."),
|
|
41322
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
41323
|
+
colorInput(BULLISH, "Grow Above", "growAboveColor", "Histogram color when above zero and rising."),
|
|
41324
|
+
colorInput(transp(BULLISH, 50), "Fall Above", "fallAboveColor", "Histogram color when above zero and falling."),
|
|
41325
|
+
colorInput(transp(BEARISH, 50), "Grow Below", "growBelowColor", "Histogram color when below zero and rising."),
|
|
41326
|
+
colorInput(BEARISH, "Fall Below", "fallBelowColor", "Histogram color when below zero and falling.")
|
|
40793
41327
|
],
|
|
40794
41328
|
compute: (bars, inputs) => {
|
|
40795
41329
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
40796
41330
|
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
40797
41331
|
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
40798
41332
|
const hist = sub(line, signal);
|
|
41333
|
+
const colors = momentumColumnColors(hist, {
|
|
41334
|
+
growAbove: str3(inputs, "growAboveColor", BULLISH),
|
|
41335
|
+
fallAbove: str3(inputs, "fallAboveColor", transp(BULLISH, 50)),
|
|
41336
|
+
growBelow: str3(inputs, "growBelowColor", transp(BEARISH, 50)),
|
|
41337
|
+
fallBelow: str3(inputs, "fallBelowColor", BEARISH)
|
|
41338
|
+
});
|
|
40799
41339
|
return {
|
|
40800
41340
|
plots: [
|
|
40801
|
-
{ key: "hist", title: "Histogram", values: hist, kind: "
|
|
40802
|
-
{ key: "macd", title: "MACD", values: line, color:
|
|
40803
|
-
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
41341
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "columns", color: BULLISH, colors, base: 0 },
|
|
41342
|
+
{ key: "macd", title: "MACD", values: line, color: str3(inputs, "color", INFO) },
|
|
41343
|
+
{ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) }
|
|
40804
41344
|
],
|
|
40805
|
-
levels:
|
|
41345
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL, lineStyle: "dashed", title: "Zero Line" }]
|
|
40806
41346
|
};
|
|
40807
41347
|
}
|
|
40808
41348
|
};
|
|
@@ -40812,26 +41352,49 @@ var ppo = {
|
|
|
40812
41352
|
shortTitle: "PPO",
|
|
40813
41353
|
overlay: false,
|
|
40814
41354
|
inputs: [
|
|
40815
|
-
|
|
40816
|
-
lengthInput(
|
|
40817
|
-
lengthInput(
|
|
40818
|
-
|
|
41355
|
+
sourceInput("Close", "source", "Source", "Price series fed into the fast and slow averages."),
|
|
41356
|
+
lengthInput(12, "fastLength", "Fast Length", 5e3, "Length of the fast moving average."),
|
|
41357
|
+
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."),
|
|
41358
|
+
optionInput("maType", "Average Type", "EMA", ["EMA", "SMA"], "Moving average type used for the fast and slow averages."),
|
|
41359
|
+
lengthInput(9, "signalLength", "Signal Smoothing", 500, "Length of the signal line that smooths the PPO line."),
|
|
41360
|
+
optionInput("signalType", "Signal Type", "EMA", ["EMA", "SMA"], "Moving average type used for the signal line."),
|
|
41361
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the PPO line, gradient fill and histogram above the zero line."),
|
|
41362
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the PPO line, gradient fill and histogram below the zero line."),
|
|
41363
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
41364
|
+
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."),
|
|
41365
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the zero line and the PPO line with a vertical gradient.")
|
|
40819
41366
|
],
|
|
40820
41367
|
compute: (bars, inputs) => {
|
|
40821
41368
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
40822
|
-
const
|
|
40823
|
-
const
|
|
41369
|
+
const average = (kind2, v, len) => kind2 === "SMA" ? sma(v, len) : ema(v, len);
|
|
41370
|
+
const kind = str3(inputs, "maType", "EMA");
|
|
41371
|
+
const fast = average(kind, src, num3(inputs, "fastLength", 12));
|
|
41372
|
+
const slow = average(kind, src, num3(inputs, "slowLength", 26));
|
|
40824
41373
|
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
40825
|
-
const signal =
|
|
41374
|
+
const signal = average(str3(inputs, "signalType", "EMA"), line, num3(inputs, "signalLength", 9));
|
|
40826
41375
|
const hist = sub(line, signal);
|
|
40827
|
-
|
|
40828
|
-
|
|
40829
|
-
|
|
40830
|
-
|
|
40831
|
-
|
|
40832
|
-
|
|
40833
|
-
|
|
40834
|
-
|
|
41376
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
41377
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
41378
|
+
const out = centeredOscillator({
|
|
41379
|
+
key: "ppo",
|
|
41380
|
+
title: "PPO Line",
|
|
41381
|
+
values: line,
|
|
41382
|
+
ink: { bull, bear, neutral: bear },
|
|
41383
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
41384
|
+
fill: bool(inputs, "gradientFill", true),
|
|
41385
|
+
extraPlots: [{ key: "signal", title: "Signal Line", values: signal, color: str3(inputs, "signalColor", WARNING) }]
|
|
41386
|
+
});
|
|
41387
|
+
if (bool(inputs, "showHistogram", true)) {
|
|
41388
|
+
const colors = hist.map((x, i) => {
|
|
41389
|
+
if (!Number.isFinite(x)) return null;
|
|
41390
|
+
const prev2 = i > 0 && Number.isFinite(hist[i - 1]) ? hist[i - 1] : x;
|
|
41391
|
+
const ink = x >= 0 ? bull : bear;
|
|
41392
|
+
const expanding = x >= 0 ? x > prev2 : x < prev2;
|
|
41393
|
+
return transp(ink, expanding ? 40 : 70);
|
|
41394
|
+
});
|
|
41395
|
+
out.plots.unshift({ key: "hist", title: "Histogram", values: hist, kind: "columns", color: bull, colors, base: 0 });
|
|
41396
|
+
}
|
|
41397
|
+
return out;
|
|
40835
41398
|
}
|
|
40836
41399
|
};
|
|
40837
41400
|
var awesome = {
|
|
@@ -40839,48 +41402,67 @@ var awesome = {
|
|
|
40839
41402
|
title: "Awesome Oscillator",
|
|
40840
41403
|
shortTitle: "AO",
|
|
40841
41404
|
overlay: false,
|
|
40842
|
-
inputs: [
|
|
40843
|
-
|
|
41405
|
+
inputs: [
|
|
41406
|
+
colorInput(BULLISH, "Rising", "risingColor", "Histogram color when the oscillator is higher than on the previous bar."),
|
|
41407
|
+
colorInput(BEARISH, "Falling", "fallingColor", "Histogram color when the oscillator is lower than or equal to the previous bar."),
|
|
41408
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
41409
|
+
],
|
|
41410
|
+
compute: (bars, inputs) => {
|
|
40844
41411
|
const hl2 = sourceValues(bars, "HL2");
|
|
40845
41412
|
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
41413
|
+
const rising = str3(inputs, "risingColor", BULLISH);
|
|
41414
|
+
const falling = str3(inputs, "fallingColor", BEARISH);
|
|
40846
41415
|
const colors = ao.map((x, i) => {
|
|
40847
41416
|
if (!Number.isFinite(x)) return null;
|
|
40848
41417
|
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
40849
|
-
return Number.isFinite(prev2) && x
|
|
41418
|
+
return Number.isFinite(prev2) && x > prev2 ? rising : falling;
|
|
40850
41419
|
});
|
|
40851
41420
|
return {
|
|
40852
|
-
plots: [{ key: "ao", title: "AO", values: ao, kind: "
|
|
40853
|
-
levels:
|
|
41421
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "columns", color: rising, colors, base: 0 }],
|
|
41422
|
+
levels: [{ key: "zero", price: 0, color: str3(inputs, "zeroColor", NEUTRAL), lineStyle: "dashed", title: "Zero Line" }]
|
|
40854
41423
|
};
|
|
40855
41424
|
}
|
|
40856
41425
|
};
|
|
41426
|
+
var CCI_SCALING = 0.015;
|
|
40857
41427
|
var cci = {
|
|
40858
41428
|
type: "commodity-channel-index",
|
|
40859
41429
|
title: "Commodity Channel Index",
|
|
40860
41430
|
shortTitle: "CCI",
|
|
40861
41431
|
overlay: false,
|
|
40862
|
-
inputs: [
|
|
41432
|
+
inputs: [
|
|
41433
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used for the moving average and the mean absolute deviation of the source."),
|
|
41434
|
+
sourceInput("HLC3", "source", "Source", "Price series measured by the CCI; the typical price hlc3 is the classic choice."),
|
|
41435
|
+
floatInput("upper", "Upper Level", 100, -1e3, 1e3, 10, "Overbought reference level, +100 classically."),
|
|
41436
|
+
floatInput("lower", "Lower Level", -100, -1e3, 1e3, 10, "Oversold reference level, \u2212100 classically."),
|
|
41437
|
+
colorInput(NEUTRAL, "CCI Line", "color", "Color of the CCI line."),
|
|
41438
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the CCI reads above zero."),
|
|
41439
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the CCI reads below zero."),
|
|
41440
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the upper, lower and zero reference lines.")
|
|
41441
|
+
],
|
|
40863
41442
|
compute: (bars, inputs) => {
|
|
40864
41443
|
const len = num3(inputs, "length", 20);
|
|
40865
41444
|
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
40866
41445
|
const basis = sma(src, len);
|
|
40867
|
-
const dev =
|
|
40868
|
-
for (let i = len - 1; i < src.length; i++) {
|
|
40869
|
-
const mean = basis[i];
|
|
40870
|
-
if (!Number.isFinite(mean)) continue;
|
|
40871
|
-
let d = 0;
|
|
40872
|
-
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
40873
|
-
dev[i] = d / len;
|
|
40874
|
-
}
|
|
41446
|
+
const dev = meanDev(src, len);
|
|
40875
41447
|
const values = src.map((x, i) => {
|
|
40876
41448
|
const b = basis[i];
|
|
40877
41449
|
const d = dev[i];
|
|
40878
|
-
|
|
41450
|
+
if (!Number.isFinite(b) || !Number.isFinite(d)) return Number.NaN;
|
|
41451
|
+
return d === 0 ? 0 : (x - b) / (CCI_SCALING * d);
|
|
41452
|
+
});
|
|
41453
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
41454
|
+
return centeredOscillator({
|
|
41455
|
+
key: "cci",
|
|
41456
|
+
title: "CCI",
|
|
41457
|
+
values,
|
|
41458
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
41459
|
+
zeroInk: levelInk,
|
|
41460
|
+
zeroLineStyle: "dotted",
|
|
41461
|
+
extraLevels: [
|
|
41462
|
+
{ key: "upper", price: num3(inputs, "upper", 100), color: levelInk, lineStyle: "dashed", title: "Upper Level" },
|
|
41463
|
+
{ key: "lower", price: num3(inputs, "lower", -100), color: levelInk, lineStyle: "dashed", title: "Lower Level" }
|
|
41464
|
+
]
|
|
40879
41465
|
});
|
|
40880
|
-
return {
|
|
40881
|
-
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40882
|
-
levels: guideLevels(-100, 100, 0)
|
|
40883
|
-
};
|
|
40884
41466
|
}
|
|
40885
41467
|
};
|
|
40886
41468
|
var williamsR = {
|
|
@@ -40888,31 +41470,78 @@ var williamsR = {
|
|
|
40888
41470
|
title: "Williams %R",
|
|
40889
41471
|
shortTitle: "%R",
|
|
40890
41472
|
overlay: false,
|
|
40891
|
-
inputs: [
|
|
41473
|
+
inputs: [
|
|
41474
|
+
lengthInput(14, "length", "Length", 5e3, "Lookback length of the highest high / lowest low range. 14 is the classic setting."),
|
|
41475
|
+
sourceInput("Close", "source", "Source", "Series located within the lookback range. Close is the classic choice."),
|
|
41476
|
+
...thresholdInputs(-20, -80, -100, 0),
|
|
41477
|
+
colorInput(NEUTRAL, "%R Line", "color", "Color of the %R line between the thresholds. Toward either level it blends into that level\u2019s color."),
|
|
41478
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
41479
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level.")
|
|
41480
|
+
],
|
|
40892
41481
|
compute: (bars, inputs) => {
|
|
40893
|
-
const
|
|
40894
|
-
|
|
40895
|
-
|
|
40896
|
-
|
|
40897
|
-
|
|
41482
|
+
const len = num3(inputs, "length", 14);
|
|
41483
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41484
|
+
const hh = highest(highs(bars), len);
|
|
41485
|
+
const ll = lowest(lows(bars), len);
|
|
41486
|
+
const values = src.map((x, i) => {
|
|
41487
|
+
const h = hh[i];
|
|
41488
|
+
const l = ll[i];
|
|
41489
|
+
return Number.isFinite(h) && Number.isFinite(l) && h > l ? -100 * (h - x) / (h - l) : Number.NaN;
|
|
41490
|
+
});
|
|
41491
|
+
return boundedOscillator({
|
|
41492
|
+
key: "wr",
|
|
41493
|
+
title: "%R",
|
|
41494
|
+
values,
|
|
41495
|
+
scale: { top: 0, bottom: -100 },
|
|
41496
|
+
overbought: num3(inputs, "overbought", -20),
|
|
41497
|
+
oversold: num3(inputs, "oversold", -80),
|
|
41498
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
41499
|
+
levelInk: NEUTRAL
|
|
41500
|
+
});
|
|
40898
41501
|
}
|
|
40899
41502
|
};
|
|
40900
|
-
var
|
|
41503
|
+
var cmoSpec = {
|
|
40901
41504
|
type: "chande-momentum-oscillator",
|
|
40902
41505
|
title: "Chande Momentum Oscillator",
|
|
40903
41506
|
shortTitle: "CMO",
|
|
40904
41507
|
overlay: false,
|
|
40905
|
-
inputs: [
|
|
41508
|
+
inputs: [
|
|
41509
|
+
lengthInput(9, "length", "Length", 5e3, "Number of bars over which up moves and down moves are summed."),
|
|
41510
|
+
sourceInput("Close", "source", "Source", "Price series used to measure bar-to-bar changes."),
|
|
41511
|
+
floatInput("overbought", "Overbought Level", 50, 0, 100, 1, "Level above which the oscillator is considered overbought."),
|
|
41512
|
+
floatInput("oversold", "Oversold Level", -50, -100, 0, 1, "Level below which the oscillator is considered oversold."),
|
|
41513
|
+
colorInput(NEUTRAL, "CMO Line", "color", "Color of the CMO line."),
|
|
41514
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while momentum is positive, and shading of the oversold zone."),
|
|
41515
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while momentum is negative, and shading of the overbought zone."),
|
|
41516
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought, oversold, and zero lines.")
|
|
41517
|
+
],
|
|
40906
41518
|
compute: (bars, inputs) => {
|
|
40907
41519
|
const len = num3(inputs, "length", 9);
|
|
40908
41520
|
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
40909
|
-
const
|
|
40910
|
-
const
|
|
40911
|
-
const values = zip(
|
|
40912
|
-
|
|
40913
|
-
|
|
40914
|
-
|
|
40915
|
-
|
|
41521
|
+
const up = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
41522
|
+
const down = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
41523
|
+
const values = zip(up, down, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
41524
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
41525
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
41526
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
41527
|
+
const overbought = num3(inputs, "overbought", 50);
|
|
41528
|
+
const oversold = num3(inputs, "oversold", -50);
|
|
41529
|
+
const out = centeredOscillator({
|
|
41530
|
+
key: "cmo",
|
|
41531
|
+
title: "CMO",
|
|
41532
|
+
values,
|
|
41533
|
+
ink: { bull, bear, neutral: str3(inputs, "color", NEUTRAL) },
|
|
41534
|
+
zeroInk: levelInk,
|
|
41535
|
+
zeroLineStyle: "dotted",
|
|
41536
|
+
extraLevels: [
|
|
41537
|
+
{ key: "overbought", price: overbought, color: levelInk, lineStyle: "dashed", title: "Overbought" },
|
|
41538
|
+
{ key: "oversold", price: oversold, color: levelInk, lineStyle: "dashed", title: "Oversold" }
|
|
41539
|
+
]
|
|
41540
|
+
});
|
|
41541
|
+
const zones = thresholdZones(values.length, { overbought, oversold, top: 100, bottom: -100 }, { overbought: bear, oversold: bull });
|
|
41542
|
+
out.plots.push(...zones.plots);
|
|
41543
|
+
out.bands = [...out.bands ?? [], ...zones.bands];
|
|
41544
|
+
return out;
|
|
40916
41545
|
}
|
|
40917
41546
|
};
|
|
40918
41547
|
var connorsRsi = {
|
|
@@ -40921,10 +41550,14 @@ var connorsRsi = {
|
|
|
40921
41550
|
shortTitle: "CRSI",
|
|
40922
41551
|
overlay: false,
|
|
40923
41552
|
inputs: [
|
|
40924
|
-
lengthInput(3, "rsiLength", "RSI
|
|
40925
|
-
lengthInput(2, "streakLength", "Streak RSI
|
|
40926
|
-
lengthInput(100, "rankLength", "Percent
|
|
40927
|
-
|
|
41553
|
+
lengthInput(3, "rsiLength", "RSI Length", 5e3, "Length of the Wilder RSI applied to the closing price."),
|
|
41554
|
+
lengthInput(2, "streakLength", "Streak RSI Length", 5e3, "Length of the Wilder RSI applied to the up/down close streak."),
|
|
41555
|
+
lengthInput(100, "rankLength", "Percent Rank Length", 5e3, "Lookback of the percent rank applied to the 1-bar rate of change of the closing price."),
|
|
41556
|
+
...thresholdInputs(90, 10),
|
|
41557
|
+
colorInput(NEUTRAL, "CRSI Line", "color", "Color of the Connors RSI line."),
|
|
41558
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Line, fill and zone color toward and below the oversold level."),
|
|
41559
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Line, fill and zone color toward and above the overbought level."),
|
|
41560
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought and oversold levels.")
|
|
40928
41561
|
],
|
|
40929
41562
|
compute: (bars, inputs) => {
|
|
40930
41563
|
const c = closes(bars);
|
|
@@ -40942,10 +41575,17 @@ var connorsRsi = {
|
|
|
40942
41575
|
const z = r[i];
|
|
40943
41576
|
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
40944
41577
|
});
|
|
40945
|
-
return {
|
|
40946
|
-
|
|
40947
|
-
|
|
40948
|
-
|
|
41578
|
+
return boundedOscillator({
|
|
41579
|
+
key: "crsi",
|
|
41580
|
+
title: "Connors RSI",
|
|
41581
|
+
values,
|
|
41582
|
+
scale: { top: 100, bottom: 0 },
|
|
41583
|
+
overbought: num3(inputs, "overbought", 90),
|
|
41584
|
+
oversold: num3(inputs, "oversold", 10),
|
|
41585
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
41586
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
41587
|
+
midpointWash: true
|
|
41588
|
+
});
|
|
40949
41589
|
}
|
|
40950
41590
|
};
|
|
40951
41591
|
var fisher = {
|
|
@@ -40953,7 +41593,13 @@ var fisher = {
|
|
|
40953
41593
|
title: "Fisher Transform",
|
|
40954
41594
|
shortTitle: "Fisher",
|
|
40955
41595
|
overlay: false,
|
|
40956
|
-
inputs: [
|
|
41596
|
+
inputs: [
|
|
41597
|
+
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."),
|
|
41598
|
+
colorInput(NEUTRAL, "Fisher Line", "color", "Color of the Fisher line when it sits exactly on its trigger."),
|
|
41599
|
+
colorInput(NEUTRAL, "Trigger Line", "triggerColor", "Color of the trigger line, the Fisher value delayed one bar."),
|
|
41600
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the Fisher line and fill while it is above the trigger."),
|
|
41601
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the Fisher line and fill while it is below the trigger.")
|
|
41602
|
+
],
|
|
40957
41603
|
compute: (bars, inputs) => {
|
|
40958
41604
|
const len = num3(inputs, "length", 9);
|
|
40959
41605
|
const hl2 = sourceValues(bars, "HL2");
|
|
@@ -40961,23 +41607,39 @@ var fisher = {
|
|
|
40961
41607
|
const lo = lowest(hl2, len);
|
|
40962
41608
|
const n = bars.length;
|
|
40963
41609
|
const out = new Array(n).fill(Number.NaN);
|
|
41610
|
+
let position = 0;
|
|
40964
41611
|
let value = 0;
|
|
40965
|
-
let fish = 0;
|
|
40966
41612
|
for (let i = 0; i < n; i++) {
|
|
40967
41613
|
const h = hi[i];
|
|
40968
41614
|
const l = lo[i];
|
|
40969
41615
|
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
40970
|
-
const
|
|
40971
|
-
|
|
40972
|
-
|
|
40973
|
-
|
|
40974
|
-
|
|
41616
|
+
const raw = h > l ? (hl2[i] - l) / (h - l) - 0.5 : 0;
|
|
41617
|
+
position = 0.66 * raw + 0.67 * position;
|
|
41618
|
+
position = position > 0.99 ? 0.999 : position < -0.99 ? -0.999 : position;
|
|
41619
|
+
value = 0.5 * Math.log((1 + position) / (1 - position)) + 0.5 * value;
|
|
41620
|
+
out[i] = value;
|
|
41621
|
+
}
|
|
41622
|
+
const trigger = shift(out, 1);
|
|
41623
|
+
const neutral = str3(inputs, "color", NEUTRAL);
|
|
41624
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
41625
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
41626
|
+
const inkOf = (i) => {
|
|
41627
|
+
const f = out[i];
|
|
41628
|
+
const t = trigger[i];
|
|
41629
|
+
if (!Number.isFinite(t)) return neutral;
|
|
41630
|
+
return f > t ? bull : f < t ? bear : neutral;
|
|
41631
|
+
};
|
|
40975
41632
|
return {
|
|
40976
41633
|
plots: [
|
|
40977
|
-
{ key: "fisher", title: "Fisher", values: out, color:
|
|
40978
|
-
{ key: "trigger", title: "Trigger", values:
|
|
41634
|
+
{ key: "fisher", title: "Fisher", values: out, color: neutral, width: 2, colors: out.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) },
|
|
41635
|
+
{ key: "trigger", title: "Trigger", values: trigger, color: str3(inputs, "triggerColor", NEUTRAL) }
|
|
40979
41636
|
],
|
|
40980
|
-
|
|
41637
|
+
bands: [{ key: "spread", from: "fisher", to: "trigger", color: transp(neutral, 80), colors: out.map((x, i) => Number.isFinite(x) ? transp(inkOf(i), 80) : null) }],
|
|
41638
|
+
levels: [
|
|
41639
|
+
{ key: "upper", price: 1.5, color: NEUTRAL, lineStyle: "dashed", title: "Upper Level" },
|
|
41640
|
+
{ key: "lower", price: -1.5, color: NEUTRAL, lineStyle: "dashed", title: "Lower Level" },
|
|
41641
|
+
{ key: "zero", price: 0, color: NEUTRAL, lineStyle: "dotted", title: "Zero Line" }
|
|
41642
|
+
]
|
|
40981
41643
|
};
|
|
40982
41644
|
}
|
|
40983
41645
|
};
|
|
@@ -40985,93 +41647,180 @@ var trix = {
|
|
|
40985
41647
|
type: "trix",
|
|
40986
41648
|
title: "TRIX",
|
|
40987
41649
|
overlay: false,
|
|
40988
|
-
inputs: [
|
|
41650
|
+
inputs: [
|
|
41651
|
+
lengthInput(18, "length", "Length", 5e3, "Length of each of the three EMA smoothing passes applied to the close price."),
|
|
41652
|
+
lengthInput(9, "signalLength", "Signal Length", 500, "Length of the EMA applied to TRIX to form the signal line."),
|
|
41653
|
+
colorInput(NEUTRAL, "TRIX Color", "color", "Color of the TRIX line."),
|
|
41654
|
+
colorInput(WARNING, "Signal Color", "signalColor", "Color of the signal line."),
|
|
41655
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the TRIX line and its gradient fill while TRIX holds above zero."),
|
|
41656
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the TRIX line and its gradient fill while TRIX holds below zero.")
|
|
41657
|
+
],
|
|
40989
41658
|
compute: (bars, inputs) => {
|
|
40990
41659
|
const len = num3(inputs, "length", 18);
|
|
40991
|
-
const
|
|
40992
|
-
const
|
|
40993
|
-
|
|
40994
|
-
|
|
40995
|
-
|
|
40996
|
-
|
|
40997
|
-
|
|
41660
|
+
const smooth = ema(ema(ema(closes(bars), len), len), len);
|
|
41661
|
+
const values = roc(smooth, 1);
|
|
41662
|
+
return centeredOscillator({
|
|
41663
|
+
key: "trix",
|
|
41664
|
+
title: "TRIX",
|
|
41665
|
+
values,
|
|
41666
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
41667
|
+
zeroInk: NEUTRAL,
|
|
41668
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(values, num3(inputs, "signalLength", 9)), color: str3(inputs, "signalColor", WARNING) }]
|
|
41669
|
+
});
|
|
40998
41670
|
}
|
|
40999
41671
|
};
|
|
41000
|
-
function
|
|
41672
|
+
function tsiRatio(src, long, short) {
|
|
41001
41673
|
const mom = change(src);
|
|
41002
41674
|
const numer = ema(ema(mom, long), short);
|
|
41003
41675
|
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
41004
|
-
return zip(numer, denom, (a, b) => b === 0 ? 0 :
|
|
41676
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : a / b);
|
|
41005
41677
|
}
|
|
41006
41678
|
var tsi = {
|
|
41007
41679
|
type: "true-strength-index",
|
|
41008
41680
|
title: "True Strength Index",
|
|
41009
41681
|
shortTitle: "TSI",
|
|
41010
41682
|
overlay: false,
|
|
41011
|
-
inputs: [
|
|
41683
|
+
inputs: [
|
|
41684
|
+
lengthInput(25, "longLength", "Long Length", 5e3, "Length of the first (long) EMA applied to the 1-bar price change."),
|
|
41685
|
+
lengthInput(13, "shortLength", "Short Length", 5e3, "Length of the second (short) EMA applied to the long-smoothed price change."),
|
|
41686
|
+
lengthInput(13, "signalLength", "Signal Length", 500, "Length of the EMA of TSI used as the signal line."),
|
|
41687
|
+
colorInput(NEUTRAL, "TSI", "color", "Color of the TSI line."),
|
|
41688
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line."),
|
|
41689
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the TSI line and its gradient fill while TSI holds above zero."),
|
|
41690
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the TSI line and its gradient fill while TSI holds below zero.")
|
|
41691
|
+
],
|
|
41012
41692
|
compute: (bars, inputs) => {
|
|
41013
|
-
const
|
|
41014
|
-
return {
|
|
41015
|
-
|
|
41016
|
-
|
|
41017
|
-
|
|
41018
|
-
|
|
41019
|
-
|
|
41020
|
-
|
|
41693
|
+
const values = map(tsiRatio(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13)), (x) => 100 * x);
|
|
41694
|
+
return centeredOscillator({
|
|
41695
|
+
key: "tsi",
|
|
41696
|
+
title: "TSI",
|
|
41697
|
+
values,
|
|
41698
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
41699
|
+
zeroInk: NEUTRAL,
|
|
41700
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(values, num3(inputs, "signalLength", 13)), color: str3(inputs, "signalColor", WARNING) }]
|
|
41701
|
+
});
|
|
41021
41702
|
}
|
|
41022
41703
|
};
|
|
41704
|
+
var SMI_INDICATOR = "Indicator";
|
|
41705
|
+
var SMI_OSCILLATOR = "Oscillator";
|
|
41023
41706
|
var smiErgodic = {
|
|
41024
41707
|
type: "smi-ergodic",
|
|
41025
41708
|
title: "SMI Ergodic Oscillator",
|
|
41026
41709
|
shortTitle: "SMIE",
|
|
41027
41710
|
overlay: false,
|
|
41028
|
-
inputs: [
|
|
41711
|
+
inputs: [
|
|
41712
|
+
lengthInput(20, "longLength", "Long Length", 5e3, "Length of the first EMA smoothing applied to the momentum of the closing price."),
|
|
41713
|
+
lengthInput(5, "shortLength", "Short Length", 5e3, "Length of the second EMA smoothing applied to the momentum of the closing price."),
|
|
41714
|
+
lengthInput(5, "signalLength", "Signal Length", 500, "Length of the EMA of the SMI that forms the signal line."),
|
|
41715
|
+
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."),
|
|
41716
|
+
colorInput(NEUTRAL, "SMI", "color", "Color of the SMI line while it sits exactly on its signal line."),
|
|
41717
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line."),
|
|
41718
|
+
colorInput(BULLISH, "Oscillator Grow", "growColor", "Color of the rising oscillator histogram, and of the SMI line while it leads its signal."),
|
|
41719
|
+
colorInput(BEARISH, "Oscillator Fall", "fallColor", "Color of the falling oscillator histogram, and of the SMI line while it lags its signal.")
|
|
41720
|
+
],
|
|
41029
41721
|
compute: (bars, inputs) => {
|
|
41030
|
-
const
|
|
41031
|
-
const signal = ema(
|
|
41032
|
-
const
|
|
41722
|
+
const smi = tsiRatio(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
41723
|
+
const signal = ema(smi, num3(inputs, "signalLength", 5));
|
|
41724
|
+
const grow = str3(inputs, "growColor", BULLISH);
|
|
41725
|
+
const fall = str3(inputs, "fallColor", BEARISH);
|
|
41726
|
+
const zero = [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed", title: "Zero" }];
|
|
41727
|
+
if (str3(inputs, "display", SMI_INDICATOR) === SMI_OSCILLATOR) {
|
|
41728
|
+
const osc = sub(smi, signal);
|
|
41729
|
+
const colors = osc.map((x, i) => {
|
|
41730
|
+
if (!Number.isFinite(x)) return null;
|
|
41731
|
+
const prev2 = i > 0 && Number.isFinite(osc[i - 1]) ? osc[i - 1] : x;
|
|
41732
|
+
return x > prev2 ? grow : fall;
|
|
41733
|
+
});
|
|
41734
|
+
return { plots: [{ key: "osc", title: "Oscillator", values: osc, kind: "columns", color: grow, colors, base: 0 }], levels: zero };
|
|
41735
|
+
}
|
|
41736
|
+
const neutral = str3(inputs, "color", NEUTRAL);
|
|
41737
|
+
const inkOf = (i) => {
|
|
41738
|
+
const a = smi[i];
|
|
41739
|
+
const b = signal[i];
|
|
41740
|
+
if (!Number.isFinite(b)) return neutral;
|
|
41741
|
+
return a > b ? grow : a < b ? fall : neutral;
|
|
41742
|
+
};
|
|
41033
41743
|
return {
|
|
41034
41744
|
plots: [
|
|
41035
|
-
{ key: "
|
|
41036
|
-
{ key: "
|
|
41037
|
-
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
41745
|
+
{ key: "smi", title: "SMI", values: smi, color: neutral, colors: smi.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) },
|
|
41746
|
+
{ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) }
|
|
41038
41747
|
],
|
|
41039
|
-
|
|
41748
|
+
bands: [{ key: "spread", from: "smi", to: "signal", color: transp(neutral, 75), colors: smi.map((x, i) => Number.isFinite(x) ? transp(inkOf(i), 75) : null) }],
|
|
41749
|
+
levels: zero
|
|
41040
41750
|
};
|
|
41041
41751
|
}
|
|
41042
41752
|
};
|
|
41753
|
+
var STC_MID = 50;
|
|
41754
|
+
var TRIGGERS = "Trigger Levels";
|
|
41043
41755
|
var stc = {
|
|
41044
41756
|
type: "schaff-trend-cycle",
|
|
41045
41757
|
title: "Schaff Trend Cycle",
|
|
41046
41758
|
shortTitle: "Schaff Trend Cycle",
|
|
41047
41759
|
overlay: false,
|
|
41048
|
-
inputs: [
|
|
41760
|
+
inputs: [
|
|
41761
|
+
sourceInput("Close", "source", "Source", "Price series used to build the MACD that the trend cycle is measured on."),
|
|
41762
|
+
lengthInput(23, "fastLength", "Fast Length", 5e3, "Fast EMA length of the underlying MACD."),
|
|
41763
|
+
lengthInput(50, "slowLength", "Slow Length", 5e3, "Slow EMA length of the underlying MACD."),
|
|
41764
|
+
{ ...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 },
|
|
41765
|
+
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."),
|
|
41766
|
+
{ ...floatInput("upper", "Upper Trigger", 75, 0, 100, 1, "Upper trigger line. A cross down through it flags an emerging downtrend."), group: TRIGGERS },
|
|
41767
|
+
{ ...floatInput("lower", "Lower Trigger", 25, 0, 100, 1, "Lower trigger line. A cross up through it flags an emerging uptrend."), group: TRIGGERS },
|
|
41768
|
+
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."),
|
|
41769
|
+
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."),
|
|
41770
|
+
colorInput(NEUTRAL, "Levels Color", "levelsColor", "Color of the dashed trigger lines and the dotted midline."),
|
|
41771
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Vertical gradient between the midline and the trend cycle, fading out toward the midline.")
|
|
41772
|
+
],
|
|
41049
41773
|
compute: (bars, inputs) => {
|
|
41050
41774
|
const len = num3(inputs, "cycleLength", 10);
|
|
41051
|
-
const
|
|
41775
|
+
const factor = num3(inputs, "factor", 0.5);
|
|
41776
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41052
41777
|
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
41053
|
-
const
|
|
41778
|
+
const stochNorm = (v) => {
|
|
41054
41779
|
const lo = lowest(v, len);
|
|
41055
41780
|
const hi = highest(v, len);
|
|
41056
41781
|
const out = new Array(v.length).fill(Number.NaN);
|
|
41057
|
-
let
|
|
41058
|
-
let smoothed = Number.NaN;
|
|
41782
|
+
let pct = Number.NaN;
|
|
41059
41783
|
for (let i = 0; i < v.length; i++) {
|
|
41060
41784
|
const x = v[i];
|
|
41061
41785
|
const l = lo[i];
|
|
41062
41786
|
const h = hi[i];
|
|
41063
41787
|
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
41064
|
-
|
|
41065
|
-
|
|
41066
|
-
|
|
41788
|
+
if (h > l) pct = 100 * (x - l) / (h - l);
|
|
41789
|
+
out[i] = pct;
|
|
41790
|
+
}
|
|
41791
|
+
return out;
|
|
41792
|
+
};
|
|
41793
|
+
const smoothStep = (v) => {
|
|
41794
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
41795
|
+
let smoothed = Number.NaN;
|
|
41796
|
+
for (let i = 0; i < v.length; i++) {
|
|
41797
|
+
const x = v[i];
|
|
41798
|
+
if (!Number.isFinite(x)) continue;
|
|
41799
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + factor * (x - smoothed) : x;
|
|
41067
41800
|
out[i] = smoothed;
|
|
41068
41801
|
}
|
|
41069
41802
|
return out;
|
|
41070
41803
|
};
|
|
41071
|
-
const values =
|
|
41804
|
+
const values = smoothStep(stochNorm(smoothStep(stochNorm(macdLine))));
|
|
41805
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
41806
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
41807
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
41808
|
+
const plots = [
|
|
41809
|
+
{ key: "stc", title: "Schaff Trend Cycle", values, color: bear, width: 2, colors: values.map((x) => Number.isFinite(x) ? x > STC_MID ? bull : bear : null) }
|
|
41810
|
+
];
|
|
41811
|
+
const bands = [];
|
|
41812
|
+
if (bool(inputs, "gradientFill", true)) {
|
|
41813
|
+
plots.push(anchorPlot("midAnchor", "Midline Anchor", values.length, STC_MID));
|
|
41814
|
+
bands.push({ key: "wash", from: "stc", to: "midAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, STC_MID, (x) => x > STC_MID ? bull : bear) });
|
|
41815
|
+
}
|
|
41072
41816
|
return {
|
|
41073
|
-
plots
|
|
41074
|
-
|
|
41817
|
+
plots,
|
|
41818
|
+
bands,
|
|
41819
|
+
levels: [
|
|
41820
|
+
{ key: "upper", price: num3(inputs, "upper", 75), color: transp(levelInk, 25), lineStyle: "dashed", title: "Upper Trigger" },
|
|
41821
|
+
{ key: "lower", price: num3(inputs, "lower", 25), color: transp(levelInk, 25), lineStyle: "dashed", title: "Lower Trigger" },
|
|
41822
|
+
{ key: "midline", price: STC_MID, color: transp(levelInk, 60), lineStyle: "dotted", title: "Midline" }
|
|
41823
|
+
]
|
|
41075
41824
|
};
|
|
41076
41825
|
}
|
|
41077
41826
|
};
|
|
@@ -41080,22 +41829,37 @@ var kst = {
|
|
|
41080
41829
|
title: "Know Sure Thing",
|
|
41081
41830
|
shortTitle: "KST",
|
|
41082
41831
|
overlay: false,
|
|
41083
|
-
inputs: [
|
|
41832
|
+
inputs: [
|
|
41833
|
+
lengthInput(10, "rocLength1", "ROC Length #1", 5e3, "Lookback of the first (shortest) rate of change."),
|
|
41834
|
+
lengthInput(15, "rocLength2", "ROC Length #2", 5e3, "Lookback of the second rate of change."),
|
|
41835
|
+
lengthInput(20, "rocLength3", "ROC Length #3", 5e3, "Lookback of the third rate of change."),
|
|
41836
|
+
lengthInput(30, "rocLength4", "ROC Length #4", 5e3, "Lookback of the fourth (longest) rate of change."),
|
|
41837
|
+
lengthInput(10, "smaLength1", "SMA Length #1", 5e3, "Smoothing length applied to the first rate of change (RCMA #1)."),
|
|
41838
|
+
lengthInput(10, "smaLength2", "SMA Length #2", 5e3, "Smoothing length applied to the second rate of change (RCMA #2)."),
|
|
41839
|
+
lengthInput(10, "smaLength3", "SMA Length #3", 5e3, "Smoothing length applied to the third rate of change (RCMA #3)."),
|
|
41840
|
+
lengthInput(15, "smaLength4", "SMA Length #4", 5e3, "Smoothing length applied to the fourth rate of change (RCMA #4)."),
|
|
41841
|
+
lengthInput(9, "signalLength", "Signal Length", 500, "Simple moving average length of the signal line."),
|
|
41842
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the KST line while above zero."),
|
|
41843
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the KST line while below zero."),
|
|
41844
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line.")
|
|
41845
|
+
],
|
|
41084
41846
|
compute: (bars, inputs) => {
|
|
41085
|
-
const
|
|
41086
|
-
const
|
|
41087
|
-
|
|
41088
|
-
|
|
41089
|
-
|
|
41090
|
-
|
|
41091
|
-
|
|
41092
|
-
return {
|
|
41093
|
-
|
|
41094
|
-
|
|
41095
|
-
|
|
41096
|
-
|
|
41097
|
-
|
|
41098
|
-
|
|
41847
|
+
const c = closes(bars);
|
|
41848
|
+
const rcma = (rocKey, rocDefault, smaKey, smaDefault) => sma(roc(c, num3(inputs, rocKey, rocDefault)), num3(inputs, smaKey, smaDefault));
|
|
41849
|
+
const values = [rcma("rocLength1", 10, "smaLength1", 10), rcma("rocLength2", 15, "smaLength2", 10), rcma("rocLength3", 20, "smaLength3", 10), rcma("rocLength4", 30, "smaLength4", 15)].reduce(
|
|
41850
|
+
(acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)),
|
|
41851
|
+
new Array(c.length).fill(0)
|
|
41852
|
+
);
|
|
41853
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
41854
|
+
return centeredOscillator({
|
|
41855
|
+
key: "kst",
|
|
41856
|
+
title: "KST",
|
|
41857
|
+
values,
|
|
41858
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
41859
|
+
zeroInk: NEUTRAL,
|
|
41860
|
+
valueTransparency: 60,
|
|
41861
|
+
extraPlots: [{ key: "signal", title: "Signal", values: sma(values, num3(inputs, "signalLength", 9)), color: str3(inputs, "signalColor", WARNING) }]
|
|
41862
|
+
});
|
|
41099
41863
|
}
|
|
41100
41864
|
};
|
|
41101
41865
|
var coppock = {
|
|
@@ -41104,19 +41868,25 @@ var coppock = {
|
|
|
41104
41868
|
shortTitle: "Coppock",
|
|
41105
41869
|
overlay: false,
|
|
41106
41870
|
inputs: [
|
|
41107
|
-
lengthInput(10, "wmaLength", "WMA
|
|
41108
|
-
lengthInput(14, "longRoc", "Long RoC
|
|
41109
|
-
lengthInput(11, "shortRoc", "Short RoC
|
|
41110
|
-
sourceInput(),
|
|
41111
|
-
colorInput()
|
|
41871
|
+
lengthInput(10, "wmaLength", "WMA Length", 5e3, "Length of the weighted moving average applied to the sum of the two rates of change."),
|
|
41872
|
+
lengthInput(14, "longRoc", "Long RoC Length", 5e3, "Lookback of the long rate of change, in bars. The original definition uses 14 monthly closes."),
|
|
41873
|
+
lengthInput(11, "shortRoc", "Short RoC Length", 5e3, "Lookback of the short rate of change, in bars. The original definition uses 11 monthly closes."),
|
|
41874
|
+
sourceInput("Close", "source", "Source", "Price series used for the rate of change calculations."),
|
|
41875
|
+
colorInput(NEUTRAL, "Coppock Curve", "color", "Color of the Coppock Curve line."),
|
|
41876
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the curve reads above zero."),
|
|
41877
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the curve reads below zero."),
|
|
41878
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
41112
41879
|
],
|
|
41113
41880
|
compute: (bars, inputs) => {
|
|
41114
41881
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41115
41882
|
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
41116
|
-
return {
|
|
41117
|
-
|
|
41118
|
-
|
|
41119
|
-
|
|
41883
|
+
return centeredOscillator({
|
|
41884
|
+
key: "coppock",
|
|
41885
|
+
title: "Coppock Curve",
|
|
41886
|
+
values,
|
|
41887
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
41888
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
41889
|
+
});
|
|
41120
41890
|
}
|
|
41121
41891
|
};
|
|
41122
41892
|
var dpo = {
|
|
@@ -41124,42 +41894,76 @@ var dpo = {
|
|
|
41124
41894
|
title: "Detrended Price Oscillator",
|
|
41125
41895
|
shortTitle: "DPO",
|
|
41126
41896
|
overlay: false,
|
|
41127
|
-
inputs: [
|
|
41897
|
+
inputs: [
|
|
41898
|
+
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."),
|
|
41899
|
+
sourceInput("Close", "source", "Source", "Price series used in the detrending calculation."),
|
|
41900
|
+
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."),
|
|
41901
|
+
colorInput(NEUTRAL, "DPO", "color", "Color of the line when it sits exactly at zero."),
|
|
41902
|
+
colorInput(BULLISH, "Above Zero", "bullColor", "Color of the line and fill while the oscillator is above zero."),
|
|
41903
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while the oscillator is below zero."),
|
|
41904
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
41905
|
+
],
|
|
41128
41906
|
compute: (bars, inputs) => {
|
|
41129
41907
|
const len = num3(inputs, "length", 21);
|
|
41130
|
-
const
|
|
41131
|
-
const
|
|
41132
|
-
|
|
41133
|
-
|
|
41134
|
-
|
|
41135
|
-
|
|
41908
|
+
const barsBack = Math.trunc(len / 2) + 1;
|
|
41909
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41910
|
+
const basis = sma(src, len);
|
|
41911
|
+
const centered = bool(inputs, "centered", false);
|
|
41912
|
+
const values = centered ? shift(sub(shift(src, barsBack), basis), -barsBack) : sub(src, shift(basis, barsBack));
|
|
41913
|
+
return centeredOscillator({
|
|
41914
|
+
key: "dpo",
|
|
41915
|
+
title: "DPO",
|
|
41916
|
+
values,
|
|
41917
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
41918
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
41919
|
+
});
|
|
41136
41920
|
}
|
|
41137
41921
|
};
|
|
41922
|
+
var ROC_PERCENT = "ROC %";
|
|
41923
|
+
var ROC_MOMENTUM = "Momentum";
|
|
41138
41924
|
var rocSpec = {
|
|
41139
41925
|
type: "rate-of-change",
|
|
41140
41926
|
title: "Rate of Change",
|
|
41141
41927
|
shortTitle: "ROC",
|
|
41142
41928
|
overlay: false,
|
|
41143
|
-
inputs: [
|
|
41144
|
-
|
|
41145
|
-
|
|
41146
|
-
|
|
41147
|
-
|
|
41148
|
-
|
|
41149
|
-
|
|
41150
|
-
|
|
41151
|
-
|
|
41152
|
-
|
|
41153
|
-
|
|
41154
|
-
|
|
41155
|
-
|
|
41929
|
+
inputs: [
|
|
41930
|
+
lengthInput(9, "length", "Length", 5e3, "Lookback period; the source is compared with its value this many bars ago."),
|
|
41931
|
+
sourceInput("Close", "source", "Source", "Series used as input for the calculation."),
|
|
41932
|
+
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."),
|
|
41933
|
+
colorInput(NEUTRAL, "Line", "color", "Color of the line when the reading sits exactly at zero."),
|
|
41934
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the line and fill while the reading is above zero."),
|
|
41935
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line and fill while the reading is below zero."),
|
|
41936
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the line and zero, following the line color.")
|
|
41937
|
+
],
|
|
41938
|
+
compute: (bars, inputs) => {
|
|
41939
|
+
const len = num3(inputs, "length", 9);
|
|
41940
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41941
|
+
const values = str3(inputs, "mode", ROC_PERCENT) === ROC_MOMENTUM ? change(src, len) : roc(src, len);
|
|
41942
|
+
return centeredOscillator({
|
|
41943
|
+
key: "roc",
|
|
41944
|
+
title: "ROC",
|
|
41945
|
+
values,
|
|
41946
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
41947
|
+
zeroInk: NEUTRAL,
|
|
41948
|
+
fill: bool(inputs, "gradientFill", true)
|
|
41949
|
+
});
|
|
41950
|
+
}
|
|
41156
41951
|
};
|
|
41157
41952
|
var ultimate = {
|
|
41158
41953
|
type: "ultimate-oscillator",
|
|
41159
41954
|
title: "Ultimate Oscillator",
|
|
41160
41955
|
shortTitle: "UO",
|
|
41161
41956
|
overlay: false,
|
|
41162
|
-
inputs: [
|
|
41957
|
+
inputs: [
|
|
41958
|
+
lengthInput(7, "fastLength", "Fast Length", 5e3, "Bars in the fastest averaging window. Weighted 4 in the final blend, so it dominates the oscillator."),
|
|
41959
|
+
lengthInput(14, "middleLength", "Middle Length", 5e3, "Bars in the intermediate averaging window. Weighted 2 in the final blend."),
|
|
41960
|
+
lengthInput(28, "slowLength", "Slow Length", 5e3, "Bars in the slowest averaging window. Weighted 1 in the final blend."),
|
|
41961
|
+
...thresholdInputs(70, 30, 0, 100, "Overbought", "Oversold"),
|
|
41962
|
+
colorInput(NEUTRAL, "Oscillator", "color", "Color of the line midway between the levels. Toward either level it blends into that level\u2019s color."),
|
|
41963
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought and oversold levels."),
|
|
41964
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line at the oversold level and of the oversold zone shading."),
|
|
41965
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line at the overbought level and of the overbought zone shading.")
|
|
41966
|
+
],
|
|
41163
41967
|
compute: (bars, inputs) => {
|
|
41164
41968
|
const n = bars.length;
|
|
41165
41969
|
const bp = new Array(n).fill(Number.NaN);
|
|
@@ -41167,10 +41971,9 @@ var ultimate = {
|
|
|
41167
41971
|
for (let i = 0; i < n; i++) {
|
|
41168
41972
|
const b = bars[i];
|
|
41169
41973
|
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
41170
|
-
const
|
|
41171
|
-
|
|
41172
|
-
|
|
41173
|
-
tr[i] = hi - lo;
|
|
41974
|
+
const trueLow = Math.min(b.low, pc);
|
|
41975
|
+
bp[i] = b.close - trueLow;
|
|
41976
|
+
tr[i] = Math.max(b.high, pc) - trueLow;
|
|
41174
41977
|
}
|
|
41175
41978
|
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
41176
41979
|
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
@@ -41181,10 +41984,17 @@ var ultimate = {
|
|
|
41181
41984
|
const z = a28[i];
|
|
41182
41985
|
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
41183
41986
|
});
|
|
41184
|
-
return {
|
|
41185
|
-
|
|
41186
|
-
|
|
41187
|
-
|
|
41987
|
+
return boundedOscillator({
|
|
41988
|
+
key: "uo",
|
|
41989
|
+
title: "UO",
|
|
41990
|
+
values,
|
|
41991
|
+
scale: { top: 100, bottom: 0 },
|
|
41992
|
+
overbought: num3(inputs, "overbought", 70),
|
|
41993
|
+
oversold: num3(inputs, "oversold", 30),
|
|
41994
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
41995
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
41996
|
+
zoneTransparency: 91
|
|
41997
|
+
});
|
|
41188
41998
|
}
|
|
41189
41999
|
};
|
|
41190
42000
|
var rvgi = {
|
|
@@ -41192,7 +42002,11 @@ var rvgi = {
|
|
|
41192
42002
|
title: "Relative Vigor Index",
|
|
41193
42003
|
shortTitle: "RVGI",
|
|
41194
42004
|
overlay: false,
|
|
41195
|
-
inputs: [
|
|
42005
|
+
inputs: [
|
|
42006
|
+
lengthInput(10, "length", "Length", 5e3, "Number of bars summed for the smoothed numerator (close \u2212 open) and denominator (high \u2212 low)."),
|
|
42007
|
+
colorInput(BULLISH, "RVGI", "color", "Color of the RVGI line."),
|
|
42008
|
+
colorInput(BEARISH, "Signal", "signalColor", "Color of the signal line.")
|
|
42009
|
+
],
|
|
41196
42010
|
compute: (bars, inputs) => {
|
|
41197
42011
|
const len = num3(inputs, "length", 10);
|
|
41198
42012
|
const co = swma(sub(closes(bars), opens(bars)));
|
|
@@ -41200,31 +42014,49 @@ var rvgi = {
|
|
|
41200
42014
|
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
41201
42015
|
return {
|
|
41202
42016
|
plots: [
|
|
41203
|
-
{ key: "rvgi", title: "RVGI", values: line, color:
|
|
41204
|
-
{ key: "signal", title: "Signal", values: swma(line), color:
|
|
42017
|
+
{ key: "rvgi", title: "RVGI", values: line, color: str3(inputs, "color", BULLISH) },
|
|
42018
|
+
{ key: "signal", title: "Signal", values: swma(line), color: str3(inputs, "signalColor", BEARISH) }
|
|
41205
42019
|
],
|
|
41206
|
-
levels:
|
|
42020
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed", title: "Zero" }]
|
|
41207
42021
|
};
|
|
41208
42022
|
}
|
|
41209
42023
|
};
|
|
42024
|
+
var RVI_EMA = "EMA";
|
|
42025
|
+
var RVI_RMA = "Wilder (RMA)";
|
|
41210
42026
|
var relVolatility = {
|
|
41211
42027
|
type: "relative-volatility-index",
|
|
41212
42028
|
title: "Relative Volatility Index",
|
|
41213
42029
|
shortTitle: "RVI",
|
|
41214
42030
|
overlay: false,
|
|
41215
|
-
inputs: [
|
|
42031
|
+
inputs: [
|
|
42032
|
+
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."),
|
|
42033
|
+
lengthInput(14, "smoothLength", "Smoothing Length", 5e3, "Number of bars used to smooth the up and down volatility streams."),
|
|
42034
|
+
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."),
|
|
42035
|
+
...thresholdInputs(80, 20),
|
|
42036
|
+
colorInput(NEUTRAL, "RVI Line", "color", "Color of the RVI line at the midline. Toward either threshold it blends into that threshold\u2019s color."),
|
|
42037
|
+
colorInput(BULLISH, "Up Volatility", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
42038
|
+
colorInput(BEARISH, "Down Volatility", "oversoldColor", "Color of the line and zone shading at and below the oversold level.")
|
|
42039
|
+
],
|
|
41216
42040
|
compute: (bars, inputs) => {
|
|
42041
|
+
const c = closes(bars);
|
|
42042
|
+
const sigma = stdev(c, num3(inputs, "stdevLength", 10));
|
|
42043
|
+
const d = change(c);
|
|
42044
|
+
const up = zip(sigma, d, (s, x) => x > 0 ? s : 0);
|
|
42045
|
+
const down = zip(sigma, d, (s, x) => x < 0 ? s : 0);
|
|
41217
42046
|
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
41218
|
-
const
|
|
41219
|
-
const
|
|
41220
|
-
|
|
41221
|
-
|
|
41222
|
-
|
|
41223
|
-
|
|
41224
|
-
|
|
41225
|
-
|
|
41226
|
-
|
|
41227
|
-
|
|
42047
|
+
const smooth = str3(inputs, "smoothType", RVI_EMA) === RVI_RMA ? rma : ema;
|
|
42048
|
+
const values = zip(smooth(up, smoothLen), smooth(down, smoothLen), (u, w) => u + w === 0 ? 0 : 100 * u / (u + w));
|
|
42049
|
+
return boundedOscillator({
|
|
42050
|
+
key: "rvi",
|
|
42051
|
+
title: "RVI",
|
|
42052
|
+
values,
|
|
42053
|
+
scale: { top: 100, bottom: 0 },
|
|
42054
|
+
overbought: num3(inputs, "overbought", 80),
|
|
42055
|
+
oversold: num3(inputs, "oversold", 20),
|
|
42056
|
+
ink: { low: str3(inputs, "oversoldColor", BEARISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BULLISH) },
|
|
42057
|
+
levelInk: NEUTRAL,
|
|
42058
|
+
width: 2
|
|
42059
|
+
});
|
|
41228
42060
|
}
|
|
41229
42061
|
};
|
|
41230
42062
|
var bop = {
|
|
@@ -41232,28 +42064,43 @@ var bop = {
|
|
|
41232
42064
|
title: "Balance of Power",
|
|
41233
42065
|
shortTitle: "BOP",
|
|
41234
42066
|
overlay: false,
|
|
41235
|
-
inputs: [
|
|
42067
|
+
inputs: [
|
|
42068
|
+
boolInput("smoothing", "Smoothing", false, void 0, "Smooth the raw Balance of Power with a simple moving average instead of plotting the single-bar value."),
|
|
42069
|
+
lengthInput(14, "smoothLength", "Length", 5e3, "Simple moving average length applied when smoothing is enabled."),
|
|
42070
|
+
colorInput(NEUTRAL, "BOP Color", "color", "Color of the line at exactly zero."),
|
|
42071
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the Balance of Power is above zero \u2014 buyers in control of the bar."),
|
|
42072
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the Balance of Power is below zero \u2014 sellers in control of the bar.")
|
|
42073
|
+
],
|
|
41236
42074
|
compute: (bars, inputs) => {
|
|
41237
|
-
const
|
|
41238
|
-
|
|
41239
|
-
|
|
41240
|
-
|
|
41241
|
-
|
|
42075
|
+
const raw = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
42076
|
+
const values = bool(inputs, "smoothing", false) ? sma(raw, num3(inputs, "smoothLength", 14)) : raw;
|
|
42077
|
+
return centeredOscillator({
|
|
42078
|
+
key: "bop",
|
|
42079
|
+
title: "BOP",
|
|
42080
|
+
values,
|
|
42081
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
42082
|
+
zeroInk: NEUTRAL,
|
|
42083
|
+
valueTransparency: 60
|
|
42084
|
+
});
|
|
41242
42085
|
}
|
|
41243
42086
|
};
|
|
41244
42087
|
var elderRay = {
|
|
41245
42088
|
type: "elder-ray",
|
|
41246
42089
|
title: "Elder Ray",
|
|
41247
42090
|
overlay: false,
|
|
41248
|
-
inputs: [
|
|
42091
|
+
inputs: [
|
|
42092
|
+
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."),
|
|
42093
|
+
colorInput(BULLISH, "Bull Power", "bullColor", "Color of the Bull Power columns."),
|
|
42094
|
+
colorInput(BEARISH, "Bear Power", "bearColor", "Color of the Bear Power columns.")
|
|
42095
|
+
],
|
|
41249
42096
|
compute: (bars, inputs) => {
|
|
41250
42097
|
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
41251
42098
|
return {
|
|
41252
42099
|
plots: [
|
|
41253
|
-
{ key: "bull", title: "Bull
|
|
41254
|
-
{ key: "bear", title: "Bear
|
|
42100
|
+
{ key: "bull", title: "Bull Power", values: sub(highs(bars), basis), kind: "columns", color: str3(inputs, "bullColor", BULLISH), base: 0 },
|
|
42101
|
+
{ key: "bear", title: "Bear Power", values: sub(lows(bars), basis), kind: "columns", color: str3(inputs, "bearColor", BEARISH), base: 0 }
|
|
41255
42102
|
],
|
|
41256
|
-
levels:
|
|
42103
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 30), lineStyle: "dashed", title: "Zero Line" }]
|
|
41257
42104
|
};
|
|
41258
42105
|
}
|
|
41259
42106
|
};
|
|
@@ -41262,31 +42109,66 @@ var ttmSqueeze = {
|
|
|
41262
42109
|
title: "TTM Squeeze",
|
|
41263
42110
|
overlay: false,
|
|
41264
42111
|
inputs: [
|
|
41265
|
-
lengthInput(20),
|
|
41266
|
-
|
|
41267
|
-
|
|
42112
|
+
{ ...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 },
|
|
42113
|
+
sourceInput("Close", "source", "Source", "Price series used for the shared moving average basis, the standard deviation, and the momentum displacement."),
|
|
42114
|
+
floatInput("bbMult", "Bollinger Bands Multiplier", 2, 0.1, 50, 0.1, "Standard deviation multiplier for the Bollinger Bands."),
|
|
42115
|
+
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."),
|
|
42116
|
+
colorInput(BULLISH, "Momentum Up", "bullColor", "Histogram color while momentum is positive. Bars building away from zero print solid, bars fading back print dimmed."),
|
|
42117
|
+
colorInput(BEARISH, "Momentum Down", "bearColor", "Histogram color while momentum is negative. Bars building away from zero print solid, bars fading back print dimmed."),
|
|
42118
|
+
colorInput(WARNING, "Squeeze On", "squeezeOnColor", "Zero-line dot color while both Bollinger Bands sit fully inside the Keltner Channels."),
|
|
42119
|
+
colorInput(NEUTRAL, "Squeeze Off", "squeezeOffColor", "Zero-line dot color while the Bollinger Bands trade outside the Keltner Channels."),
|
|
42120
|
+
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.")
|
|
41268
42121
|
],
|
|
41269
42122
|
compute: (bars, inputs) => {
|
|
41270
42123
|
const len = num3(inputs, "length", 20);
|
|
41271
|
-
const
|
|
41272
|
-
const basis = sma(
|
|
41273
|
-
const dev = map(stdev(
|
|
41274
|
-
const kcRange = map(
|
|
41275
|
-
const
|
|
41276
|
-
const anchor = zip(
|
|
41277
|
-
const mom = linreg(sub(
|
|
41278
|
-
const
|
|
42124
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
42125
|
+
const basis = sma(src, len);
|
|
42126
|
+
const dev = map(stdev(src, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
42127
|
+
const kcRange = map(atr(bars, len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
42128
|
+
const rangeMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
42129
|
+
const anchor = zip(rangeMid, basis, (d, b) => (d + b) / 2);
|
|
42130
|
+
const mom = linreg(sub(src, anchor), len, 0);
|
|
42131
|
+
const squeezeOn = basis.map((b, i) => {
|
|
41279
42132
|
const d = dev[i];
|
|
41280
42133
|
const r = kcRange[i];
|
|
41281
|
-
|
|
42134
|
+
if (!Number.isFinite(b) || !Number.isFinite(d) || !Number.isFinite(r)) return null;
|
|
42135
|
+
return d < r;
|
|
41282
42136
|
});
|
|
41283
|
-
const
|
|
41284
|
-
|
|
41285
|
-
|
|
41286
|
-
|
|
41287
|
-
|
|
41288
|
-
|
|
41289
|
-
|
|
42137
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
42138
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
42139
|
+
const onInk = str3(inputs, "squeezeOnColor", WARNING);
|
|
42140
|
+
const offInk = str3(inputs, "squeezeOffColor", NEUTRAL);
|
|
42141
|
+
const momColors = mom.map((x, i) => {
|
|
42142
|
+
if (!Number.isFinite(x)) return null;
|
|
42143
|
+
const prev2 = i > 0 && Number.isFinite(mom[i - 1]) ? mom[i - 1] : x;
|
|
42144
|
+
const rising = x > prev2;
|
|
42145
|
+
return x >= 0 ? rising ? bull : transp(bull, 60) : rising ? transp(bear, 60) : bear;
|
|
42146
|
+
});
|
|
42147
|
+
const plots = [
|
|
42148
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "columns", color: bull, colors: momColors, base: 0 },
|
|
42149
|
+
{
|
|
42150
|
+
key: "state",
|
|
42151
|
+
title: "Squeeze State",
|
|
42152
|
+
values: squeezeOn.map((s) => s == null ? Number.NaN : 0),
|
|
42153
|
+
kind: "circles",
|
|
42154
|
+
color: offInk,
|
|
42155
|
+
width: 2,
|
|
42156
|
+
colors: squeezeOn.map((s) => s == null ? null : s ? onInk : offInk)
|
|
42157
|
+
}
|
|
42158
|
+
];
|
|
42159
|
+
if (bool(inputs, "markFires", true)) {
|
|
42160
|
+
const fires = squeezeOn.map((s, i) => s === false && squeezeOn[i - 1] === true ? 0 : Number.NaN);
|
|
42161
|
+
plots.push({
|
|
42162
|
+
key: "fire",
|
|
42163
|
+
title: "Squeeze Fire",
|
|
42164
|
+
values: fires,
|
|
42165
|
+
kind: "cross",
|
|
42166
|
+
color: offInk,
|
|
42167
|
+
width: 3,
|
|
42168
|
+
colors: fires.map((x, i) => Number.isFinite(x) ? Number.isFinite(mom[i]) ? mom[i] >= 0 ? bull : bear : offInk : null)
|
|
42169
|
+
});
|
|
42170
|
+
}
|
|
42171
|
+
return { plots };
|
|
41290
42172
|
}
|
|
41291
42173
|
};
|
|
41292
42174
|
var oscillatorSpecs = [
|
|
@@ -41298,7 +42180,7 @@ var oscillatorSpecs = [
|
|
|
41298
42180
|
awesome,
|
|
41299
42181
|
cci,
|
|
41300
42182
|
williamsR,
|
|
41301
|
-
|
|
42183
|
+
cmoSpec,
|
|
41302
42184
|
connorsRsi,
|
|
41303
42185
|
fisher,
|
|
41304
42186
|
trix,
|
|
@@ -41322,15 +42204,19 @@ var aroon = {
|
|
|
41322
42204
|
type: "aroon",
|
|
41323
42205
|
title: "Aroon",
|
|
41324
42206
|
overlay: false,
|
|
41325
|
-
inputs: [
|
|
42207
|
+
inputs: [
|
|
42208
|
+
lengthInput(14, "length", "Length", 5e3, "Lookback length used to locate the highest high and lowest low."),
|
|
42209
|
+
colorInput(BULLISH, "Aroon Up", "upColor", "Color of the Aroon Up line."),
|
|
42210
|
+
colorInput(BEARISH, "Aroon Down", "downColor", "Color of the Aroon Down line.")
|
|
42211
|
+
],
|
|
41326
42212
|
compute: (bars, inputs) => {
|
|
41327
42213
|
const len = num3(inputs, "length", 14);
|
|
41328
|
-
const up = map(barsSinceHighest(highs(bars), len
|
|
41329
|
-
const down = map(barsSinceLowest(lows(bars), len
|
|
42214
|
+
const up = map(barsSinceHighest(highs(bars), len), (x) => 100 * (len - x) / len);
|
|
42215
|
+
const down = map(barsSinceLowest(lows(bars), len), (x) => 100 * (len - x) / len);
|
|
41330
42216
|
return {
|
|
41331
42217
|
plots: [
|
|
41332
|
-
{ key: "up", title: "Aroon
|
|
41333
|
-
{ key: "down", title: "Aroon
|
|
42218
|
+
{ key: "up", title: "Aroon Up", values: up, color: str3(inputs, "upColor", BULLISH) },
|
|
42219
|
+
{ key: "down", title: "Aroon Down", values: down, color: str3(inputs, "downColor", BEARISH) }
|
|
41334
42220
|
]
|
|
41335
42221
|
};
|
|
41336
42222
|
}
|
|
@@ -41340,10 +42226,16 @@ var adx = {
|
|
|
41340
42226
|
title: "Average Directional Index",
|
|
41341
42227
|
shortTitle: "ADX",
|
|
41342
42228
|
overlay: false,
|
|
41343
|
-
inputs: [
|
|
42229
|
+
inputs: [
|
|
42230
|
+
lengthInput(14, "diLength", "DI Length", 5e3, "Wilder RMA smoothing length applied to directional movement and true range when computing +DI and \u2212DI."),
|
|
42231
|
+
lengthInput(14, "adxLength", "ADX Smoothing", 5e3, "Wilder RMA smoothing length applied to DX to obtain the ADX line."),
|
|
42232
|
+
floatInput("keyLevel", "Key Level", 25, 0, 100, 1, "Trend strength reference level; ADX above it is commonly read as a trending market."),
|
|
42233
|
+
colorInput(BULLISH, "+DI Color", "plusDiColor", "Color of the plus directional indicator, and of the ADX line above the key level while +DI leads."),
|
|
42234
|
+
colorInput(BEARISH, "-DI Color", "minusDiColor", "Color of the minus directional indicator, and of the ADX line above the key level while \u2212DI leads."),
|
|
42235
|
+
colorInput(NEUTRAL, "ADX Color", "adxColor", "Color of the ADX line while it sits below the key level \u2014 no trend strong enough to trade.")
|
|
42236
|
+
],
|
|
41344
42237
|
compute: (bars, inputs) => {
|
|
41345
42238
|
const diLen = num3(inputs, "diLength", 14);
|
|
41346
|
-
const adxLen = num3(inputs, "adxLength", 14);
|
|
41347
42239
|
const n = bars.length;
|
|
41348
42240
|
const plusDm = new Array(n).fill(Number.NaN);
|
|
41349
42241
|
const minusDm = new Array(n).fill(Number.NaN);
|
|
@@ -41357,13 +42249,23 @@ var adx = {
|
|
|
41357
42249
|
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
41358
42250
|
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
41359
42251
|
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
42252
|
+
const adxLine = rma(dx, num3(inputs, "adxLength", 14));
|
|
42253
|
+
const plusInk = str3(inputs, "plusDiColor", BULLISH);
|
|
42254
|
+
const minusInk = str3(inputs, "minusDiColor", BEARISH);
|
|
42255
|
+
const neutralInk = str3(inputs, "adxColor", NEUTRAL);
|
|
42256
|
+
const keyLevel = num3(inputs, "keyLevel", 25);
|
|
42257
|
+
const adxColors = adxLine.map((x, i) => {
|
|
42258
|
+
if (!Number.isFinite(x)) return null;
|
|
42259
|
+
if (x <= keyLevel) return neutralInk;
|
|
42260
|
+
return (plusDi[i] ?? 0) > (minusDi[i] ?? 0) ? plusInk : minusInk;
|
|
42261
|
+
});
|
|
41360
42262
|
return {
|
|
41361
42263
|
plots: [
|
|
41362
|
-
{ key: "
|
|
41363
|
-
{ key: "
|
|
41364
|
-
{ key: "
|
|
42264
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: transp(plusInk, 30) },
|
|
42265
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: transp(minusInk, 30) },
|
|
42266
|
+
{ key: "adx", title: "ADX", values: adxLine, color: neutralInk, width: 2, colors: adxColors }
|
|
41365
42267
|
],
|
|
41366
|
-
levels: [{ key: "
|
|
42268
|
+
levels: [{ key: "keyLevel", price: keyLevel, color: NEUTRAL, lineStyle: "dashed", title: "Key Level" }]
|
|
41367
42269
|
};
|
|
41368
42270
|
}
|
|
41369
42271
|
};
|
|
@@ -41372,7 +42274,12 @@ var vortex = {
|
|
|
41372
42274
|
title: "Vortex Indicator",
|
|
41373
42275
|
shortTitle: "VI",
|
|
41374
42276
|
overlay: false,
|
|
41375
|
-
inputs: [
|
|
42277
|
+
inputs: [
|
|
42278
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to sum the vortex movements (VM+ and VM\u2212) and the true range."),
|
|
42279
|
+
colorInput(BULLISH, "VI+", "plusColor", "Color of the positive vortex line (VI+)."),
|
|
42280
|
+
colorInput(BEARISH, "VI-", "minusColor", "Color of the negative vortex line (VI\u2212)."),
|
|
42281
|
+
colorInput(NEUTRAL, "Baseline", "baselineColor", "Color of the dotted baseline drawn at 1.")
|
|
42282
|
+
],
|
|
41376
42283
|
compute: (bars, inputs) => {
|
|
41377
42284
|
const len = num3(inputs, "length", 14);
|
|
41378
42285
|
const n = bars.length;
|
|
@@ -41385,25 +42292,48 @@ var vortex = {
|
|
|
41385
42292
|
const trSum = sum(trueRange(bars), len);
|
|
41386
42293
|
return {
|
|
41387
42294
|
plots: [
|
|
41388
|
-
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color:
|
|
41389
|
-
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color:
|
|
42295
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: str3(inputs, "plusColor", BULLISH) },
|
|
42296
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: str3(inputs, "minusColor", BEARISH) }
|
|
41390
42297
|
],
|
|
41391
|
-
levels: [{ key: "
|
|
42298
|
+
levels: [{ key: "baseline", price: 1, color: str3(inputs, "baselineColor", NEUTRAL), lineStyle: "dotted", title: "Baseline" }]
|
|
41392
42299
|
};
|
|
41393
42300
|
}
|
|
41394
42301
|
};
|
|
41395
42302
|
var trendSpecs = [aroon, adx, vortex];
|
|
41396
42303
|
|
|
41397
42304
|
// src/core/native-indicators/classics/volatility.ts
|
|
42305
|
+
function expansionColors(values, calm, expanding) {
|
|
42306
|
+
return values.map((x, i) => {
|
|
42307
|
+
if (!Number.isFinite(x)) return null;
|
|
42308
|
+
const prev2 = i > 0 && Number.isFinite(values[i - 1]) ? values[i - 1] : x;
|
|
42309
|
+
return x > prev2 ? expanding : calm;
|
|
42310
|
+
});
|
|
42311
|
+
}
|
|
42312
|
+
function washedToZero(key, title, values, colors, fallback, valueTransparency, width = 1) {
|
|
42313
|
+
return {
|
|
42314
|
+
plots: [{ key, title, values, color: fallback, width, colors }, anchorPlot("zeroAnchor", "Zero Anchor", values.length, 0)],
|
|
42315
|
+
bands: [{ key: "wash", from: key, to: "zeroAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, 0, (_x, i) => colors[i] ?? fallback, valueTransparency) }]
|
|
42316
|
+
};
|
|
42317
|
+
}
|
|
41398
42318
|
var atrSpec = {
|
|
41399
42319
|
type: "average-true-range",
|
|
41400
42320
|
title: "Average True Range",
|
|
41401
42321
|
shortTitle: "ATR",
|
|
41402
42322
|
overlay: false,
|
|
41403
|
-
inputs: [
|
|
41404
|
-
|
|
41405
|
-
|
|
41406
|
-
|
|
42323
|
+
inputs: [
|
|
42324
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to average the true range."),
|
|
42325
|
+
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."),
|
|
42326
|
+
colorInput(NEUTRAL, "ATR Line", "color", "Color of the ATR line while volatility contracts or holds flat."),
|
|
42327
|
+
colorInput(WARNING, "Expanding", "expandColor", "Color of the ATR line while it rises \u2014 volatility expanding bar to bar.")
|
|
42328
|
+
],
|
|
42329
|
+
compute: (bars, inputs) => {
|
|
42330
|
+
const len = num3(inputs, "length", 14);
|
|
42331
|
+
const tr = trueRange(bars);
|
|
42332
|
+
const kind = str3(inputs, "smoothing", "RMA");
|
|
42333
|
+
const values = kind === "SMA" ? sma(tr, len) : kind === "EMA" ? ema(tr, len) : kind === "WMA" ? wma(tr, len) : rma(tr, len);
|
|
42334
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
42335
|
+
return washedToZero("atr", "ATR", values, expansionColors(values, calm, str3(inputs, "expandColor", WARNING)), calm, 75);
|
|
42336
|
+
}
|
|
41407
42337
|
};
|
|
41408
42338
|
var historicalVolatility = {
|
|
41409
42339
|
type: "historical-volatility",
|
|
@@ -41411,14 +42341,23 @@ var historicalVolatility = {
|
|
|
41411
42341
|
shortTitle: "HV",
|
|
41412
42342
|
overlay: false,
|
|
41413
42343
|
inputs: [
|
|
41414
|
-
lengthInput(10),
|
|
41415
|
-
|
|
41416
|
-
|
|
42344
|
+
{ ...lengthInput(10, "length", "Length", 5e3, "Lookback window in bars for the standard deviation of close-to-close log returns."), min: 2 },
|
|
42345
|
+
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."),
|
|
42346
|
+
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."),
|
|
42347
|
+
colorInput(WARNING, "Volatility Line", "color", "Color of the line at its recent highs; it fades toward grey as volatility contracts.")
|
|
41417
42348
|
],
|
|
41418
42349
|
compute: (bars, inputs) => {
|
|
41419
|
-
const
|
|
41420
|
-
const
|
|
41421
|
-
|
|
42350
|
+
const len = num3(inputs, "length", 10);
|
|
42351
|
+
const logReturns = sub(map(closes(bars), Math.log), shift(map(closes(bars), Math.log), 1));
|
|
42352
|
+
const meanReturn = map(sum(logReturns, len), (s) => s / len);
|
|
42353
|
+
const squaredSum = sum(map(logReturns, (r) => r * r), len);
|
|
42354
|
+
const factor = bool(inputs, "annualize", true) ? Math.sqrt(num3(inputs, "periodsPerYear", 252)) : 1;
|
|
42355
|
+
const values = zip(squaredSum, meanReturn, (sq, m) => Math.sqrt(Math.max(sq - len * m * m, 0) / (len - 1)) * factor * 100);
|
|
42356
|
+
const low = lowest(values, 100);
|
|
42357
|
+
const high = highest(values, 100);
|
|
42358
|
+
const accent = str3(inputs, "color", WARNING);
|
|
42359
|
+
const colors = values.map((x, i) => Number.isFinite(x) ? gradient(x, low[i] ?? 0, high[i] ?? 0, NEUTRAL, accent) : null);
|
|
42360
|
+
return washedToZero("hv", "Historical Volatility", values, colors, accent, 75, 2);
|
|
41422
42361
|
}
|
|
41423
42362
|
};
|
|
41424
42363
|
var chaikinVolatility = {
|
|
@@ -41426,13 +42365,26 @@ var chaikinVolatility = {
|
|
|
41426
42365
|
title: "Chaikin Volatility",
|
|
41427
42366
|
shortTitle: "CHV",
|
|
41428
42367
|
overlay: false,
|
|
41429
|
-
inputs: [
|
|
42368
|
+
inputs: [
|
|
42369
|
+
lengthInput(10, "emaLength", "EMA Length", 5e3, "Length of the exponential moving average applied to the bar high-low range."),
|
|
42370
|
+
lengthInput(10, "rocLength", "ROC Length", 5e3, "Number of bars used for the rate of change of the smoothed high-low range."),
|
|
42371
|
+
colorInput(NEUTRAL, "CHV Color", "color", "Color of the Chaikin Volatility line."),
|
|
42372
|
+
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.")
|
|
42373
|
+
],
|
|
41430
42374
|
compute: (bars, inputs) => {
|
|
41431
42375
|
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
41432
|
-
|
|
41433
|
-
|
|
41434
|
-
|
|
41435
|
-
|
|
42376
|
+
const prior = shift(range, num3(inputs, "rocLength", 10));
|
|
42377
|
+
const values = zip(range, prior, (r, p) => p === 0 ? Number.NaN : 100 * (r - p) / p);
|
|
42378
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
42379
|
+
const expansion = str3(inputs, "expansionColor", WARNING);
|
|
42380
|
+
return centeredOscillator({
|
|
42381
|
+
key: "chv",
|
|
42382
|
+
title: "Chaikin Volatility",
|
|
42383
|
+
values,
|
|
42384
|
+
// Only expansion is a state worth accenting; contraction stays structural grey.
|
|
42385
|
+
ink: { bull: expansion, bear: calm, neutral: calm },
|
|
42386
|
+
zeroInk: NEUTRAL
|
|
42387
|
+
});
|
|
41436
42388
|
}
|
|
41437
42389
|
};
|
|
41438
42390
|
var massIndex = {
|
|
@@ -41440,14 +42392,42 @@ var massIndex = {
|
|
|
41440
42392
|
title: "Mass Index",
|
|
41441
42393
|
shortTitle: "MI",
|
|
41442
42394
|
overlay: false,
|
|
41443
|
-
inputs: [
|
|
42395
|
+
inputs: [
|
|
42396
|
+
lengthInput(25, "length", "Sum Length", 5e3, "Number of bars over which the single/double EMA ratio is summed."),
|
|
42397
|
+
lengthInput(9, "emaLength", "EMA Length", 5e3, "Length of the single EMA of the high-low range and of its double smoothing."),
|
|
42398
|
+
floatInput("setupLevel", "Setup Level", 27, 0, 1e3, 0.1, "Reversal bulge set-up level; a rise above it arms the pattern."),
|
|
42399
|
+
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."),
|
|
42400
|
+
colorInput(NEUTRAL, "Mass Index", "color", "Line color while no reversal bulge is armed."),
|
|
42401
|
+
colorInput(WARNING, "Armed", "armedColor", "Line color from the rise above the setup level until the decline below the trigger level."),
|
|
42402
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Reversal bulge levels color.")
|
|
42403
|
+
],
|
|
41444
42404
|
compute: (bars, inputs) => {
|
|
41445
|
-
const
|
|
41446
|
-
const single = ema(
|
|
41447
|
-
const ratio = zip(single, ema(single,
|
|
42405
|
+
const emaLen = num3(inputs, "emaLength", 9);
|
|
42406
|
+
const single = ema(sub(highs(bars), lows(bars)), emaLen);
|
|
42407
|
+
const ratio = zip(single, ema(single, emaLen), (a, b) => b === 0 ? 1 : a / b);
|
|
42408
|
+
const values = sum(ratio, num3(inputs, "length", 25));
|
|
42409
|
+
const setup = num3(inputs, "setupLevel", 27);
|
|
42410
|
+
const trigger = num3(inputs, "triggerLevel", 26.5);
|
|
42411
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
42412
|
+
const armedInk = str3(inputs, "armedColor", WARNING);
|
|
42413
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
42414
|
+
let armed = false;
|
|
42415
|
+
const colors = values.map((x) => {
|
|
42416
|
+
if (!Number.isFinite(x)) return null;
|
|
42417
|
+
armed = x > setup ? true : x < trigger ? false : armed;
|
|
42418
|
+
return armed ? armedInk : calm;
|
|
42419
|
+
});
|
|
41448
42420
|
return {
|
|
41449
|
-
plots: [
|
|
41450
|
-
|
|
42421
|
+
plots: [
|
|
42422
|
+
{ key: "mi", title: "Mass Index", values, color: calm, colors },
|
|
42423
|
+
anchorPlot("setupAnchor", "Setup Anchor", values.length, setup),
|
|
42424
|
+
anchorPlot("triggerAnchor", "Trigger Anchor", values.length, trigger)
|
|
42425
|
+
],
|
|
42426
|
+
bands: [{ key: "bulge", from: "setupAnchor", to: "triggerAnchor", color: transp(armedInk, 90) }],
|
|
42427
|
+
levels: [
|
|
42428
|
+
{ key: "setup", price: setup, color: levelInk, lineStyle: "dashed", title: "Setup Level" },
|
|
42429
|
+
{ key: "trigger", price: trigger, color: levelInk, lineStyle: "dashed", title: "Trigger Level" }
|
|
42430
|
+
]
|
|
41451
42431
|
};
|
|
41452
42432
|
}
|
|
41453
42433
|
};
|
|
@@ -41456,30 +42436,83 @@ var standardDeviation = {
|
|
|
41456
42436
|
title: "Standard Deviation",
|
|
41457
42437
|
shortTitle: "StdDev",
|
|
41458
42438
|
overlay: false,
|
|
41459
|
-
inputs: [
|
|
41460
|
-
|
|
41461
|
-
|
|
41462
|
-
|
|
41463
|
-
|
|
41464
|
-
|
|
41465
|
-
|
|
41466
|
-
|
|
41467
|
-
|
|
41468
|
-
|
|
41469
|
-
|
|
41470
|
-
})
|
|
42439
|
+
inputs: [
|
|
42440
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used to compute the standard deviation."),
|
|
42441
|
+
sourceInput("Close", "source", "Source", "Price series the standard deviation is calculated on."),
|
|
42442
|
+
colorInput(NEUTRAL, "Standard Deviation", "color", "Line color while volatility contracts or holds flat."),
|
|
42443
|
+
colorInput(WARNING, "Expanding", "expandColor", "Line and fill color while volatility expands bar to bar.")
|
|
42444
|
+
],
|
|
42445
|
+
compute: (bars, inputs) => {
|
|
42446
|
+
const values = stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20));
|
|
42447
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
42448
|
+
return washedToZero("stdev", "Standard Deviation", values, expansionColors(values, calm, str3(inputs, "expandColor", WARNING)), calm, 80);
|
|
42449
|
+
}
|
|
41471
42450
|
};
|
|
41472
42451
|
var ulcerIndex = {
|
|
41473
42452
|
type: "ulcer-index",
|
|
41474
42453
|
title: "Ulcer Index",
|
|
41475
42454
|
overlay: false,
|
|
41476
|
-
inputs: [
|
|
42455
|
+
inputs: [
|
|
42456
|
+
{ ...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 },
|
|
42457
|
+
sourceInput("Close", "source", "Source", "Price series the Ulcer Index is computed on. Close is the standard choice."),
|
|
42458
|
+
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."),
|
|
42459
|
+
colorInput(NEUTRAL, "Ulcer Index", "color", "Color of the index near zero; it blends toward the stress color as the reading approaches the threshold."),
|
|
42460
|
+
colorInput(BEARISH, "Stress", "stressColor", "Color of the index at and beyond the alert threshold."),
|
|
42461
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Vertical gradient between zero and the Ulcer Index, fully transparent at zero."),
|
|
42462
|
+
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."),
|
|
42463
|
+
colorInput(transp(BEARISH, 50), "Drawdown Color", "drawdownColor", "Color of the drawdown columns."),
|
|
42464
|
+
colorInput(NEUTRAL, "Threshold", "thresholdColor", "Color of the alert threshold line.")
|
|
42465
|
+
],
|
|
41477
42466
|
compute: (bars, inputs) => {
|
|
41478
42467
|
const len = num3(inputs, "length", 14);
|
|
41479
|
-
const
|
|
41480
|
-
const
|
|
41481
|
-
const values =
|
|
41482
|
-
|
|
42468
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
42469
|
+
const n = src.length;
|
|
42470
|
+
const values = new Array(n).fill(Number.NaN);
|
|
42471
|
+
const drawdown = new Array(n).fill(Number.NaN);
|
|
42472
|
+
for (let i = len - 1; i < n; i++) {
|
|
42473
|
+
let runMax = Number.NaN;
|
|
42474
|
+
let sumSq = 0;
|
|
42475
|
+
let valid = true;
|
|
42476
|
+
let last = Number.NaN;
|
|
42477
|
+
for (let k = i - len + 1; k <= i; k++) {
|
|
42478
|
+
const price = src[k];
|
|
42479
|
+
if (!Number.isFinite(price)) {
|
|
42480
|
+
valid = false;
|
|
42481
|
+
break;
|
|
42482
|
+
}
|
|
42483
|
+
runMax = Number.isFinite(runMax) ? Math.max(runMax, price) : price;
|
|
42484
|
+
if (runMax <= 0) {
|
|
42485
|
+
valid = false;
|
|
42486
|
+
break;
|
|
42487
|
+
}
|
|
42488
|
+
last = 100 * (price - runMax) / runMax;
|
|
42489
|
+
sumSq += last * last;
|
|
42490
|
+
}
|
|
42491
|
+
if (!valid) continue;
|
|
42492
|
+
values[i] = Math.sqrt(sumSq / len);
|
|
42493
|
+
drawdown[i] = last;
|
|
42494
|
+
}
|
|
42495
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
42496
|
+
const stress = str3(inputs, "stressColor", BEARISH);
|
|
42497
|
+
const threshold = num3(inputs, "threshold", 5);
|
|
42498
|
+
const colors = values.map((x) => Number.isFinite(x) ? threshold > 0 ? gradient(x, 0, threshold, calm, stress) : stress : null);
|
|
42499
|
+
const plots = [{ key: "ui", title: "Ulcer Index", values, color: calm, colors }];
|
|
42500
|
+
const bands = [];
|
|
42501
|
+
if (bool(inputs, "gradientFill", true)) {
|
|
42502
|
+
plots.push(anchorPlot("zeroAnchor", "Zero Base", n, 0));
|
|
42503
|
+
bands.push({ key: "wash", from: "ui", to: "zeroAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, 0, (_x, i) => colors[i] ?? calm) });
|
|
42504
|
+
}
|
|
42505
|
+
if (bool(inputs, "showDrawdown", false)) {
|
|
42506
|
+
plots.push({ key: "dd", title: "Drawdown %", values: drawdown, kind: "columns", color: str3(inputs, "drawdownColor", transp(BEARISH, 50)), base: 0 });
|
|
42507
|
+
}
|
|
42508
|
+
return {
|
|
42509
|
+
plots,
|
|
42510
|
+
bands,
|
|
42511
|
+
levels: [
|
|
42512
|
+
{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dotted", title: "Zero Level" },
|
|
42513
|
+
{ key: "threshold", price: threshold, color: str3(inputs, "thresholdColor", NEUTRAL), lineStyle: "dashed", title: "Alert Threshold" }
|
|
42514
|
+
]
|
|
42515
|
+
};
|
|
41483
42516
|
}
|
|
41484
42517
|
};
|
|
41485
42518
|
var choppiness = {
|
|
@@ -41487,19 +42520,33 @@ var choppiness = {
|
|
|
41487
42520
|
title: "Choppiness Index",
|
|
41488
42521
|
shortTitle: "CHOP",
|
|
41489
42522
|
overlay: false,
|
|
41490
|
-
inputs: [
|
|
42523
|
+
inputs: [
|
|
42524
|
+
{ ...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 },
|
|
42525
|
+
floatInput("upperBand", "Upper Band", 61.8, 0, 100, 0.1, "Readings above this level indicate a choppy, range-bound market."),
|
|
42526
|
+
floatInput("lowerBand", "Lower Band", 38.2, 0, 100, 0.1, "Readings below this level indicate a strong trend."),
|
|
42527
|
+
colorInput(NEUTRAL, "CHOP Line", "color", "Color of the Choppiness Index line."),
|
|
42528
|
+
colorInput(BULLISH, "Trending", "trendingColor", "Line, fill and zone color toward and below the lower band \u2014 a trending market."),
|
|
42529
|
+
colorInput(BEARISH, "Choppy", "choppyColor", "Line, fill and zone color toward and above the upper band \u2014 a choppy, range-bound market."),
|
|
42530
|
+
colorInput(transp(NEUTRAL, 90), "Band Fill", "fillColor", "Fill color of the zone between the upper and lower bands.")
|
|
42531
|
+
],
|
|
41491
42532
|
compute: (bars, inputs) => {
|
|
41492
42533
|
const len = num3(inputs, "length", 14);
|
|
41493
42534
|
const trSum = sum(trueRange(bars), len);
|
|
41494
42535
|
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
41495
42536
|
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
41496
|
-
return {
|
|
41497
|
-
|
|
41498
|
-
|
|
41499
|
-
|
|
41500
|
-
|
|
41501
|
-
|
|
41502
|
-
|
|
42537
|
+
return boundedOscillator({
|
|
42538
|
+
key: "chop",
|
|
42539
|
+
title: "CHOP",
|
|
42540
|
+
values,
|
|
42541
|
+
scale: { top: 100, bottom: 0 },
|
|
42542
|
+
overbought: num3(inputs, "upperBand", 61.8),
|
|
42543
|
+
oversold: num3(inputs, "lowerBand", 38.2),
|
|
42544
|
+
ink: { low: str3(inputs, "trendingColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "choppyColor", BEARISH) },
|
|
42545
|
+
levelInk: NEUTRAL,
|
|
42546
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 90)),
|
|
42547
|
+
midpointWash: true,
|
|
42548
|
+
levelTitles: { overbought: "Upper Band", oversold: "Lower Band" }
|
|
42549
|
+
});
|
|
41503
42550
|
}
|
|
41504
42551
|
};
|
|
41505
42552
|
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
@@ -41512,17 +42559,85 @@ function moneyFlowVolume(bars) {
|
|
|
41512
42559
|
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
41513
42560
|
});
|
|
41514
42561
|
}
|
|
42562
|
+
function slopeColors2(values, bull, bear, flat) {
|
|
42563
|
+
return values.map((x, i) => {
|
|
42564
|
+
if (!Number.isFinite(x)) return null;
|
|
42565
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
42566
|
+
if (!Number.isFinite(prev2)) return flat;
|
|
42567
|
+
return x > prev2 ? bull : x < prev2 ? bear : flat;
|
|
42568
|
+
});
|
|
42569
|
+
}
|
|
42570
|
+
function volumeIndex(bars, src, base, when) {
|
|
42571
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
42572
|
+
let index = base;
|
|
42573
|
+
for (let i = 0; i < bars.length; i++) {
|
|
42574
|
+
const v = bars[i].volume;
|
|
42575
|
+
const pv = i > 0 ? bars[i - 1].volume : null;
|
|
42576
|
+
const prev2 = i > 0 ? src[i - 1] : Number.NaN;
|
|
42577
|
+
const active = v != null && pv != null && Number.isFinite(v) && Number.isFinite(pv) && when(v, pv);
|
|
42578
|
+
if (active && Number.isFinite(prev2) && prev2 !== 0) index *= 1 + (src[i] - prev2) / prev2;
|
|
42579
|
+
out[i] = index;
|
|
42580
|
+
}
|
|
42581
|
+
return out;
|
|
42582
|
+
}
|
|
42583
|
+
function regimeIndex(key, title, values, signal, ink, showSignal, gradientFill, singleColor = false) {
|
|
42584
|
+
const inkOf = (i) => {
|
|
42585
|
+
const s = signal[i];
|
|
42586
|
+
const x = values[i];
|
|
42587
|
+
if (singleColor || !Number.isFinite(s)) return ink.neutral;
|
|
42588
|
+
return x > s ? ink.bull : x < s ? ink.bear : ink.neutral;
|
|
42589
|
+
};
|
|
42590
|
+
const plots = [
|
|
42591
|
+
{
|
|
42592
|
+
key: "signal",
|
|
42593
|
+
title: "Signal EMA",
|
|
42594
|
+
values: signal,
|
|
42595
|
+
color: ink.signal,
|
|
42596
|
+
...showSignal ? {} : { display: { pane: false } }
|
|
42597
|
+
},
|
|
42598
|
+
{ key, title, values, color: ink.neutral, width: 2, colors: values.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) }
|
|
42599
|
+
];
|
|
42600
|
+
const bands = gradientFill ? [
|
|
42601
|
+
{
|
|
42602
|
+
key: "regime",
|
|
42603
|
+
from: key,
|
|
42604
|
+
to: "signal",
|
|
42605
|
+
color: transp(NEUTRAL, 100),
|
|
42606
|
+
gradient: values.map((x, i) => {
|
|
42607
|
+
const s = signal[i];
|
|
42608
|
+
if (!Number.isFinite(x) || !Number.isFinite(s)) return null;
|
|
42609
|
+
const above = x > s;
|
|
42610
|
+
const c = above ? ink.bull : ink.bear;
|
|
42611
|
+
return { topValue: Math.max(x, s), bottomValue: Math.min(x, s), topColor: transp(c, above ? 50 : 100), bottomColor: transp(c, above ? 100 : 50) };
|
|
42612
|
+
})
|
|
42613
|
+
}
|
|
42614
|
+
] : [];
|
|
42615
|
+
return { plots, bands };
|
|
42616
|
+
}
|
|
41515
42617
|
var obv = {
|
|
41516
42618
|
type: "on-balance-volume",
|
|
41517
42619
|
title: "On Balance Volume",
|
|
41518
42620
|
shortTitle: "OBV",
|
|
41519
42621
|
overlay: false,
|
|
41520
|
-
inputs: [
|
|
42622
|
+
inputs: [
|
|
42623
|
+
boolInput("smoothing", "Smoothing MA", false, SETTINGS, "Display a moving average of OBV to act as a smoothing/signal line."),
|
|
42624
|
+
optionInput("maType", "MA Type", "SMA", ["SMA", "EMA", "WMA", "RMA (SMMA)"], "Moving average type used for the smoothing MA."),
|
|
42625
|
+
lengthInput(20, "maLength", "MA Length", 5e3, "Number of bars used for the smoothing MA."),
|
|
42626
|
+
colorInput(INFO, "OBV", "color", "Color of the OBV line."),
|
|
42627
|
+
colorInput(WARNING, "Smoothing MA", "maColor", "Color of the smoothing MA line.")
|
|
42628
|
+
],
|
|
41521
42629
|
compute: (bars, inputs) => {
|
|
41522
42630
|
const vol = volumes(bars);
|
|
41523
42631
|
const d = change(closes(bars));
|
|
41524
|
-
const
|
|
41525
|
-
|
|
42632
|
+
const values = cumSum(zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0));
|
|
42633
|
+
const plots = [{ key: "obv", title: "OBV", values, color: str3(inputs, "color", INFO) }];
|
|
42634
|
+
if (bool(inputs, "smoothing", false)) {
|
|
42635
|
+
const len = num3(inputs, "maLength", 20);
|
|
42636
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
42637
|
+
const ma = kind === "EMA" ? ema(values, len) : kind === "WMA" ? wma(values, len) : kind === "RMA (SMMA)" ? rma(values, len) : sma(values, len);
|
|
42638
|
+
plots.push({ key: "ma", title: "Smoothing MA", values: ma, color: str3(inputs, "maColor", WARNING) });
|
|
42639
|
+
}
|
|
42640
|
+
return { plots };
|
|
41526
42641
|
}
|
|
41527
42642
|
};
|
|
41528
42643
|
var accDist = {
|
|
@@ -41530,24 +42645,50 @@ var accDist = {
|
|
|
41530
42645
|
title: "Accumulation / Distribution",
|
|
41531
42646
|
shortTitle: "A/D",
|
|
41532
42647
|
overlay: false,
|
|
41533
|
-
inputs: [
|
|
41534
|
-
|
|
41535
|
-
|
|
41536
|
-
|
|
42648
|
+
inputs: [
|
|
42649
|
+
boolInput("slopeColoring", "Slope Coloring", true, STYLE, "Color the line by its slope \u2014 bullish while rising (accumulation), bearish while falling (distribution)."),
|
|
42650
|
+
colorInput(NEUTRAL, "A/D Line", "color", "Color of the line when slope coloring is disabled or the line is flat."),
|
|
42651
|
+
colorInput(BULLISH, "Rising", "bullColor", "Line color while the A/D Line rises."),
|
|
42652
|
+
colorInput(BEARISH, "Falling", "bearColor", "Line color while the A/D Line falls.")
|
|
42653
|
+
],
|
|
42654
|
+
compute: (bars, inputs) => {
|
|
42655
|
+
const values = cumSum(moneyFlowVolume(bars));
|
|
42656
|
+
const flat = str3(inputs, "color", NEUTRAL);
|
|
42657
|
+
return {
|
|
42658
|
+
plots: [
|
|
42659
|
+
{
|
|
42660
|
+
key: "ad",
|
|
42661
|
+
title: "A/D",
|
|
42662
|
+
values,
|
|
42663
|
+
color: flat,
|
|
42664
|
+
...bool(inputs, "slopeColoring", true) ? { colors: slopeColors2(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), flat) } : {}
|
|
42665
|
+
}
|
|
42666
|
+
]
|
|
42667
|
+
};
|
|
42668
|
+
}
|
|
41537
42669
|
};
|
|
41538
42670
|
var cmf = {
|
|
41539
42671
|
type: "chaikin-money-flow",
|
|
41540
42672
|
title: "Chaikin Money Flow",
|
|
41541
42673
|
shortTitle: "CMF",
|
|
41542
42674
|
overlay: false,
|
|
41543
|
-
inputs: [
|
|
42675
|
+
inputs: [
|
|
42676
|
+
lengthInput(20, "length", "Length", 5e3, "Lookback length over which money flow volume and volume are summed."),
|
|
42677
|
+
colorInput(NEUTRAL, "CMF Line", "color", "Color of the Chaikin Money Flow line."),
|
|
42678
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the CMF reads above zero (net money flowing in)."),
|
|
42679
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the CMF reads below zero (net money flowing out)."),
|
|
42680
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
42681
|
+
],
|
|
41544
42682
|
compute: (bars, inputs) => {
|
|
41545
42683
|
const len = num3(inputs, "length", 20);
|
|
41546
42684
|
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
41547
|
-
return {
|
|
41548
|
-
|
|
41549
|
-
|
|
41550
|
-
|
|
42685
|
+
return centeredOscillator({
|
|
42686
|
+
key: "cmf",
|
|
42687
|
+
title: "CMF",
|
|
42688
|
+
values,
|
|
42689
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
42690
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
42691
|
+
});
|
|
41551
42692
|
}
|
|
41552
42693
|
};
|
|
41553
42694
|
var chaikinOscillator = {
|
|
@@ -41555,14 +42696,23 @@ var chaikinOscillator = {
|
|
|
41555
42696
|
title: "Chaikin Oscillator",
|
|
41556
42697
|
shortTitle: "Chaikin Osc",
|
|
41557
42698
|
overlay: false,
|
|
41558
|
-
inputs: [
|
|
42699
|
+
inputs: [
|
|
42700
|
+
lengthInput(3, "fastLength", "Fast Length", 5e3, "Length of the fast EMA applied to the accumulation/distribution line."),
|
|
42701
|
+
lengthInput(10, "slowLength", "Slow Length", 5e3, "Length of the slow EMA applied to the accumulation/distribution line."),
|
|
42702
|
+
colorInput(NEUTRAL, "Oscillator", "color", "Color of the Chaikin Oscillator line."),
|
|
42703
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the oscillator reads above zero (accumulation)."),
|
|
42704
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the oscillator reads below zero (distribution).")
|
|
42705
|
+
],
|
|
41559
42706
|
compute: (bars, inputs) => {
|
|
41560
42707
|
const ad = cumSum(moneyFlowVolume(bars));
|
|
41561
42708
|
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
41562
|
-
return {
|
|
41563
|
-
|
|
41564
|
-
|
|
41565
|
-
|
|
42709
|
+
return centeredOscillator({
|
|
42710
|
+
key: "co",
|
|
42711
|
+
title: "Chaikin Oscillator",
|
|
42712
|
+
values,
|
|
42713
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
42714
|
+
zeroInk: NEUTRAL
|
|
42715
|
+
});
|
|
41566
42716
|
}
|
|
41567
42717
|
};
|
|
41568
42718
|
var eom = {
|
|
@@ -41570,20 +42720,28 @@ var eom = {
|
|
|
41570
42720
|
title: "Ease of Movement",
|
|
41571
42721
|
shortTitle: "EOM",
|
|
41572
42722
|
overlay: false,
|
|
41573
|
-
inputs: [
|
|
42723
|
+
inputs: [
|
|
42724
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars in the simple moving average smoothing the 1-bar Ease of Movement values."),
|
|
42725
|
+
intInput("divisor", "Divisor", 1e4, 1, 1e9, "Volume scaling constant of the box ratio; larger values scale the oscillator up. 10000 is the conventional default."),
|
|
42726
|
+
colorInput(NEUTRAL, "EOM Line", "color", "Color of the line when it sits exactly at zero."),
|
|
42727
|
+
colorInput(BULLISH, "Above Zero", "bullColor", "Color of the line and fill while Ease of Movement is positive."),
|
|
42728
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while Ease of Movement is negative.")
|
|
42729
|
+
],
|
|
41574
42730
|
compute: (bars, inputs) => {
|
|
41575
42731
|
const div = num3(inputs, "divisor", 1e4);
|
|
41576
|
-
const
|
|
41577
|
-
const move = change(hl2);
|
|
42732
|
+
const move = change(sourceValues(bars, "HL2"));
|
|
41578
42733
|
const raw = bars.map((b, i) => {
|
|
41579
42734
|
const m = move[i];
|
|
41580
|
-
if (b.volume == null || !Number.isFinite(b.volume) ||
|
|
41581
|
-
return div * m * (b.high - b.low) / b.volume;
|
|
42735
|
+
if (b.volume == null || !Number.isFinite(b.volume) || !Number.isFinite(m)) return Number.NaN;
|
|
42736
|
+
return b.volume === 0 ? 0 : div * m * (b.high - b.low) / b.volume;
|
|
42737
|
+
});
|
|
42738
|
+
return centeredOscillator({
|
|
42739
|
+
key: "eom",
|
|
42740
|
+
title: "EOM",
|
|
42741
|
+
values: sma(raw, num3(inputs, "length", 14)),
|
|
42742
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
42743
|
+
zeroInk: NEUTRAL
|
|
41582
42744
|
});
|
|
41583
|
-
return {
|
|
41584
|
-
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
41585
|
-
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
41586
|
-
};
|
|
41587
42745
|
}
|
|
41588
42746
|
};
|
|
41589
42747
|
var forceIndex = {
|
|
@@ -41591,13 +42749,24 @@ var forceIndex = {
|
|
|
41591
42749
|
title: "Force Index",
|
|
41592
42750
|
shortTitle: "FI",
|
|
41593
42751
|
overlay: false,
|
|
41594
|
-
inputs: [
|
|
42752
|
+
inputs: [
|
|
42753
|
+
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."),
|
|
42754
|
+
colorInput(BULLISH, "Force Index", "color", "Color of the line while above zero (buying pressure)."),
|
|
42755
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line while below zero (selling pressure).")
|
|
42756
|
+
],
|
|
41595
42757
|
compute: (bars, inputs) => {
|
|
41596
42758
|
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
41597
|
-
|
|
41598
|
-
|
|
41599
|
-
|
|
41600
|
-
|
|
42759
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
42760
|
+
return centeredOscillator({
|
|
42761
|
+
key: "force",
|
|
42762
|
+
title: "Force Index",
|
|
42763
|
+
values: ema(raw, num3(inputs, "length", 13)),
|
|
42764
|
+
// The reference reads an exact zero as buying pressure, so neutral shares the bull ink.
|
|
42765
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
42766
|
+
zeroInk: NEUTRAL,
|
|
42767
|
+
width: 2,
|
|
42768
|
+
valueTransparency: 60
|
|
42769
|
+
});
|
|
41601
42770
|
}
|
|
41602
42771
|
};
|
|
41603
42772
|
var klinger = {
|
|
@@ -41605,19 +42774,44 @@ var klinger = {
|
|
|
41605
42774
|
title: "Klinger Oscillator",
|
|
41606
42775
|
shortTitle: "KVO",
|
|
41607
42776
|
overlay: false,
|
|
41608
|
-
inputs: [
|
|
42777
|
+
inputs: [
|
|
42778
|
+
lengthInput(34, "fastLength", "Fast Length", 5e3, "Length of the fast EMA applied to the volume force."),
|
|
42779
|
+
lengthInput(55, "slowLength", "Slow Length", 5e3, "Length of the slow EMA applied to the volume force."),
|
|
42780
|
+
lengthInput(13, "signalLength", "Signal Length", 500, "Length of the EMA applied to the oscillator to obtain the signal line."),
|
|
42781
|
+
colorInput(BULLISH, "Bullish", "color", "Color of the Klinger oscillator while above zero."),
|
|
42782
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the Klinger oscillator while below zero."),
|
|
42783
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line.")
|
|
42784
|
+
],
|
|
41609
42785
|
compute: (bars, inputs) => {
|
|
41610
|
-
const
|
|
41611
|
-
const
|
|
41612
|
-
|
|
41613
|
-
|
|
41614
|
-
|
|
41615
|
-
|
|
41616
|
-
|
|
41617
|
-
|
|
41618
|
-
|
|
41619
|
-
|
|
41620
|
-
|
|
42786
|
+
const n = bars.length;
|
|
42787
|
+
const force = new Array(n).fill(Number.NaN);
|
|
42788
|
+
let trend = 1;
|
|
42789
|
+
let prevTrend = 1;
|
|
42790
|
+
let cm = 0;
|
|
42791
|
+
let prevDm = 0;
|
|
42792
|
+
for (let i = 0; i < n; i++) {
|
|
42793
|
+
const b = bars[i];
|
|
42794
|
+
const hlc = b.high + b.low + b.close;
|
|
42795
|
+
const prevHlc = i > 0 ? bars[i - 1].high + bars[i - 1].low + bars[i - 1].close : hlc;
|
|
42796
|
+
trend = hlc > prevHlc ? 1 : -1;
|
|
42797
|
+
const dm = b.high - b.low;
|
|
42798
|
+
cm = i > 0 && trend === prevTrend ? cm + dm : prevDm + dm;
|
|
42799
|
+
const v = b.volume;
|
|
42800
|
+
force[i] = v == null || !Number.isFinite(v) ? Number.NaN : cm === 0 ? 0 : v * Math.abs(2 * (dm / cm) - 1) * trend * 100;
|
|
42801
|
+
prevTrend = trend;
|
|
42802
|
+
prevDm = dm;
|
|
42803
|
+
}
|
|
42804
|
+
const line = sub(ema(force, num3(inputs, "fastLength", 34)), ema(force, num3(inputs, "slowLength", 55)));
|
|
42805
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
42806
|
+
return centeredOscillator({
|
|
42807
|
+
key: "kvo",
|
|
42808
|
+
title: "KVO",
|
|
42809
|
+
values: line,
|
|
42810
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
42811
|
+
zeroInk: transp(NEUTRAL, 50),
|
|
42812
|
+
valueTransparency: 60,
|
|
42813
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: str3(inputs, "signalColor", WARNING) }]
|
|
42814
|
+
});
|
|
41621
42815
|
}
|
|
41622
42816
|
};
|
|
41623
42817
|
var mfi = {
|
|
@@ -41625,7 +42819,15 @@ var mfi = {
|
|
|
41625
42819
|
title: "Money Flow Index",
|
|
41626
42820
|
shortTitle: "MFI",
|
|
41627
42821
|
overlay: false,
|
|
41628
|
-
inputs: [
|
|
42822
|
+
inputs: [
|
|
42823
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to sum positive and negative money flow."),
|
|
42824
|
+
floatInput("overbought", "Overbought Level", 80, 0, 100, 1, "Level above which the MFI is considered overbought."),
|
|
42825
|
+
floatInput("oversold", "Oversold Level", 20, 0, 100, 1, "Level below which the MFI is considered oversold."),
|
|
42826
|
+
colorInput(NEUTRAL, "MFI", "color", "Color of the MFI line between the levels. Toward either level it blends into that level\u2019s color."),
|
|
42827
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
42828
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level."),
|
|
42829
|
+
colorInput(transp(NEUTRAL, 94), "OB/OS Zone Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
42830
|
+
],
|
|
41629
42831
|
compute: (bars, inputs) => {
|
|
41630
42832
|
const len = num3(inputs, "length", 14);
|
|
41631
42833
|
const tp = sourceValues(bars, "HLC3");
|
|
@@ -41633,48 +42835,57 @@ var mfi = {
|
|
|
41633
42835
|
const d = change(tp);
|
|
41634
42836
|
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
41635
42837
|
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
41636
|
-
const values = zip(pos, neg, (p,
|
|
41637
|
-
return {
|
|
41638
|
-
|
|
41639
|
-
|
|
41640
|
-
|
|
41641
|
-
|
|
41642
|
-
|
|
41643
|
-
|
|
42838
|
+
const values = zip(pos, neg, (p, w) => p + w > 0 ? 100 * p / (p + w) : Number.NaN);
|
|
42839
|
+
return boundedOscillator({
|
|
42840
|
+
key: "mfi",
|
|
42841
|
+
title: "MFI",
|
|
42842
|
+
values,
|
|
42843
|
+
scale: { top: 100, bottom: 0 },
|
|
42844
|
+
overbought: num3(inputs, "overbought", 80),
|
|
42845
|
+
oversold: num3(inputs, "oversold", 20),
|
|
42846
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
42847
|
+
levelInk: NEUTRAL,
|
|
42848
|
+
zoneTransparency: 91,
|
|
42849
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 94))
|
|
42850
|
+
});
|
|
41644
42851
|
}
|
|
41645
42852
|
};
|
|
41646
|
-
function
|
|
41647
|
-
|
|
41648
|
-
|
|
41649
|
-
|
|
41650
|
-
|
|
41651
|
-
|
|
41652
|
-
|
|
41653
|
-
|
|
41654
|
-
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
41655
|
-
if (started) out[i] = index;
|
|
41656
|
-
continue;
|
|
41657
|
-
}
|
|
41658
|
-
started = true;
|
|
41659
|
-
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
41660
|
-
out[i] = index;
|
|
41661
|
-
}
|
|
41662
|
-
return out;
|
|
42853
|
+
function regimeStyleInputs(signalTitle) {
|
|
42854
|
+
return [
|
|
42855
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the line and gradient fill while it holds above its signal EMA."),
|
|
42856
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line and gradient fill while it holds below its signal EMA."),
|
|
42857
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor", "Color used while the signal EMA is still forming, or the line sits exactly on it."),
|
|
42858
|
+
colorInput(NEUTRAL, signalTitle, "signalColor", "Color of the signal EMA overlay."),
|
|
42859
|
+
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.")
|
|
42860
|
+
];
|
|
41663
42861
|
}
|
|
42862
|
+
var NVI_POSITION = "Position vs Average";
|
|
42863
|
+
var NVI_SINGLE = "Single Color";
|
|
41664
42864
|
var nvi = {
|
|
41665
42865
|
type: "negative-volume-index",
|
|
41666
42866
|
title: "Negative Volume Index",
|
|
41667
42867
|
shortTitle: "Negative Volume Index",
|
|
41668
42868
|
overlay: false,
|
|
41669
|
-
inputs: [
|
|
42869
|
+
inputs: [
|
|
42870
|
+
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."),
|
|
42871
|
+
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."),
|
|
42872
|
+
lengthInput(255, "signalLength", "Signal Length", 5e3, "Length of the signal EMA. The convention is one trading year \u2014 255 sessions on daily charts."),
|
|
42873
|
+
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."),
|
|
42874
|
+
...regimeStyleInputs("Signal EMA")
|
|
42875
|
+
],
|
|
41670
42876
|
compute: (bars, inputs) => {
|
|
41671
|
-
const
|
|
41672
|
-
|
|
41673
|
-
|
|
41674
|
-
|
|
41675
|
-
|
|
41676
|
-
|
|
41677
|
-
|
|
42877
|
+
const values = volumeIndex(bars, closes(bars), num3(inputs, "base", 1e3), (v, pv) => v < pv);
|
|
42878
|
+
const signal = ema(values, num3(inputs, "signalLength", 255));
|
|
42879
|
+
return regimeIndex(
|
|
42880
|
+
"nvi",
|
|
42881
|
+
"NVI",
|
|
42882
|
+
values,
|
|
42883
|
+
signal,
|
|
42884
|
+
{ bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "neutralColor", NEUTRAL), signal: str3(inputs, "signalColor", NEUTRAL) },
|
|
42885
|
+
bool(inputs, "showSignal", true),
|
|
42886
|
+
bool(inputs, "gradientFill", true),
|
|
42887
|
+
str3(inputs, "colorMode", NVI_POSITION) === NVI_SINGLE
|
|
42888
|
+
);
|
|
41678
42889
|
}
|
|
41679
42890
|
};
|
|
41680
42891
|
var pvi = {
|
|
@@ -41682,15 +42893,26 @@ var pvi = {
|
|
|
41682
42893
|
title: "Positive Volume Index",
|
|
41683
42894
|
shortTitle: "Positive Volume Index",
|
|
41684
42895
|
overlay: false,
|
|
41685
|
-
inputs: [
|
|
42896
|
+
inputs: [
|
|
42897
|
+
sourceInput("Close", "source", "Source", "Price series whose one-bar percentage change moves PVI on its active sessions."),
|
|
42898
|
+
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."),
|
|
42899
|
+
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."),
|
|
42900
|
+
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."),
|
|
42901
|
+
...regimeStyleInputs("Signal Line")
|
|
42902
|
+
],
|
|
41686
42903
|
compute: (bars, inputs) => {
|
|
41687
|
-
const
|
|
41688
|
-
|
|
41689
|
-
|
|
41690
|
-
|
|
41691
|
-
|
|
41692
|
-
|
|
41693
|
-
|
|
42904
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
42905
|
+
const values = volumeIndex(bars, src, num3(inputs, "base", 1e3), (v, pv) => v > pv);
|
|
42906
|
+
const signal = ema(values, num3(inputs, "signalLength", 255));
|
|
42907
|
+
return regimeIndex(
|
|
42908
|
+
"pvi",
|
|
42909
|
+
"PVI",
|
|
42910
|
+
values,
|
|
42911
|
+
signal,
|
|
42912
|
+
{ bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "neutralColor", NEUTRAL), signal: str3(inputs, "signalColor", NEUTRAL) },
|
|
42913
|
+
bool(inputs, "showSignal", true),
|
|
42914
|
+
bool(inputs, "gradientFill", true)
|
|
42915
|
+
);
|
|
41694
42916
|
}
|
|
41695
42917
|
};
|
|
41696
42918
|
var pvt = {
|
|
@@ -41698,7 +42920,15 @@ var pvt = {
|
|
|
41698
42920
|
title: "Price Volume Trend",
|
|
41699
42921
|
shortTitle: "PVT",
|
|
41700
42922
|
overlay: false,
|
|
41701
|
-
inputs: [
|
|
42923
|
+
inputs: [
|
|
42924
|
+
boolInput("showSignal", "Signal Line", false, SETTINGS, "Display a moving average of PVT acting as a signal line."),
|
|
42925
|
+
optionInput("signalType", "Signal Type", "SMA", ["SMA", "EMA"], "Moving average type used for the signal line."),
|
|
42926
|
+
lengthInput(21, "signalLength", "Signal Length", 5e3, "Number of bars used to compute the signal line."),
|
|
42927
|
+
colorInput(NEUTRAL, "PVT Color", "color", "Color of the PVT line while it is flat."),
|
|
42928
|
+
colorInput(BULLISH, "Rising", "bullColor", "Color of the PVT line while it rises bar to bar."),
|
|
42929
|
+
colorInput(BEARISH, "Falling", "bearColor", "Color of the PVT line while it falls bar to bar."),
|
|
42930
|
+
colorInput(WARNING, "Signal Color", "signalColor", "Color of the signal line.")
|
|
42931
|
+
],
|
|
41702
42932
|
compute: (bars, inputs) => {
|
|
41703
42933
|
const c = closes(bars);
|
|
41704
42934
|
const raw = bars.map((b, i) => {
|
|
@@ -41706,7 +42936,17 @@ var pvt = {
|
|
|
41706
42936
|
const pc = c[i - 1];
|
|
41707
42937
|
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
41708
42938
|
});
|
|
41709
|
-
|
|
42939
|
+
const values = cumSum(raw);
|
|
42940
|
+
const flat = str3(inputs, "color", NEUTRAL);
|
|
42941
|
+
const plots = [
|
|
42942
|
+
{ key: "pvt", title: "PVT", values, color: flat, width: 2, colors: slopeColors2(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), flat) }
|
|
42943
|
+
];
|
|
42944
|
+
if (bool(inputs, "showSignal", false)) {
|
|
42945
|
+
const len = num3(inputs, "signalLength", 21);
|
|
42946
|
+
const signal = str3(inputs, "signalType", "SMA") === "EMA" ? ema(values, len) : sma(values, len);
|
|
42947
|
+
plots.push({ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) });
|
|
42948
|
+
}
|
|
42949
|
+
return { plots };
|
|
41710
42950
|
}
|
|
41711
42951
|
};
|
|
41712
42952
|
var volumeOscillator = {
|
|
@@ -41714,39 +42954,75 @@ var volumeOscillator = {
|
|
|
41714
42954
|
title: "Volume Oscillator",
|
|
41715
42955
|
shortTitle: "Vol Osc",
|
|
41716
42956
|
overlay: false,
|
|
41717
|
-
inputs: [
|
|
42957
|
+
inputs: [
|
|
42958
|
+
lengthInput(5, "fastLength", "Fast Length", 5e3, "Length of the fast EMA of volume."),
|
|
42959
|
+
lengthInput(10, "slowLength", "Slow Length", 5e3, "Length of the slow EMA of volume."),
|
|
42960
|
+
colorInput(BULLISH, "Above Zero", "color", "Color of the line and fill while it holds above zero \u2014 volume expanding."),
|
|
42961
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while it holds below zero \u2014 volume contracting.")
|
|
42962
|
+
],
|
|
41718
42963
|
compute: (bars, inputs) => {
|
|
41719
42964
|
const vol = volumes(bars);
|
|
41720
42965
|
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
41721
42966
|
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
41722
|
-
const values = zip(fast, slow, (f, s) => s === 0 ?
|
|
41723
|
-
|
|
41724
|
-
|
|
41725
|
-
|
|
41726
|
-
|
|
42967
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? 0 : 100 * (f - s) / s);
|
|
42968
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
42969
|
+
return centeredOscillator({
|
|
42970
|
+
key: "vo",
|
|
42971
|
+
title: "Volume Oscillator",
|
|
42972
|
+
values,
|
|
42973
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
42974
|
+
zeroInk: NEUTRAL,
|
|
42975
|
+
valueTransparency: 60
|
|
42976
|
+
});
|
|
41727
42977
|
}
|
|
41728
42978
|
};
|
|
42979
|
+
var OSCILLATOR = "Oscillator";
|
|
41729
42980
|
var pvo = {
|
|
41730
42981
|
type: "pvo",
|
|
41731
42982
|
title: "Percentage Volume Oscillator",
|
|
41732
42983
|
shortTitle: "PVO",
|
|
41733
42984
|
overlay: false,
|
|
41734
|
-
inputs: [
|
|
42985
|
+
inputs: [
|
|
42986
|
+
{ ...lengthInput(12, "fastLength", "Fast Length", 5e3, "Period of the fast EMA of volume. The conventional setting is 12."), group: OSCILLATOR },
|
|
42987
|
+
{ ...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 },
|
|
42988
|
+
{ ...lengthInput(9, "signalLength", "Signal Length", 500, "Period of the EMA applied to the PVO to form the signal line."), group: OSCILLATOR },
|
|
42989
|
+
colorInput(BULLISH, "Expansion Color", "color", "Color of the oscillator and fill while participation expands."),
|
|
42990
|
+
colorInput(BEARISH, "Contraction Color", "bearColor", "Color of the oscillator and fill while participation contracts."),
|
|
42991
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
42992
|
+
colorInput(transp(NEUTRAL, 50), "Zero Line", "zeroColor", "Color of the zero reference line."),
|
|
42993
|
+
boolInput("showSignal", "Show Signal Line", true, STYLE, "Plot the signal line, an EMA of the PVO."),
|
|
42994
|
+
boolInput("showHistogram", "Show Histogram", true, STYLE, "Plot the difference between the PVO and its signal line as columns behind the oscillator.")
|
|
42995
|
+
],
|
|
41735
42996
|
compute: (bars, inputs) => {
|
|
41736
42997
|
const vol = volumes(bars);
|
|
41737
42998
|
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
41738
42999
|
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
41739
|
-
const
|
|
41740
|
-
const signal = ema(
|
|
41741
|
-
const
|
|
41742
|
-
|
|
41743
|
-
|
|
41744
|
-
|
|
41745
|
-
|
|
41746
|
-
|
|
41747
|
-
|
|
41748
|
-
|
|
41749
|
-
|
|
43000
|
+
const values = zip(fast, slow, (f, s) => s > 0 ? 100 * (f - s) / s : Number.NaN);
|
|
43001
|
+
const signal = ema(values, num3(inputs, "signalLength", 9));
|
|
43002
|
+
const expansion = str3(inputs, "color", BULLISH);
|
|
43003
|
+
const contraction = str3(inputs, "bearColor", BEARISH);
|
|
43004
|
+
const extraPlots = [];
|
|
43005
|
+
if (bool(inputs, "showHistogram", true)) {
|
|
43006
|
+
const hist = sub(values, signal);
|
|
43007
|
+
extraPlots.push({
|
|
43008
|
+
key: "hist",
|
|
43009
|
+
title: "Histogram",
|
|
43010
|
+
values: hist,
|
|
43011
|
+
kind: "columns",
|
|
43012
|
+
color: transp(expansion, 60),
|
|
43013
|
+
colors: hist.map((x) => Number.isFinite(x) ? transp(x > 0 ? expansion : contraction, 60) : null),
|
|
43014
|
+
base: 0
|
|
43015
|
+
});
|
|
43016
|
+
}
|
|
43017
|
+
if (bool(inputs, "showSignal", true)) extraPlots.push({ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) });
|
|
43018
|
+
return centeredOscillator({
|
|
43019
|
+
key: "pvo",
|
|
43020
|
+
title: "PVO",
|
|
43021
|
+
values,
|
|
43022
|
+
ink: { bull: expansion, bear: contraction, neutral: contraction },
|
|
43023
|
+
zeroInk: str3(inputs, "zeroColor", transp(NEUTRAL, 50)),
|
|
43024
|
+
extraPlots
|
|
43025
|
+
});
|
|
41750
43026
|
}
|
|
41751
43027
|
};
|
|
41752
43028
|
var vfi = {
|
|
@@ -41755,55 +43031,86 @@ var vfi = {
|
|
|
41755
43031
|
shortTitle: "Volume Flow Indicator",
|
|
41756
43032
|
overlay: false,
|
|
41757
43033
|
inputs: [
|
|
41758
|
-
lengthInput(130),
|
|
41759
|
-
|
|
41760
|
-
|
|
41761
|
-
lengthInput(
|
|
43034
|
+
{ ...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 },
|
|
43035
|
+
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."),
|
|
43036
|
+
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."),
|
|
43037
|
+
{ ...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 },
|
|
43038
|
+
boolInput("smooth", "Smooth VFI", true, SETTINGS, "Applies the published light smoothing \u2014 an EMA of the summed line \u2014 before plotting."),
|
|
43039
|
+
lengthInput(3, "smoothLength", "Smoothing Length", 500, "Length of the smoothing EMA. The published version uses 3 bars."),
|
|
43040
|
+
boolInput("showSignal", "Signal Line", true, SETTINGS, "Plots an EMA of VFI as a signal line."),
|
|
43041
|
+
lengthInput(5, "signalLength", "Signal Length", 500, "Length of the signal-line EMA."),
|
|
43042
|
+
colorInput(BULLISH, "Bullish", "color", "Color of the VFI line and gradient fill above the zero line \u2014 the net accumulation regime."),
|
|
43043
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the VFI line and gradient fill below the zero line \u2014 the net distribution regime."),
|
|
43044
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
43045
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the zero line and the VFI line with a vertical gradient.")
|
|
41762
43046
|
],
|
|
41763
43047
|
compute: (bars, inputs) => {
|
|
41764
43048
|
const len = num3(inputs, "length", 130);
|
|
43049
|
+
const typical = sourceValues(bars, "HLC3");
|
|
43050
|
+
const logChange = sub(map(typical, Math.log), shift(map(typical, Math.log), 1));
|
|
43051
|
+
const volatility = stdev(logChange, num3(inputs, "volLength", 30));
|
|
41765
43052
|
const coef = num3(inputs, "coef", 0.2);
|
|
41766
43053
|
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
41767
|
-
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
41768
|
-
const inter = change(typical);
|
|
41769
|
-
const vInter = stdev(inter, 30);
|
|
41770
43054
|
const vol = volumes(bars);
|
|
41771
|
-
const
|
|
41772
|
-
const
|
|
41773
|
-
const
|
|
41774
|
-
const
|
|
43055
|
+
const avgVolume = shift(sma(vol, len), 1);
|
|
43056
|
+
const typicalChange = change(typical);
|
|
43057
|
+
const signed3 = bars.map((b, i) => {
|
|
43058
|
+
const sd = volatility[i];
|
|
43059
|
+
const m = typicalChange[i];
|
|
41775
43060
|
const v = vol[i];
|
|
41776
|
-
|
|
41777
|
-
const
|
|
41778
|
-
|
|
41779
|
-
const capped = Math.min(v,
|
|
43061
|
+
if (!Number.isFinite(sd) || !Number.isFinite(m) || !Number.isFinite(v)) return Number.NaN;
|
|
43062
|
+
const cutoff = coef * sd * b.close;
|
|
43063
|
+
const avg = avgVolume[i];
|
|
43064
|
+
const capped = Number.isFinite(avg) ? Math.min(v, avg * volCoef) : v;
|
|
41780
43065
|
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
41781
43066
|
});
|
|
41782
|
-
const
|
|
41783
|
-
|
|
41784
|
-
|
|
43067
|
+
const raw = zip(sum(signed3, len), avgVolume, (f, a) => a > 0 ? f / a : Number.NaN);
|
|
43068
|
+
const values = bool(inputs, "smooth", true) ? ema(raw, num3(inputs, "smoothLength", 3)) : raw;
|
|
43069
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
43070
|
+
const extraPlots = bool(inputs, "showSignal", true) ? [{ key: "signal", title: "Signal Line", values: ema(values, num3(inputs, "signalLength", 5)), color: str3(inputs, "signalColor", WARNING) }] : [];
|
|
43071
|
+
return centeredOscillator({
|
|
43072
|
+
key: "vfi",
|
|
43073
|
+
title: "VFI",
|
|
43074
|
+
values,
|
|
43075
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "bearColor", BEARISH) },
|
|
43076
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
43077
|
+
fill: bool(inputs, "gradientFill", true),
|
|
43078
|
+
extraPlots
|
|
41785
43079
|
});
|
|
41786
|
-
const flowSum = sum(raw, len);
|
|
41787
|
-
const line = zip(flowSum, values, (f, a) => f / a);
|
|
41788
|
-
return {
|
|
41789
|
-
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
41790
|
-
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
41791
|
-
};
|
|
41792
43080
|
}
|
|
41793
43081
|
};
|
|
43082
|
+
var II_OSCILLATOR = "Normalized Oscillator (II%)";
|
|
43083
|
+
var II_CUMULATIVE = "Cumulative Line";
|
|
41794
43084
|
var intradayIntensity = {
|
|
41795
43085
|
type: "intraday-intensity",
|
|
41796
43086
|
title: "Intraday Intensity",
|
|
41797
43087
|
shortTitle: "Intraday Intensity",
|
|
41798
43088
|
overlay: false,
|
|
41799
|
-
inputs: [
|
|
43089
|
+
inputs: [
|
|
43090
|
+
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."),
|
|
43091
|
+
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."),
|
|
43092
|
+
colorInput(BULLISH, "Bullish", "color", "Color of positive readings, the rising cumulative line and the fill above zero \u2014 the accumulation side."),
|
|
43093
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of negative readings, the falling cumulative line and the fill below zero \u2014 the distribution side."),
|
|
43094
|
+
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.")
|
|
43095
|
+
],
|
|
41800
43096
|
compute: (bars, inputs) => {
|
|
43097
|
+
const intensity = moneyFlowVolume(bars);
|
|
43098
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
43099
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
43100
|
+
if (str3(inputs, "mode", II_OSCILLATOR) === II_CUMULATIVE) {
|
|
43101
|
+
const values2 = cumSum(intensity);
|
|
43102
|
+
return { plots: [{ key: "ii", title: "Intraday Intensity", values: values2, color: bull, colors: slopeColors2(values2, bull, bear, bull) }] };
|
|
43103
|
+
}
|
|
41801
43104
|
const len = num3(inputs, "length", 21);
|
|
41802
|
-
const values = zip(sum(
|
|
41803
|
-
return {
|
|
41804
|
-
|
|
41805
|
-
|
|
41806
|
-
|
|
43105
|
+
const values = zip(sum(intensity, len), sum(volumes(bars), len), (m, v) => v > 0 ? 100 * m / v : Number.NaN);
|
|
43106
|
+
return centeredOscillator({
|
|
43107
|
+
key: "ii",
|
|
43108
|
+
title: "Intraday Intensity",
|
|
43109
|
+
values,
|
|
43110
|
+
ink: { bull, bear, neutral: bear },
|
|
43111
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
43112
|
+
fill: bool(inputs, "gradientFill", true)
|
|
43113
|
+
});
|
|
41807
43114
|
}
|
|
41808
43115
|
};
|
|
41809
43116
|
var volumeSpecs = [
|
|
@@ -41836,15 +43143,24 @@ function periodKey(anchor, time) {
|
|
|
41836
43143
|
}
|
|
41837
43144
|
return Math.floor(time / DAY_MS2);
|
|
41838
43145
|
}
|
|
43146
|
+
function barInterval(bars) {
|
|
43147
|
+
if (bars.length < 2) return 0;
|
|
43148
|
+
const gaps = [];
|
|
43149
|
+
for (let i = 1; i < bars.length; i++) gaps.push(bars[i].time - bars[i - 1].time);
|
|
43150
|
+
gaps.sort((a, b) => a - b);
|
|
43151
|
+
return gaps[Math.floor(gaps.length / 2)] ?? 0;
|
|
43152
|
+
}
|
|
41839
43153
|
var parabolicSar = {
|
|
41840
43154
|
type: "parabolic-sar",
|
|
41841
43155
|
title: "Parabolic SAR",
|
|
41842
43156
|
shortTitle: "SAR",
|
|
41843
43157
|
overlay: true,
|
|
41844
43158
|
inputs: [
|
|
41845
|
-
|
|
41846
|
-
|
|
41847
|
-
|
|
43159
|
+
floatInput("start", "Start", 0.02, 0, 1, 0.01, "Initial acceleration factor applied when a new trend begins."),
|
|
43160
|
+
floatInput("increment", "Increment", 0.02, 0, 1, 0.01, "Amount added to the acceleration factor each time the trend makes a new extreme point."),
|
|
43161
|
+
floatInput("maximum", "Maximum", 0.2, 0.01, 1, 0.01, "Upper cap on the acceleration factor."),
|
|
43162
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the SAR crosses while they trail below price \u2014 uptrend, the stop sits under the market."),
|
|
43163
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the SAR crosses while they sit above price \u2014 downtrend, the stop sits over the market.")
|
|
41848
43164
|
],
|
|
41849
43165
|
compute: (bars, inputs) => {
|
|
41850
43166
|
const start = num3(inputs, "start", 0.02);
|
|
@@ -41852,7 +43168,6 @@ var parabolicSar = {
|
|
|
41852
43168
|
const max = num3(inputs, "maximum", 0.2);
|
|
41853
43169
|
const n = bars.length;
|
|
41854
43170
|
const values = new Array(n).fill(Number.NaN);
|
|
41855
|
-
const colors = new Array(n).fill(null);
|
|
41856
43171
|
if (n >= 2) {
|
|
41857
43172
|
let long = bars[1].close >= bars[0].close;
|
|
41858
43173
|
let sar = long ? bars[0].low : bars[0].high;
|
|
@@ -41877,18 +43192,21 @@ var parabolicSar = {
|
|
|
41877
43192
|
af = Math.min(max, af + inc);
|
|
41878
43193
|
}
|
|
41879
43194
|
values[i] = sar;
|
|
41880
|
-
colors[i] = long ? BULLISH : BEARISH;
|
|
41881
43195
|
}
|
|
41882
43196
|
}
|
|
41883
|
-
|
|
43197
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
43198
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
43199
|
+
const colors = values.map((x, i) => Number.isFinite(x) ? x < bars[i].close ? bull : bear : null);
|
|
43200
|
+
return { plots: [{ key: "sar", title: "SAR", values, kind: "cross", color: bull, colors }] };
|
|
41884
43201
|
}
|
|
41885
43202
|
};
|
|
41886
|
-
var
|
|
41887
|
-
var
|
|
43203
|
+
var VWAP_ANCHORS = ["Session", "Week", "Month", "Quarter", "Year"];
|
|
43204
|
+
var BANDS_STDEV = "Standard Deviation";
|
|
43205
|
+
var BANDS_PERCENT = "Percentage";
|
|
41888
43206
|
var VWAP_BANDS = [
|
|
41889
|
-
{
|
|
41890
|
-
{
|
|
41891
|
-
{
|
|
43207
|
+
{ show: "band1", mult: "band1Mult", defMult: 1, defOn: true },
|
|
43208
|
+
{ show: "band2", mult: "band2Mult", defMult: 2, defOn: false },
|
|
43209
|
+
{ show: "band3", mult: "band3Mult", defMult: 3, defOn: false }
|
|
41892
43210
|
];
|
|
41893
43211
|
var vwap = {
|
|
41894
43212
|
type: "vwap",
|
|
@@ -41896,39 +43214,36 @@ var vwap = {
|
|
|
41896
43214
|
shortTitle: "VWAP",
|
|
41897
43215
|
overlay: true,
|
|
41898
43216
|
inputs: [
|
|
41899
|
-
|
|
41900
|
-
sourceInput("HLC3"),
|
|
41901
|
-
|
|
43217
|
+
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)."),
|
|
43218
|
+
sourceInput("HLC3", "source", "Source", "Price used in the volume weighted average. Typical price (HLC3) is the classic choice."),
|
|
43219
|
+
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."),
|
|
41902
43220
|
...VWAP_BANDS.flatMap((b, i) => [
|
|
41903
|
-
|
|
41904
|
-
|
|
43221
|
+
boolInput(b.show, `Band #${i + 1}`, b.defOn, SETTINGS, `Show the ${["first", "second", "third"][i]} band pair.`),
|
|
43222
|
+
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.")
|
|
41905
43223
|
]),
|
|
41906
|
-
|
|
41907
|
-
|
|
41908
|
-
|
|
41909
|
-
|
|
41910
|
-
|
|
41911
|
-
{ ...colorInput(withAlpha2(b.defColor), "", b.fillColor), inline: b.color, group: VWAP_STYLE }
|
|
41912
|
-
])
|
|
43224
|
+
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."),
|
|
43225
|
+
colorInput(BULLISH, "Bullish", "bullColor", "VWAP line color while price closes above it, and the color of the lower (support) bands."),
|
|
43226
|
+
colorInput(BEARISH, "Bearish", "bearColor", "VWAP line color while price closes below it, and the color of the upper (resistance) bands."),
|
|
43227
|
+
colorInput(NEUTRAL, "Bands", "bandsColor", "Deviation bands fill color."),
|
|
43228
|
+
boolInput("fill", "Bands Fill", true, STYLE, "Fill the area between each visible band pair.")
|
|
41913
43229
|
],
|
|
41914
43230
|
compute: (bars, inputs) => {
|
|
41915
|
-
const anchor = str3(inputs, "anchor", "
|
|
43231
|
+
const anchor = str3(inputs, "anchor", "Session");
|
|
41916
43232
|
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
41917
43233
|
const n = bars.length;
|
|
43234
|
+
const hidden = bool(inputs, "hideDwm", true) && barInterval(bars) >= DAY_MS2;
|
|
41918
43235
|
const values = new Array(n).fill(Number.NaN);
|
|
41919
|
-
const bands = VWAP_BANDS.filter((b) => bool(inputs, b.
|
|
43236
|
+
const bands = VWAP_BANDS.filter((b) => bool(inputs, b.show, b.defOn)).map((b) => ({
|
|
41920
43237
|
mult: Math.max(0, num3(inputs, b.mult, b.defMult)),
|
|
41921
|
-
color: str3(inputs, b.color, b.defColor),
|
|
41922
|
-
fill: bool(inputs, b.fill, true),
|
|
41923
|
-
fillColor: str3(inputs, b.fillColor, withAlpha2(b.defColor)),
|
|
41924
43238
|
up: new Array(n).fill(Number.NaN),
|
|
41925
43239
|
down: new Array(n).fill(Number.NaN)
|
|
41926
43240
|
}));
|
|
43241
|
+
const percentMode = str3(inputs, "bandsMode", BANDS_STDEV) === BANDS_PERCENT;
|
|
41927
43242
|
let period = Number.NaN;
|
|
41928
43243
|
let cumPV = 0;
|
|
41929
43244
|
let cumPV2 = 0;
|
|
41930
43245
|
let cumV = 0;
|
|
41931
|
-
for (let i = 0; i < n; i++) {
|
|
43246
|
+
for (let i = 0; i < n && !hidden; i++) {
|
|
41932
43247
|
const b = bars[i];
|
|
41933
43248
|
const key = periodKey(anchor, b.time);
|
|
41934
43249
|
if (key !== period) {
|
|
@@ -41946,38 +43261,58 @@ var vwap = {
|
|
|
41946
43261
|
}
|
|
41947
43262
|
if (cumV <= 0) continue;
|
|
41948
43263
|
const mean = cumPV / cumV;
|
|
41949
|
-
const
|
|
43264
|
+
const offset = percentMode ? mean / 100 : Math.sqrt(Math.max(0, cumPV2 / cumV - mean * mean));
|
|
41950
43265
|
values[i] = mean;
|
|
41951
43266
|
for (const band of bands) {
|
|
41952
|
-
band.up[i] = mean + band.mult *
|
|
41953
|
-
band.down[i] = mean - band.mult *
|
|
43267
|
+
band.up[i] = mean + band.mult * offset;
|
|
43268
|
+
band.down[i] = mean - band.mult * offset;
|
|
41954
43269
|
}
|
|
41955
43270
|
}
|
|
41956
|
-
const
|
|
43271
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
43272
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
43273
|
+
const fillColor = transp(str3(inputs, "bandsColor", NEUTRAL), 95);
|
|
43274
|
+
const plots = [
|
|
43275
|
+
{ key: "vwap", title: "VWAP", values, color: bull, colors: values.map((x, i) => Number.isFinite(x) ? bars[i].close >= x ? bull : bear : null) }
|
|
43276
|
+
];
|
|
41957
43277
|
const fills = [];
|
|
41958
43278
|
bands.forEach((band, i) => {
|
|
41959
43279
|
plots.push(
|
|
41960
|
-
{ key: `up${i}`, title: `Upper
|
|
41961
|
-
{ key: `down${i}`, title: `Lower
|
|
43280
|
+
{ key: `up${i}`, title: `Upper Band #${i + 1}`, values: band.up, color: transp(bear, 40) },
|
|
43281
|
+
{ key: `down${i}`, title: `Lower Band #${i + 1}`, values: band.down, color: transp(bull, 40) }
|
|
41962
43282
|
);
|
|
41963
|
-
if (
|
|
43283
|
+
if (bool(inputs, "fill", true)) fills.push({ key: `band${i}`, from: `up${i}`, to: `down${i}`, color: fillColor });
|
|
41964
43284
|
});
|
|
41965
43285
|
return { plots, bands: fills };
|
|
41966
43286
|
}
|
|
41967
43287
|
};
|
|
43288
|
+
var PIVOT_TRADITIONAL = "Traditional";
|
|
43289
|
+
var PIVOT_FIBONACCI = "Fibonacci";
|
|
43290
|
+
var PIVOT_CAMARILLA = "Camarilla";
|
|
43291
|
+
var PIVOT_WOODIE = "Woodie";
|
|
43292
|
+
var PIVOT_AUTO = "Auto";
|
|
41968
43293
|
var pivotPoints = {
|
|
41969
43294
|
type: "pivot-points",
|
|
41970
|
-
title: "Pivot Points",
|
|
43295
|
+
title: "Pivot Points Standard",
|
|
41971
43296
|
shortTitle: "Pivots",
|
|
41972
43297
|
overlay: true,
|
|
41973
|
-
inputs: [
|
|
43298
|
+
inputs: [
|
|
43299
|
+
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."),
|
|
43300
|
+
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."),
|
|
43301
|
+
colorInput(WARNING, "Pivot", "pivotColor", "Color of the central pivot (P) level."),
|
|
43302
|
+
colorInput(BEARISH, "Resistances", "resistanceColor", "Color of the resistance (R) levels."),
|
|
43303
|
+
colorInput(BULLISH, "Supports", "supportColor", "Color of the support (S) levels.")
|
|
43304
|
+
],
|
|
41974
43305
|
compute: (bars, inputs) => {
|
|
41975
|
-
const
|
|
43306
|
+
const interval = barInterval(bars);
|
|
43307
|
+
const requested = str3(inputs, "anchor", PIVOT_AUTO);
|
|
43308
|
+
const auto = interval <= 15 * 6e4 ? "Session" : interval < DAY_MS2 ? "Week" : interval <= DAY_MS2 ? "Month" : "Year";
|
|
43309
|
+
const anchor = requested === "Daily" ? "Session" : requested === "Weekly" ? "Week" : requested === "Monthly" ? "Month" : requested === "Yearly" ? "Year" : auto;
|
|
43310
|
+
const kind = str3(inputs, "kind", PIVOT_TRADITIONAL);
|
|
41976
43311
|
const aggs = /* @__PURE__ */ new Map();
|
|
41977
43312
|
for (const b of bars) {
|
|
41978
43313
|
const key = periodKey(anchor, b.time);
|
|
41979
43314
|
const a = aggs.get(key);
|
|
41980
|
-
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
43315
|
+
if (!a) aggs.set(key, { open: b.open, high: b.high, low: b.low, close: b.close });
|
|
41981
43316
|
else {
|
|
41982
43317
|
a.high = Math.max(a.high, b.high);
|
|
41983
43318
|
a.low = Math.min(a.low, b.low);
|
|
@@ -41986,31 +43321,61 @@ var pivotPoints = {
|
|
|
41986
43321
|
}
|
|
41987
43322
|
const n = bars.length;
|
|
41988
43323
|
const mk = () => new Array(n).fill(Number.NaN);
|
|
41989
|
-
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
43324
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk(), r4: mk(), s4: mk() };
|
|
43325
|
+
let lastKey = Number.NaN;
|
|
41990
43326
|
for (let i = 0; i < n; i++) {
|
|
41991
|
-
const
|
|
41992
|
-
|
|
41993
|
-
|
|
43327
|
+
const key = periodKey(anchor, bars[i].time);
|
|
43328
|
+
const fresh = key !== lastKey;
|
|
43329
|
+
lastKey = key;
|
|
43330
|
+
if (fresh) continue;
|
|
43331
|
+
const prev2 = aggs.get(key - 1);
|
|
43332
|
+
const curr = aggs.get(key);
|
|
43333
|
+
if (!prev2 || !curr) continue;
|
|
43334
|
+
const range = prev2.high - prev2.low;
|
|
43335
|
+
const traditional = (prev2.high + prev2.low + prev2.close) / 3;
|
|
43336
|
+
const p = kind === PIVOT_WOODIE ? (prev2.high + prev2.low + 2 * curr.open) / 4 : traditional;
|
|
41994
43337
|
levels.p[i] = p;
|
|
41995
|
-
|
|
41996
|
-
|
|
41997
|
-
|
|
41998
|
-
|
|
41999
|
-
|
|
42000
|
-
|
|
42001
|
-
|
|
42002
|
-
|
|
42003
|
-
|
|
42004
|
-
|
|
42005
|
-
|
|
42006
|
-
|
|
42007
|
-
|
|
42008
|
-
|
|
42009
|
-
|
|
42010
|
-
|
|
42011
|
-
|
|
42012
|
-
|
|
42013
|
-
|
|
43338
|
+
if (kind === PIVOT_FIBONACCI) {
|
|
43339
|
+
levels.r1[i] = traditional + 0.382 * range;
|
|
43340
|
+
levels.s1[i] = traditional - 0.382 * range;
|
|
43341
|
+
levels.r2[i] = traditional + 0.618 * range;
|
|
43342
|
+
levels.s2[i] = traditional - 0.618 * range;
|
|
43343
|
+
levels.r3[i] = traditional + range;
|
|
43344
|
+
levels.s3[i] = traditional - range;
|
|
43345
|
+
} else if (kind === PIVOT_CAMARILLA) {
|
|
43346
|
+
const cam = 1.1 * range;
|
|
43347
|
+
levels.r1[i] = prev2.close + cam / 12;
|
|
43348
|
+
levels.s1[i] = prev2.close - cam / 12;
|
|
43349
|
+
levels.r2[i] = prev2.close + cam / 6;
|
|
43350
|
+
levels.s2[i] = prev2.close - cam / 6;
|
|
43351
|
+
levels.r3[i] = prev2.close + cam / 4;
|
|
43352
|
+
levels.s3[i] = prev2.close - cam / 4;
|
|
43353
|
+
levels.r4[i] = prev2.close + cam / 2;
|
|
43354
|
+
levels.s4[i] = prev2.close - cam / 2;
|
|
43355
|
+
} else {
|
|
43356
|
+
levels.r1[i] = 2 * p - prev2.low;
|
|
43357
|
+
levels.s1[i] = 2 * p - prev2.high;
|
|
43358
|
+
levels.r2[i] = p + range;
|
|
43359
|
+
levels.s2[i] = p - range;
|
|
43360
|
+
if (kind !== PIVOT_WOODIE) {
|
|
43361
|
+
levels.r3[i] = prev2.high + 2 * (p - prev2.low);
|
|
43362
|
+
levels.s3[i] = prev2.low - 2 * (prev2.high - p);
|
|
43363
|
+
}
|
|
43364
|
+
}
|
|
43365
|
+
}
|
|
43366
|
+
const resistance = str3(inputs, "resistanceColor", BEARISH);
|
|
43367
|
+
const support = str3(inputs, "supportColor", BULLISH);
|
|
43368
|
+
const plot = (key, title, color) => ({ key, title, values: levels[key], color });
|
|
43369
|
+
const plots = [
|
|
43370
|
+
plot("p", "P", str3(inputs, "pivotColor", WARNING)),
|
|
43371
|
+
plot("r1", "R1", resistance),
|
|
43372
|
+
plot("s1", "S1", support),
|
|
43373
|
+
plot("r2", "R2", resistance),
|
|
43374
|
+
plot("s2", "S2", support)
|
|
43375
|
+
];
|
|
43376
|
+
if (kind !== PIVOT_WOODIE) plots.push(plot("r3", "R3", resistance), plot("s3", "S3", support));
|
|
43377
|
+
if (kind === PIVOT_CAMARILLA) plots.push(plot("r4", "R4", resistance), plot("s4", "S4", support));
|
|
43378
|
+
return { plots };
|
|
42014
43379
|
}
|
|
42015
43380
|
};
|
|
42016
43381
|
var fiftyTwoWeek = {
|
|
@@ -42018,15 +43383,25 @@ var fiftyTwoWeek = {
|
|
|
42018
43383
|
title: "52 Week High/Low",
|
|
42019
43384
|
shortTitle: "52W H/L",
|
|
42020
43385
|
overlay: true,
|
|
42021
|
-
inputs: [
|
|
43386
|
+
inputs: [
|
|
43387
|
+
intInput("weeks", "Weeks", 52, 1, 520, "Length of the rolling window, in weeks."),
|
|
43388
|
+
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."),
|
|
43389
|
+
colorInput(BULLISH, "52 Week High", "highColor", "Color of the 52 week high level."),
|
|
43390
|
+
colorInput(BEARISH, "52 Week Low", "lowColor", "Color of the 52 week low level."),
|
|
43391
|
+
colorInput(NEUTRAL, "All-Time High/Low", "allTimeColor", "Color of the all-time high and all-time low levels.")
|
|
43392
|
+
],
|
|
42022
43393
|
compute: (bars, inputs) => {
|
|
42023
43394
|
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
42024
43395
|
const n = bars.length;
|
|
42025
43396
|
const hi = new Array(n).fill(Number.NaN);
|
|
42026
43397
|
const lo = new Array(n).fill(Number.NaN);
|
|
43398
|
+
const ath = new Array(n).fill(Number.NaN);
|
|
43399
|
+
const atl = new Array(n).fill(Number.NaN);
|
|
42027
43400
|
const maxIdx = [];
|
|
42028
43401
|
const minIdx = [];
|
|
42029
43402
|
let from = 0;
|
|
43403
|
+
let runHigh = -Infinity;
|
|
43404
|
+
let runLow = Infinity;
|
|
42030
43405
|
for (let i = 0; i < n; i++) {
|
|
42031
43406
|
const b = bars[i];
|
|
42032
43407
|
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
@@ -42038,13 +43413,20 @@ var fiftyTwoWeek = {
|
|
|
42038
43413
|
while (minIdx[0] < from) minIdx.shift();
|
|
42039
43414
|
hi[i] = bars[maxIdx[0]].high;
|
|
42040
43415
|
lo[i] = bars[minIdx[0]].low;
|
|
43416
|
+
runHigh = Math.max(runHigh, b.high);
|
|
43417
|
+
runLow = Math.min(runLow, b.low);
|
|
43418
|
+
ath[i] = runHigh;
|
|
43419
|
+
atl[i] = runLow;
|
|
43420
|
+
}
|
|
43421
|
+
const plots = [
|
|
43422
|
+
{ key: "high", title: "52 Week High", values: hi, kind: "step", color: str3(inputs, "highColor", BULLISH), width: 2 },
|
|
43423
|
+
{ key: "low", title: "52 Week Low", values: lo, kind: "step", color: str3(inputs, "lowColor", BEARISH), width: 2 }
|
|
43424
|
+
];
|
|
43425
|
+
if (bool(inputs, "showAllTime", false)) {
|
|
43426
|
+
const ink = str3(inputs, "allTimeColor", NEUTRAL);
|
|
43427
|
+
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 });
|
|
42041
43428
|
}
|
|
42042
|
-
return {
|
|
42043
|
-
plots: [
|
|
42044
|
-
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
42045
|
-
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
42046
|
-
]
|
|
42047
|
-
};
|
|
43429
|
+
return { plots };
|
|
42048
43430
|
}
|
|
42049
43431
|
};
|
|
42050
43432
|
var zigzag = {
|
|
@@ -42052,9 +43434,11 @@ var zigzag = {
|
|
|
42052
43434
|
title: "ZigZag",
|
|
42053
43435
|
overlay: true,
|
|
42054
43436
|
inputs: [
|
|
42055
|
-
|
|
42056
|
-
{
|
|
42057
|
-
colorInput()
|
|
43437
|
+
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."),
|
|
43438
|
+
{ ...intInput("depth", "Depth", 10, 2, 500, "Minimum number of bars required between two consecutive pivots.") },
|
|
43439
|
+
colorInput(BULLISH, "Up Leg", "bullColor", "Color of rising zigzag segments."),
|
|
43440
|
+
colorInput(BEARISH, "Down Leg", "bearColor", "Color of falling zigzag segments."),
|
|
43441
|
+
{ key: "lineWidth", title: "Width", type: "int", defval: 2, min: 1, max: 10, step: 1, group: STYLE, tooltip: "Width of the zigzag segments." }
|
|
42058
43442
|
],
|
|
42059
43443
|
compute: (bars, inputs) => {
|
|
42060
43444
|
const dev = num3(inputs, "deviation", 5) / 100;
|
|
@@ -42067,14 +43451,14 @@ var zigzag = {
|
|
|
42067
43451
|
const b = bars[i];
|
|
42068
43452
|
if (up) {
|
|
42069
43453
|
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
42070
|
-
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
43454
|
+
else if (b.low <= ext.price * (1 - dev) && ext.i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
42071
43455
|
pivots.push(ext);
|
|
42072
43456
|
up = false;
|
|
42073
43457
|
ext = { i, price: b.low };
|
|
42074
43458
|
}
|
|
42075
43459
|
} else if (b.low <= ext.price) {
|
|
42076
43460
|
ext = { i, price: b.low };
|
|
42077
|
-
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
43461
|
+
} else if (b.high >= ext.price * (1 + dev) && ext.i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
42078
43462
|
pivots.push(ext);
|
|
42079
43463
|
up = true;
|
|
42080
43464
|
ext = { i, price: b.high };
|
|
@@ -42082,22 +43466,29 @@ var zigzag = {
|
|
|
42082
43466
|
}
|
|
42083
43467
|
pivots.push(ext);
|
|
42084
43468
|
}
|
|
42085
|
-
|
|
42086
|
-
|
|
42087
|
-
|
|
42088
|
-
|
|
42089
|
-
|
|
42090
|
-
|
|
42091
|
-
|
|
42092
|
-
|
|
42093
|
-
|
|
42094
|
-
|
|
42095
|
-
|
|
42096
|
-
|
|
42097
|
-
|
|
42098
|
-
|
|
42099
|
-
|
|
42100
|
-
|
|
43469
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
43470
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
43471
|
+
const width = num3(inputs, "lineWidth", 2);
|
|
43472
|
+
const polylines = [];
|
|
43473
|
+
for (let k = 1; k < pivots.length; k++) {
|
|
43474
|
+
const a = pivots[k - 1];
|
|
43475
|
+
const b = pivots[k];
|
|
43476
|
+
polylines.push({
|
|
43477
|
+
key: `leg${k}`,
|
|
43478
|
+
points: [
|
|
43479
|
+
{ xloc: "bar_time", x: bars[a.i].time, price: a.price },
|
|
43480
|
+
{ xloc: "bar_time", x: bars[b.i].time, price: b.price }
|
|
43481
|
+
],
|
|
43482
|
+
curved: false,
|
|
43483
|
+
closed: false,
|
|
43484
|
+
lineColor: b.price >= a.price ? bull : bear,
|
|
43485
|
+
lineWidth: width,
|
|
43486
|
+
lineStyle: "solid",
|
|
43487
|
+
arrowLeft: false,
|
|
43488
|
+
arrowRight: false
|
|
43489
|
+
});
|
|
43490
|
+
}
|
|
43491
|
+
return { plots: [], polylines };
|
|
42101
43492
|
}
|
|
42102
43493
|
};
|
|
42103
43494
|
var williamsFractal = {
|
|
@@ -42105,10 +43496,16 @@ var williamsFractal = {
|
|
|
42105
43496
|
title: "Williams Fractal",
|
|
42106
43497
|
shortTitle: "Fractals",
|
|
42107
43498
|
overlay: true,
|
|
42108
|
-
inputs: [
|
|
43499
|
+
inputs: [
|
|
43500
|
+
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."),
|
|
43501
|
+
colorInput(BEARISH, "Up Fractal", "upColor", "Color of the up fractal marker (swing high), drawn above the fractal bar."),
|
|
43502
|
+
colorInput(BULLISH, "Down Fractal", "downColor", "Color of the down fractal marker (swing low), drawn below the fractal bar.")
|
|
43503
|
+
],
|
|
42109
43504
|
compute: (bars, inputs) => {
|
|
42110
43505
|
const p = num3(inputs, "periods", 2);
|
|
42111
43506
|
const markers = [];
|
|
43507
|
+
const up = str3(inputs, "upColor", BEARISH);
|
|
43508
|
+
const down = str3(inputs, "downColor", BULLISH);
|
|
42112
43509
|
const isExtreme = (i, pick, better) => {
|
|
42113
43510
|
const v = pick(bars[i]);
|
|
42114
43511
|
for (let k = i - p; k <= i + p; k++) {
|
|
@@ -42118,12 +43515,8 @@ var williamsFractal = {
|
|
|
42118
43515
|
return true;
|
|
42119
43516
|
};
|
|
42120
43517
|
for (let i = p; i < bars.length - p; i++) {
|
|
42121
|
-
if (isExtreme(i, (b) => b.high, (a, b) => a
|
|
42122
|
-
|
|
42123
|
-
}
|
|
42124
|
-
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
42125
|
-
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
42126
|
-
}
|
|
43518
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a > b)) markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: up });
|
|
43519
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a < b)) markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: down });
|
|
42127
43520
|
}
|
|
42128
43521
|
return { plots: [], markers };
|
|
42129
43522
|
}
|
|
@@ -43824,6 +45217,8 @@ var ChartCell = class {
|
|
|
43824
45217
|
this.activeRangeId = null;
|
|
43825
45218
|
/** Latched verdict of {@link sessionAvailable} (async metadata, sticky per symbol). */
|
|
43826
45219
|
this.sessionAvailableFlag = false;
|
|
45220
|
+
/** The symbol of the most recent metadata probe — an older one landing late is dropped. */
|
|
45221
|
+
this.metadataProbeSymbol = null;
|
|
43827
45222
|
/** Latched: the symbol's extended tape wraps midnight (an overnight roll market) —
|
|
43828
45223
|
* one extended-hours shading phase instead of the pre/post split. */
|
|
43829
45224
|
this.sessionOvernightFlag = false;
|
|
@@ -43892,7 +45287,11 @@ var ChartCell = class {
|
|
|
43892
45287
|
nativeBackend: deps.nativeBackend,
|
|
43893
45288
|
// The user's drawings option minus its toolbar: one SHARED bar serves
|
|
43894
45289
|
// the whole workspace (per-cell bars would cost a 44px gutter each).
|
|
43895
|
-
drawings: cellDrawings(deps.chartDefaults.drawings)
|
|
45290
|
+
drawings: cellDrawings(deps.chartDefaults.drawings),
|
|
45291
|
+
// The renderer's own Time zone row edits a resolved IANA zone; the cell
|
|
45292
|
+
// contributes the workspace picker instead (`time-zone`, with the
|
|
45293
|
+
// exchange rule — see pushSettingsSections).
|
|
45294
|
+
settings: { ...deps.chartDefaults.settings, hidden: [...deps.chartDefaults.settings?.hidden ?? [], "symbol.timezone"] }
|
|
43896
45295
|
},
|
|
43897
45296
|
{ dataFeed: deps.feed }
|
|
43898
45297
|
);
|
|
@@ -43941,7 +45340,10 @@ var ChartCell = class {
|
|
|
43941
45340
|
const list = providers.length > 0 ? providers.join(", ") : "none";
|
|
43942
45341
|
this.deps.toast(`No registered provider serves "${symbol2}" (registered: ${list})`, "error", 6e3);
|
|
43943
45342
|
});
|
|
43944
|
-
this.inner.on("load:start", () =>
|
|
45343
|
+
this.inner.on("load:start", ({ symbol: symbol2 }) => {
|
|
45344
|
+
this.watermark?.setLoading(true);
|
|
45345
|
+
this.refreshSymbolMetadata(symbol2);
|
|
45346
|
+
});
|
|
43945
45347
|
this.inner.on("load:end", () => {
|
|
43946
45348
|
this.watermark?.setLoading(false);
|
|
43947
45349
|
this.refreshSessionShading();
|
|
@@ -44112,12 +45514,19 @@ var ChartCell = class {
|
|
|
44112
45514
|
}
|
|
44113
45515
|
/** Re-read the symbol's metadata: session posture (RTH/ETH toggle, shading) and its
|
|
44114
45516
|
* trading zone (the exchange rule). Async — the workspace re-projects when a verdict lands. */
|
|
44115
|
-
refreshSymbolMetadata() {
|
|
45517
|
+
refreshSymbolMetadata(symbol = this.state.symbol, retry = true) {
|
|
44116
45518
|
const chart = this.inner;
|
|
44117
|
-
const symbol = this.state.symbol;
|
|
44118
45519
|
if (!chart || !symbol) return;
|
|
45520
|
+
this.metadataProbeSymbol = symbol;
|
|
44119
45521
|
void chart.data.symbolInfo(symbol).then((si) => {
|
|
44120
|
-
if (this.inner !== chart) return;
|
|
45522
|
+
if (this.inner !== chart || this.metadataProbeSymbol !== symbol) return;
|
|
45523
|
+
if (si === void 0 && retry) {
|
|
45524
|
+
void chart.data.ready().then(() => {
|
|
45525
|
+
if (this.inner === chart && this.metadataProbeSymbol === symbol) this.refreshSymbolMetadata(symbol, false);
|
|
45526
|
+
});
|
|
45527
|
+
return;
|
|
45528
|
+
}
|
|
45529
|
+
if (this.state.symbol !== symbol) return;
|
|
44121
45530
|
const available = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
|
|
44122
45531
|
const overnight = parseSessionSpec(si)?.overnight === true;
|
|
44123
45532
|
const zone = typeof si?.timezone === "string" && si.timezone !== "" ? si.timezone : void 0;
|
|
@@ -44232,6 +45641,24 @@ var ChartCell = class {
|
|
|
44232
45641
|
}
|
|
44233
45642
|
]
|
|
44234
45643
|
};
|
|
45644
|
+
const timezoneSection = {
|
|
45645
|
+
title: "Time zone",
|
|
45646
|
+
id: "time-zone",
|
|
45647
|
+
placement: "symbol",
|
|
45648
|
+
rows: [
|
|
45649
|
+
{
|
|
45650
|
+
kind: "select",
|
|
45651
|
+
label: "Time zone",
|
|
45652
|
+
id: "zone",
|
|
45653
|
+
options: timezoneMenuRows(this.deps.timezone()).map((r) => [r.value, r.label]),
|
|
45654
|
+
get: () => {
|
|
45655
|
+
const choice = this.deps.timezone();
|
|
45656
|
+
return isExchangeTimezone(choice) ? choice : normalizeTimezone(choice);
|
|
45657
|
+
},
|
|
45658
|
+
set: (v) => this.deps.setTimezone(v)
|
|
45659
|
+
}
|
|
45660
|
+
]
|
|
45661
|
+
};
|
|
44235
45662
|
const watermarkSection = {
|
|
44236
45663
|
title: "Watermark",
|
|
44237
45664
|
id: "watermark",
|
|
@@ -44300,7 +45727,7 @@ var ChartCell = class {
|
|
|
44300
45727
|
]
|
|
44301
45728
|
});
|
|
44302
45729
|
}
|
|
44303
|
-
sections.push(advanced);
|
|
45730
|
+
sections.push(advanced, timezoneSection);
|
|
44304
45731
|
if (this.sessionAvailableFlag) sections.push(sessionSection);
|
|
44305
45732
|
sections.push(watermarkSection);
|
|
44306
45733
|
chart.renderer.setSettingsSections(sections);
|