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