@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { deserializeDrawing, chartTypes, rendererLayers, isLineLikeSeries, seriesShownOn, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, nativeInstanceChannel, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, DEFAULT_DRAWING_COLOR, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, seriesInScale, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS, stableSeriesId } from './chunk-BFA32GOU.js';
|
|
2
2
|
import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, CALLOUT_STYLE_ID, CALLOUT_CSS, Popover, blendOver, splitColor, CalloutBubble, Menu, TextArea, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-WTWGEB7K.js';
|
|
3
|
-
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL,
|
|
3
|
+
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, INFO, BULLISH, BEARISH, isDarkColor, injectStyles, SERIES_LINE, CHIP_PLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, iconMarkup, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, SLATE, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-CAFCLMPF.js';
|
|
4
4
|
|
|
5
5
|
// src/core/events/EventBus.ts
|
|
6
6
|
var TypedEventBus = class {
|
|
@@ -1179,6 +1179,8 @@ var MultiProviderFeed = class {
|
|
|
1179
1179
|
this.liveBars = [];
|
|
1180
1180
|
/** Sync-accessible symbol metadata, warmed by load()/symbolInfoFor (the engine reads it synchronously). */
|
|
1181
1181
|
this.symInfoCache = /* @__PURE__ */ new Map();
|
|
1182
|
+
/** Requests in flight, keyed like the cache — every concurrent asker shares one round trip. */
|
|
1183
|
+
this.symInfoInflight = /* @__PURE__ */ new Map();
|
|
1182
1184
|
this.cache = new CachingDataFeed(new RegistryFetchFeed(this.registry), store);
|
|
1183
1185
|
}
|
|
1184
1186
|
// ── Registry surface (driven by chart.data / DataControl) ──────────────
|
|
@@ -1239,9 +1241,36 @@ var MultiProviderFeed = class {
|
|
|
1239
1241
|
async symbolInfoFor(raw) {
|
|
1240
1242
|
const resolved = this.registry.resolve(raw, { default: this.primaryProvider });
|
|
1241
1243
|
if (!resolved) return void 0;
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1244
|
+
return this.fetchSymbolInfo(resolved);
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* One round trip per symbol, shared. A market switch probes this metadata from five
|
|
1248
|
+
* places at once (the bar load's prefetch, the renderer's tick size, the market-status
|
|
1249
|
+
* badge, the session shading, the session toggle) — without the in-flight map each
|
|
1250
|
+
* would open its own request for the same answer, and the last one to land decided how
|
|
1251
|
+
* late the chrome settled.
|
|
1252
|
+
*
|
|
1253
|
+
* Cached for {@link SYMBOL_INFO_TTL_MS}: this metadata is stable within a session
|
|
1254
|
+
* (tick size, session vocabulary, listing prefix), but not immutable — a continuous
|
|
1255
|
+
* futures row rolls its current contract — so the entry expires rather than pinning the
|
|
1256
|
+
* session's first answer forever. A failed probe is NOT remembered: the next asker retries.
|
|
1257
|
+
*/
|
|
1258
|
+
fetchSymbolInfo(resolved) {
|
|
1259
|
+
const key = symKey(resolved);
|
|
1260
|
+
const hit = this.symInfoCache.get(key);
|
|
1261
|
+
if (hit && Date.now() - hit.at < SYMBOL_INFO_TTL_MS) return Promise.resolve(hit.info);
|
|
1262
|
+
const inflight = this.symInfoInflight.get(key);
|
|
1263
|
+
if (inflight) return inflight;
|
|
1264
|
+
const getSymbolInfo = this.registry.get(resolved.provider)?.getSymbolInfo;
|
|
1265
|
+
if (!getSymbolInfo) return Promise.resolve(void 0);
|
|
1266
|
+
const p = Promise.resolve(getSymbolInfo.call(this.registry.get(resolved.provider), resolved.ticker)).then((info) => {
|
|
1267
|
+
if (info) this.symInfoCache.set(key, { info, at: Date.now() });
|
|
1268
|
+
return info;
|
|
1269
|
+
}).finally(() => {
|
|
1270
|
+
this.symInfoInflight.delete(key);
|
|
1271
|
+
});
|
|
1272
|
+
this.symInfoInflight.set(key, p);
|
|
1273
|
+
return p;
|
|
1245
1274
|
}
|
|
1246
1275
|
/**
|
|
1247
1276
|
* Per-symbol capabilities, resolved through the owning provider's `capabilitiesFor`
|
|
@@ -1285,16 +1314,10 @@ var MultiProviderFeed = class {
|
|
|
1285
1314
|
symbolInfo(cfg) {
|
|
1286
1315
|
if (cfg.data && cfg.data.length > 0) return void 0;
|
|
1287
1316
|
const resolved = this.registry.resolve(rawSymbol(cfg), { default: this.primaryProvider });
|
|
1288
|
-
return resolved ? this.symInfoCache.get(symKey(resolved)) : void 0;
|
|
1317
|
+
return resolved ? this.symInfoCache.get(symKey(resolved))?.info : void 0;
|
|
1289
1318
|
}
|
|
1290
1319
|
prefetchSymbolInfo(resolved) {
|
|
1291
|
-
|
|
1292
|
-
if (this.symInfoCache.has(key)) return;
|
|
1293
|
-
const provider = this.registry.get(resolved.provider);
|
|
1294
|
-
if (!provider?.getSymbolInfo) return;
|
|
1295
|
-
void provider.getSymbolInfo(resolved.ticker).then((info) => {
|
|
1296
|
-
if (info) this.symInfoCache.set(key, info);
|
|
1297
|
-
}).catch(() => {
|
|
1320
|
+
void this.fetchSymbolInfo(resolved).catch(() => {
|
|
1298
1321
|
});
|
|
1299
1322
|
}
|
|
1300
1323
|
async loadRange(cfg, range) {
|
|
@@ -1344,8 +1367,9 @@ function canonical(cfg, resolved) {
|
|
|
1344
1367
|
return { ...cfg, symbol: `${resolved.provider}:${resolved.ticker}` };
|
|
1345
1368
|
}
|
|
1346
1369
|
function symKey(resolved) {
|
|
1347
|
-
return `${resolved.provider}|${resolved.ticker}`;
|
|
1370
|
+
return `${resolved.provider}|${resolved.ticker.trim().toUpperCase()}`;
|
|
1348
1371
|
}
|
|
1372
|
+
var SYMBOL_INFO_TTL_MS = 10 * 6e4;
|
|
1349
1373
|
async function safeBars(provider, ticker, tf, range) {
|
|
1350
1374
|
try {
|
|
1351
1375
|
return await provider.getBars(ticker, tf, range);
|
|
@@ -11470,7 +11494,7 @@ var SettingsDialog = class {
|
|
|
11470
11494
|
if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
|
|
11471
11495
|
else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
11472
11496
|
else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
11473
|
-
else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options
|
|
11497
|
+
else body.append(this.selectRowLabeled(hr.label, hr.get(), normalizeSelectOptions(hr.options), (v) => hr.set(v)));
|
|
11474
11498
|
}
|
|
11475
11499
|
}
|
|
11476
11500
|
};
|
|
@@ -24397,7 +24421,8 @@ var ClassicIndicator = class {
|
|
|
24397
24421
|
lineStyle: plot.lineStyle ?? "solid",
|
|
24398
24422
|
...plot.base != null ? { base: plot.base } : {}
|
|
24399
24423
|
},
|
|
24400
|
-
...plot.overlay ? { overlay: true } : {}
|
|
24424
|
+
...plot.overlay ? { overlay: true } : {},
|
|
24425
|
+
...plot.display ? { display: plot.display } : {}
|
|
24401
24426
|
});
|
|
24402
24427
|
});
|
|
24403
24428
|
if (out.markers && out.markers.length > 0) {
|
|
@@ -24419,7 +24444,9 @@ var ClassicIndicator = class {
|
|
|
24419
24444
|
paneId: "",
|
|
24420
24445
|
fromSeriesId: from,
|
|
24421
24446
|
toSeriesId: to,
|
|
24422
|
-
color: band.color
|
|
24447
|
+
color: band.color,
|
|
24448
|
+
...band.colors ? { colors: [...band.colors] } : {},
|
|
24449
|
+
...band.gradient ? { gradient: [...band.gradient] } : {}
|
|
24423
24450
|
}
|
|
24424
24451
|
];
|
|
24425
24452
|
});
|
|
@@ -24577,6 +24604,16 @@ function stdev(v, len) {
|
|
|
24577
24604
|
return Math.sqrt(sq / len);
|
|
24578
24605
|
});
|
|
24579
24606
|
}
|
|
24607
|
+
function meanDev(v, len) {
|
|
24608
|
+
return windowed(v, len, (w, from, to) => {
|
|
24609
|
+
let s = 0;
|
|
24610
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
24611
|
+
const mean = s / len;
|
|
24612
|
+
let dev = 0;
|
|
24613
|
+
for (let k = from; k <= to; k++) dev += Math.abs(w[k] - mean);
|
|
24614
|
+
return dev / len;
|
|
24615
|
+
});
|
|
24616
|
+
}
|
|
24580
24617
|
function highest(v, len) {
|
|
24581
24618
|
return windowed(v, len, (w, from, to) => {
|
|
24582
24619
|
let m = -Infinity;
|
|
@@ -24708,7 +24745,7 @@ function stoch(v, hi, lo, len) {
|
|
|
24708
24745
|
}
|
|
24709
24746
|
return out;
|
|
24710
24747
|
}
|
|
24711
|
-
function linreg(v, len) {
|
|
24748
|
+
function linreg(v, len, offset = 0) {
|
|
24712
24749
|
const sx = (len - 1) * len / 2;
|
|
24713
24750
|
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
24714
24751
|
const denom = len * sxx - sx * sx;
|
|
@@ -24722,9 +24759,101 @@ function linreg(v, len) {
|
|
|
24722
24759
|
}
|
|
24723
24760
|
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
24724
24761
|
const intercept = (sy - slope * sx) / len;
|
|
24725
|
-
return intercept + slope * (len - 1);
|
|
24762
|
+
return intercept + slope * (len - 1 - offset);
|
|
24763
|
+
});
|
|
24764
|
+
}
|
|
24765
|
+
function hma(v, len) {
|
|
24766
|
+
const n = Math.max(2, Math.trunc(len));
|
|
24767
|
+
const raw = sub(map(wma(v, Math.max(1, Math.trunc(n / 2))), (x) => 2 * x), wma(v, n));
|
|
24768
|
+
return wma(raw, Math.max(1, Math.round(Math.sqrt(n))));
|
|
24769
|
+
}
|
|
24770
|
+
function alma(v, len, offset, sigma) {
|
|
24771
|
+
const m = offset * (len - 1);
|
|
24772
|
+
const s = len / Math.max(sigma, 1e-9);
|
|
24773
|
+
const weights = new Array(len);
|
|
24774
|
+
let norm = 0;
|
|
24775
|
+
for (let i = 0; i < len; i++) {
|
|
24776
|
+
const d = i - m;
|
|
24777
|
+
const w = Math.exp(-(d * d) / (2 * s * s));
|
|
24778
|
+
weights[i] = w;
|
|
24779
|
+
norm += w;
|
|
24780
|
+
}
|
|
24781
|
+
return windowed(v, len, (win, from, to) => {
|
|
24782
|
+
let acc = 0;
|
|
24783
|
+
for (let k = from; k <= to; k++) acc += win[k] * weights[k - from];
|
|
24784
|
+
return norm === 0 ? Number.NaN : acc / norm;
|
|
24785
|
+
});
|
|
24786
|
+
}
|
|
24787
|
+
function dema(v, len) {
|
|
24788
|
+
const e1 = ema(v, len);
|
|
24789
|
+
return sub(map(e1, (x) => 2 * x), ema(e1, len));
|
|
24790
|
+
}
|
|
24791
|
+
function tema(v, len) {
|
|
24792
|
+
const e1 = ema(v, len);
|
|
24793
|
+
const e2 = ema(e1, len);
|
|
24794
|
+
const e3 = ema(e2, len);
|
|
24795
|
+
return zip(zip(map(e1, (x) => 3 * x), map(e2, (x) => 3 * x), (a, b) => a - b), e3, (a, b) => a + b);
|
|
24796
|
+
}
|
|
24797
|
+
function kama(v, len, fastAlpha = 2 / 3, slowAlpha = 2 / 31) {
|
|
24798
|
+
const direction = map(change(v, len), Math.abs);
|
|
24799
|
+
const volatility = sum(map(change(v), Math.abs), len);
|
|
24800
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
24801
|
+
let prev2 = Number.NaN;
|
|
24802
|
+
for (let i = 0; i < v.length; i++) {
|
|
24803
|
+
const x = v[i];
|
|
24804
|
+
if (!Number.isFinite(x)) continue;
|
|
24805
|
+
const d = direction[i];
|
|
24806
|
+
const w = volatility[i];
|
|
24807
|
+
if (!Number.isFinite(d) || !Number.isFinite(w)) {
|
|
24808
|
+
prev2 = Number.isFinite(prev2) ? prev2 : x;
|
|
24809
|
+
out[i] = prev2;
|
|
24810
|
+
continue;
|
|
24811
|
+
}
|
|
24812
|
+
const er = w === 0 ? 0 : d / w;
|
|
24813
|
+
const sc = (er * (fastAlpha - slowAlpha) + slowAlpha) ** 2;
|
|
24814
|
+
prev2 = Number.isFinite(prev2) ? prev2 + sc * (x - prev2) : x;
|
|
24815
|
+
out[i] = prev2;
|
|
24816
|
+
}
|
|
24817
|
+
return out;
|
|
24818
|
+
}
|
|
24819
|
+
function mcginley(v, len) {
|
|
24820
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
24821
|
+
let prev2 = Number.NaN;
|
|
24822
|
+
for (let i = 0; i < v.length; i++) {
|
|
24823
|
+
const x = v[i];
|
|
24824
|
+
if (!Number.isFinite(x)) continue;
|
|
24825
|
+
if (!Number.isFinite(prev2)) {
|
|
24826
|
+
prev2 = x;
|
|
24827
|
+
} else {
|
|
24828
|
+
const ratio = prev2 === 0 ? 0 : x / prev2;
|
|
24829
|
+
const denom = len * ratio ** 4;
|
|
24830
|
+
prev2 += denom === 0 ? 0 : (x - prev2) / denom;
|
|
24831
|
+
}
|
|
24832
|
+
out[i] = prev2;
|
|
24833
|
+
}
|
|
24834
|
+
return out;
|
|
24835
|
+
}
|
|
24836
|
+
function hamming(v, len, a0 = 0.54, a1 = 0.46) {
|
|
24837
|
+
if (len <= 1) return v.map((x) => Number.isFinite(x) ? x : Number.NaN);
|
|
24838
|
+
const weights = new Array(len);
|
|
24839
|
+
let norm = 0;
|
|
24840
|
+
for (let i = 0; i < len; i++) {
|
|
24841
|
+
const w = a0 - a1 * Math.cos(2 * Math.PI * i / (len - 1));
|
|
24842
|
+
weights[i] = w;
|
|
24843
|
+
norm += w;
|
|
24844
|
+
}
|
|
24845
|
+
return windowed(v, len, (win, from, to) => {
|
|
24846
|
+
let acc = 0;
|
|
24847
|
+
for (let k = from; k <= to; k++) acc += win[k] * weights[to - k];
|
|
24848
|
+
return norm === 0 ? Number.NaN : acc / norm;
|
|
24726
24849
|
});
|
|
24727
24850
|
}
|
|
24851
|
+
function cmo(v, len) {
|
|
24852
|
+
const d = change(v);
|
|
24853
|
+
const up = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
24854
|
+
const dn = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
24855
|
+
return zip(up, dn, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
24856
|
+
}
|
|
24728
24857
|
function swma(v) {
|
|
24729
24858
|
const out = new Array(v.length).fill(Number.NaN);
|
|
24730
24859
|
for (let i = 3; i < v.length; i++) {
|
|
@@ -24774,38 +24903,253 @@ function cumSum(v) {
|
|
|
24774
24903
|
}
|
|
24775
24904
|
|
|
24776
24905
|
// src/core/native-indicators/classics/shared.ts
|
|
24777
|
-
|
|
24778
|
-
|
|
24906
|
+
var SETTINGS = "Settings";
|
|
24907
|
+
var STYLE = "Style";
|
|
24908
|
+
function lengthInput(defval, key = "length", title = "Length", max = 5e3, tooltip) {
|
|
24909
|
+
return { key, title, type: "int", defval, min: 1, max, step: 1, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
24910
|
+
}
|
|
24911
|
+
function intInput(key, title, defval, min = 1, max = 5e3, tooltip) {
|
|
24912
|
+
return { key, title, type: "int", defval, min, max, step: 1, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
24913
|
+
}
|
|
24914
|
+
function floatInput(key, title, defval, min = 0, max = 1e3, step = 0.1, tooltip) {
|
|
24915
|
+
return { key, title, type: "float", defval, min, max, step, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
24779
24916
|
}
|
|
24780
|
-
function
|
|
24781
|
-
return { key
|
|
24917
|
+
function boolInput(key, title, defval, group = SETTINGS, tooltip) {
|
|
24918
|
+
return { key, title, type: "bool", defval, group, ...tooltip != null ? { tooltip } : {} };
|
|
24782
24919
|
}
|
|
24783
|
-
function
|
|
24784
|
-
return { key, title, type: "
|
|
24920
|
+
function optionInput(key, title, defval, options, tooltip) {
|
|
24921
|
+
return { key, title, type: "string", defval, options, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
24922
|
+
}
|
|
24923
|
+
function sourceInput(defval = "Close", key = "source", title = "Source", tooltip) {
|
|
24924
|
+
return { key, title, type: "string", defval, options: SOURCES2, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
24925
|
+
}
|
|
24926
|
+
function colorInput(defval = INFO, title = "Color", key = "color", tooltip) {
|
|
24927
|
+
return { key, title, type: "color", defval, group: STYLE, ...tooltip != null ? { tooltip } : {} };
|
|
24928
|
+
}
|
|
24929
|
+
function widthInput(defval = 2, key = "lineWidth", title = "Line Width") {
|
|
24930
|
+
return { key, title, type: "int", defval, min: 1, max: 5, step: 1, group: STYLE, tooltip: "Width of the plotted line, in pixels." };
|
|
24931
|
+
}
|
|
24932
|
+
function transp(hex, transparency) {
|
|
24933
|
+
return withAlpha2(hex, (100 - transparency) / 100);
|
|
24785
24934
|
}
|
|
24786
24935
|
function withAlpha2(hex, alpha = 0.08) {
|
|
24787
24936
|
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
24788
24937
|
if (!m) return hex;
|
|
24789
|
-
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
24938
|
+
const a = Math.round(Math.max(0, Math.min(1, alpha)) * 255).toString(16).padStart(2, "0");
|
|
24790
24939
|
return `#${m[1]}${a}`;
|
|
24791
24940
|
}
|
|
24941
|
+
function channelStyleInputs(basisTitle = "Basis") {
|
|
24942
|
+
return [
|
|
24943
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the lower band and of the basis while price holds above it."),
|
|
24944
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the upper band and of the basis while price sits below it."),
|
|
24945
|
+
colorInput(NEUTRAL, basisTitle, "basisColor", "Color of the basis line while price sits exactly on it.")
|
|
24946
|
+
];
|
|
24947
|
+
}
|
|
24948
|
+
function directionalChannel(bars, basis, upper, lower, ink, key) {
|
|
24949
|
+
const basisColors = basis.map((b, i) => {
|
|
24950
|
+
if (!Number.isFinite(b)) return null;
|
|
24951
|
+
const c = bars[i].close;
|
|
24952
|
+
return c > b ? ink.bull : c < b ? ink.bear : ink.neutral;
|
|
24953
|
+
});
|
|
24954
|
+
const wash = upper.map((u, i) => {
|
|
24955
|
+
const l = lower[i];
|
|
24956
|
+
if (!Number.isFinite(u) || !Number.isFinite(l)) return null;
|
|
24957
|
+
return { topValue: u, bottomValue: l, topColor: transp(ink.bear, 90), bottomColor: transp(ink.bull, 90) };
|
|
24958
|
+
});
|
|
24959
|
+
return {
|
|
24960
|
+
plots: [
|
|
24961
|
+
{ key: "basis", title: "Basis", values: [...basis], color: ink.neutral, colors: basisColors },
|
|
24962
|
+
{ key: "upper", title: "Upper", values: [...upper], color: transp(ink.bear, 30) },
|
|
24963
|
+
{ key: "lower", title: "Lower", values: [...lower], color: transp(ink.bull, 30) }
|
|
24964
|
+
],
|
|
24965
|
+
bands: [{ key, from: "upper", to: "lower", color: transp(NEUTRAL, 90), gradient: wash }]
|
|
24966
|
+
};
|
|
24967
|
+
}
|
|
24968
|
+
function anchorPlot(key, title, length, level) {
|
|
24969
|
+
return {
|
|
24970
|
+
key,
|
|
24971
|
+
title,
|
|
24972
|
+
values: new Array(length).fill(level),
|
|
24973
|
+
color: transp(NEUTRAL, 100),
|
|
24974
|
+
display: { pane: false, legend: false, dataWindow: false }
|
|
24975
|
+
};
|
|
24976
|
+
}
|
|
24977
|
+
function thresholdZones(length, levels, ink, transparency = 92) {
|
|
24978
|
+
return {
|
|
24979
|
+
plots: [
|
|
24980
|
+
anchorPlot("obAnchor", "Overbought Anchor", length, levels.overbought),
|
|
24981
|
+
anchorPlot("osAnchor", "Oversold Anchor", length, levels.oversold),
|
|
24982
|
+
anchorPlot("topAnchor", "Top Anchor", length, levels.top),
|
|
24983
|
+
anchorPlot("bottomAnchor", "Bottom Anchor", length, levels.bottom)
|
|
24984
|
+
],
|
|
24985
|
+
bands: [
|
|
24986
|
+
{ key: "obZone", from: "obAnchor", to: "topAnchor", color: transp(ink.overbought, transparency) },
|
|
24987
|
+
{ key: "osZone", from: "osAnchor", to: "bottomAnchor", color: transp(ink.oversold, transparency) }
|
|
24988
|
+
]
|
|
24989
|
+
};
|
|
24990
|
+
}
|
|
24991
|
+
function rampColors(values, levels, ink) {
|
|
24992
|
+
return values.map((x) => {
|
|
24993
|
+
if (!Number.isFinite(x)) return null;
|
|
24994
|
+
return x <= levels.mid ? gradient(x, levels.low, levels.mid, ink.low, ink.mid) : gradient(x, levels.mid, levels.high, ink.mid, ink.high);
|
|
24995
|
+
});
|
|
24996
|
+
}
|
|
24997
|
+
function baselineGradient(values, baseline, inkOf, valueTransparency = 50, baseTransparency = 100) {
|
|
24998
|
+
return values.map((x, i) => {
|
|
24999
|
+
if (!Number.isFinite(x)) return null;
|
|
25000
|
+
const ink = inkOf(x, i);
|
|
25001
|
+
const above = x > baseline;
|
|
25002
|
+
return {
|
|
25003
|
+
topValue: Math.max(x, baseline),
|
|
25004
|
+
bottomValue: Math.min(x, baseline),
|
|
25005
|
+
topColor: transp(ink, above ? valueTransparency : baseTransparency),
|
|
25006
|
+
bottomColor: transp(ink, above ? baseTransparency : valueTransparency)
|
|
25007
|
+
};
|
|
25008
|
+
});
|
|
25009
|
+
}
|
|
25010
|
+
function momentumColumnColors(values, ink) {
|
|
25011
|
+
return values.map((x, i) => {
|
|
25012
|
+
if (!Number.isFinite(x)) return null;
|
|
25013
|
+
const prev2 = i > 0 && Number.isFinite(values[i - 1]) ? values[i - 1] : x;
|
|
25014
|
+
const rising = x > prev2;
|
|
25015
|
+
if (x >= 0) return rising ? ink.growAbove : ink.fallAbove;
|
|
25016
|
+
return rising ? ink.growBelow : ink.fallBelow;
|
|
25017
|
+
});
|
|
25018
|
+
}
|
|
25019
|
+
function boundedOscillator(opts) {
|
|
25020
|
+
const mid = (opts.overbought + opts.oversold) / 2;
|
|
25021
|
+
const ramp = rampColors(opts.values, { low: opts.oversold, mid, high: opts.overbought }, opts.ink);
|
|
25022
|
+
const zones = thresholdZones(
|
|
25023
|
+
opts.values.length,
|
|
25024
|
+
{ overbought: opts.overbought, oversold: opts.oversold, top: opts.scale.top, bottom: opts.scale.bottom },
|
|
25025
|
+
{ overbought: opts.ink.high, oversold: opts.ink.low },
|
|
25026
|
+
opts.zoneTransparency
|
|
25027
|
+
);
|
|
25028
|
+
const plots = [{ key: opts.key, title: opts.title, values: opts.values, color: opts.ink.mid, width: opts.width ?? 1, colors: ramp }, ...zones.plots];
|
|
25029
|
+
const bands = [...zones.bands];
|
|
25030
|
+
if (opts.bandFill != null) bands.push({ key: "band", from: "obAnchor", to: "osAnchor", color: opts.bandFill });
|
|
25031
|
+
if (opts.midpointWash) {
|
|
25032
|
+
plots.push(anchorPlot("midAnchor", "Midpoint Anchor", opts.values.length, mid));
|
|
25033
|
+
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) });
|
|
25034
|
+
}
|
|
25035
|
+
const levels = [
|
|
25036
|
+
{ key: "overbought", price: opts.overbought, color: opts.levelInk, lineStyle: "dashed", title: opts.levelTitles?.overbought ?? "Overbought" },
|
|
25037
|
+
{ key: "oversold", price: opts.oversold, color: opts.levelInk, lineStyle: "dashed", title: opts.levelTitles?.oversold ?? "Oversold" }
|
|
25038
|
+
];
|
|
25039
|
+
if (opts.midline != null) levels.splice(1, 0, { key: "midline", price: opts.midline, color: opts.levelInk, lineStyle: "dotted", title: "Midline" });
|
|
25040
|
+
return { plots, bands, levels };
|
|
25041
|
+
}
|
|
25042
|
+
function centeredOscillator(opts) {
|
|
25043
|
+
const baseline = opts.baseline ?? 0;
|
|
25044
|
+
const inkOf = (x) => x > baseline ? opts.ink.bull : x < baseline ? opts.ink.bear : opts.ink.neutral;
|
|
25045
|
+
const plots = [
|
|
25046
|
+
{ 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) },
|
|
25047
|
+
...opts.extraPlots ?? []
|
|
25048
|
+
];
|
|
25049
|
+
const bands = [];
|
|
25050
|
+
if (opts.fill !== false) {
|
|
25051
|
+
plots.push(anchorPlot("baseline", "Baseline Anchor", opts.values.length, baseline));
|
|
25052
|
+
bands.push({ key: "wash", from: opts.key, to: "baseline", color: transp(NEUTRAL, 100), gradient: baselineGradient(opts.values, baseline, inkOf, opts.valueTransparency) });
|
|
25053
|
+
}
|
|
25054
|
+
return {
|
|
25055
|
+
plots,
|
|
25056
|
+
bands,
|
|
25057
|
+
levels: [{ key: "zero", price: baseline, color: opts.zeroInk, lineStyle: opts.zeroLineStyle ?? "dashed", title: opts.zeroTitle ?? "Zero Line" }, ...opts.extraLevels ?? []]
|
|
25058
|
+
};
|
|
25059
|
+
}
|
|
25060
|
+
function channels(hex) {
|
|
25061
|
+
const m = /^#([0-9a-f]{6})/i.exec(hex.trim());
|
|
25062
|
+
if (!m) return null;
|
|
25063
|
+
const n = parseInt(m[1], 16);
|
|
25064
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
25065
|
+
}
|
|
25066
|
+
function gradient(value, low, high, from, to) {
|
|
25067
|
+
const a = channels(from);
|
|
25068
|
+
const b = channels(to);
|
|
25069
|
+
if (!a || !b || !Number.isFinite(value)) return from;
|
|
25070
|
+
const span = high - low;
|
|
25071
|
+
const t = span === 0 ? 0 : Math.max(0, Math.min(1, (value - low) / span));
|
|
25072
|
+
const mix = (i) => Math.round(a[i] + (b[i] - a[i]) * t).toString(16).padStart(2, "0");
|
|
25073
|
+
return `#${mix(0)}${mix(1)}${mix(2)}`;
|
|
25074
|
+
}
|
|
24792
25075
|
|
|
24793
25076
|
// src/core/native-indicators/classics/averages.ts
|
|
24794
|
-
var
|
|
24795
|
-
var
|
|
25077
|
+
var MA_TYPES = ["SMA", "EMA", "WMA", "RMA (SMMA)", "HMA", "ALMA", "VWMA", "DEMA", "TEMA", "KAMA", "LSMA", "McGinley", "Hamming"];
|
|
25078
|
+
var ENVELOPE_MA_TYPES = ["SMA", "EMA", "WMA", "RMA (SMMA)", "HMA", "VWMA"];
|
|
25079
|
+
function movingAverageOf(kind, src, bars, len, opts = {}) {
|
|
25080
|
+
switch (kind) {
|
|
25081
|
+
case "EMA":
|
|
25082
|
+
return ema(src, len);
|
|
25083
|
+
case "WMA":
|
|
25084
|
+
return wma(src, len);
|
|
25085
|
+
case "RMA":
|
|
25086
|
+
case "RMA (SMMA)":
|
|
25087
|
+
return rma(src, len);
|
|
25088
|
+
case "HMA":
|
|
25089
|
+
return hma(src, len);
|
|
25090
|
+
case "ALMA":
|
|
25091
|
+
return alma(src, len, opts.almaOffset ?? 0.85, opts.almaSigma ?? 6);
|
|
25092
|
+
case "VWMA":
|
|
25093
|
+
return vwma(src, volumes(bars), len);
|
|
25094
|
+
case "DEMA":
|
|
25095
|
+
return dema(src, len);
|
|
25096
|
+
case "TEMA":
|
|
25097
|
+
return tema(src, len);
|
|
25098
|
+
case "KAMA":
|
|
25099
|
+
return kama(src, len);
|
|
25100
|
+
case "LSMA":
|
|
25101
|
+
return linreg(src, len, opts.lsmaOffset ?? 0);
|
|
25102
|
+
case "McGinley":
|
|
25103
|
+
return mcginley(src, len);
|
|
25104
|
+
case "Hamming":
|
|
25105
|
+
return hamming(src, len);
|
|
25106
|
+
default:
|
|
25107
|
+
return sma(src, len);
|
|
25108
|
+
}
|
|
25109
|
+
}
|
|
25110
|
+
function slopeColors(values, bull, bear, flat) {
|
|
25111
|
+
return values.map((x, i) => {
|
|
25112
|
+
if (!Number.isFinite(x)) return null;
|
|
25113
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
25114
|
+
if (!Number.isFinite(prev2)) return flat;
|
|
25115
|
+
return x > prev2 ? bull : x < prev2 ? bear : flat;
|
|
25116
|
+
});
|
|
25117
|
+
}
|
|
25118
|
+
function directionInputs() {
|
|
25119
|
+
return [
|
|
25120
|
+
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."),
|
|
25121
|
+
colorInput(BULLISH, "Bullish", "bullColor"),
|
|
25122
|
+
colorInput(BEARISH, "Bearish", "bearColor"),
|
|
25123
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor"),
|
|
25124
|
+
widthInput()
|
|
25125
|
+
];
|
|
25126
|
+
}
|
|
25127
|
+
function slopeColoredPlot(key, title, values, inputs) {
|
|
25128
|
+
const neutral = str3(inputs, "neutralColor", NEUTRAL);
|
|
25129
|
+
const colored = bool(inputs, "trendColor", true);
|
|
25130
|
+
return {
|
|
25131
|
+
key,
|
|
25132
|
+
title,
|
|
25133
|
+
values,
|
|
25134
|
+
color: neutral,
|
|
25135
|
+
width: num3(inputs, "lineWidth", 2),
|
|
25136
|
+
...colored ? { colors: slopeColors(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), neutral) } : {}
|
|
25137
|
+
};
|
|
25138
|
+
}
|
|
25139
|
+
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." };
|
|
24796
25140
|
function fixedAverage(type, title, shortTitle, smooth) {
|
|
24797
25141
|
return {
|
|
24798
25142
|
type,
|
|
24799
25143
|
title,
|
|
24800
25144
|
shortTitle,
|
|
24801
25145
|
overlay: true,
|
|
24802
|
-
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
25146
|
+
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()],
|
|
24803
25147
|
compute: (bars, inputs) => {
|
|
24804
25148
|
const len = num3(inputs, "length", 20);
|
|
24805
25149
|
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
24806
25150
|
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
24807
25151
|
if (offset !== 0) values = shift(values, offset);
|
|
24808
|
-
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color",
|
|
25152
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
24809
25153
|
}
|
|
24810
25154
|
};
|
|
24811
25155
|
}
|
|
@@ -24817,36 +25161,37 @@ var movingAverage = {
|
|
|
24817
25161
|
shortTitle: "MA",
|
|
24818
25162
|
overlay: true,
|
|
24819
25163
|
inputs: [
|
|
24820
|
-
|
|
24821
|
-
lengthInput(20),
|
|
24822
|
-
sourceInput(),
|
|
25164
|
+
optionInput("maType", "MA 1 Type", "SMA", MA_TYPES, "Moving average calculation used for MA 1."),
|
|
25165
|
+
lengthInput(20, "length", "MA 1 Length", 5e3, "Number of bars used in the MA 1 calculation."),
|
|
25166
|
+
sourceInput("Close", "source", "MA 1 Source", "Price series MA 1 is calculated on."),
|
|
25167
|
+
boolInput("showMa2", "Show MA 2", false, SETTINGS, "Displays the optional second moving average."),
|
|
25168
|
+
optionInput("ma2Type", "MA 2 Type", "SMA", MA_TYPES, "Moving average calculation used for MA 2."),
|
|
25169
|
+
lengthInput(50, "ma2Length", "MA 2 Length", 5e3, "Number of bars used in the MA 2 calculation."),
|
|
25170
|
+
sourceInput("Close", "ma2Source", "MA 2 Source", "Price series MA 2 is calculated on."),
|
|
25171
|
+
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."),
|
|
25172
|
+
floatInput("almaSigma", "ALMA Sigma", 6, 0.1, 100, 0.5, "ALMA type only. Larger values widen the Gaussian window for a smoother average."),
|
|
25173
|
+
intInput("lsmaOffset", "LSMA Offset", 0, 0, 500, "LSMA type only. Bar offset applied to the linear regression value."),
|
|
24823
25174
|
offsetInput,
|
|
24824
|
-
colorInput()
|
|
25175
|
+
colorInput(INFO, "MA 1", "color", "MA 1 line color."),
|
|
25176
|
+
colorInput(WARNING, "MA 2", "ma2Color", "MA 2 line color.")
|
|
24825
25177
|
],
|
|
24826
25178
|
compute: (bars, inputs) => {
|
|
24827
|
-
const
|
|
24828
|
-
|
|
25179
|
+
const opts = {
|
|
25180
|
+
almaOffset: num3(inputs, "almaOffset", 0.85),
|
|
25181
|
+
almaSigma: num3(inputs, "almaSigma", 6),
|
|
25182
|
+
lsmaOffset: Math.trunc(num3(inputs, "lsmaOffset", 0))
|
|
25183
|
+
};
|
|
25184
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
25185
|
+
const displace = (v) => offset === 0 ? v : shift(v, offset);
|
|
24829
25186
|
const kind = str3(inputs, "maType", "SMA");
|
|
24830
|
-
|
|
24831
|
-
|
|
24832
|
-
|
|
24833
|
-
|
|
24834
|
-
|
|
24835
|
-
|
|
24836
|
-
values = wma(src, len);
|
|
24837
|
-
break;
|
|
24838
|
-
case "RMA":
|
|
24839
|
-
values = rma(src, len);
|
|
24840
|
-
break;
|
|
24841
|
-
case "VWMA":
|
|
24842
|
-
values = vwma(src, volumes(bars), len);
|
|
24843
|
-
break;
|
|
24844
|
-
default:
|
|
24845
|
-
values = sma(src, len);
|
|
25187
|
+
const ma1 = displace(movingAverageOf(kind, sourceValues(bars, str3(inputs, "source", "Close")), bars, num3(inputs, "length", 20), opts));
|
|
25188
|
+
const plots = [{ key: "ma", title: kind, values: ma1, color: str3(inputs, "color", INFO), width: 2 }];
|
|
25189
|
+
if (bool(inputs, "showMa2", false)) {
|
|
25190
|
+
const kind2 = str3(inputs, "ma2Type", "SMA");
|
|
25191
|
+
const ma2 = displace(movingAverageOf(kind2, sourceValues(bars, str3(inputs, "ma2Source", "Close")), bars, num3(inputs, "ma2Length", 50), opts));
|
|
25192
|
+
plots.push({ key: "ma2", title: `${kind2} 2`, values: ma2, color: str3(inputs, "ma2Color", WARNING), width: 2 });
|
|
24846
25193
|
}
|
|
24847
|
-
|
|
24848
|
-
if (offset !== 0) values = shift(values, offset);
|
|
24849
|
-
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
25194
|
+
return { plots };
|
|
24850
25195
|
}
|
|
24851
25196
|
};
|
|
24852
25197
|
var smoothedMa = {
|
|
@@ -24854,17 +25199,13 @@ var smoothedMa = {
|
|
|
24854
25199
|
title: "Smoothed Moving Average",
|
|
24855
25200
|
shortTitle: "RMA",
|
|
24856
25201
|
overlay: true,
|
|
24857
|
-
inputs: [
|
|
25202
|
+
inputs: [
|
|
25203
|
+
sourceInput("Close", "source", "Source", "Price series the average smooths. Wilder computed his 1978 indicators on the close, but the recursion accepts any source."),
|
|
25204
|
+
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."),
|
|
25205
|
+
...directionInputs()
|
|
25206
|
+
],
|
|
24858
25207
|
compute: (bars, inputs) => ({
|
|
24859
|
-
plots: [
|
|
24860
|
-
{
|
|
24861
|
-
key: "rma",
|
|
24862
|
-
title: "RMA",
|
|
24863
|
-
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
24864
|
-
color: str3(inputs, "color", SERIES_LINE),
|
|
24865
|
-
width: 2
|
|
24866
|
-
}
|
|
24867
|
-
]
|
|
25208
|
+
plots: [slopeColoredPlot("rma", "RMA", rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)), inputs)]
|
|
24868
25209
|
})
|
|
24869
25210
|
};
|
|
24870
25211
|
var zlema = {
|
|
@@ -24872,61 +25213,86 @@ var zlema = {
|
|
|
24872
25213
|
title: "Zero-Lag Exponential Moving Average",
|
|
24873
25214
|
shortTitle: "ZLEMA",
|
|
24874
25215
|
overlay: true,
|
|
24875
|
-
inputs: [
|
|
25216
|
+
inputs: [
|
|
25217
|
+
sourceInput("Close", "source", "Source", "Price series the average de-lags and then smooths."),
|
|
25218
|
+
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."),
|
|
25219
|
+
...directionInputs()
|
|
25220
|
+
],
|
|
24876
25221
|
compute: (bars, inputs) => {
|
|
24877
|
-
const len = num3(inputs, "length",
|
|
25222
|
+
const len = num3(inputs, "length", 21);
|
|
24878
25223
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
24879
|
-
const lag = Math.
|
|
24880
|
-
const
|
|
24881
|
-
|
|
24882
|
-
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
25224
|
+
const lag = Math.round((len - 1) / 2);
|
|
25225
|
+
const delagged = zip(src, shift(src, lag), (a, b) => 2 * a - b);
|
|
25226
|
+
return { plots: [slopeColoredPlot("zlema", "ZLEMA", ema(delagged, len), inputs)] };
|
|
24883
25227
|
}
|
|
24884
25228
|
};
|
|
25229
|
+
var MOMENTUM_SCALER = "Momentum (|CMO|)";
|
|
25230
|
+
var VOLATILITY_SCALER = "Volatility (StDev Ratio)";
|
|
25231
|
+
var REGIME = "Trend Regime";
|
|
24885
25232
|
var vidya = {
|
|
24886
25233
|
type: "vidya",
|
|
24887
25234
|
title: "Variable Index Dynamic Average",
|
|
24888
25235
|
shortTitle: "VIDYA",
|
|
24889
25236
|
overlay: true,
|
|
24890
|
-
inputs: [
|
|
25237
|
+
inputs: [
|
|
25238
|
+
sourceInput("Close", "source", "Source", "Price series the average smooths. VIDYA is classically computed on the close."),
|
|
25239
|
+
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."),
|
|
25240
|
+
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."),
|
|
25241
|
+
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."),
|
|
25242
|
+
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."),
|
|
25243
|
+
{ ...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 },
|
|
25244
|
+
{ ...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 },
|
|
25245
|
+
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."),
|
|
25246
|
+
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."),
|
|
25247
|
+
colorInput(BULLISH, "Bullish", "bullColor"),
|
|
25248
|
+
colorInput(BEARISH, "Bearish", "bearColor"),
|
|
25249
|
+
colorInput(NEUTRAL, "Flat", "flatColor"),
|
|
25250
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor"),
|
|
25251
|
+
widthInput()
|
|
25252
|
+
],
|
|
24891
25253
|
compute: (bars, inputs) => {
|
|
24892
|
-
const len = num3(inputs, "length",
|
|
24893
|
-
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
25254
|
+
const len = num3(inputs, "length", 12);
|
|
24894
25255
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
24895
|
-
const
|
|
24896
|
-
const
|
|
24897
|
-
const
|
|
24898
|
-
|
|
24899
|
-
|
|
24900
|
-
|
|
24901
|
-
|
|
24902
|
-
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
24903
|
-
const x = d[k];
|
|
24904
|
-
if (!Number.isFinite(x)) {
|
|
24905
|
-
ok = false;
|
|
24906
|
-
break;
|
|
24907
|
-
}
|
|
24908
|
-
if (x > 0) u += x;
|
|
24909
|
-
else w -= x;
|
|
24910
|
-
}
|
|
24911
|
-
if (ok) {
|
|
24912
|
-
su[i] = u;
|
|
24913
|
-
sd[i] = w;
|
|
24914
|
-
}
|
|
24915
|
-
}
|
|
24916
|
-
const alpha = 2 / (len + 1);
|
|
25256
|
+
const scalerLen = num3(inputs, "scalerLength", 9);
|
|
25257
|
+
const momentum = map(cmo(src, scalerLen), (x) => Math.abs(x) / 100);
|
|
25258
|
+
const shortDev = stdev(src, scalerLen);
|
|
25259
|
+
const refDev = stdev(src, num3(inputs, "refLength", 30));
|
|
25260
|
+
const volatility = zip(shortDev, refDev, (s, r) => r > 0 ? s / r : 0);
|
|
25261
|
+
const scale = str3(inputs, "scaler", MOMENTUM_SCALER) === VOLATILITY_SCALER ? volatility : momentum;
|
|
25262
|
+
const alphaBase = 2 / (len + 1);
|
|
24917
25263
|
const out = new Array(src.length).fill(Number.NaN);
|
|
24918
25264
|
let prev2 = Number.NaN;
|
|
24919
25265
|
for (let i = 0; i < src.length; i++) {
|
|
24920
25266
|
const x = src[i];
|
|
24921
|
-
|
|
24922
|
-
const
|
|
24923
|
-
if (!Number.isFinite(
|
|
24924
|
-
|
|
24925
|
-
|
|
24926
|
-
|
|
25267
|
+
if (!Number.isFinite(x)) continue;
|
|
25268
|
+
const k = scale[i];
|
|
25269
|
+
if (!Number.isFinite(prev2) || !Number.isFinite(k)) prev2 = x;
|
|
25270
|
+
else {
|
|
25271
|
+
const alpha = Math.min(alphaBase * k, 1);
|
|
25272
|
+
prev2 = alpha * x + (1 - alpha) * prev2;
|
|
25273
|
+
}
|
|
24927
25274
|
out[i] = prev2;
|
|
24928
25275
|
}
|
|
24929
|
-
|
|
25276
|
+
const neutral = str3(inputs, "neutralColor", NEUTRAL);
|
|
25277
|
+
if (!bool(inputs, "trendColor", true)) {
|
|
25278
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: neutral, width: num3(inputs, "lineWidth", 2) }] };
|
|
25279
|
+
}
|
|
25280
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
25281
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
25282
|
+
const flat = str3(inputs, "flatColor", NEUTRAL);
|
|
25283
|
+
const threshold = num3(inputs, "flatThreshold", 0.05);
|
|
25284
|
+
const range = atr(bars, num3(inputs, "atrLength", 14));
|
|
25285
|
+
const fade = bool(inputs, "adaptiveGradient", true);
|
|
25286
|
+
const colors = out.map((x, i) => {
|
|
25287
|
+
if (!Number.isFinite(x)) return null;
|
|
25288
|
+
const r = range[i];
|
|
25289
|
+
const slope = i > 0 && Number.isFinite(out[i - 1]) ? x - out[i - 1] : 0;
|
|
25290
|
+
const normalized = Number.isFinite(r) && r !== 0 ? slope / r : 0;
|
|
25291
|
+
const ink = normalized > threshold ? bull : normalized < -threshold ? bear : null;
|
|
25292
|
+
if (ink == null) return flat;
|
|
25293
|
+
return fade ? gradient(Math.min(scale[i] ?? 0, 1), 0, 1, flat, ink) : ink;
|
|
25294
|
+
});
|
|
25295
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: flat, width: num3(inputs, "lineWidth", 2), colors }] };
|
|
24930
25296
|
}
|
|
24931
25297
|
};
|
|
24932
25298
|
var maEnvelope = {
|
|
@@ -24935,115 +25301,218 @@ var maEnvelope = {
|
|
|
24935
25301
|
shortTitle: "MA Env",
|
|
24936
25302
|
overlay: true,
|
|
24937
25303
|
inputs: [
|
|
24938
|
-
|
|
24939
|
-
lengthInput(20),
|
|
24940
|
-
sourceInput(),
|
|
24941
|
-
|
|
24942
|
-
|
|
25304
|
+
optionInput("maType", "MA Type", "SMA", ENVELOPE_MA_TYPES, "Moving average type used as the envelope basis."),
|
|
25305
|
+
{ ...lengthInput(20, "length", "Length", 5e3, "Number of bars in the basis moving average."), min: 2 },
|
|
25306
|
+
sourceInput("Close", "source", "Source", "Price series the basis moving average is calculated on."),
|
|
25307
|
+
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)."),
|
|
25308
|
+
...channelStyleInputs()
|
|
24943
25309
|
],
|
|
24944
25310
|
compute: (bars, inputs) => {
|
|
24945
|
-
const len = num3(inputs, "length", 20);
|
|
24946
25311
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
24947
|
-
const basis = str3(inputs, "maType", "SMA")
|
|
25312
|
+
const basis = movingAverageOf(str3(inputs, "maType", "SMA"), src, bars, num3(inputs, "length", 20));
|
|
24948
25313
|
const k = num3(inputs, "percent", 2.5) / 100;
|
|
24949
|
-
const ink = str3(inputs, "
|
|
24950
|
-
return
|
|
24951
|
-
plots: [
|
|
24952
|
-
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
24953
|
-
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
24954
|
-
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
24955
|
-
],
|
|
24956
|
-
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
24957
|
-
};
|
|
25314
|
+
const ink = { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "basisColor", NEUTRAL) };
|
|
25315
|
+
return directionalChannel(bars, basis, map(basis, (x) => x * (1 + k)), map(basis, (x) => x * (1 - k)), ink, "envelope");
|
|
24958
25316
|
}
|
|
24959
25317
|
};
|
|
25318
|
+
var LINREG_CURVE = "Curve";
|
|
25319
|
+
var LINREG_SLOPE = "Slope";
|
|
24960
25320
|
var linearRegression = {
|
|
24961
25321
|
type: "linear-regression",
|
|
24962
25322
|
title: "Linear Regression Curve",
|
|
24963
25323
|
shortTitle: "LinReg",
|
|
24964
|
-
|
|
24965
|
-
|
|
24966
|
-
|
|
24967
|
-
plots
|
|
24968
|
-
|
|
24969
|
-
|
|
24970
|
-
|
|
24971
|
-
|
|
24972
|
-
|
|
24973
|
-
|
|
24974
|
-
|
|
24975
|
-
|
|
24976
|
-
|
|
25324
|
+
// Slope lives in its own pane; Curve force-overlays itself onto the price candles.
|
|
25325
|
+
overlay: false,
|
|
25326
|
+
inputs: [
|
|
25327
|
+
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."),
|
|
25328
|
+
{ ...lengthInput(100, "length", "Length", 5e3, "Number of bars used to fit the linear regression."), min: 2 },
|
|
25329
|
+
sourceInput("Close", "source", "Source", "Price series the regression is fitted to."),
|
|
25330
|
+
colorInput(BULLISH, "Rising", "bullColor", "Color of the regression curve and slope line while the fit slopes upward."),
|
|
25331
|
+
colorInput(BEARISH, "Falling", "bearColor", "Color of the regression curve and slope line while the fit slopes downward."),
|
|
25332
|
+
colorInput(NEUTRAL, "Regression Line", "lineColor", "Color of the regression curve and slope line while the fit is flat.")
|
|
25333
|
+
],
|
|
25334
|
+
compute: (bars, inputs) => {
|
|
25335
|
+
const len = num3(inputs, "length", 100);
|
|
25336
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
25337
|
+
const curve = linreg(src, len, 0);
|
|
25338
|
+
const slope = zip(curve, linreg(src, len, 1), (a, b) => a - b);
|
|
25339
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
25340
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
25341
|
+
const flat = str3(inputs, "lineColor", NEUTRAL);
|
|
25342
|
+
const colors = slope.map((s) => Number.isFinite(s) ? s > 0 ? bull : s < 0 ? bear : flat : null);
|
|
25343
|
+
if (str3(inputs, "mode", LINREG_CURVE) === LINREG_SLOPE) {
|
|
25344
|
+
return {
|
|
25345
|
+
plots: [{ key: "slope", title: "Slope", values: slope, color: flat, width: 2, colors }],
|
|
25346
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed" }]
|
|
25347
|
+
};
|
|
25348
|
+
}
|
|
25349
|
+
return { plots: [{ key: "curve", title: "Curve", values: curve, color: flat, width: 2, colors, overlay: true }] };
|
|
25350
|
+
}
|
|
24977
25351
|
};
|
|
24978
25352
|
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
24979
25353
|
|
|
24980
25354
|
// src/core/native-indicators/classics/bands.ts
|
|
25355
|
+
var JAW_COLOR = "#ab47bc";
|
|
25356
|
+
var TEETH_COLOR = WARNING;
|
|
25357
|
+
var LIPS_COLOR = "#ffd700";
|
|
24981
25358
|
function bollinger(bars, inputs) {
|
|
24982
25359
|
const len = num3(inputs, "length", 20);
|
|
24983
25360
|
const mult = num3(inputs, "mult", 2);
|
|
24984
25361
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
24985
25362
|
const basis = sma(src, len);
|
|
24986
25363
|
const dev = map(stdev(src, len), (x) => x * mult);
|
|
24987
|
-
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
25364
|
+
return { src, basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
25365
|
+
}
|
|
25366
|
+
function bollingerInputs(multMin, multStep) {
|
|
25367
|
+
return [
|
|
25368
|
+
{ ...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 },
|
|
25369
|
+
sourceInput("Close", "source", "Source", "Price series the bands are computed from."),
|
|
25370
|
+
floatInput("mult", "Multiplier", 2, multMin, 50, multStep, "Standard deviation multiplier for the upper and lower bands.")
|
|
25371
|
+
];
|
|
24988
25372
|
}
|
|
24989
|
-
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
24990
25373
|
var bollingerBands = {
|
|
24991
25374
|
type: "bollinger-bands",
|
|
24992
25375
|
title: "Bollinger Bands",
|
|
24993
25376
|
shortTitle: "BB",
|
|
24994
25377
|
overlay: true,
|
|
24995
|
-
inputs: [
|
|
25378
|
+
inputs: [
|
|
25379
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used to calculate the basis SMA and the standard deviation."),
|
|
25380
|
+
sourceInput("Close", "source", "Source", "Price series the bands are calculated on."),
|
|
25381
|
+
floatInput("mult", "Multiplier", 2, 0, 50, 0.1, "Number of standard deviations added above and below the basis to form the bands."),
|
|
25382
|
+
colorInput(WARNING, "Basis", "basisColor", "Color of the basis (middle band) moving average."),
|
|
25383
|
+
colorInput(INFO, "Bands", "color", "Color of the upper and lower bands."),
|
|
25384
|
+
colorInput(transp(INFO, 92), "Fill", "fillColor", "Fill color of the area between the upper and lower bands.")
|
|
25385
|
+
],
|
|
24996
25386
|
compute: (bars, inputs) => {
|
|
24997
25387
|
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
24998
|
-
const ink = str3(inputs, "color",
|
|
25388
|
+
const ink = str3(inputs, "color", INFO);
|
|
24999
25389
|
return {
|
|
25000
25390
|
plots: [
|
|
25001
|
-
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
25391
|
+
{ key: "basis", title: "Basis", values: basis, color: str3(inputs, "basisColor", WARNING) },
|
|
25002
25392
|
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
25003
25393
|
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
25004
25394
|
],
|
|
25005
|
-
bands: [{ key: "bb", from: "upper", to: "lower", color:
|
|
25395
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: str3(inputs, "fillColor", transp(INFO, 92)) }]
|
|
25006
25396
|
};
|
|
25007
25397
|
}
|
|
25008
25398
|
};
|
|
25399
|
+
var PERCENT_B_MID = 0.5;
|
|
25400
|
+
var THRESHOLDS = "Thresholds";
|
|
25009
25401
|
var percentB = {
|
|
25010
25402
|
type: "percent-b",
|
|
25011
25403
|
title: "Bollinger Bands %B",
|
|
25012
25404
|
shortTitle: "%B",
|
|
25013
25405
|
overlay: false,
|
|
25014
|
-
inputs: [
|
|
25406
|
+
inputs: [
|
|
25407
|
+
...bollingerInputs(1e-3, 0.25),
|
|
25408
|
+
{ ...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 },
|
|
25409
|
+
{ ...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 },
|
|
25410
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of %B above the 0.5 midline, where price trades in the upper half of the bands."),
|
|
25411
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of %B below the 0.5 midline, where price trades in the lower half of the bands."),
|
|
25412
|
+
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."),
|
|
25413
|
+
colorInput(NEUTRAL, "Levels Color", "levelsColor", "Color of the dashed overbought/oversold levels and of the dotted midline.")
|
|
25414
|
+
],
|
|
25015
25415
|
compute: (bars, inputs) => {
|
|
25016
|
-
const { upper, lower } = bollinger(bars, inputs);
|
|
25017
|
-
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
25416
|
+
const { src, upper, lower } = bollinger(bars, inputs);
|
|
25018
25417
|
const values = src.map((x, i) => {
|
|
25019
25418
|
const u = upper[i];
|
|
25020
25419
|
const l = lower[i];
|
|
25021
25420
|
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
25022
25421
|
});
|
|
25422
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
25423
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
25424
|
+
const levels = str3(inputs, "levelsColor", NEUTRAL);
|
|
25425
|
+
const colors = values.map((x) => Number.isFinite(x) ? x > PERCENT_B_MID ? bull : bear : null);
|
|
25426
|
+
const plots = [
|
|
25427
|
+
{ key: "pb", title: "%B", values, color: bear, colors },
|
|
25428
|
+
{ 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 } }
|
|
25429
|
+
];
|
|
25430
|
+
const bands = bool(inputs, "gradientFill", true) ? [
|
|
25431
|
+
{
|
|
25432
|
+
key: "pbFill",
|
|
25433
|
+
from: "pb",
|
|
25434
|
+
to: "mid",
|
|
25435
|
+
color: transp(NEUTRAL, 100),
|
|
25436
|
+
// Opaque at the oscillator, invisible where it meets the midline.
|
|
25437
|
+
gradient: values.map((x) => {
|
|
25438
|
+
if (!Number.isFinite(x)) return null;
|
|
25439
|
+
const up = x > PERCENT_B_MID;
|
|
25440
|
+
const ink = up ? bull : bear;
|
|
25441
|
+
return {
|
|
25442
|
+
topValue: Math.max(x, PERCENT_B_MID),
|
|
25443
|
+
bottomValue: Math.min(x, PERCENT_B_MID),
|
|
25444
|
+
topColor: up ? transp(ink, 50) : transp(ink, 100),
|
|
25445
|
+
bottomColor: up ? transp(ink, 100) : transp(ink, 50)
|
|
25446
|
+
};
|
|
25447
|
+
})
|
|
25448
|
+
}
|
|
25449
|
+
] : [];
|
|
25023
25450
|
return {
|
|
25024
|
-
plots
|
|
25451
|
+
plots,
|
|
25452
|
+
bands,
|
|
25025
25453
|
levels: [
|
|
25026
|
-
{ key: "
|
|
25027
|
-
{ key: "
|
|
25028
|
-
{ key: "
|
|
25454
|
+
{ key: "overbought", price: num3(inputs, "overbought", 1), color: levels, lineStyle: "dashed", title: "Overbought" },
|
|
25455
|
+
{ key: "oversold", price: num3(inputs, "oversold", 0), color: levels, lineStyle: "dashed", title: "Oversold" },
|
|
25456
|
+
{ key: "midline", price: PERCENT_B_MID, color: transp(levels, 50), lineStyle: "dotted", title: "Midline" }
|
|
25029
25457
|
]
|
|
25030
25458
|
};
|
|
25031
25459
|
}
|
|
25032
25460
|
};
|
|
25461
|
+
var SQUEEZE_BULGE = "Squeeze & Bulge";
|
|
25033
25462
|
var bandwidth = {
|
|
25034
25463
|
type: "bandwidth",
|
|
25035
25464
|
title: "Bollinger Bands Width",
|
|
25036
25465
|
shortTitle: "BandWidth",
|
|
25037
25466
|
overlay: false,
|
|
25038
|
-
inputs: [
|
|
25467
|
+
inputs: [
|
|
25468
|
+
...bollingerInputs(1e-3, 0.25),
|
|
25469
|
+
{ ...intInput("lookback", "Reference Lookback", 125, 2, 5e3, "Window used to locate the lowest (Squeeze) and highest (Bulge) BandWidth references."), group: SQUEEZE_BULGE },
|
|
25470
|
+
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."),
|
|
25471
|
+
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."),
|
|
25472
|
+
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."),
|
|
25473
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between zero and BandWidth with a vertical gradient, fully transparent at zero."),
|
|
25474
|
+
colorInput(WARNING, "Squeeze Level", "squeezeColor", "Color of the lowest-BandWidth reference line."),
|
|
25475
|
+
colorInput(BEARISH, "Bulge Level", "bulgeColor", "Color of the highest-BandWidth reference line.")
|
|
25476
|
+
],
|
|
25039
25477
|
compute: (bars, inputs) => {
|
|
25040
25478
|
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
25041
25479
|
const values = basis.map((b, i) => {
|
|
25042
25480
|
const u = upper[i];
|
|
25043
25481
|
const l = lower[i];
|
|
25044
|
-
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b
|
|
25482
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b : Number.NaN;
|
|
25045
25483
|
});
|
|
25046
|
-
|
|
25484
|
+
const lookback = num3(inputs, "lookback", 125);
|
|
25485
|
+
const squeezeLevel = lowest(values, lookback);
|
|
25486
|
+
const bulgeLevel = highest(values, lookback);
|
|
25487
|
+
const base = str3(inputs, "color", NEUTRAL);
|
|
25488
|
+
const squeezeInk = str3(inputs, "squeezeColor", WARNING);
|
|
25489
|
+
const bulgeInk = str3(inputs, "bulgeColor", BEARISH);
|
|
25490
|
+
const colors = values.map((x, i) => {
|
|
25491
|
+
if (!Number.isFinite(x)) return null;
|
|
25492
|
+
if (x <= squeezeLevel[i]) return squeezeInk;
|
|
25493
|
+
if (x >= bulgeLevel[i]) return bulgeInk;
|
|
25494
|
+
return base;
|
|
25495
|
+
});
|
|
25496
|
+
const plots = [
|
|
25497
|
+
{ key: "bbw", title: "BandWidth", values, color: base, colors },
|
|
25498
|
+
{ key: "zero", title: "Zero Line", values: values.map((x) => Number.isFinite(x) ? 0 : Number.NaN), color: transp(NEUTRAL, 90) }
|
|
25499
|
+
];
|
|
25500
|
+
if (bool(inputs, "showSqueeze", true)) plots.push({ key: "squeeze", title: "Squeeze Level", values: squeezeLevel, color: squeezeInk });
|
|
25501
|
+
if (bool(inputs, "showBulge", true)) plots.push({ key: "bulge", title: "Bulge Level", values: bulgeLevel, color: bulgeInk });
|
|
25502
|
+
const bands = bool(inputs, "gradientFill", true) ? [
|
|
25503
|
+
{
|
|
25504
|
+
key: "bbwFill",
|
|
25505
|
+
from: "bbw",
|
|
25506
|
+
to: "zero",
|
|
25507
|
+
color: transp(NEUTRAL, 100),
|
|
25508
|
+
gradient: values.map((x, i) => {
|
|
25509
|
+
if (!Number.isFinite(x)) return null;
|
|
25510
|
+
const ink = colors[i] ?? base;
|
|
25511
|
+
return { topValue: x, bottomValue: 0, topColor: transp(ink, 50), bottomColor: transp(ink, 100) };
|
|
25512
|
+
})
|
|
25513
|
+
}
|
|
25514
|
+
] : [];
|
|
25515
|
+
return { plots, bands };
|
|
25047
25516
|
}
|
|
25048
25517
|
};
|
|
25049
25518
|
var keltner = {
|
|
@@ -25052,26 +25521,17 @@ var keltner = {
|
|
|
25052
25521
|
shortTitle: "KC",
|
|
25053
25522
|
overlay: true,
|
|
25054
25523
|
inputs: [
|
|
25055
|
-
lengthInput(20),
|
|
25056
|
-
|
|
25057
|
-
|
|
25058
|
-
sourceInput(),
|
|
25059
|
-
|
|
25524
|
+
lengthInput(20, "length", "EMA Length", 5e3, "Number of bars used to compute the exponential moving average basis line."),
|
|
25525
|
+
intInput("atrLength", "ATR Length", 10, 1, 5e3, "Number of bars used to compute the Average True Range (Wilder smoothing)."),
|
|
25526
|
+
floatInput("mult", "Multiplier", 2, 0, 50, 0.1, "ATR multiple used to offset the upper and lower bands from the basis line."),
|
|
25527
|
+
sourceInput("Close", "source", "Source", "Price series used to compute the basis line."),
|
|
25528
|
+
...channelStyleInputs("Basis Color")
|
|
25060
25529
|
],
|
|
25061
25530
|
compute: (bars, inputs) => {
|
|
25062
|
-
const
|
|
25063
|
-
const
|
|
25064
|
-
const
|
|
25065
|
-
|
|
25066
|
-
const ink = str3(inputs, "color", SERIES_LINE);
|
|
25067
|
-
return {
|
|
25068
|
-
plots: [
|
|
25069
|
-
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
25070
|
-
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
25071
|
-
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
25072
|
-
],
|
|
25073
|
-
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
25074
|
-
};
|
|
25531
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20));
|
|
25532
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * num3(inputs, "mult", 2));
|
|
25533
|
+
const ink = { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "basisColor", NEUTRAL) };
|
|
25534
|
+
return directionalChannel(bars, basis, zip(basis, range, (b, r) => b + r), zip(basis, range, (b, r) => b - r), ink, "kc");
|
|
25075
25535
|
}
|
|
25076
25536
|
};
|
|
25077
25537
|
var donchian = {
|
|
@@ -25079,19 +25539,24 @@ var donchian = {
|
|
|
25079
25539
|
title: "Donchian Channels",
|
|
25080
25540
|
shortTitle: "DC",
|
|
25081
25541
|
overlay: true,
|
|
25082
|
-
inputs: [
|
|
25542
|
+
inputs: [
|
|
25543
|
+
lengthInput(20, "length", "Length", 5e3, "Lookback length. The upper and lower channels are the highest high and lowest low over this many bars."),
|
|
25544
|
+
colorInput(BEARISH, "Upper", "upperColor", "Color of the upper channel line."),
|
|
25545
|
+
colorInput(NEUTRAL, "Middle", "middleColor", "Color of the channel midline."),
|
|
25546
|
+
colorInput(BULLISH, "Lower", "lowerColor", "Color of the lower channel line."),
|
|
25547
|
+
colorInput(transp(NEUTRAL, 92), "Fill", "fillColor", "Color of the channel interior fill.")
|
|
25548
|
+
],
|
|
25083
25549
|
compute: (bars, inputs) => {
|
|
25084
25550
|
const len = num3(inputs, "length", 20);
|
|
25085
25551
|
const upper = highest(highs(bars), len);
|
|
25086
25552
|
const lower = lowest(lows(bars), len);
|
|
25087
|
-
const ink = str3(inputs, "color", SERIES_LINE);
|
|
25088
25553
|
return {
|
|
25089
25554
|
plots: [
|
|
25090
|
-
{ key: "
|
|
25091
|
-
{ key: "
|
|
25092
|
-
{ key: "lower", title: "Lower", values: lower, color:
|
|
25555
|
+
{ key: "upper", title: "Upper", values: upper, color: str3(inputs, "upperColor", BEARISH) },
|
|
25556
|
+
{ key: "middle", title: "Middle", kind: "circles", values: zip(upper, lower, (u, l) => (u + l) / 2), color: str3(inputs, "middleColor", NEUTRAL) },
|
|
25557
|
+
{ key: "lower", title: "Lower", values: lower, color: str3(inputs, "lowerColor", BULLISH) }
|
|
25093
25558
|
],
|
|
25094
|
-
bands: [{ key: "dc", from: "upper", to: "lower", color:
|
|
25559
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: str3(inputs, "fillColor", transp(NEUTRAL, 92)) }]
|
|
25095
25560
|
};
|
|
25096
25561
|
}
|
|
25097
25562
|
};
|
|
@@ -25100,16 +25565,37 @@ var chandelier = {
|
|
|
25100
25565
|
title: "Chandelier Exit",
|
|
25101
25566
|
shortTitle: "CE",
|
|
25102
25567
|
overlay: true,
|
|
25103
|
-
inputs: [
|
|
25568
|
+
inputs: [
|
|
25569
|
+
lengthInput(22, "length", "Length", 5e3, "Lookback used for both the highest high / lowest low extremes and the ATR."),
|
|
25570
|
+
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)."),
|
|
25571
|
+
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."),
|
|
25572
|
+
colorInput(BULLISH, "Long Stop", "longColor", "Color of the long trailing stop line."),
|
|
25573
|
+
colorInput(BEARISH, "Short Stop", "shortColor", "Color of the short trailing stop line.")
|
|
25574
|
+
],
|
|
25104
25575
|
compute: (bars, inputs) => {
|
|
25105
25576
|
const len = num3(inputs, "length", 22);
|
|
25106
25577
|
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
25107
|
-
const
|
|
25108
|
-
const
|
|
25578
|
+
const basicLong = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
25579
|
+
const basicShort = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
25580
|
+
let long = basicLong;
|
|
25581
|
+
let short = basicShort;
|
|
25582
|
+
if (bool(inputs, "ratchet", false)) {
|
|
25583
|
+
long = new Array(bars.length).fill(Number.NaN);
|
|
25584
|
+
short = new Array(bars.length).fill(Number.NaN);
|
|
25585
|
+
for (let i = 0; i < bars.length; i++) {
|
|
25586
|
+
const bl = basicLong[i];
|
|
25587
|
+
const bs = basicShort[i];
|
|
25588
|
+
const prevClose = i > 0 ? bars[i - 1].close : bars[i].close;
|
|
25589
|
+
const prevLong = i > 0 && Number.isFinite(long[i - 1]) ? long[i - 1] : bl;
|
|
25590
|
+
const prevShort = i > 0 && Number.isFinite(short[i - 1]) ? short[i - 1] : bs;
|
|
25591
|
+
long[i] = Number.isFinite(bl) ? prevClose > prevLong ? Math.max(bl, prevLong) : bl : Number.NaN;
|
|
25592
|
+
short[i] = Number.isFinite(bs) ? prevClose < prevShort ? Math.min(bs, prevShort) : bs : Number.NaN;
|
|
25593
|
+
}
|
|
25594
|
+
}
|
|
25109
25595
|
return {
|
|
25110
25596
|
plots: [
|
|
25111
|
-
{ key: "long", title: "Long
|
|
25112
|
-
{ key: "short", title: "Short
|
|
25597
|
+
{ key: "long", title: "Long Stop", values: long, color: str3(inputs, "longColor", BULLISH) },
|
|
25598
|
+
{ key: "short", title: "Short Stop", values: short, color: str3(inputs, "shortColor", BEARISH) }
|
|
25113
25599
|
]
|
|
25114
25600
|
};
|
|
25115
25601
|
}
|
|
@@ -25120,21 +25606,22 @@ var chandeKroll = {
|
|
|
25120
25606
|
shortTitle: "CKS",
|
|
25121
25607
|
overlay: true,
|
|
25122
25608
|
inputs: [
|
|
25123
|
-
|
|
25124
|
-
|
|
25125
|
-
|
|
25609
|
+
intInput("p", "ATR Length (P)", 10, 1, 5e3, "Lookback used for both the ATR and the highest high / lowest low price extremes."),
|
|
25610
|
+
floatInput("x", "ATR Multiplier (X)", 1, 0, 50, 0.1, "Multiplier applied to the ATR to offset the preliminary stops from the price extremes."),
|
|
25611
|
+
intInput("q", "Stop Length (Q)", 9, 1, 5e3, "Lookback over which the preliminary stops are extended into the final stop levels."),
|
|
25612
|
+
colorInput(BEARISH, "Stop Short", "shortColor", "Color of the protective stop line for short positions."),
|
|
25613
|
+
colorInput(BULLISH, "Stop Long", "longColor", "Color of the protective stop line for long positions.")
|
|
25126
25614
|
],
|
|
25127
25615
|
compute: (bars, inputs) => {
|
|
25128
25616
|
const p = num3(inputs, "p", 10);
|
|
25129
|
-
const x = num3(inputs, "x", 1);
|
|
25130
25617
|
const q = num3(inputs, "q", 9);
|
|
25131
|
-
const range = map(atr(bars, p), (v) => v * x);
|
|
25618
|
+
const range = map(atr(bars, p), (v) => v * num3(inputs, "x", 1));
|
|
25132
25619
|
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
25133
25620
|
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
25134
25621
|
return {
|
|
25135
25622
|
plots: [
|
|
25136
|
-
{ key: "stopShort", title: "Stop
|
|
25137
|
-
{ key: "stopLong", title: "Stop
|
|
25623
|
+
{ key: "stopShort", title: "Stop Short", values: highest(firstHigh, q), color: str3(inputs, "shortColor", BEARISH) },
|
|
25624
|
+
{ key: "stopLong", title: "Stop Long", values: lowest(firstLow, q), color: str3(inputs, "longColor", BULLISH) }
|
|
25138
25625
|
]
|
|
25139
25626
|
};
|
|
25140
25627
|
}
|
|
@@ -25145,36 +25632,42 @@ var supertrend = {
|
|
|
25145
25632
|
shortTitle: "SuperTrend",
|
|
25146
25633
|
overlay: true,
|
|
25147
25634
|
inputs: [
|
|
25148
|
-
|
|
25149
|
-
|
|
25635
|
+
intInput("atrLength", "ATR Length", 10, 1, 5e3, "Number of bars used to compute the Wilder ATR."),
|
|
25636
|
+
floatInput("mult", "Factor", 3, 0, 50, 0.1, "ATR multiplier setting how far the trailing stop sits from the bar midpoint."),
|
|
25637
|
+
colorInput(BULLISH, "Uptrend Color", "upColor", "Line color while the trend is up."),
|
|
25638
|
+
colorInput(BEARISH, "Downtrend Color", "downColor", "Line color while the trend is down.")
|
|
25150
25639
|
],
|
|
25151
25640
|
compute: (bars, inputs) => {
|
|
25152
|
-
const len = num3(inputs, "atrLength", 10);
|
|
25153
25641
|
const mult = num3(inputs, "mult", 3);
|
|
25154
|
-
const range = atr(bars,
|
|
25642
|
+
const range = atr(bars, num3(inputs, "atrLength", 10));
|
|
25155
25643
|
const n = bars.length;
|
|
25156
|
-
const
|
|
25157
|
-
const
|
|
25158
|
-
let
|
|
25159
|
-
let
|
|
25160
|
-
let trend = 1;
|
|
25644
|
+
const up = new Array(n).fill(Number.NaN);
|
|
25645
|
+
const down = new Array(n).fill(Number.NaN);
|
|
25646
|
+
let finalUpper = Number.NaN;
|
|
25647
|
+
let finalLower = Number.NaN;
|
|
25648
|
+
let trend = -1;
|
|
25161
25649
|
for (let i = 0; i < n; i++) {
|
|
25162
25650
|
const r = range[i];
|
|
25163
25651
|
if (!Number.isFinite(r)) continue;
|
|
25164
25652
|
const b = bars[i];
|
|
25165
25653
|
const mid = (b.high + b.low) / 2;
|
|
25166
|
-
const
|
|
25167
|
-
const
|
|
25654
|
+
const basicUpper = mid + mult * r;
|
|
25655
|
+
const basicLower = mid - mult * r;
|
|
25656
|
+
const prevUpper = Number.isFinite(finalUpper) ? finalUpper : basicUpper;
|
|
25657
|
+
const prevLower = Number.isFinite(finalLower) ? finalLower : basicLower;
|
|
25168
25658
|
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
25169
|
-
|
|
25170
|
-
|
|
25171
|
-
|
|
25172
|
-
|
|
25173
|
-
|
|
25174
|
-
st[i] = trend === 1 ? up : dn;
|
|
25659
|
+
finalUpper = prevClose <= prevUpper ? Math.min(basicUpper, prevUpper) : basicUpper;
|
|
25660
|
+
finalLower = prevClose >= prevLower ? Math.max(basicLower, prevLower) : basicLower;
|
|
25661
|
+
trend = b.close > finalUpper ? 1 : b.close < finalLower ? -1 : trend;
|
|
25662
|
+
if (trend === 1) up[i] = finalLower;
|
|
25663
|
+
else down[i] = finalUpper;
|
|
25175
25664
|
}
|
|
25176
|
-
|
|
25177
|
-
|
|
25665
|
+
return {
|
|
25666
|
+
plots: [
|
|
25667
|
+
{ key: "up", title: "Uptrend", values: up, color: str3(inputs, "upColor", BULLISH) },
|
|
25668
|
+
{ key: "down", title: "Downtrend", values: down, color: str3(inputs, "downColor", BEARISH) }
|
|
25669
|
+
]
|
|
25670
|
+
};
|
|
25178
25671
|
}
|
|
25179
25672
|
};
|
|
25180
25673
|
var alligator = {
|
|
@@ -25183,12 +25676,15 @@ var alligator = {
|
|
|
25183
25676
|
shortTitle: "Alligator",
|
|
25184
25677
|
overlay: true,
|
|
25185
25678
|
inputs: [
|
|
25186
|
-
|
|
25187
|
-
|
|
25188
|
-
|
|
25189
|
-
|
|
25190
|
-
|
|
25191
|
-
|
|
25679
|
+
intInput("jawLength", "Jaw Length", 13, 1, 5e3, "Smoothing length of the Jaw SMMA of median price (hl2), 13 in the classic setup."),
|
|
25680
|
+
intInput("teethLength", "Teeth Length", 8, 1, 5e3, "Smoothing length of the Teeth SMMA of median price (hl2), 8 in the classic setup."),
|
|
25681
|
+
intInput("lipsLength", "Lips Length", 5, 1, 5e3, "Smoothing length of the Lips SMMA of median price (hl2), 5 in the classic setup."),
|
|
25682
|
+
intInput("jawOffset", "Jaw Offset", 8, 0, 100, "Number of bars the Jaw plot is shifted into the future, 8 in the classic setup."),
|
|
25683
|
+
intInput("teethOffset", "Teeth Offset", 5, 0, 100, "Number of bars the Teeth plot is shifted into the future, 5 in the classic setup."),
|
|
25684
|
+
intInput("lipsOffset", "Lips Offset", 3, 0, 100, "Number of bars the Lips plot is shifted into the future, 3 in the classic setup."),
|
|
25685
|
+
colorInput(JAW_COLOR, "Jaw", "jawColor", "Color of the Jaw line."),
|
|
25686
|
+
colorInput(TEETH_COLOR, "Teeth", "teethColor", "Color of the Teeth line."),
|
|
25687
|
+
colorInput(LIPS_COLOR, "Lips", "lipsColor", "Color of the Lips line.")
|
|
25192
25688
|
],
|
|
25193
25689
|
compute: (bars, inputs) => {
|
|
25194
25690
|
const hl2 = sourceValues(bars, "HL2");
|
|
@@ -25197,99 +25693,134 @@ var alligator = {
|
|
|
25197
25693
|
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
25198
25694
|
return {
|
|
25199
25695
|
plots: [
|
|
25200
|
-
{ key: "jaw", title: "Jaw", values: jaw, color:
|
|
25201
|
-
{ key: "teeth", title: "Teeth", values: teeth, color:
|
|
25202
|
-
{ key: "lips", title: "Lips", values: lips, color:
|
|
25696
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: str3(inputs, "jawColor", JAW_COLOR) },
|
|
25697
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: str3(inputs, "teethColor", TEETH_COLOR) },
|
|
25698
|
+
{ key: "lips", title: "Lips", values: lips, color: str3(inputs, "lipsColor", LIPS_COLOR) }
|
|
25203
25699
|
]
|
|
25204
25700
|
};
|
|
25205
25701
|
}
|
|
25206
25702
|
};
|
|
25703
|
+
var GATOR_LINES = "Alligator Lines";
|
|
25704
|
+
var SMMA_TYPE = "SMMA (Wilder)";
|
|
25207
25705
|
var gator = {
|
|
25208
25706
|
type: "gator-oscillator",
|
|
25209
25707
|
title: "Gator Oscillator",
|
|
25210
25708
|
shortTitle: "Gator Oscillator",
|
|
25211
25709
|
overlay: false,
|
|
25212
|
-
inputs: [
|
|
25213
|
-
|
|
25214
|
-
|
|
25215
|
-
|
|
25216
|
-
|
|
25217
|
-
|
|
25710
|
+
inputs: [
|
|
25711
|
+
{ ...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 },
|
|
25712
|
+
{ ...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 },
|
|
25713
|
+
{ ...intInput("jawLength", "Jaw Length", 13, 1, 500, "Averaging length of the jaw line."), group: GATOR_LINES },
|
|
25714
|
+
{ ...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 },
|
|
25715
|
+
{ ...intInput("teethLength", "Teeth Length", 8, 1, 500, "Averaging length of the teeth line."), group: GATOR_LINES },
|
|
25716
|
+
{ ...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 },
|
|
25717
|
+
{ ...intInput("lipsLength", "Lips Length", 5, 1, 500, "Averaging length of the lips line."), group: GATOR_LINES },
|
|
25718
|
+
{ ...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 },
|
|
25719
|
+
colorInput(BULLISH, "Expanding", "growColor", "Column color while a spread grew against its own previous bar \u2014 the averages are spreading apart."),
|
|
25720
|
+
colorInput(BEARISH, "Contracting", "shrinkColor", "Column color while a spread shrank \u2014 the averages are converging. Color encodes expansion, not trade direction."),
|
|
25721
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero line separating the two histograms.")
|
|
25722
|
+
],
|
|
25723
|
+
compute: (bars, inputs) => {
|
|
25724
|
+
const src = sourceValues(bars, str3(inputs, "source", "HL2"));
|
|
25725
|
+
const kind = str3(inputs, "maType", SMMA_TYPE);
|
|
25726
|
+
const smooth = (len) => kind === "SMA" ? sma(src, len) : kind === "EMA" ? ema(src, len) : rma(src, len);
|
|
25727
|
+
const jaw = shift(smooth(num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
25728
|
+
const teeth = shift(smooth(num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
25729
|
+
const lips = shift(smooth(num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
25218
25730
|
const upper = map(sub(jaw, teeth), Math.abs);
|
|
25219
|
-
const lower = map(sub(teeth, lips),
|
|
25220
|
-
const
|
|
25221
|
-
|
|
25222
|
-
|
|
25223
|
-
|
|
25224
|
-
|
|
25731
|
+
const lower = map(sub(teeth, lips), Math.abs);
|
|
25732
|
+
const grow = str3(inputs, "growColor", BULLISH);
|
|
25733
|
+
const shrink = str3(inputs, "shrinkColor", BEARISH);
|
|
25734
|
+
const expansionColors2 = (v) => {
|
|
25735
|
+
let growing = false;
|
|
25736
|
+
return v.map((x, i) => {
|
|
25737
|
+
if (!Number.isFinite(x)) return null;
|
|
25738
|
+
const prev2 = i > 0 ? v[i - 1] : Number.NaN;
|
|
25739
|
+
if (Number.isFinite(prev2)) growing = x > prev2 ? true : x < prev2 ? false : growing;
|
|
25740
|
+
return growing ? grow : shrink;
|
|
25741
|
+
});
|
|
25742
|
+
};
|
|
25225
25743
|
return {
|
|
25226
25744
|
plots: [
|
|
25227
|
-
{ key: "upper", title: "Upper", values: upper, kind: "
|
|
25228
|
-
{ key: "lower", title: "Lower", values: lower, kind: "
|
|
25229
|
-
]
|
|
25745
|
+
{ key: "upper", title: "Upper Histogram (Jaw - Teeth)", values: upper, kind: "columns", color: grow, colors: expansionColors2(upper), base: 0 },
|
|
25746
|
+
{ key: "lower", title: "Lower Histogram (Teeth - Lips)", values: map(lower, (x) => -x), kind: "columns", color: shrink, colors: expansionColors2(lower), base: 0 }
|
|
25747
|
+
],
|
|
25748
|
+
levels: [{ key: "zero", price: 0, color: str3(inputs, "zeroColor", NEUTRAL), lineStyle: "solid", title: "Zero Line" }]
|
|
25230
25749
|
};
|
|
25231
25750
|
}
|
|
25232
25751
|
};
|
|
25233
25752
|
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
25234
25753
|
|
|
25235
25754
|
// src/core/native-indicators/classics/oscillators.ts
|
|
25236
|
-
function
|
|
25237
|
-
|
|
25238
|
-
|
|
25239
|
-
|
|
25755
|
+
function thresholdInputs(obDefault, osDefault, min = 0, max = 100, obTitle = "Overbought Level", osTitle = "Oversold Level") {
|
|
25756
|
+
return [
|
|
25757
|
+
floatInput("overbought", obTitle, obDefault, min, max, 1, "Level above which the oscillator is considered overbought."),
|
|
25758
|
+
floatInput("oversold", osTitle, osDefault, min, max, 1, "Level below which the oscillator is considered oversold.")
|
|
25240
25759
|
];
|
|
25241
|
-
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
25242
|
-
return levels;
|
|
25243
|
-
}
|
|
25244
|
-
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
25245
|
-
function histColors(values) {
|
|
25246
|
-
return values.map((x, i) => {
|
|
25247
|
-
if (!Number.isFinite(x)) return null;
|
|
25248
|
-
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
25249
|
-
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
25250
|
-
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
25251
|
-
return rising ? `${BEARISH}80` : BEARISH;
|
|
25252
|
-
});
|
|
25253
25760
|
}
|
|
25254
25761
|
var rsiSpec = {
|
|
25255
25762
|
type: "rsi",
|
|
25256
25763
|
title: "Relative Strength Index",
|
|
25257
25764
|
shortTitle: "RSI",
|
|
25258
25765
|
overlay: false,
|
|
25259
|
-
inputs: [
|
|
25260
|
-
|
|
25261
|
-
|
|
25262
|
-
|
|
25263
|
-
|
|
25264
|
-
|
|
25265
|
-
|
|
25266
|
-
|
|
25267
|
-
|
|
25268
|
-
|
|
25269
|
-
|
|
25270
|
-
|
|
25766
|
+
inputs: [
|
|
25767
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used in the Wilder smoothing of average gains and losses."),
|
|
25768
|
+
sourceInput("Close", "source", "Source", "Price series the RSI is calculated on."),
|
|
25769
|
+
...thresholdInputs(70, 30, 0, 100, "Overbought", "Oversold"),
|
|
25770
|
+
colorInput(NEUTRAL, "RSI", "color", "Color of the RSI line at the midline. Toward either threshold it blends into that threshold\u2019s color."),
|
|
25771
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level."),
|
|
25772
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
25773
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought, midline and oversold levels."),
|
|
25774
|
+
colorInput(transp(NEUTRAL, 90), "Band Fill", "fillColor", "Color of the fill between the overbought and oversold levels.")
|
|
25775
|
+
],
|
|
25776
|
+
compute: (bars, inputs) => boundedOscillator({
|
|
25777
|
+
key: "rsi",
|
|
25778
|
+
title: "RSI",
|
|
25779
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
25780
|
+
scale: { top: 100, bottom: 0 },
|
|
25781
|
+
overbought: num3(inputs, "overbought", 70),
|
|
25782
|
+
oversold: num3(inputs, "oversold", 30),
|
|
25783
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
25784
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
25785
|
+
width: 2,
|
|
25786
|
+
midline: 50,
|
|
25787
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 90))
|
|
25271
25788
|
})
|
|
25272
25789
|
};
|
|
25790
|
+
function stochasticOutput(k, d, inputs) {
|
|
25791
|
+
const overbought = num3(inputs, "overbought", 80);
|
|
25792
|
+
const oversold = num3(inputs, "oversold", 20);
|
|
25793
|
+
return {
|
|
25794
|
+
plots: [
|
|
25795
|
+
{ key: "k", title: "%K", values: k, color: str3(inputs, "kColor", INFO) },
|
|
25796
|
+
{ key: "d", title: "%D", values: d, color: str3(inputs, "dColor", WARNING) },
|
|
25797
|
+
anchorPlot("obAnchor", "Overbought Anchor", k.length, overbought),
|
|
25798
|
+
anchorPlot("osAnchor", "Oversold Anchor", k.length, oversold)
|
|
25799
|
+
],
|
|
25800
|
+
bands: [{ key: "band", from: "obAnchor", to: "osAnchor", color: str3(inputs, "fillColor", transp(INFO, 90)) }],
|
|
25801
|
+
levels: [
|
|
25802
|
+
{ key: "overbought", price: overbought, color: NEUTRAL, lineStyle: "dashed", title: "Overbought" },
|
|
25803
|
+
{ key: "oversold", price: oversold, color: NEUTRAL, lineStyle: "dashed", title: "Oversold" }
|
|
25804
|
+
]
|
|
25805
|
+
};
|
|
25806
|
+
}
|
|
25273
25807
|
var stochastic = {
|
|
25274
25808
|
type: "stochastic",
|
|
25275
25809
|
title: "Stochastic",
|
|
25276
25810
|
shortTitle: "Stoch",
|
|
25277
25811
|
overlay: false,
|
|
25278
25812
|
inputs: [
|
|
25279
|
-
lengthInput(14, "kLength", "%K
|
|
25280
|
-
lengthInput(
|
|
25281
|
-
lengthInput(3, "dLength", "%D
|
|
25813
|
+
lengthInput(14, "kLength", "%K Length", 5e3, "Number of bars used for the highest high / lowest low range of the raw %K."),
|
|
25814
|
+
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."),
|
|
25815
|
+
lengthInput(3, "dLength", "%D Smoothing", 500, "Simple moving average length of the %D signal line, applied to the smoothed %K."),
|
|
25816
|
+
...thresholdInputs(80, 20),
|
|
25817
|
+
colorInput(INFO, "%K", "kColor", "Color of the %K line."),
|
|
25818
|
+
colorInput(WARNING, "%D", "dColor", "Color of the %D signal line."),
|
|
25819
|
+
colorInput(transp(INFO, 90), "Band Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
25282
25820
|
],
|
|
25283
25821
|
compute: (bars, inputs) => {
|
|
25284
|
-
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing",
|
|
25285
|
-
|
|
25286
|
-
return {
|
|
25287
|
-
plots: [
|
|
25288
|
-
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
25289
|
-
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
25290
|
-
],
|
|
25291
|
-
levels: guideLevels(20, 80)
|
|
25292
|
-
};
|
|
25822
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
25823
|
+
return stochasticOutput(k, sma(k, num3(inputs, "dLength", 3)), inputs);
|
|
25293
25824
|
}
|
|
25294
25825
|
};
|
|
25295
25826
|
var stochasticRsi = {
|
|
@@ -25298,23 +25829,20 @@ var stochasticRsi = {
|
|
|
25298
25829
|
shortTitle: "Stoch RSI",
|
|
25299
25830
|
overlay: false,
|
|
25300
25831
|
inputs: [
|
|
25301
|
-
|
|
25302
|
-
lengthInput(14, "
|
|
25303
|
-
lengthInput(
|
|
25304
|
-
lengthInput(3, "
|
|
25305
|
-
|
|
25832
|
+
sourceInput("Close", "source", "RSI Source", "Price series the RSI is computed on."),
|
|
25833
|
+
lengthInput(14, "rsiLength", "RSI Length", 5e3, "Lookback length of the Wilder RSI."),
|
|
25834
|
+
lengthInput(14, "stochLength", "Stochastic Length", 5e3, "Lookback over which the highest and lowest RSI values are taken."),
|
|
25835
|
+
lengthInput(3, "kSmoothing", "%K Smoothing", 500, "Simple moving average length applied to the stochastic of RSI to form %K."),
|
|
25836
|
+
lengthInput(3, "dLength", "%D Smoothing", 500, "Simple moving average length applied to %K to form the %D signal line."),
|
|
25837
|
+
...thresholdInputs(80, 20),
|
|
25838
|
+
colorInput(INFO, "%K Color", "kColor", "Color of the %K line."),
|
|
25839
|
+
colorInput(WARNING, "%D Color", "dColor", "Color of the %D signal line."),
|
|
25840
|
+
colorInput(transp(INFO, 90), "Zone Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
25306
25841
|
],
|
|
25307
25842
|
compute: (bars, inputs) => {
|
|
25308
25843
|
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
25309
25844
|
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
25310
|
-
|
|
25311
|
-
return {
|
|
25312
|
-
plots: [
|
|
25313
|
-
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
25314
|
-
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
25315
|
-
],
|
|
25316
|
-
levels: guideLevels(20, 80)
|
|
25317
|
-
};
|
|
25845
|
+
return stochasticOutput(k, sma(k, num3(inputs, "dLength", 3)), inputs);
|
|
25318
25846
|
}
|
|
25319
25847
|
};
|
|
25320
25848
|
var macd = {
|
|
@@ -25322,23 +25850,35 @@ var macd = {
|
|
|
25322
25850
|
title: "MACD",
|
|
25323
25851
|
overlay: false,
|
|
25324
25852
|
inputs: [
|
|
25325
|
-
lengthInput(12, "fastLength", "Fast
|
|
25326
|
-
lengthInput(26, "slowLength", "Slow
|
|
25327
|
-
lengthInput(9, "signalLength", "Signal
|
|
25328
|
-
sourceInput()
|
|
25853
|
+
lengthInput(12, "fastLength", "Fast Length", 5e3, "Length of the fast EMA."),
|
|
25854
|
+
lengthInput(26, "slowLength", "Slow Length", 5e3, "Length of the slow EMA."),
|
|
25855
|
+
lengthInput(9, "signalLength", "Signal Smoothing", 500, "Length of the EMA applied to the MACD line to obtain the signal line."),
|
|
25856
|
+
sourceInput("Close", "source", "Source", "Price series used to compute the fast and slow EMAs."),
|
|
25857
|
+
colorInput(INFO, "MACD Line", "color", "Color of the MACD line."),
|
|
25858
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
25859
|
+
colorInput(BULLISH, "Grow Above", "growAboveColor", "Histogram color when above zero and rising."),
|
|
25860
|
+
colorInput(transp(BULLISH, 50), "Fall Above", "fallAboveColor", "Histogram color when above zero and falling."),
|
|
25861
|
+
colorInput(transp(BEARISH, 50), "Grow Below", "growBelowColor", "Histogram color when below zero and rising."),
|
|
25862
|
+
colorInput(BEARISH, "Fall Below", "fallBelowColor", "Histogram color when below zero and falling.")
|
|
25329
25863
|
],
|
|
25330
25864
|
compute: (bars, inputs) => {
|
|
25331
25865
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
25332
25866
|
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
25333
25867
|
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
25334
25868
|
const hist = sub(line, signal);
|
|
25869
|
+
const colors = momentumColumnColors(hist, {
|
|
25870
|
+
growAbove: str3(inputs, "growAboveColor", BULLISH),
|
|
25871
|
+
fallAbove: str3(inputs, "fallAboveColor", transp(BULLISH, 50)),
|
|
25872
|
+
growBelow: str3(inputs, "growBelowColor", transp(BEARISH, 50)),
|
|
25873
|
+
fallBelow: str3(inputs, "fallBelowColor", BEARISH)
|
|
25874
|
+
});
|
|
25335
25875
|
return {
|
|
25336
25876
|
plots: [
|
|
25337
|
-
{ key: "hist", title: "Histogram", values: hist, kind: "
|
|
25338
|
-
{ key: "macd", title: "MACD", values: line, color:
|
|
25339
|
-
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
25877
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "columns", color: BULLISH, colors, base: 0 },
|
|
25878
|
+
{ key: "macd", title: "MACD", values: line, color: str3(inputs, "color", INFO) },
|
|
25879
|
+
{ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) }
|
|
25340
25880
|
],
|
|
25341
|
-
levels:
|
|
25881
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL, lineStyle: "dashed", title: "Zero Line" }]
|
|
25342
25882
|
};
|
|
25343
25883
|
}
|
|
25344
25884
|
};
|
|
@@ -25348,26 +25888,49 @@ var ppo = {
|
|
|
25348
25888
|
shortTitle: "PPO",
|
|
25349
25889
|
overlay: false,
|
|
25350
25890
|
inputs: [
|
|
25351
|
-
|
|
25352
|
-
lengthInput(
|
|
25353
|
-
lengthInput(
|
|
25354
|
-
|
|
25891
|
+
sourceInput("Close", "source", "Source", "Price series fed into the fast and slow averages."),
|
|
25892
|
+
lengthInput(12, "fastLength", "Fast Length", 5e3, "Length of the fast moving average."),
|
|
25893
|
+
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."),
|
|
25894
|
+
optionInput("maType", "Average Type", "EMA", ["EMA", "SMA"], "Moving average type used for the fast and slow averages."),
|
|
25895
|
+
lengthInput(9, "signalLength", "Signal Smoothing", 500, "Length of the signal line that smooths the PPO line."),
|
|
25896
|
+
optionInput("signalType", "Signal Type", "EMA", ["EMA", "SMA"], "Moving average type used for the signal line."),
|
|
25897
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the PPO line, gradient fill and histogram above the zero line."),
|
|
25898
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the PPO line, gradient fill and histogram below the zero line."),
|
|
25899
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
25900
|
+
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."),
|
|
25901
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the zero line and the PPO line with a vertical gradient.")
|
|
25355
25902
|
],
|
|
25356
25903
|
compute: (bars, inputs) => {
|
|
25357
25904
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
25358
|
-
const
|
|
25359
|
-
const
|
|
25905
|
+
const average = (kind2, v, len) => kind2 === "SMA" ? sma(v, len) : ema(v, len);
|
|
25906
|
+
const kind = str3(inputs, "maType", "EMA");
|
|
25907
|
+
const fast = average(kind, src, num3(inputs, "fastLength", 12));
|
|
25908
|
+
const slow = average(kind, src, num3(inputs, "slowLength", 26));
|
|
25360
25909
|
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
25361
|
-
const signal =
|
|
25910
|
+
const signal = average(str3(inputs, "signalType", "EMA"), line, num3(inputs, "signalLength", 9));
|
|
25362
25911
|
const hist = sub(line, signal);
|
|
25363
|
-
|
|
25364
|
-
|
|
25365
|
-
|
|
25366
|
-
|
|
25367
|
-
|
|
25368
|
-
|
|
25369
|
-
|
|
25370
|
-
|
|
25912
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
25913
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
25914
|
+
const out = centeredOscillator({
|
|
25915
|
+
key: "ppo",
|
|
25916
|
+
title: "PPO Line",
|
|
25917
|
+
values: line,
|
|
25918
|
+
ink: { bull, bear, neutral: bear },
|
|
25919
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
25920
|
+
fill: bool(inputs, "gradientFill", true),
|
|
25921
|
+
extraPlots: [{ key: "signal", title: "Signal Line", values: signal, color: str3(inputs, "signalColor", WARNING) }]
|
|
25922
|
+
});
|
|
25923
|
+
if (bool(inputs, "showHistogram", true)) {
|
|
25924
|
+
const colors = hist.map((x, i) => {
|
|
25925
|
+
if (!Number.isFinite(x)) return null;
|
|
25926
|
+
const prev2 = i > 0 && Number.isFinite(hist[i - 1]) ? hist[i - 1] : x;
|
|
25927
|
+
const ink = x >= 0 ? bull : bear;
|
|
25928
|
+
const expanding = x >= 0 ? x > prev2 : x < prev2;
|
|
25929
|
+
return transp(ink, expanding ? 40 : 70);
|
|
25930
|
+
});
|
|
25931
|
+
out.plots.unshift({ key: "hist", title: "Histogram", values: hist, kind: "columns", color: bull, colors, base: 0 });
|
|
25932
|
+
}
|
|
25933
|
+
return out;
|
|
25371
25934
|
}
|
|
25372
25935
|
};
|
|
25373
25936
|
var awesome = {
|
|
@@ -25375,80 +25938,146 @@ var awesome = {
|
|
|
25375
25938
|
title: "Awesome Oscillator",
|
|
25376
25939
|
shortTitle: "AO",
|
|
25377
25940
|
overlay: false,
|
|
25378
|
-
inputs: [
|
|
25379
|
-
|
|
25941
|
+
inputs: [
|
|
25942
|
+
colorInput(BULLISH, "Rising", "risingColor", "Histogram color when the oscillator is higher than on the previous bar."),
|
|
25943
|
+
colorInput(BEARISH, "Falling", "fallingColor", "Histogram color when the oscillator is lower than or equal to the previous bar."),
|
|
25944
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
25945
|
+
],
|
|
25946
|
+
compute: (bars, inputs) => {
|
|
25380
25947
|
const hl2 = sourceValues(bars, "HL2");
|
|
25381
25948
|
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
25949
|
+
const rising = str3(inputs, "risingColor", BULLISH);
|
|
25950
|
+
const falling = str3(inputs, "fallingColor", BEARISH);
|
|
25382
25951
|
const colors = ao.map((x, i) => {
|
|
25383
25952
|
if (!Number.isFinite(x)) return null;
|
|
25384
25953
|
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
25385
|
-
return Number.isFinite(prev2) && x
|
|
25954
|
+
return Number.isFinite(prev2) && x > prev2 ? rising : falling;
|
|
25386
25955
|
});
|
|
25387
25956
|
return {
|
|
25388
|
-
plots: [{ key: "ao", title: "AO", values: ao, kind: "
|
|
25389
|
-
levels:
|
|
25957
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "columns", color: rising, colors, base: 0 }],
|
|
25958
|
+
levels: [{ key: "zero", price: 0, color: str3(inputs, "zeroColor", NEUTRAL), lineStyle: "dashed", title: "Zero Line" }]
|
|
25390
25959
|
};
|
|
25391
25960
|
}
|
|
25392
25961
|
};
|
|
25962
|
+
var CCI_SCALING = 0.015;
|
|
25393
25963
|
var cci = {
|
|
25394
25964
|
type: "commodity-channel-index",
|
|
25395
25965
|
title: "Commodity Channel Index",
|
|
25396
25966
|
shortTitle: "CCI",
|
|
25397
25967
|
overlay: false,
|
|
25398
|
-
inputs: [
|
|
25968
|
+
inputs: [
|
|
25969
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used for the moving average and the mean absolute deviation of the source."),
|
|
25970
|
+
sourceInput("HLC3", "source", "Source", "Price series measured by the CCI; the typical price hlc3 is the classic choice."),
|
|
25971
|
+
floatInput("upper", "Upper Level", 100, -1e3, 1e3, 10, "Overbought reference level, +100 classically."),
|
|
25972
|
+
floatInput("lower", "Lower Level", -100, -1e3, 1e3, 10, "Oversold reference level, \u2212100 classically."),
|
|
25973
|
+
colorInput(NEUTRAL, "CCI Line", "color", "Color of the CCI line."),
|
|
25974
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the CCI reads above zero."),
|
|
25975
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the CCI reads below zero."),
|
|
25976
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the upper, lower and zero reference lines.")
|
|
25977
|
+
],
|
|
25399
25978
|
compute: (bars, inputs) => {
|
|
25400
25979
|
const len = num3(inputs, "length", 20);
|
|
25401
25980
|
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
25402
25981
|
const basis = sma(src, len);
|
|
25403
|
-
const dev =
|
|
25404
|
-
for (let i = len - 1; i < src.length; i++) {
|
|
25405
|
-
const mean = basis[i];
|
|
25406
|
-
if (!Number.isFinite(mean)) continue;
|
|
25407
|
-
let d = 0;
|
|
25408
|
-
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
25409
|
-
dev[i] = d / len;
|
|
25410
|
-
}
|
|
25982
|
+
const dev = meanDev(src, len);
|
|
25411
25983
|
const values = src.map((x, i) => {
|
|
25412
25984
|
const b = basis[i];
|
|
25413
25985
|
const d = dev[i];
|
|
25414
|
-
|
|
25986
|
+
if (!Number.isFinite(b) || !Number.isFinite(d)) return Number.NaN;
|
|
25987
|
+
return d === 0 ? 0 : (x - b) / (CCI_SCALING * d);
|
|
25415
25988
|
});
|
|
25416
|
-
|
|
25417
|
-
|
|
25418
|
-
|
|
25419
|
-
|
|
25420
|
-
|
|
25989
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
25990
|
+
return centeredOscillator({
|
|
25991
|
+
key: "cci",
|
|
25992
|
+
title: "CCI",
|
|
25993
|
+
values,
|
|
25994
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
25995
|
+
zeroInk: levelInk,
|
|
25996
|
+
zeroLineStyle: "dotted",
|
|
25997
|
+
extraLevels: [
|
|
25998
|
+
{ key: "upper", price: num3(inputs, "upper", 100), color: levelInk, lineStyle: "dashed", title: "Upper Level" },
|
|
25999
|
+
{ key: "lower", price: num3(inputs, "lower", -100), color: levelInk, lineStyle: "dashed", title: "Lower Level" }
|
|
26000
|
+
]
|
|
26001
|
+
});
|
|
26002
|
+
}
|
|
25421
26003
|
};
|
|
25422
26004
|
var williamsR = {
|
|
25423
26005
|
type: "williams-percent-r",
|
|
25424
26006
|
title: "Williams %R",
|
|
25425
26007
|
shortTitle: "%R",
|
|
25426
26008
|
overlay: false,
|
|
25427
|
-
inputs: [
|
|
26009
|
+
inputs: [
|
|
26010
|
+
lengthInput(14, "length", "Length", 5e3, "Lookback length of the highest high / lowest low range. 14 is the classic setting."),
|
|
26011
|
+
sourceInput("Close", "source", "Source", "Series located within the lookback range. Close is the classic choice."),
|
|
26012
|
+
...thresholdInputs(-20, -80, -100, 0),
|
|
26013
|
+
colorInput(NEUTRAL, "%R Line", "color", "Color of the %R line between the thresholds. Toward either level it blends into that level\u2019s color."),
|
|
26014
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
26015
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level.")
|
|
26016
|
+
],
|
|
25428
26017
|
compute: (bars, inputs) => {
|
|
25429
|
-
const
|
|
25430
|
-
|
|
25431
|
-
|
|
25432
|
-
|
|
25433
|
-
|
|
26018
|
+
const len = num3(inputs, "length", 14);
|
|
26019
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
26020
|
+
const hh = highest(highs(bars), len);
|
|
26021
|
+
const ll = lowest(lows(bars), len);
|
|
26022
|
+
const values = src.map((x, i) => {
|
|
26023
|
+
const h = hh[i];
|
|
26024
|
+
const l = ll[i];
|
|
26025
|
+
return Number.isFinite(h) && Number.isFinite(l) && h > l ? -100 * (h - x) / (h - l) : Number.NaN;
|
|
26026
|
+
});
|
|
26027
|
+
return boundedOscillator({
|
|
26028
|
+
key: "wr",
|
|
26029
|
+
title: "%R",
|
|
26030
|
+
values,
|
|
26031
|
+
scale: { top: 0, bottom: -100 },
|
|
26032
|
+
overbought: num3(inputs, "overbought", -20),
|
|
26033
|
+
oversold: num3(inputs, "oversold", -80),
|
|
26034
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
26035
|
+
levelInk: NEUTRAL
|
|
26036
|
+
});
|
|
25434
26037
|
}
|
|
25435
26038
|
};
|
|
25436
|
-
var
|
|
26039
|
+
var cmoSpec = {
|
|
25437
26040
|
type: "chande-momentum-oscillator",
|
|
25438
26041
|
title: "Chande Momentum Oscillator",
|
|
25439
26042
|
shortTitle: "CMO",
|
|
25440
26043
|
overlay: false,
|
|
25441
|
-
inputs: [
|
|
26044
|
+
inputs: [
|
|
26045
|
+
lengthInput(9, "length", "Length", 5e3, "Number of bars over which up moves and down moves are summed."),
|
|
26046
|
+
sourceInput("Close", "source", "Source", "Price series used to measure bar-to-bar changes."),
|
|
26047
|
+
floatInput("overbought", "Overbought Level", 50, 0, 100, 1, "Level above which the oscillator is considered overbought."),
|
|
26048
|
+
floatInput("oversold", "Oversold Level", -50, -100, 0, 1, "Level below which the oscillator is considered oversold."),
|
|
26049
|
+
colorInput(NEUTRAL, "CMO Line", "color", "Color of the CMO line."),
|
|
26050
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while momentum is positive, and shading of the oversold zone."),
|
|
26051
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while momentum is negative, and shading of the overbought zone."),
|
|
26052
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought, oversold, and zero lines.")
|
|
26053
|
+
],
|
|
25442
26054
|
compute: (bars, inputs) => {
|
|
25443
26055
|
const len = num3(inputs, "length", 9);
|
|
25444
26056
|
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
25445
|
-
const
|
|
25446
|
-
const
|
|
25447
|
-
const values = zip(
|
|
25448
|
-
|
|
25449
|
-
|
|
25450
|
-
|
|
25451
|
-
|
|
26057
|
+
const up = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
26058
|
+
const down = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
26059
|
+
const values = zip(up, down, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
26060
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
26061
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
26062
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
26063
|
+
const overbought = num3(inputs, "overbought", 50);
|
|
26064
|
+
const oversold = num3(inputs, "oversold", -50);
|
|
26065
|
+
const out = centeredOscillator({
|
|
26066
|
+
key: "cmo",
|
|
26067
|
+
title: "CMO",
|
|
26068
|
+
values,
|
|
26069
|
+
ink: { bull, bear, neutral: str3(inputs, "color", NEUTRAL) },
|
|
26070
|
+
zeroInk: levelInk,
|
|
26071
|
+
zeroLineStyle: "dotted",
|
|
26072
|
+
extraLevels: [
|
|
26073
|
+
{ key: "overbought", price: overbought, color: levelInk, lineStyle: "dashed", title: "Overbought" },
|
|
26074
|
+
{ key: "oversold", price: oversold, color: levelInk, lineStyle: "dashed", title: "Oversold" }
|
|
26075
|
+
]
|
|
26076
|
+
});
|
|
26077
|
+
const zones = thresholdZones(values.length, { overbought, oversold, top: 100, bottom: -100 }, { overbought: bear, oversold: bull });
|
|
26078
|
+
out.plots.push(...zones.plots);
|
|
26079
|
+
out.bands = [...out.bands ?? [], ...zones.bands];
|
|
26080
|
+
return out;
|
|
25452
26081
|
}
|
|
25453
26082
|
};
|
|
25454
26083
|
var connorsRsi = {
|
|
@@ -25457,10 +26086,14 @@ var connorsRsi = {
|
|
|
25457
26086
|
shortTitle: "CRSI",
|
|
25458
26087
|
overlay: false,
|
|
25459
26088
|
inputs: [
|
|
25460
|
-
lengthInput(3, "rsiLength", "RSI
|
|
25461
|
-
lengthInput(2, "streakLength", "Streak RSI
|
|
25462
|
-
lengthInput(100, "rankLength", "Percent
|
|
25463
|
-
|
|
26089
|
+
lengthInput(3, "rsiLength", "RSI Length", 5e3, "Length of the Wilder RSI applied to the closing price."),
|
|
26090
|
+
lengthInput(2, "streakLength", "Streak RSI Length", 5e3, "Length of the Wilder RSI applied to the up/down close streak."),
|
|
26091
|
+
lengthInput(100, "rankLength", "Percent Rank Length", 5e3, "Lookback of the percent rank applied to the 1-bar rate of change of the closing price."),
|
|
26092
|
+
...thresholdInputs(90, 10),
|
|
26093
|
+
colorInput(NEUTRAL, "CRSI Line", "color", "Color of the Connors RSI line."),
|
|
26094
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Line, fill and zone color toward and below the oversold level."),
|
|
26095
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Line, fill and zone color toward and above the overbought level."),
|
|
26096
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought and oversold levels.")
|
|
25464
26097
|
],
|
|
25465
26098
|
compute: (bars, inputs) => {
|
|
25466
26099
|
const c = closes(bars);
|
|
@@ -25478,10 +26111,17 @@ var connorsRsi = {
|
|
|
25478
26111
|
const z = r[i];
|
|
25479
26112
|
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
25480
26113
|
});
|
|
25481
|
-
return {
|
|
25482
|
-
|
|
25483
|
-
|
|
25484
|
-
|
|
26114
|
+
return boundedOscillator({
|
|
26115
|
+
key: "crsi",
|
|
26116
|
+
title: "Connors RSI",
|
|
26117
|
+
values,
|
|
26118
|
+
scale: { top: 100, bottom: 0 },
|
|
26119
|
+
overbought: num3(inputs, "overbought", 90),
|
|
26120
|
+
oversold: num3(inputs, "oversold", 10),
|
|
26121
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
26122
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
26123
|
+
midpointWash: true
|
|
26124
|
+
});
|
|
25485
26125
|
}
|
|
25486
26126
|
};
|
|
25487
26127
|
var fisher = {
|
|
@@ -25489,7 +26129,13 @@ var fisher = {
|
|
|
25489
26129
|
title: "Fisher Transform",
|
|
25490
26130
|
shortTitle: "Fisher",
|
|
25491
26131
|
overlay: false,
|
|
25492
|
-
inputs: [
|
|
26132
|
+
inputs: [
|
|
26133
|
+
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."),
|
|
26134
|
+
colorInput(NEUTRAL, "Fisher Line", "color", "Color of the Fisher line when it sits exactly on its trigger."),
|
|
26135
|
+
colorInput(NEUTRAL, "Trigger Line", "triggerColor", "Color of the trigger line, the Fisher value delayed one bar."),
|
|
26136
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the Fisher line and fill while it is above the trigger."),
|
|
26137
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the Fisher line and fill while it is below the trigger.")
|
|
26138
|
+
],
|
|
25493
26139
|
compute: (bars, inputs) => {
|
|
25494
26140
|
const len = num3(inputs, "length", 9);
|
|
25495
26141
|
const hl2 = sourceValues(bars, "HL2");
|
|
@@ -25497,23 +26143,39 @@ var fisher = {
|
|
|
25497
26143
|
const lo = lowest(hl2, len);
|
|
25498
26144
|
const n = bars.length;
|
|
25499
26145
|
const out = new Array(n).fill(Number.NaN);
|
|
26146
|
+
let position = 0;
|
|
25500
26147
|
let value = 0;
|
|
25501
|
-
let fish = 0;
|
|
25502
26148
|
for (let i = 0; i < n; i++) {
|
|
25503
26149
|
const h = hi[i];
|
|
25504
26150
|
const l = lo[i];
|
|
25505
26151
|
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
25506
|
-
const
|
|
25507
|
-
|
|
25508
|
-
|
|
25509
|
-
|
|
25510
|
-
|
|
26152
|
+
const raw = h > l ? (hl2[i] - l) / (h - l) - 0.5 : 0;
|
|
26153
|
+
position = 0.66 * raw + 0.67 * position;
|
|
26154
|
+
position = position > 0.99 ? 0.999 : position < -0.99 ? -0.999 : position;
|
|
26155
|
+
value = 0.5 * Math.log((1 + position) / (1 - position)) + 0.5 * value;
|
|
26156
|
+
out[i] = value;
|
|
26157
|
+
}
|
|
26158
|
+
const trigger = shift(out, 1);
|
|
26159
|
+
const neutral = str3(inputs, "color", NEUTRAL);
|
|
26160
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
26161
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
26162
|
+
const inkOf = (i) => {
|
|
26163
|
+
const f = out[i];
|
|
26164
|
+
const t = trigger[i];
|
|
26165
|
+
if (!Number.isFinite(t)) return neutral;
|
|
26166
|
+
return f > t ? bull : f < t ? bear : neutral;
|
|
26167
|
+
};
|
|
25511
26168
|
return {
|
|
25512
26169
|
plots: [
|
|
25513
|
-
{ key: "fisher", title: "Fisher", values: out, color:
|
|
25514
|
-
{ key: "trigger", title: "Trigger", values:
|
|
26170
|
+
{ key: "fisher", title: "Fisher", values: out, color: neutral, width: 2, colors: out.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) },
|
|
26171
|
+
{ key: "trigger", title: "Trigger", values: trigger, color: str3(inputs, "triggerColor", NEUTRAL) }
|
|
25515
26172
|
],
|
|
25516
|
-
|
|
26173
|
+
bands: [{ key: "spread", from: "fisher", to: "trigger", color: transp(neutral, 80), colors: out.map((x, i) => Number.isFinite(x) ? transp(inkOf(i), 80) : null) }],
|
|
26174
|
+
levels: [
|
|
26175
|
+
{ key: "upper", price: 1.5, color: NEUTRAL, lineStyle: "dashed", title: "Upper Level" },
|
|
26176
|
+
{ key: "lower", price: -1.5, color: NEUTRAL, lineStyle: "dashed", title: "Lower Level" },
|
|
26177
|
+
{ key: "zero", price: 0, color: NEUTRAL, lineStyle: "dotted", title: "Zero Line" }
|
|
26178
|
+
]
|
|
25517
26179
|
};
|
|
25518
26180
|
}
|
|
25519
26181
|
};
|
|
@@ -25521,93 +26183,180 @@ var trix = {
|
|
|
25521
26183
|
type: "trix",
|
|
25522
26184
|
title: "TRIX",
|
|
25523
26185
|
overlay: false,
|
|
25524
|
-
inputs: [
|
|
26186
|
+
inputs: [
|
|
26187
|
+
lengthInput(18, "length", "Length", 5e3, "Length of each of the three EMA smoothing passes applied to the close price."),
|
|
26188
|
+
lengthInput(9, "signalLength", "Signal Length", 500, "Length of the EMA applied to TRIX to form the signal line."),
|
|
26189
|
+
colorInput(NEUTRAL, "TRIX Color", "color", "Color of the TRIX line."),
|
|
26190
|
+
colorInput(WARNING, "Signal Color", "signalColor", "Color of the signal line."),
|
|
26191
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the TRIX line and its gradient fill while TRIX holds above zero."),
|
|
26192
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the TRIX line and its gradient fill while TRIX holds below zero.")
|
|
26193
|
+
],
|
|
25525
26194
|
compute: (bars, inputs) => {
|
|
25526
26195
|
const len = num3(inputs, "length", 18);
|
|
25527
|
-
const
|
|
25528
|
-
const
|
|
25529
|
-
|
|
25530
|
-
|
|
25531
|
-
|
|
25532
|
-
|
|
25533
|
-
|
|
26196
|
+
const smooth = ema(ema(ema(closes(bars), len), len), len);
|
|
26197
|
+
const values = roc(smooth, 1);
|
|
26198
|
+
return centeredOscillator({
|
|
26199
|
+
key: "trix",
|
|
26200
|
+
title: "TRIX",
|
|
26201
|
+
values,
|
|
26202
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
26203
|
+
zeroInk: NEUTRAL,
|
|
26204
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(values, num3(inputs, "signalLength", 9)), color: str3(inputs, "signalColor", WARNING) }]
|
|
26205
|
+
});
|
|
25534
26206
|
}
|
|
25535
26207
|
};
|
|
25536
|
-
function
|
|
26208
|
+
function tsiRatio(src, long, short) {
|
|
25537
26209
|
const mom = change(src);
|
|
25538
26210
|
const numer = ema(ema(mom, long), short);
|
|
25539
26211
|
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
25540
|
-
return zip(numer, denom, (a, b) => b === 0 ? 0 :
|
|
26212
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : a / b);
|
|
25541
26213
|
}
|
|
25542
26214
|
var tsi = {
|
|
25543
26215
|
type: "true-strength-index",
|
|
25544
26216
|
title: "True Strength Index",
|
|
25545
26217
|
shortTitle: "TSI",
|
|
25546
26218
|
overlay: false,
|
|
25547
|
-
inputs: [
|
|
26219
|
+
inputs: [
|
|
26220
|
+
lengthInput(25, "longLength", "Long Length", 5e3, "Length of the first (long) EMA applied to the 1-bar price change."),
|
|
26221
|
+
lengthInput(13, "shortLength", "Short Length", 5e3, "Length of the second (short) EMA applied to the long-smoothed price change."),
|
|
26222
|
+
lengthInput(13, "signalLength", "Signal Length", 500, "Length of the EMA of TSI used as the signal line."),
|
|
26223
|
+
colorInput(NEUTRAL, "TSI", "color", "Color of the TSI line."),
|
|
26224
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line."),
|
|
26225
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the TSI line and its gradient fill while TSI holds above zero."),
|
|
26226
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the TSI line and its gradient fill while TSI holds below zero.")
|
|
26227
|
+
],
|
|
25548
26228
|
compute: (bars, inputs) => {
|
|
25549
|
-
const
|
|
25550
|
-
return {
|
|
25551
|
-
|
|
25552
|
-
|
|
25553
|
-
|
|
25554
|
-
|
|
25555
|
-
|
|
25556
|
-
|
|
26229
|
+
const values = map(tsiRatio(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13)), (x) => 100 * x);
|
|
26230
|
+
return centeredOscillator({
|
|
26231
|
+
key: "tsi",
|
|
26232
|
+
title: "TSI",
|
|
26233
|
+
values,
|
|
26234
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
26235
|
+
zeroInk: NEUTRAL,
|
|
26236
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(values, num3(inputs, "signalLength", 13)), color: str3(inputs, "signalColor", WARNING) }]
|
|
26237
|
+
});
|
|
25557
26238
|
}
|
|
25558
26239
|
};
|
|
26240
|
+
var SMI_INDICATOR = "Indicator";
|
|
26241
|
+
var SMI_OSCILLATOR = "Oscillator";
|
|
25559
26242
|
var smiErgodic = {
|
|
25560
26243
|
type: "smi-ergodic",
|
|
25561
26244
|
title: "SMI Ergodic Oscillator",
|
|
25562
26245
|
shortTitle: "SMIE",
|
|
25563
26246
|
overlay: false,
|
|
25564
|
-
inputs: [
|
|
26247
|
+
inputs: [
|
|
26248
|
+
lengthInput(20, "longLength", "Long Length", 5e3, "Length of the first EMA smoothing applied to the momentum of the closing price."),
|
|
26249
|
+
lengthInput(5, "shortLength", "Short Length", 5e3, "Length of the second EMA smoothing applied to the momentum of the closing price."),
|
|
26250
|
+
lengthInput(5, "signalLength", "Signal Length", 500, "Length of the EMA of the SMI that forms the signal line."),
|
|
26251
|
+
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."),
|
|
26252
|
+
colorInput(NEUTRAL, "SMI", "color", "Color of the SMI line while it sits exactly on its signal line."),
|
|
26253
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line."),
|
|
26254
|
+
colorInput(BULLISH, "Oscillator Grow", "growColor", "Color of the rising oscillator histogram, and of the SMI line while it leads its signal."),
|
|
26255
|
+
colorInput(BEARISH, "Oscillator Fall", "fallColor", "Color of the falling oscillator histogram, and of the SMI line while it lags its signal.")
|
|
26256
|
+
],
|
|
25565
26257
|
compute: (bars, inputs) => {
|
|
25566
|
-
const
|
|
25567
|
-
const signal = ema(
|
|
25568
|
-
const
|
|
26258
|
+
const smi = tsiRatio(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
26259
|
+
const signal = ema(smi, num3(inputs, "signalLength", 5));
|
|
26260
|
+
const grow = str3(inputs, "growColor", BULLISH);
|
|
26261
|
+
const fall = str3(inputs, "fallColor", BEARISH);
|
|
26262
|
+
const zero = [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed", title: "Zero" }];
|
|
26263
|
+
if (str3(inputs, "display", SMI_INDICATOR) === SMI_OSCILLATOR) {
|
|
26264
|
+
const osc = sub(smi, signal);
|
|
26265
|
+
const colors = osc.map((x, i) => {
|
|
26266
|
+
if (!Number.isFinite(x)) return null;
|
|
26267
|
+
const prev2 = i > 0 && Number.isFinite(osc[i - 1]) ? osc[i - 1] : x;
|
|
26268
|
+
return x > prev2 ? grow : fall;
|
|
26269
|
+
});
|
|
26270
|
+
return { plots: [{ key: "osc", title: "Oscillator", values: osc, kind: "columns", color: grow, colors, base: 0 }], levels: zero };
|
|
26271
|
+
}
|
|
26272
|
+
const neutral = str3(inputs, "color", NEUTRAL);
|
|
26273
|
+
const inkOf = (i) => {
|
|
26274
|
+
const a = smi[i];
|
|
26275
|
+
const b = signal[i];
|
|
26276
|
+
if (!Number.isFinite(b)) return neutral;
|
|
26277
|
+
return a > b ? grow : a < b ? fall : neutral;
|
|
26278
|
+
};
|
|
25569
26279
|
return {
|
|
25570
26280
|
plots: [
|
|
25571
|
-
{ key: "
|
|
25572
|
-
{ key: "
|
|
25573
|
-
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
26281
|
+
{ key: "smi", title: "SMI", values: smi, color: neutral, colors: smi.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) },
|
|
26282
|
+
{ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) }
|
|
25574
26283
|
],
|
|
25575
|
-
|
|
26284
|
+
bands: [{ key: "spread", from: "smi", to: "signal", color: transp(neutral, 75), colors: smi.map((x, i) => Number.isFinite(x) ? transp(inkOf(i), 75) : null) }],
|
|
26285
|
+
levels: zero
|
|
25576
26286
|
};
|
|
25577
26287
|
}
|
|
25578
26288
|
};
|
|
26289
|
+
var STC_MID = 50;
|
|
26290
|
+
var TRIGGERS = "Trigger Levels";
|
|
25579
26291
|
var stc = {
|
|
25580
26292
|
type: "schaff-trend-cycle",
|
|
25581
26293
|
title: "Schaff Trend Cycle",
|
|
25582
26294
|
shortTitle: "Schaff Trend Cycle",
|
|
25583
26295
|
overlay: false,
|
|
25584
|
-
inputs: [
|
|
26296
|
+
inputs: [
|
|
26297
|
+
sourceInput("Close", "source", "Source", "Price series used to build the MACD that the trend cycle is measured on."),
|
|
26298
|
+
lengthInput(23, "fastLength", "Fast Length", 5e3, "Fast EMA length of the underlying MACD."),
|
|
26299
|
+
lengthInput(50, "slowLength", "Slow Length", 5e3, "Slow EMA length of the underlying MACD."),
|
|
26300
|
+
{ ...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 },
|
|
26301
|
+
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."),
|
|
26302
|
+
{ ...floatInput("upper", "Upper Trigger", 75, 0, 100, 1, "Upper trigger line. A cross down through it flags an emerging downtrend."), group: TRIGGERS },
|
|
26303
|
+
{ ...floatInput("lower", "Lower Trigger", 25, 0, 100, 1, "Lower trigger line. A cross up through it flags an emerging uptrend."), group: TRIGGERS },
|
|
26304
|
+
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."),
|
|
26305
|
+
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."),
|
|
26306
|
+
colorInput(NEUTRAL, "Levels Color", "levelsColor", "Color of the dashed trigger lines and the dotted midline."),
|
|
26307
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Vertical gradient between the midline and the trend cycle, fading out toward the midline.")
|
|
26308
|
+
],
|
|
25585
26309
|
compute: (bars, inputs) => {
|
|
25586
26310
|
const len = num3(inputs, "cycleLength", 10);
|
|
25587
|
-
const
|
|
26311
|
+
const factor = num3(inputs, "factor", 0.5);
|
|
26312
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
25588
26313
|
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
25589
|
-
const
|
|
26314
|
+
const stochNorm = (v) => {
|
|
25590
26315
|
const lo = lowest(v, len);
|
|
25591
26316
|
const hi = highest(v, len);
|
|
25592
26317
|
const out = new Array(v.length).fill(Number.NaN);
|
|
25593
|
-
let
|
|
25594
|
-
let smoothed = Number.NaN;
|
|
26318
|
+
let pct = Number.NaN;
|
|
25595
26319
|
for (let i = 0; i < v.length; i++) {
|
|
25596
26320
|
const x = v[i];
|
|
25597
26321
|
const l = lo[i];
|
|
25598
26322
|
const h = hi[i];
|
|
25599
26323
|
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
25600
|
-
|
|
25601
|
-
|
|
25602
|
-
|
|
26324
|
+
if (h > l) pct = 100 * (x - l) / (h - l);
|
|
26325
|
+
out[i] = pct;
|
|
26326
|
+
}
|
|
26327
|
+
return out;
|
|
26328
|
+
};
|
|
26329
|
+
const smoothStep = (v) => {
|
|
26330
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
26331
|
+
let smoothed = Number.NaN;
|
|
26332
|
+
for (let i = 0; i < v.length; i++) {
|
|
26333
|
+
const x = v[i];
|
|
26334
|
+
if (!Number.isFinite(x)) continue;
|
|
26335
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + factor * (x - smoothed) : x;
|
|
25603
26336
|
out[i] = smoothed;
|
|
25604
26337
|
}
|
|
25605
26338
|
return out;
|
|
25606
26339
|
};
|
|
25607
|
-
const values =
|
|
26340
|
+
const values = smoothStep(stochNorm(smoothStep(stochNorm(macdLine))));
|
|
26341
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
26342
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
26343
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
26344
|
+
const plots = [
|
|
26345
|
+
{ key: "stc", title: "Schaff Trend Cycle", values, color: bear, width: 2, colors: values.map((x) => Number.isFinite(x) ? x > STC_MID ? bull : bear : null) }
|
|
26346
|
+
];
|
|
26347
|
+
const bands = [];
|
|
26348
|
+
if (bool(inputs, "gradientFill", true)) {
|
|
26349
|
+
plots.push(anchorPlot("midAnchor", "Midline Anchor", values.length, STC_MID));
|
|
26350
|
+
bands.push({ key: "wash", from: "stc", to: "midAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, STC_MID, (x) => x > STC_MID ? bull : bear) });
|
|
26351
|
+
}
|
|
25608
26352
|
return {
|
|
25609
|
-
plots
|
|
25610
|
-
|
|
26353
|
+
plots,
|
|
26354
|
+
bands,
|
|
26355
|
+
levels: [
|
|
26356
|
+
{ key: "upper", price: num3(inputs, "upper", 75), color: transp(levelInk, 25), lineStyle: "dashed", title: "Upper Trigger" },
|
|
26357
|
+
{ key: "lower", price: num3(inputs, "lower", 25), color: transp(levelInk, 25), lineStyle: "dashed", title: "Lower Trigger" },
|
|
26358
|
+
{ key: "midline", price: STC_MID, color: transp(levelInk, 60), lineStyle: "dotted", title: "Midline" }
|
|
26359
|
+
]
|
|
25611
26360
|
};
|
|
25612
26361
|
}
|
|
25613
26362
|
};
|
|
@@ -25616,22 +26365,37 @@ var kst = {
|
|
|
25616
26365
|
title: "Know Sure Thing",
|
|
25617
26366
|
shortTitle: "KST",
|
|
25618
26367
|
overlay: false,
|
|
25619
|
-
inputs: [
|
|
26368
|
+
inputs: [
|
|
26369
|
+
lengthInput(10, "rocLength1", "ROC Length #1", 5e3, "Lookback of the first (shortest) rate of change."),
|
|
26370
|
+
lengthInput(15, "rocLength2", "ROC Length #2", 5e3, "Lookback of the second rate of change."),
|
|
26371
|
+
lengthInput(20, "rocLength3", "ROC Length #3", 5e3, "Lookback of the third rate of change."),
|
|
26372
|
+
lengthInput(30, "rocLength4", "ROC Length #4", 5e3, "Lookback of the fourth (longest) rate of change."),
|
|
26373
|
+
lengthInput(10, "smaLength1", "SMA Length #1", 5e3, "Smoothing length applied to the first rate of change (RCMA #1)."),
|
|
26374
|
+
lengthInput(10, "smaLength2", "SMA Length #2", 5e3, "Smoothing length applied to the second rate of change (RCMA #2)."),
|
|
26375
|
+
lengthInput(10, "smaLength3", "SMA Length #3", 5e3, "Smoothing length applied to the third rate of change (RCMA #3)."),
|
|
26376
|
+
lengthInput(15, "smaLength4", "SMA Length #4", 5e3, "Smoothing length applied to the fourth rate of change (RCMA #4)."),
|
|
26377
|
+
lengthInput(9, "signalLength", "Signal Length", 500, "Simple moving average length of the signal line."),
|
|
26378
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the KST line while above zero."),
|
|
26379
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the KST line while below zero."),
|
|
26380
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line.")
|
|
26381
|
+
],
|
|
25620
26382
|
compute: (bars, inputs) => {
|
|
25621
|
-
const
|
|
25622
|
-
const
|
|
25623
|
-
|
|
25624
|
-
|
|
25625
|
-
|
|
25626
|
-
|
|
25627
|
-
|
|
25628
|
-
return {
|
|
25629
|
-
|
|
25630
|
-
|
|
25631
|
-
|
|
25632
|
-
|
|
25633
|
-
|
|
25634
|
-
|
|
26383
|
+
const c = closes(bars);
|
|
26384
|
+
const rcma = (rocKey, rocDefault, smaKey, smaDefault) => sma(roc(c, num3(inputs, rocKey, rocDefault)), num3(inputs, smaKey, smaDefault));
|
|
26385
|
+
const values = [rcma("rocLength1", 10, "smaLength1", 10), rcma("rocLength2", 15, "smaLength2", 10), rcma("rocLength3", 20, "smaLength3", 10), rcma("rocLength4", 30, "smaLength4", 15)].reduce(
|
|
26386
|
+
(acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)),
|
|
26387
|
+
new Array(c.length).fill(0)
|
|
26388
|
+
);
|
|
26389
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
26390
|
+
return centeredOscillator({
|
|
26391
|
+
key: "kst",
|
|
26392
|
+
title: "KST",
|
|
26393
|
+
values,
|
|
26394
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
26395
|
+
zeroInk: NEUTRAL,
|
|
26396
|
+
valueTransparency: 60,
|
|
26397
|
+
extraPlots: [{ key: "signal", title: "Signal", values: sma(values, num3(inputs, "signalLength", 9)), color: str3(inputs, "signalColor", WARNING) }]
|
|
26398
|
+
});
|
|
25635
26399
|
}
|
|
25636
26400
|
};
|
|
25637
26401
|
var coppock = {
|
|
@@ -25640,19 +26404,25 @@ var coppock = {
|
|
|
25640
26404
|
shortTitle: "Coppock",
|
|
25641
26405
|
overlay: false,
|
|
25642
26406
|
inputs: [
|
|
25643
|
-
lengthInput(10, "wmaLength", "WMA
|
|
25644
|
-
lengthInput(14, "longRoc", "Long RoC
|
|
25645
|
-
lengthInput(11, "shortRoc", "Short RoC
|
|
25646
|
-
sourceInput(),
|
|
25647
|
-
colorInput()
|
|
26407
|
+
lengthInput(10, "wmaLength", "WMA Length", 5e3, "Length of the weighted moving average applied to the sum of the two rates of change."),
|
|
26408
|
+
lengthInput(14, "longRoc", "Long RoC Length", 5e3, "Lookback of the long rate of change, in bars. The original definition uses 14 monthly closes."),
|
|
26409
|
+
lengthInput(11, "shortRoc", "Short RoC Length", 5e3, "Lookback of the short rate of change, in bars. The original definition uses 11 monthly closes."),
|
|
26410
|
+
sourceInput("Close", "source", "Source", "Price series used for the rate of change calculations."),
|
|
26411
|
+
colorInput(NEUTRAL, "Coppock Curve", "color", "Color of the Coppock Curve line."),
|
|
26412
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the curve reads above zero."),
|
|
26413
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the curve reads below zero."),
|
|
26414
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
25648
26415
|
],
|
|
25649
26416
|
compute: (bars, inputs) => {
|
|
25650
26417
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
25651
26418
|
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
25652
|
-
return {
|
|
25653
|
-
|
|
25654
|
-
|
|
25655
|
-
|
|
26419
|
+
return centeredOscillator({
|
|
26420
|
+
key: "coppock",
|
|
26421
|
+
title: "Coppock Curve",
|
|
26422
|
+
values,
|
|
26423
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
26424
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
26425
|
+
});
|
|
25656
26426
|
}
|
|
25657
26427
|
};
|
|
25658
26428
|
var dpo = {
|
|
@@ -25660,42 +26430,76 @@ var dpo = {
|
|
|
25660
26430
|
title: "Detrended Price Oscillator",
|
|
25661
26431
|
shortTitle: "DPO",
|
|
25662
26432
|
overlay: false,
|
|
25663
|
-
inputs: [
|
|
26433
|
+
inputs: [
|
|
26434
|
+
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."),
|
|
26435
|
+
sourceInput("Close", "source", "Source", "Price series used in the detrending calculation."),
|
|
26436
|
+
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."),
|
|
26437
|
+
colorInput(NEUTRAL, "DPO", "color", "Color of the line when it sits exactly at zero."),
|
|
26438
|
+
colorInput(BULLISH, "Above Zero", "bullColor", "Color of the line and fill while the oscillator is above zero."),
|
|
26439
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while the oscillator is below zero."),
|
|
26440
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
26441
|
+
],
|
|
25664
26442
|
compute: (bars, inputs) => {
|
|
25665
26443
|
const len = num3(inputs, "length", 21);
|
|
25666
|
-
const
|
|
25667
|
-
const
|
|
25668
|
-
|
|
25669
|
-
|
|
25670
|
-
|
|
25671
|
-
|
|
26444
|
+
const barsBack = Math.trunc(len / 2) + 1;
|
|
26445
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
26446
|
+
const basis = sma(src, len);
|
|
26447
|
+
const centered = bool(inputs, "centered", false);
|
|
26448
|
+
const values = centered ? shift(sub(shift(src, barsBack), basis), -barsBack) : sub(src, shift(basis, barsBack));
|
|
26449
|
+
return centeredOscillator({
|
|
26450
|
+
key: "dpo",
|
|
26451
|
+
title: "DPO",
|
|
26452
|
+
values,
|
|
26453
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
26454
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
26455
|
+
});
|
|
25672
26456
|
}
|
|
25673
26457
|
};
|
|
26458
|
+
var ROC_PERCENT = "ROC %";
|
|
26459
|
+
var ROC_MOMENTUM = "Momentum";
|
|
25674
26460
|
var rocSpec = {
|
|
25675
26461
|
type: "rate-of-change",
|
|
25676
26462
|
title: "Rate of Change",
|
|
25677
26463
|
shortTitle: "ROC",
|
|
25678
26464
|
overlay: false,
|
|
25679
|
-
inputs: [
|
|
25680
|
-
|
|
25681
|
-
|
|
25682
|
-
|
|
25683
|
-
|
|
25684
|
-
|
|
25685
|
-
|
|
25686
|
-
|
|
25687
|
-
|
|
25688
|
-
|
|
25689
|
-
|
|
25690
|
-
|
|
25691
|
-
|
|
26465
|
+
inputs: [
|
|
26466
|
+
lengthInput(9, "length", "Length", 5e3, "Lookback period; the source is compared with its value this many bars ago."),
|
|
26467
|
+
sourceInput("Close", "source", "Source", "Series used as input for the calculation."),
|
|
26468
|
+
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."),
|
|
26469
|
+
colorInput(NEUTRAL, "Line", "color", "Color of the line when the reading sits exactly at zero."),
|
|
26470
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the line and fill while the reading is above zero."),
|
|
26471
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line and fill while the reading is below zero."),
|
|
26472
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the line and zero, following the line color.")
|
|
26473
|
+
],
|
|
26474
|
+
compute: (bars, inputs) => {
|
|
26475
|
+
const len = num3(inputs, "length", 9);
|
|
26476
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
26477
|
+
const values = str3(inputs, "mode", ROC_PERCENT) === ROC_MOMENTUM ? change(src, len) : roc(src, len);
|
|
26478
|
+
return centeredOscillator({
|
|
26479
|
+
key: "roc",
|
|
26480
|
+
title: "ROC",
|
|
26481
|
+
values,
|
|
26482
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
26483
|
+
zeroInk: NEUTRAL,
|
|
26484
|
+
fill: bool(inputs, "gradientFill", true)
|
|
26485
|
+
});
|
|
26486
|
+
}
|
|
25692
26487
|
};
|
|
25693
26488
|
var ultimate = {
|
|
25694
26489
|
type: "ultimate-oscillator",
|
|
25695
26490
|
title: "Ultimate Oscillator",
|
|
25696
26491
|
shortTitle: "UO",
|
|
25697
26492
|
overlay: false,
|
|
25698
|
-
inputs: [
|
|
26493
|
+
inputs: [
|
|
26494
|
+
lengthInput(7, "fastLength", "Fast Length", 5e3, "Bars in the fastest averaging window. Weighted 4 in the final blend, so it dominates the oscillator."),
|
|
26495
|
+
lengthInput(14, "middleLength", "Middle Length", 5e3, "Bars in the intermediate averaging window. Weighted 2 in the final blend."),
|
|
26496
|
+
lengthInput(28, "slowLength", "Slow Length", 5e3, "Bars in the slowest averaging window. Weighted 1 in the final blend."),
|
|
26497
|
+
...thresholdInputs(70, 30, 0, 100, "Overbought", "Oversold"),
|
|
26498
|
+
colorInput(NEUTRAL, "Oscillator", "color", "Color of the line midway between the levels. Toward either level it blends into that level\u2019s color."),
|
|
26499
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought and oversold levels."),
|
|
26500
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line at the oversold level and of the oversold zone shading."),
|
|
26501
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line at the overbought level and of the overbought zone shading.")
|
|
26502
|
+
],
|
|
25699
26503
|
compute: (bars, inputs) => {
|
|
25700
26504
|
const n = bars.length;
|
|
25701
26505
|
const bp = new Array(n).fill(Number.NaN);
|
|
@@ -25703,10 +26507,9 @@ var ultimate = {
|
|
|
25703
26507
|
for (let i = 0; i < n; i++) {
|
|
25704
26508
|
const b = bars[i];
|
|
25705
26509
|
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
25706
|
-
const
|
|
25707
|
-
|
|
25708
|
-
|
|
25709
|
-
tr[i] = hi - lo;
|
|
26510
|
+
const trueLow = Math.min(b.low, pc);
|
|
26511
|
+
bp[i] = b.close - trueLow;
|
|
26512
|
+
tr[i] = Math.max(b.high, pc) - trueLow;
|
|
25710
26513
|
}
|
|
25711
26514
|
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
25712
26515
|
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
@@ -25717,10 +26520,17 @@ var ultimate = {
|
|
|
25717
26520
|
const z = a28[i];
|
|
25718
26521
|
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
25719
26522
|
});
|
|
25720
|
-
return {
|
|
25721
|
-
|
|
25722
|
-
|
|
25723
|
-
|
|
26523
|
+
return boundedOscillator({
|
|
26524
|
+
key: "uo",
|
|
26525
|
+
title: "UO",
|
|
26526
|
+
values,
|
|
26527
|
+
scale: { top: 100, bottom: 0 },
|
|
26528
|
+
overbought: num3(inputs, "overbought", 70),
|
|
26529
|
+
oversold: num3(inputs, "oversold", 30),
|
|
26530
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
26531
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
26532
|
+
zoneTransparency: 91
|
|
26533
|
+
});
|
|
25724
26534
|
}
|
|
25725
26535
|
};
|
|
25726
26536
|
var rvgi = {
|
|
@@ -25728,7 +26538,11 @@ var rvgi = {
|
|
|
25728
26538
|
title: "Relative Vigor Index",
|
|
25729
26539
|
shortTitle: "RVGI",
|
|
25730
26540
|
overlay: false,
|
|
25731
|
-
inputs: [
|
|
26541
|
+
inputs: [
|
|
26542
|
+
lengthInput(10, "length", "Length", 5e3, "Number of bars summed for the smoothed numerator (close \u2212 open) and denominator (high \u2212 low)."),
|
|
26543
|
+
colorInput(BULLISH, "RVGI", "color", "Color of the RVGI line."),
|
|
26544
|
+
colorInput(BEARISH, "Signal", "signalColor", "Color of the signal line.")
|
|
26545
|
+
],
|
|
25732
26546
|
compute: (bars, inputs) => {
|
|
25733
26547
|
const len = num3(inputs, "length", 10);
|
|
25734
26548
|
const co = swma(sub(closes(bars), opens(bars)));
|
|
@@ -25736,31 +26550,49 @@ var rvgi = {
|
|
|
25736
26550
|
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
25737
26551
|
return {
|
|
25738
26552
|
plots: [
|
|
25739
|
-
{ key: "rvgi", title: "RVGI", values: line, color:
|
|
25740
|
-
{ key: "signal", title: "Signal", values: swma(line), color:
|
|
26553
|
+
{ key: "rvgi", title: "RVGI", values: line, color: str3(inputs, "color", BULLISH) },
|
|
26554
|
+
{ key: "signal", title: "Signal", values: swma(line), color: str3(inputs, "signalColor", BEARISH) }
|
|
25741
26555
|
],
|
|
25742
|
-
levels:
|
|
26556
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed", title: "Zero" }]
|
|
25743
26557
|
};
|
|
25744
26558
|
}
|
|
25745
26559
|
};
|
|
26560
|
+
var RVI_EMA = "EMA";
|
|
26561
|
+
var RVI_RMA = "Wilder (RMA)";
|
|
25746
26562
|
var relVolatility = {
|
|
25747
26563
|
type: "relative-volatility-index",
|
|
25748
26564
|
title: "Relative Volatility Index",
|
|
25749
26565
|
shortTitle: "RVI",
|
|
25750
26566
|
overlay: false,
|
|
25751
|
-
inputs: [
|
|
26567
|
+
inputs: [
|
|
26568
|
+
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."),
|
|
26569
|
+
lengthInput(14, "smoothLength", "Smoothing Length", 5e3, "Number of bars used to smooth the up and down volatility streams."),
|
|
26570
|
+
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."),
|
|
26571
|
+
...thresholdInputs(80, 20),
|
|
26572
|
+
colorInput(NEUTRAL, "RVI Line", "color", "Color of the RVI line at the midline. Toward either threshold it blends into that threshold\u2019s color."),
|
|
26573
|
+
colorInput(BULLISH, "Up Volatility", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
26574
|
+
colorInput(BEARISH, "Down Volatility", "oversoldColor", "Color of the line and zone shading at and below the oversold level.")
|
|
26575
|
+
],
|
|
25752
26576
|
compute: (bars, inputs) => {
|
|
26577
|
+
const c = closes(bars);
|
|
26578
|
+
const sigma = stdev(c, num3(inputs, "stdevLength", 10));
|
|
26579
|
+
const d = change(c);
|
|
26580
|
+
const up = zip(sigma, d, (s, x) => x > 0 ? s : 0);
|
|
26581
|
+
const down = zip(sigma, d, (s, x) => x < 0 ? s : 0);
|
|
25753
26582
|
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
25754
|
-
const
|
|
25755
|
-
const
|
|
25756
|
-
|
|
25757
|
-
|
|
25758
|
-
|
|
25759
|
-
|
|
25760
|
-
|
|
25761
|
-
|
|
25762
|
-
|
|
25763
|
-
|
|
26583
|
+
const smooth = str3(inputs, "smoothType", RVI_EMA) === RVI_RMA ? rma : ema;
|
|
26584
|
+
const values = zip(smooth(up, smoothLen), smooth(down, smoothLen), (u, w) => u + w === 0 ? 0 : 100 * u / (u + w));
|
|
26585
|
+
return boundedOscillator({
|
|
26586
|
+
key: "rvi",
|
|
26587
|
+
title: "RVI",
|
|
26588
|
+
values,
|
|
26589
|
+
scale: { top: 100, bottom: 0 },
|
|
26590
|
+
overbought: num3(inputs, "overbought", 80),
|
|
26591
|
+
oversold: num3(inputs, "oversold", 20),
|
|
26592
|
+
ink: { low: str3(inputs, "oversoldColor", BEARISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BULLISH) },
|
|
26593
|
+
levelInk: NEUTRAL,
|
|
26594
|
+
width: 2
|
|
26595
|
+
});
|
|
25764
26596
|
}
|
|
25765
26597
|
};
|
|
25766
26598
|
var bop = {
|
|
@@ -25768,28 +26600,43 @@ var bop = {
|
|
|
25768
26600
|
title: "Balance of Power",
|
|
25769
26601
|
shortTitle: "BOP",
|
|
25770
26602
|
overlay: false,
|
|
25771
|
-
inputs: [
|
|
26603
|
+
inputs: [
|
|
26604
|
+
boolInput("smoothing", "Smoothing", false, void 0, "Smooth the raw Balance of Power with a simple moving average instead of plotting the single-bar value."),
|
|
26605
|
+
lengthInput(14, "smoothLength", "Length", 5e3, "Simple moving average length applied when smoothing is enabled."),
|
|
26606
|
+
colorInput(NEUTRAL, "BOP Color", "color", "Color of the line at exactly zero."),
|
|
26607
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the Balance of Power is above zero \u2014 buyers in control of the bar."),
|
|
26608
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the Balance of Power is below zero \u2014 sellers in control of the bar.")
|
|
26609
|
+
],
|
|
25772
26610
|
compute: (bars, inputs) => {
|
|
25773
|
-
const
|
|
25774
|
-
|
|
25775
|
-
|
|
25776
|
-
|
|
25777
|
-
|
|
26611
|
+
const raw = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
26612
|
+
const values = bool(inputs, "smoothing", false) ? sma(raw, num3(inputs, "smoothLength", 14)) : raw;
|
|
26613
|
+
return centeredOscillator({
|
|
26614
|
+
key: "bop",
|
|
26615
|
+
title: "BOP",
|
|
26616
|
+
values,
|
|
26617
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
26618
|
+
zeroInk: NEUTRAL,
|
|
26619
|
+
valueTransparency: 60
|
|
26620
|
+
});
|
|
25778
26621
|
}
|
|
25779
26622
|
};
|
|
25780
26623
|
var elderRay = {
|
|
25781
26624
|
type: "elder-ray",
|
|
25782
26625
|
title: "Elder Ray",
|
|
25783
26626
|
overlay: false,
|
|
25784
|
-
inputs: [
|
|
26627
|
+
inputs: [
|
|
26628
|
+
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."),
|
|
26629
|
+
colorInput(BULLISH, "Bull Power", "bullColor", "Color of the Bull Power columns."),
|
|
26630
|
+
colorInput(BEARISH, "Bear Power", "bearColor", "Color of the Bear Power columns.")
|
|
26631
|
+
],
|
|
25785
26632
|
compute: (bars, inputs) => {
|
|
25786
26633
|
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
25787
26634
|
return {
|
|
25788
26635
|
plots: [
|
|
25789
|
-
{ key: "bull", title: "Bull
|
|
25790
|
-
{ key: "bear", title: "Bear
|
|
26636
|
+
{ key: "bull", title: "Bull Power", values: sub(highs(bars), basis), kind: "columns", color: str3(inputs, "bullColor", BULLISH), base: 0 },
|
|
26637
|
+
{ key: "bear", title: "Bear Power", values: sub(lows(bars), basis), kind: "columns", color: str3(inputs, "bearColor", BEARISH), base: 0 }
|
|
25791
26638
|
],
|
|
25792
|
-
levels:
|
|
26639
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 30), lineStyle: "dashed", title: "Zero Line" }]
|
|
25793
26640
|
};
|
|
25794
26641
|
}
|
|
25795
26642
|
};
|
|
@@ -25798,31 +26645,66 @@ var ttmSqueeze = {
|
|
|
25798
26645
|
title: "TTM Squeeze",
|
|
25799
26646
|
overlay: false,
|
|
25800
26647
|
inputs: [
|
|
25801
|
-
lengthInput(20),
|
|
25802
|
-
|
|
25803
|
-
|
|
26648
|
+
{ ...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 },
|
|
26649
|
+
sourceInput("Close", "source", "Source", "Price series used for the shared moving average basis, the standard deviation, and the momentum displacement."),
|
|
26650
|
+
floatInput("bbMult", "Bollinger Bands Multiplier", 2, 0.1, 50, 0.1, "Standard deviation multiplier for the Bollinger Bands."),
|
|
26651
|
+
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."),
|
|
26652
|
+
colorInput(BULLISH, "Momentum Up", "bullColor", "Histogram color while momentum is positive. Bars building away from zero print solid, bars fading back print dimmed."),
|
|
26653
|
+
colorInput(BEARISH, "Momentum Down", "bearColor", "Histogram color while momentum is negative. Bars building away from zero print solid, bars fading back print dimmed."),
|
|
26654
|
+
colorInput(WARNING, "Squeeze On", "squeezeOnColor", "Zero-line dot color while both Bollinger Bands sit fully inside the Keltner Channels."),
|
|
26655
|
+
colorInput(NEUTRAL, "Squeeze Off", "squeezeOffColor", "Zero-line dot color while the Bollinger Bands trade outside the Keltner Channels."),
|
|
26656
|
+
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.")
|
|
25804
26657
|
],
|
|
25805
26658
|
compute: (bars, inputs) => {
|
|
25806
26659
|
const len = num3(inputs, "length", 20);
|
|
25807
|
-
const
|
|
25808
|
-
const basis = sma(
|
|
25809
|
-
const dev = map(stdev(
|
|
25810
|
-
const kcRange = map(
|
|
25811
|
-
const
|
|
25812
|
-
const anchor = zip(
|
|
25813
|
-
const mom = linreg(sub(
|
|
25814
|
-
const
|
|
26660
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
26661
|
+
const basis = sma(src, len);
|
|
26662
|
+
const dev = map(stdev(src, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
26663
|
+
const kcRange = map(atr(bars, len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
26664
|
+
const rangeMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
26665
|
+
const anchor = zip(rangeMid, basis, (d, b) => (d + b) / 2);
|
|
26666
|
+
const mom = linreg(sub(src, anchor), len, 0);
|
|
26667
|
+
const squeezeOn = basis.map((b, i) => {
|
|
25815
26668
|
const d = dev[i];
|
|
25816
26669
|
const r = kcRange[i];
|
|
25817
|
-
|
|
26670
|
+
if (!Number.isFinite(b) || !Number.isFinite(d) || !Number.isFinite(r)) return null;
|
|
26671
|
+
return d < r;
|
|
25818
26672
|
});
|
|
25819
|
-
const
|
|
25820
|
-
|
|
25821
|
-
|
|
25822
|
-
|
|
25823
|
-
|
|
25824
|
-
|
|
25825
|
-
|
|
26673
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
26674
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
26675
|
+
const onInk = str3(inputs, "squeezeOnColor", WARNING);
|
|
26676
|
+
const offInk = str3(inputs, "squeezeOffColor", NEUTRAL);
|
|
26677
|
+
const momColors = mom.map((x, i) => {
|
|
26678
|
+
if (!Number.isFinite(x)) return null;
|
|
26679
|
+
const prev2 = i > 0 && Number.isFinite(mom[i - 1]) ? mom[i - 1] : x;
|
|
26680
|
+
const rising = x > prev2;
|
|
26681
|
+
return x >= 0 ? rising ? bull : transp(bull, 60) : rising ? transp(bear, 60) : bear;
|
|
26682
|
+
});
|
|
26683
|
+
const plots = [
|
|
26684
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "columns", color: bull, colors: momColors, base: 0 },
|
|
26685
|
+
{
|
|
26686
|
+
key: "state",
|
|
26687
|
+
title: "Squeeze State",
|
|
26688
|
+
values: squeezeOn.map((s) => s == null ? Number.NaN : 0),
|
|
26689
|
+
kind: "circles",
|
|
26690
|
+
color: offInk,
|
|
26691
|
+
width: 2,
|
|
26692
|
+
colors: squeezeOn.map((s) => s == null ? null : s ? onInk : offInk)
|
|
26693
|
+
}
|
|
26694
|
+
];
|
|
26695
|
+
if (bool(inputs, "markFires", true)) {
|
|
26696
|
+
const fires = squeezeOn.map((s, i) => s === false && squeezeOn[i - 1] === true ? 0 : Number.NaN);
|
|
26697
|
+
plots.push({
|
|
26698
|
+
key: "fire",
|
|
26699
|
+
title: "Squeeze Fire",
|
|
26700
|
+
values: fires,
|
|
26701
|
+
kind: "cross",
|
|
26702
|
+
color: offInk,
|
|
26703
|
+
width: 3,
|
|
26704
|
+
colors: fires.map((x, i) => Number.isFinite(x) ? Number.isFinite(mom[i]) ? mom[i] >= 0 ? bull : bear : offInk : null)
|
|
26705
|
+
});
|
|
26706
|
+
}
|
|
26707
|
+
return { plots };
|
|
25826
26708
|
}
|
|
25827
26709
|
};
|
|
25828
26710
|
var oscillatorSpecs = [
|
|
@@ -25834,7 +26716,7 @@ var oscillatorSpecs = [
|
|
|
25834
26716
|
awesome,
|
|
25835
26717
|
cci,
|
|
25836
26718
|
williamsR,
|
|
25837
|
-
|
|
26719
|
+
cmoSpec,
|
|
25838
26720
|
connorsRsi,
|
|
25839
26721
|
fisher,
|
|
25840
26722
|
trix,
|
|
@@ -25858,15 +26740,19 @@ var aroon = {
|
|
|
25858
26740
|
type: "aroon",
|
|
25859
26741
|
title: "Aroon",
|
|
25860
26742
|
overlay: false,
|
|
25861
|
-
inputs: [
|
|
26743
|
+
inputs: [
|
|
26744
|
+
lengthInput(14, "length", "Length", 5e3, "Lookback length used to locate the highest high and lowest low."),
|
|
26745
|
+
colorInput(BULLISH, "Aroon Up", "upColor", "Color of the Aroon Up line."),
|
|
26746
|
+
colorInput(BEARISH, "Aroon Down", "downColor", "Color of the Aroon Down line.")
|
|
26747
|
+
],
|
|
25862
26748
|
compute: (bars, inputs) => {
|
|
25863
26749
|
const len = num3(inputs, "length", 14);
|
|
25864
|
-
const up = map(barsSinceHighest(highs(bars), len
|
|
25865
|
-
const down = map(barsSinceLowest(lows(bars), len
|
|
26750
|
+
const up = map(barsSinceHighest(highs(bars), len), (x) => 100 * (len - x) / len);
|
|
26751
|
+
const down = map(barsSinceLowest(lows(bars), len), (x) => 100 * (len - x) / len);
|
|
25866
26752
|
return {
|
|
25867
26753
|
plots: [
|
|
25868
|
-
{ key: "up", title: "Aroon
|
|
25869
|
-
{ key: "down", title: "Aroon
|
|
26754
|
+
{ key: "up", title: "Aroon Up", values: up, color: str3(inputs, "upColor", BULLISH) },
|
|
26755
|
+
{ key: "down", title: "Aroon Down", values: down, color: str3(inputs, "downColor", BEARISH) }
|
|
25870
26756
|
]
|
|
25871
26757
|
};
|
|
25872
26758
|
}
|
|
@@ -25876,10 +26762,16 @@ var adx = {
|
|
|
25876
26762
|
title: "Average Directional Index",
|
|
25877
26763
|
shortTitle: "ADX",
|
|
25878
26764
|
overlay: false,
|
|
25879
|
-
inputs: [
|
|
26765
|
+
inputs: [
|
|
26766
|
+
lengthInput(14, "diLength", "DI Length", 5e3, "Wilder RMA smoothing length applied to directional movement and true range when computing +DI and \u2212DI."),
|
|
26767
|
+
lengthInput(14, "adxLength", "ADX Smoothing", 5e3, "Wilder RMA smoothing length applied to DX to obtain the ADX line."),
|
|
26768
|
+
floatInput("keyLevel", "Key Level", 25, 0, 100, 1, "Trend strength reference level; ADX above it is commonly read as a trending market."),
|
|
26769
|
+
colorInput(BULLISH, "+DI Color", "plusDiColor", "Color of the plus directional indicator, and of the ADX line above the key level while +DI leads."),
|
|
26770
|
+
colorInput(BEARISH, "-DI Color", "minusDiColor", "Color of the minus directional indicator, and of the ADX line above the key level while \u2212DI leads."),
|
|
26771
|
+
colorInput(NEUTRAL, "ADX Color", "adxColor", "Color of the ADX line while it sits below the key level \u2014 no trend strong enough to trade.")
|
|
26772
|
+
],
|
|
25880
26773
|
compute: (bars, inputs) => {
|
|
25881
26774
|
const diLen = num3(inputs, "diLength", 14);
|
|
25882
|
-
const adxLen = num3(inputs, "adxLength", 14);
|
|
25883
26775
|
const n = bars.length;
|
|
25884
26776
|
const plusDm = new Array(n).fill(Number.NaN);
|
|
25885
26777
|
const minusDm = new Array(n).fill(Number.NaN);
|
|
@@ -25893,13 +26785,23 @@ var adx = {
|
|
|
25893
26785
|
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
25894
26786
|
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
25895
26787
|
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
26788
|
+
const adxLine = rma(dx, num3(inputs, "adxLength", 14));
|
|
26789
|
+
const plusInk = str3(inputs, "plusDiColor", BULLISH);
|
|
26790
|
+
const minusInk = str3(inputs, "minusDiColor", BEARISH);
|
|
26791
|
+
const neutralInk = str3(inputs, "adxColor", NEUTRAL);
|
|
26792
|
+
const keyLevel = num3(inputs, "keyLevel", 25);
|
|
26793
|
+
const adxColors = adxLine.map((x, i) => {
|
|
26794
|
+
if (!Number.isFinite(x)) return null;
|
|
26795
|
+
if (x <= keyLevel) return neutralInk;
|
|
26796
|
+
return (plusDi[i] ?? 0) > (minusDi[i] ?? 0) ? plusInk : minusInk;
|
|
26797
|
+
});
|
|
25896
26798
|
return {
|
|
25897
26799
|
plots: [
|
|
25898
|
-
{ key: "
|
|
25899
|
-
{ key: "
|
|
25900
|
-
{ key: "
|
|
26800
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: transp(plusInk, 30) },
|
|
26801
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: transp(minusInk, 30) },
|
|
26802
|
+
{ key: "adx", title: "ADX", values: adxLine, color: neutralInk, width: 2, colors: adxColors }
|
|
25901
26803
|
],
|
|
25902
|
-
levels: [{ key: "
|
|
26804
|
+
levels: [{ key: "keyLevel", price: keyLevel, color: NEUTRAL, lineStyle: "dashed", title: "Key Level" }]
|
|
25903
26805
|
};
|
|
25904
26806
|
}
|
|
25905
26807
|
};
|
|
@@ -25908,7 +26810,12 @@ var vortex = {
|
|
|
25908
26810
|
title: "Vortex Indicator",
|
|
25909
26811
|
shortTitle: "VI",
|
|
25910
26812
|
overlay: false,
|
|
25911
|
-
inputs: [
|
|
26813
|
+
inputs: [
|
|
26814
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to sum the vortex movements (VM+ and VM\u2212) and the true range."),
|
|
26815
|
+
colorInput(BULLISH, "VI+", "plusColor", "Color of the positive vortex line (VI+)."),
|
|
26816
|
+
colorInput(BEARISH, "VI-", "minusColor", "Color of the negative vortex line (VI\u2212)."),
|
|
26817
|
+
colorInput(NEUTRAL, "Baseline", "baselineColor", "Color of the dotted baseline drawn at 1.")
|
|
26818
|
+
],
|
|
25912
26819
|
compute: (bars, inputs) => {
|
|
25913
26820
|
const len = num3(inputs, "length", 14);
|
|
25914
26821
|
const n = bars.length;
|
|
@@ -25921,25 +26828,48 @@ var vortex = {
|
|
|
25921
26828
|
const trSum = sum(trueRange(bars), len);
|
|
25922
26829
|
return {
|
|
25923
26830
|
plots: [
|
|
25924
|
-
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color:
|
|
25925
|
-
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color:
|
|
26831
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: str3(inputs, "plusColor", BULLISH) },
|
|
26832
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: str3(inputs, "minusColor", BEARISH) }
|
|
25926
26833
|
],
|
|
25927
|
-
levels: [{ key: "
|
|
26834
|
+
levels: [{ key: "baseline", price: 1, color: str3(inputs, "baselineColor", NEUTRAL), lineStyle: "dotted", title: "Baseline" }]
|
|
25928
26835
|
};
|
|
25929
26836
|
}
|
|
25930
26837
|
};
|
|
25931
26838
|
var trendSpecs = [aroon, adx, vortex];
|
|
25932
26839
|
|
|
25933
26840
|
// src/core/native-indicators/classics/volatility.ts
|
|
26841
|
+
function expansionColors(values, calm, expanding) {
|
|
26842
|
+
return values.map((x, i) => {
|
|
26843
|
+
if (!Number.isFinite(x)) return null;
|
|
26844
|
+
const prev2 = i > 0 && Number.isFinite(values[i - 1]) ? values[i - 1] : x;
|
|
26845
|
+
return x > prev2 ? expanding : calm;
|
|
26846
|
+
});
|
|
26847
|
+
}
|
|
26848
|
+
function washedToZero(key, title, values, colors, fallback, valueTransparency, width = 1) {
|
|
26849
|
+
return {
|
|
26850
|
+
plots: [{ key, title, values, color: fallback, width, colors }, anchorPlot("zeroAnchor", "Zero Anchor", values.length, 0)],
|
|
26851
|
+
bands: [{ key: "wash", from: key, to: "zeroAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, 0, (_x, i) => colors[i] ?? fallback, valueTransparency) }]
|
|
26852
|
+
};
|
|
26853
|
+
}
|
|
25934
26854
|
var atrSpec = {
|
|
25935
26855
|
type: "average-true-range",
|
|
25936
26856
|
title: "Average True Range",
|
|
25937
26857
|
shortTitle: "ATR",
|
|
25938
26858
|
overlay: false,
|
|
25939
|
-
inputs: [
|
|
25940
|
-
|
|
25941
|
-
|
|
25942
|
-
|
|
26859
|
+
inputs: [
|
|
26860
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to average the true range."),
|
|
26861
|
+
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."),
|
|
26862
|
+
colorInput(NEUTRAL, "ATR Line", "color", "Color of the ATR line while volatility contracts or holds flat."),
|
|
26863
|
+
colorInput(WARNING, "Expanding", "expandColor", "Color of the ATR line while it rises \u2014 volatility expanding bar to bar.")
|
|
26864
|
+
],
|
|
26865
|
+
compute: (bars, inputs) => {
|
|
26866
|
+
const len = num3(inputs, "length", 14);
|
|
26867
|
+
const tr = trueRange(bars);
|
|
26868
|
+
const kind = str3(inputs, "smoothing", "RMA");
|
|
26869
|
+
const values = kind === "SMA" ? sma(tr, len) : kind === "EMA" ? ema(tr, len) : kind === "WMA" ? wma(tr, len) : rma(tr, len);
|
|
26870
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
26871
|
+
return washedToZero("atr", "ATR", values, expansionColors(values, calm, str3(inputs, "expandColor", WARNING)), calm, 75);
|
|
26872
|
+
}
|
|
25943
26873
|
};
|
|
25944
26874
|
var historicalVolatility = {
|
|
25945
26875
|
type: "historical-volatility",
|
|
@@ -25947,14 +26877,23 @@ var historicalVolatility = {
|
|
|
25947
26877
|
shortTitle: "HV",
|
|
25948
26878
|
overlay: false,
|
|
25949
26879
|
inputs: [
|
|
25950
|
-
lengthInput(10),
|
|
25951
|
-
|
|
25952
|
-
|
|
26880
|
+
{ ...lengthInput(10, "length", "Length", 5e3, "Lookback window in bars for the standard deviation of close-to-close log returns."), min: 2 },
|
|
26881
|
+
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."),
|
|
26882
|
+
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."),
|
|
26883
|
+
colorInput(WARNING, "Volatility Line", "color", "Color of the line at its recent highs; it fades toward grey as volatility contracts.")
|
|
25953
26884
|
],
|
|
25954
26885
|
compute: (bars, inputs) => {
|
|
25955
|
-
const
|
|
25956
|
-
const
|
|
25957
|
-
|
|
26886
|
+
const len = num3(inputs, "length", 10);
|
|
26887
|
+
const logReturns = sub(map(closes(bars), Math.log), shift(map(closes(bars), Math.log), 1));
|
|
26888
|
+
const meanReturn = map(sum(logReturns, len), (s) => s / len);
|
|
26889
|
+
const squaredSum = sum(map(logReturns, (r) => r * r), len);
|
|
26890
|
+
const factor = bool(inputs, "annualize", true) ? Math.sqrt(num3(inputs, "periodsPerYear", 252)) : 1;
|
|
26891
|
+
const values = zip(squaredSum, meanReturn, (sq, m) => Math.sqrt(Math.max(sq - len * m * m, 0) / (len - 1)) * factor * 100);
|
|
26892
|
+
const low = lowest(values, 100);
|
|
26893
|
+
const high = highest(values, 100);
|
|
26894
|
+
const accent = str3(inputs, "color", WARNING);
|
|
26895
|
+
const colors = values.map((x, i) => Number.isFinite(x) ? gradient(x, low[i] ?? 0, high[i] ?? 0, NEUTRAL, accent) : null);
|
|
26896
|
+
return washedToZero("hv", "Historical Volatility", values, colors, accent, 75, 2);
|
|
25958
26897
|
}
|
|
25959
26898
|
};
|
|
25960
26899
|
var chaikinVolatility = {
|
|
@@ -25962,13 +26901,26 @@ var chaikinVolatility = {
|
|
|
25962
26901
|
title: "Chaikin Volatility",
|
|
25963
26902
|
shortTitle: "CHV",
|
|
25964
26903
|
overlay: false,
|
|
25965
|
-
inputs: [
|
|
26904
|
+
inputs: [
|
|
26905
|
+
lengthInput(10, "emaLength", "EMA Length", 5e3, "Length of the exponential moving average applied to the bar high-low range."),
|
|
26906
|
+
lengthInput(10, "rocLength", "ROC Length", 5e3, "Number of bars used for the rate of change of the smoothed high-low range."),
|
|
26907
|
+
colorInput(NEUTRAL, "CHV Color", "color", "Color of the Chaikin Volatility line."),
|
|
26908
|
+
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.")
|
|
26909
|
+
],
|
|
25966
26910
|
compute: (bars, inputs) => {
|
|
25967
26911
|
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
25968
|
-
|
|
25969
|
-
|
|
25970
|
-
|
|
25971
|
-
|
|
26912
|
+
const prior = shift(range, num3(inputs, "rocLength", 10));
|
|
26913
|
+
const values = zip(range, prior, (r, p) => p === 0 ? Number.NaN : 100 * (r - p) / p);
|
|
26914
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
26915
|
+
const expansion = str3(inputs, "expansionColor", WARNING);
|
|
26916
|
+
return centeredOscillator({
|
|
26917
|
+
key: "chv",
|
|
26918
|
+
title: "Chaikin Volatility",
|
|
26919
|
+
values,
|
|
26920
|
+
// Only expansion is a state worth accenting; contraction stays structural grey.
|
|
26921
|
+
ink: { bull: expansion, bear: calm, neutral: calm },
|
|
26922
|
+
zeroInk: NEUTRAL
|
|
26923
|
+
});
|
|
25972
26924
|
}
|
|
25973
26925
|
};
|
|
25974
26926
|
var massIndex = {
|
|
@@ -25976,14 +26928,42 @@ var massIndex = {
|
|
|
25976
26928
|
title: "Mass Index",
|
|
25977
26929
|
shortTitle: "MI",
|
|
25978
26930
|
overlay: false,
|
|
25979
|
-
inputs: [
|
|
26931
|
+
inputs: [
|
|
26932
|
+
lengthInput(25, "length", "Sum Length", 5e3, "Number of bars over which the single/double EMA ratio is summed."),
|
|
26933
|
+
lengthInput(9, "emaLength", "EMA Length", 5e3, "Length of the single EMA of the high-low range and of its double smoothing."),
|
|
26934
|
+
floatInput("setupLevel", "Setup Level", 27, 0, 1e3, 0.1, "Reversal bulge set-up level; a rise above it arms the pattern."),
|
|
26935
|
+
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."),
|
|
26936
|
+
colorInput(NEUTRAL, "Mass Index", "color", "Line color while no reversal bulge is armed."),
|
|
26937
|
+
colorInput(WARNING, "Armed", "armedColor", "Line color from the rise above the setup level until the decline below the trigger level."),
|
|
26938
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Reversal bulge levels color.")
|
|
26939
|
+
],
|
|
25980
26940
|
compute: (bars, inputs) => {
|
|
25981
|
-
const
|
|
25982
|
-
const single = ema(
|
|
25983
|
-
const ratio = zip(single, ema(single,
|
|
26941
|
+
const emaLen = num3(inputs, "emaLength", 9);
|
|
26942
|
+
const single = ema(sub(highs(bars), lows(bars)), emaLen);
|
|
26943
|
+
const ratio = zip(single, ema(single, emaLen), (a, b) => b === 0 ? 1 : a / b);
|
|
26944
|
+
const values = sum(ratio, num3(inputs, "length", 25));
|
|
26945
|
+
const setup = num3(inputs, "setupLevel", 27);
|
|
26946
|
+
const trigger = num3(inputs, "triggerLevel", 26.5);
|
|
26947
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
26948
|
+
const armedInk = str3(inputs, "armedColor", WARNING);
|
|
26949
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
26950
|
+
let armed = false;
|
|
26951
|
+
const colors = values.map((x) => {
|
|
26952
|
+
if (!Number.isFinite(x)) return null;
|
|
26953
|
+
armed = x > setup ? true : x < trigger ? false : armed;
|
|
26954
|
+
return armed ? armedInk : calm;
|
|
26955
|
+
});
|
|
25984
26956
|
return {
|
|
25985
|
-
plots: [
|
|
25986
|
-
|
|
26957
|
+
plots: [
|
|
26958
|
+
{ key: "mi", title: "Mass Index", values, color: calm, colors },
|
|
26959
|
+
anchorPlot("setupAnchor", "Setup Anchor", values.length, setup),
|
|
26960
|
+
anchorPlot("triggerAnchor", "Trigger Anchor", values.length, trigger)
|
|
26961
|
+
],
|
|
26962
|
+
bands: [{ key: "bulge", from: "setupAnchor", to: "triggerAnchor", color: transp(armedInk, 90) }],
|
|
26963
|
+
levels: [
|
|
26964
|
+
{ key: "setup", price: setup, color: levelInk, lineStyle: "dashed", title: "Setup Level" },
|
|
26965
|
+
{ key: "trigger", price: trigger, color: levelInk, lineStyle: "dashed", title: "Trigger Level" }
|
|
26966
|
+
]
|
|
25987
26967
|
};
|
|
25988
26968
|
}
|
|
25989
26969
|
};
|
|
@@ -25992,30 +26972,83 @@ var standardDeviation = {
|
|
|
25992
26972
|
title: "Standard Deviation",
|
|
25993
26973
|
shortTitle: "StdDev",
|
|
25994
26974
|
overlay: false,
|
|
25995
|
-
inputs: [
|
|
25996
|
-
|
|
25997
|
-
|
|
25998
|
-
|
|
25999
|
-
|
|
26000
|
-
|
|
26001
|
-
|
|
26002
|
-
|
|
26003
|
-
|
|
26004
|
-
|
|
26005
|
-
|
|
26006
|
-
})
|
|
26975
|
+
inputs: [
|
|
26976
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used to compute the standard deviation."),
|
|
26977
|
+
sourceInput("Close", "source", "Source", "Price series the standard deviation is calculated on."),
|
|
26978
|
+
colorInput(NEUTRAL, "Standard Deviation", "color", "Line color while volatility contracts or holds flat."),
|
|
26979
|
+
colorInput(WARNING, "Expanding", "expandColor", "Line and fill color while volatility expands bar to bar.")
|
|
26980
|
+
],
|
|
26981
|
+
compute: (bars, inputs) => {
|
|
26982
|
+
const values = stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20));
|
|
26983
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
26984
|
+
return washedToZero("stdev", "Standard Deviation", values, expansionColors(values, calm, str3(inputs, "expandColor", WARNING)), calm, 80);
|
|
26985
|
+
}
|
|
26007
26986
|
};
|
|
26008
26987
|
var ulcerIndex = {
|
|
26009
26988
|
type: "ulcer-index",
|
|
26010
26989
|
title: "Ulcer Index",
|
|
26011
26990
|
overlay: false,
|
|
26012
|
-
inputs: [
|
|
26991
|
+
inputs: [
|
|
26992
|
+
{ ...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 },
|
|
26993
|
+
sourceInput("Close", "source", "Source", "Price series the Ulcer Index is computed on. Close is the standard choice."),
|
|
26994
|
+
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."),
|
|
26995
|
+
colorInput(NEUTRAL, "Ulcer Index", "color", "Color of the index near zero; it blends toward the stress color as the reading approaches the threshold."),
|
|
26996
|
+
colorInput(BEARISH, "Stress", "stressColor", "Color of the index at and beyond the alert threshold."),
|
|
26997
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Vertical gradient between zero and the Ulcer Index, fully transparent at zero."),
|
|
26998
|
+
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."),
|
|
26999
|
+
colorInput(transp(BEARISH, 50), "Drawdown Color", "drawdownColor", "Color of the drawdown columns."),
|
|
27000
|
+
colorInput(NEUTRAL, "Threshold", "thresholdColor", "Color of the alert threshold line.")
|
|
27001
|
+
],
|
|
26013
27002
|
compute: (bars, inputs) => {
|
|
26014
27003
|
const len = num3(inputs, "length", 14);
|
|
26015
|
-
const
|
|
26016
|
-
const
|
|
26017
|
-
const values =
|
|
26018
|
-
|
|
27004
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
27005
|
+
const n = src.length;
|
|
27006
|
+
const values = new Array(n).fill(Number.NaN);
|
|
27007
|
+
const drawdown = new Array(n).fill(Number.NaN);
|
|
27008
|
+
for (let i = len - 1; i < n; i++) {
|
|
27009
|
+
let runMax = Number.NaN;
|
|
27010
|
+
let sumSq = 0;
|
|
27011
|
+
let valid = true;
|
|
27012
|
+
let last = Number.NaN;
|
|
27013
|
+
for (let k = i - len + 1; k <= i; k++) {
|
|
27014
|
+
const price = src[k];
|
|
27015
|
+
if (!Number.isFinite(price)) {
|
|
27016
|
+
valid = false;
|
|
27017
|
+
break;
|
|
27018
|
+
}
|
|
27019
|
+
runMax = Number.isFinite(runMax) ? Math.max(runMax, price) : price;
|
|
27020
|
+
if (runMax <= 0) {
|
|
27021
|
+
valid = false;
|
|
27022
|
+
break;
|
|
27023
|
+
}
|
|
27024
|
+
last = 100 * (price - runMax) / runMax;
|
|
27025
|
+
sumSq += last * last;
|
|
27026
|
+
}
|
|
27027
|
+
if (!valid) continue;
|
|
27028
|
+
values[i] = Math.sqrt(sumSq / len);
|
|
27029
|
+
drawdown[i] = last;
|
|
27030
|
+
}
|
|
27031
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
27032
|
+
const stress = str3(inputs, "stressColor", BEARISH);
|
|
27033
|
+
const threshold = num3(inputs, "threshold", 5);
|
|
27034
|
+
const colors = values.map((x) => Number.isFinite(x) ? threshold > 0 ? gradient(x, 0, threshold, calm, stress) : stress : null);
|
|
27035
|
+
const plots = [{ key: "ui", title: "Ulcer Index", values, color: calm, colors }];
|
|
27036
|
+
const bands = [];
|
|
27037
|
+
if (bool(inputs, "gradientFill", true)) {
|
|
27038
|
+
plots.push(anchorPlot("zeroAnchor", "Zero Base", n, 0));
|
|
27039
|
+
bands.push({ key: "wash", from: "ui", to: "zeroAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, 0, (_x, i) => colors[i] ?? calm) });
|
|
27040
|
+
}
|
|
27041
|
+
if (bool(inputs, "showDrawdown", false)) {
|
|
27042
|
+
plots.push({ key: "dd", title: "Drawdown %", values: drawdown, kind: "columns", color: str3(inputs, "drawdownColor", transp(BEARISH, 50)), base: 0 });
|
|
27043
|
+
}
|
|
27044
|
+
return {
|
|
27045
|
+
plots,
|
|
27046
|
+
bands,
|
|
27047
|
+
levels: [
|
|
27048
|
+
{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dotted", title: "Zero Level" },
|
|
27049
|
+
{ key: "threshold", price: threshold, color: str3(inputs, "thresholdColor", NEUTRAL), lineStyle: "dashed", title: "Alert Threshold" }
|
|
27050
|
+
]
|
|
27051
|
+
};
|
|
26019
27052
|
}
|
|
26020
27053
|
};
|
|
26021
27054
|
var choppiness = {
|
|
@@ -26023,19 +27056,33 @@ var choppiness = {
|
|
|
26023
27056
|
title: "Choppiness Index",
|
|
26024
27057
|
shortTitle: "CHOP",
|
|
26025
27058
|
overlay: false,
|
|
26026
|
-
inputs: [
|
|
27059
|
+
inputs: [
|
|
27060
|
+
{ ...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 },
|
|
27061
|
+
floatInput("upperBand", "Upper Band", 61.8, 0, 100, 0.1, "Readings above this level indicate a choppy, range-bound market."),
|
|
27062
|
+
floatInput("lowerBand", "Lower Band", 38.2, 0, 100, 0.1, "Readings below this level indicate a strong trend."),
|
|
27063
|
+
colorInput(NEUTRAL, "CHOP Line", "color", "Color of the Choppiness Index line."),
|
|
27064
|
+
colorInput(BULLISH, "Trending", "trendingColor", "Line, fill and zone color toward and below the lower band \u2014 a trending market."),
|
|
27065
|
+
colorInput(BEARISH, "Choppy", "choppyColor", "Line, fill and zone color toward and above the upper band \u2014 a choppy, range-bound market."),
|
|
27066
|
+
colorInput(transp(NEUTRAL, 90), "Band Fill", "fillColor", "Fill color of the zone between the upper and lower bands.")
|
|
27067
|
+
],
|
|
26027
27068
|
compute: (bars, inputs) => {
|
|
26028
27069
|
const len = num3(inputs, "length", 14);
|
|
26029
27070
|
const trSum = sum(trueRange(bars), len);
|
|
26030
27071
|
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
26031
27072
|
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
26032
|
-
return {
|
|
26033
|
-
|
|
26034
|
-
|
|
26035
|
-
|
|
26036
|
-
|
|
26037
|
-
|
|
26038
|
-
|
|
27073
|
+
return boundedOscillator({
|
|
27074
|
+
key: "chop",
|
|
27075
|
+
title: "CHOP",
|
|
27076
|
+
values,
|
|
27077
|
+
scale: { top: 100, bottom: 0 },
|
|
27078
|
+
overbought: num3(inputs, "upperBand", 61.8),
|
|
27079
|
+
oversold: num3(inputs, "lowerBand", 38.2),
|
|
27080
|
+
ink: { low: str3(inputs, "trendingColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "choppyColor", BEARISH) },
|
|
27081
|
+
levelInk: NEUTRAL,
|
|
27082
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 90)),
|
|
27083
|
+
midpointWash: true,
|
|
27084
|
+
levelTitles: { overbought: "Upper Band", oversold: "Lower Band" }
|
|
27085
|
+
});
|
|
26039
27086
|
}
|
|
26040
27087
|
};
|
|
26041
27088
|
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
@@ -26048,17 +27095,85 @@ function moneyFlowVolume(bars) {
|
|
|
26048
27095
|
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
26049
27096
|
});
|
|
26050
27097
|
}
|
|
27098
|
+
function slopeColors2(values, bull, bear, flat) {
|
|
27099
|
+
return values.map((x, i) => {
|
|
27100
|
+
if (!Number.isFinite(x)) return null;
|
|
27101
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
27102
|
+
if (!Number.isFinite(prev2)) return flat;
|
|
27103
|
+
return x > prev2 ? bull : x < prev2 ? bear : flat;
|
|
27104
|
+
});
|
|
27105
|
+
}
|
|
27106
|
+
function volumeIndex(bars, src, base, when) {
|
|
27107
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
27108
|
+
let index = base;
|
|
27109
|
+
for (let i = 0; i < bars.length; i++) {
|
|
27110
|
+
const v = bars[i].volume;
|
|
27111
|
+
const pv = i > 0 ? bars[i - 1].volume : null;
|
|
27112
|
+
const prev2 = i > 0 ? src[i - 1] : Number.NaN;
|
|
27113
|
+
const active = v != null && pv != null && Number.isFinite(v) && Number.isFinite(pv) && when(v, pv);
|
|
27114
|
+
if (active && Number.isFinite(prev2) && prev2 !== 0) index *= 1 + (src[i] - prev2) / prev2;
|
|
27115
|
+
out[i] = index;
|
|
27116
|
+
}
|
|
27117
|
+
return out;
|
|
27118
|
+
}
|
|
27119
|
+
function regimeIndex(key, title, values, signal, ink, showSignal, gradientFill, singleColor = false) {
|
|
27120
|
+
const inkOf = (i) => {
|
|
27121
|
+
const s = signal[i];
|
|
27122
|
+
const x = values[i];
|
|
27123
|
+
if (singleColor || !Number.isFinite(s)) return ink.neutral;
|
|
27124
|
+
return x > s ? ink.bull : x < s ? ink.bear : ink.neutral;
|
|
27125
|
+
};
|
|
27126
|
+
const plots = [
|
|
27127
|
+
{
|
|
27128
|
+
key: "signal",
|
|
27129
|
+
title: "Signal EMA",
|
|
27130
|
+
values: signal,
|
|
27131
|
+
color: ink.signal,
|
|
27132
|
+
...showSignal ? {} : { display: { pane: false } }
|
|
27133
|
+
},
|
|
27134
|
+
{ key, title, values, color: ink.neutral, width: 2, colors: values.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) }
|
|
27135
|
+
];
|
|
27136
|
+
const bands = gradientFill ? [
|
|
27137
|
+
{
|
|
27138
|
+
key: "regime",
|
|
27139
|
+
from: key,
|
|
27140
|
+
to: "signal",
|
|
27141
|
+
color: transp(NEUTRAL, 100),
|
|
27142
|
+
gradient: values.map((x, i) => {
|
|
27143
|
+
const s = signal[i];
|
|
27144
|
+
if (!Number.isFinite(x) || !Number.isFinite(s)) return null;
|
|
27145
|
+
const above = x > s;
|
|
27146
|
+
const c = above ? ink.bull : ink.bear;
|
|
27147
|
+
return { topValue: Math.max(x, s), bottomValue: Math.min(x, s), topColor: transp(c, above ? 50 : 100), bottomColor: transp(c, above ? 100 : 50) };
|
|
27148
|
+
})
|
|
27149
|
+
}
|
|
27150
|
+
] : [];
|
|
27151
|
+
return { plots, bands };
|
|
27152
|
+
}
|
|
26051
27153
|
var obv = {
|
|
26052
27154
|
type: "on-balance-volume",
|
|
26053
27155
|
title: "On Balance Volume",
|
|
26054
27156
|
shortTitle: "OBV",
|
|
26055
27157
|
overlay: false,
|
|
26056
|
-
inputs: [
|
|
27158
|
+
inputs: [
|
|
27159
|
+
boolInput("smoothing", "Smoothing MA", false, SETTINGS, "Display a moving average of OBV to act as a smoothing/signal line."),
|
|
27160
|
+
optionInput("maType", "MA Type", "SMA", ["SMA", "EMA", "WMA", "RMA (SMMA)"], "Moving average type used for the smoothing MA."),
|
|
27161
|
+
lengthInput(20, "maLength", "MA Length", 5e3, "Number of bars used for the smoothing MA."),
|
|
27162
|
+
colorInput(INFO, "OBV", "color", "Color of the OBV line."),
|
|
27163
|
+
colorInput(WARNING, "Smoothing MA", "maColor", "Color of the smoothing MA line.")
|
|
27164
|
+
],
|
|
26057
27165
|
compute: (bars, inputs) => {
|
|
26058
27166
|
const vol = volumes(bars);
|
|
26059
27167
|
const d = change(closes(bars));
|
|
26060
|
-
const
|
|
26061
|
-
|
|
27168
|
+
const values = cumSum(zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0));
|
|
27169
|
+
const plots = [{ key: "obv", title: "OBV", values, color: str3(inputs, "color", INFO) }];
|
|
27170
|
+
if (bool(inputs, "smoothing", false)) {
|
|
27171
|
+
const len = num3(inputs, "maLength", 20);
|
|
27172
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
27173
|
+
const ma = kind === "EMA" ? ema(values, len) : kind === "WMA" ? wma(values, len) : kind === "RMA (SMMA)" ? rma(values, len) : sma(values, len);
|
|
27174
|
+
plots.push({ key: "ma", title: "Smoothing MA", values: ma, color: str3(inputs, "maColor", WARNING) });
|
|
27175
|
+
}
|
|
27176
|
+
return { plots };
|
|
26062
27177
|
}
|
|
26063
27178
|
};
|
|
26064
27179
|
var accDist = {
|
|
@@ -26066,24 +27181,50 @@ var accDist = {
|
|
|
26066
27181
|
title: "Accumulation / Distribution",
|
|
26067
27182
|
shortTitle: "A/D",
|
|
26068
27183
|
overlay: false,
|
|
26069
|
-
inputs: [
|
|
26070
|
-
|
|
26071
|
-
|
|
26072
|
-
|
|
27184
|
+
inputs: [
|
|
27185
|
+
boolInput("slopeColoring", "Slope Coloring", true, STYLE, "Color the line by its slope \u2014 bullish while rising (accumulation), bearish while falling (distribution)."),
|
|
27186
|
+
colorInput(NEUTRAL, "A/D Line", "color", "Color of the line when slope coloring is disabled or the line is flat."),
|
|
27187
|
+
colorInput(BULLISH, "Rising", "bullColor", "Line color while the A/D Line rises."),
|
|
27188
|
+
colorInput(BEARISH, "Falling", "bearColor", "Line color while the A/D Line falls.")
|
|
27189
|
+
],
|
|
27190
|
+
compute: (bars, inputs) => {
|
|
27191
|
+
const values = cumSum(moneyFlowVolume(bars));
|
|
27192
|
+
const flat = str3(inputs, "color", NEUTRAL);
|
|
27193
|
+
return {
|
|
27194
|
+
plots: [
|
|
27195
|
+
{
|
|
27196
|
+
key: "ad",
|
|
27197
|
+
title: "A/D",
|
|
27198
|
+
values,
|
|
27199
|
+
color: flat,
|
|
27200
|
+
...bool(inputs, "slopeColoring", true) ? { colors: slopeColors2(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), flat) } : {}
|
|
27201
|
+
}
|
|
27202
|
+
]
|
|
27203
|
+
};
|
|
27204
|
+
}
|
|
26073
27205
|
};
|
|
26074
27206
|
var cmf = {
|
|
26075
27207
|
type: "chaikin-money-flow",
|
|
26076
27208
|
title: "Chaikin Money Flow",
|
|
26077
27209
|
shortTitle: "CMF",
|
|
26078
27210
|
overlay: false,
|
|
26079
|
-
inputs: [
|
|
27211
|
+
inputs: [
|
|
27212
|
+
lengthInput(20, "length", "Length", 5e3, "Lookback length over which money flow volume and volume are summed."),
|
|
27213
|
+
colorInput(NEUTRAL, "CMF Line", "color", "Color of the Chaikin Money Flow line."),
|
|
27214
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the CMF reads above zero (net money flowing in)."),
|
|
27215
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the CMF reads below zero (net money flowing out)."),
|
|
27216
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
27217
|
+
],
|
|
26080
27218
|
compute: (bars, inputs) => {
|
|
26081
27219
|
const len = num3(inputs, "length", 20);
|
|
26082
27220
|
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
26083
|
-
return {
|
|
26084
|
-
|
|
26085
|
-
|
|
26086
|
-
|
|
27221
|
+
return centeredOscillator({
|
|
27222
|
+
key: "cmf",
|
|
27223
|
+
title: "CMF",
|
|
27224
|
+
values,
|
|
27225
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
27226
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
27227
|
+
});
|
|
26087
27228
|
}
|
|
26088
27229
|
};
|
|
26089
27230
|
var chaikinOscillator = {
|
|
@@ -26091,14 +27232,23 @@ var chaikinOscillator = {
|
|
|
26091
27232
|
title: "Chaikin Oscillator",
|
|
26092
27233
|
shortTitle: "Chaikin Osc",
|
|
26093
27234
|
overlay: false,
|
|
26094
|
-
inputs: [
|
|
27235
|
+
inputs: [
|
|
27236
|
+
lengthInput(3, "fastLength", "Fast Length", 5e3, "Length of the fast EMA applied to the accumulation/distribution line."),
|
|
27237
|
+
lengthInput(10, "slowLength", "Slow Length", 5e3, "Length of the slow EMA applied to the accumulation/distribution line."),
|
|
27238
|
+
colorInput(NEUTRAL, "Oscillator", "color", "Color of the Chaikin Oscillator line."),
|
|
27239
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the oscillator reads above zero (accumulation)."),
|
|
27240
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the oscillator reads below zero (distribution).")
|
|
27241
|
+
],
|
|
26095
27242
|
compute: (bars, inputs) => {
|
|
26096
27243
|
const ad = cumSum(moneyFlowVolume(bars));
|
|
26097
27244
|
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
26098
|
-
return {
|
|
26099
|
-
|
|
26100
|
-
|
|
26101
|
-
|
|
27245
|
+
return centeredOscillator({
|
|
27246
|
+
key: "co",
|
|
27247
|
+
title: "Chaikin Oscillator",
|
|
27248
|
+
values,
|
|
27249
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
27250
|
+
zeroInk: NEUTRAL
|
|
27251
|
+
});
|
|
26102
27252
|
}
|
|
26103
27253
|
};
|
|
26104
27254
|
var eom = {
|
|
@@ -26106,20 +27256,28 @@ var eom = {
|
|
|
26106
27256
|
title: "Ease of Movement",
|
|
26107
27257
|
shortTitle: "EOM",
|
|
26108
27258
|
overlay: false,
|
|
26109
|
-
inputs: [
|
|
27259
|
+
inputs: [
|
|
27260
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars in the simple moving average smoothing the 1-bar Ease of Movement values."),
|
|
27261
|
+
intInput("divisor", "Divisor", 1e4, 1, 1e9, "Volume scaling constant of the box ratio; larger values scale the oscillator up. 10000 is the conventional default."),
|
|
27262
|
+
colorInput(NEUTRAL, "EOM Line", "color", "Color of the line when it sits exactly at zero."),
|
|
27263
|
+
colorInput(BULLISH, "Above Zero", "bullColor", "Color of the line and fill while Ease of Movement is positive."),
|
|
27264
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while Ease of Movement is negative.")
|
|
27265
|
+
],
|
|
26110
27266
|
compute: (bars, inputs) => {
|
|
26111
27267
|
const div = num3(inputs, "divisor", 1e4);
|
|
26112
|
-
const
|
|
26113
|
-
const move = change(hl2);
|
|
27268
|
+
const move = change(sourceValues(bars, "HL2"));
|
|
26114
27269
|
const raw = bars.map((b, i) => {
|
|
26115
27270
|
const m = move[i];
|
|
26116
|
-
if (b.volume == null || !Number.isFinite(b.volume) ||
|
|
26117
|
-
return div * m * (b.high - b.low) / b.volume;
|
|
27271
|
+
if (b.volume == null || !Number.isFinite(b.volume) || !Number.isFinite(m)) return Number.NaN;
|
|
27272
|
+
return b.volume === 0 ? 0 : div * m * (b.high - b.low) / b.volume;
|
|
27273
|
+
});
|
|
27274
|
+
return centeredOscillator({
|
|
27275
|
+
key: "eom",
|
|
27276
|
+
title: "EOM",
|
|
27277
|
+
values: sma(raw, num3(inputs, "length", 14)),
|
|
27278
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
27279
|
+
zeroInk: NEUTRAL
|
|
26118
27280
|
});
|
|
26119
|
-
return {
|
|
26120
|
-
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
26121
|
-
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
26122
|
-
};
|
|
26123
27281
|
}
|
|
26124
27282
|
};
|
|
26125
27283
|
var forceIndex = {
|
|
@@ -26127,13 +27285,24 @@ var forceIndex = {
|
|
|
26127
27285
|
title: "Force Index",
|
|
26128
27286
|
shortTitle: "FI",
|
|
26129
27287
|
overlay: false,
|
|
26130
|
-
inputs: [
|
|
27288
|
+
inputs: [
|
|
27289
|
+
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."),
|
|
27290
|
+
colorInput(BULLISH, "Force Index", "color", "Color of the line while above zero (buying pressure)."),
|
|
27291
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line while below zero (selling pressure).")
|
|
27292
|
+
],
|
|
26131
27293
|
compute: (bars, inputs) => {
|
|
26132
27294
|
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
26133
|
-
|
|
26134
|
-
|
|
26135
|
-
|
|
26136
|
-
|
|
27295
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
27296
|
+
return centeredOscillator({
|
|
27297
|
+
key: "force",
|
|
27298
|
+
title: "Force Index",
|
|
27299
|
+
values: ema(raw, num3(inputs, "length", 13)),
|
|
27300
|
+
// The reference reads an exact zero as buying pressure, so neutral shares the bull ink.
|
|
27301
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
27302
|
+
zeroInk: NEUTRAL,
|
|
27303
|
+
width: 2,
|
|
27304
|
+
valueTransparency: 60
|
|
27305
|
+
});
|
|
26137
27306
|
}
|
|
26138
27307
|
};
|
|
26139
27308
|
var klinger = {
|
|
@@ -26141,19 +27310,44 @@ var klinger = {
|
|
|
26141
27310
|
title: "Klinger Oscillator",
|
|
26142
27311
|
shortTitle: "KVO",
|
|
26143
27312
|
overlay: false,
|
|
26144
|
-
inputs: [
|
|
27313
|
+
inputs: [
|
|
27314
|
+
lengthInput(34, "fastLength", "Fast Length", 5e3, "Length of the fast EMA applied to the volume force."),
|
|
27315
|
+
lengthInput(55, "slowLength", "Slow Length", 5e3, "Length of the slow EMA applied to the volume force."),
|
|
27316
|
+
lengthInput(13, "signalLength", "Signal Length", 500, "Length of the EMA applied to the oscillator to obtain the signal line."),
|
|
27317
|
+
colorInput(BULLISH, "Bullish", "color", "Color of the Klinger oscillator while above zero."),
|
|
27318
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the Klinger oscillator while below zero."),
|
|
27319
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line.")
|
|
27320
|
+
],
|
|
26145
27321
|
compute: (bars, inputs) => {
|
|
26146
|
-
const
|
|
26147
|
-
const
|
|
26148
|
-
|
|
26149
|
-
|
|
26150
|
-
|
|
26151
|
-
|
|
26152
|
-
|
|
26153
|
-
|
|
26154
|
-
|
|
26155
|
-
|
|
26156
|
-
|
|
27322
|
+
const n = bars.length;
|
|
27323
|
+
const force = new Array(n).fill(Number.NaN);
|
|
27324
|
+
let trend = 1;
|
|
27325
|
+
let prevTrend = 1;
|
|
27326
|
+
let cm = 0;
|
|
27327
|
+
let prevDm = 0;
|
|
27328
|
+
for (let i = 0; i < n; i++) {
|
|
27329
|
+
const b = bars[i];
|
|
27330
|
+
const hlc = b.high + b.low + b.close;
|
|
27331
|
+
const prevHlc = i > 0 ? bars[i - 1].high + bars[i - 1].low + bars[i - 1].close : hlc;
|
|
27332
|
+
trend = hlc > prevHlc ? 1 : -1;
|
|
27333
|
+
const dm = b.high - b.low;
|
|
27334
|
+
cm = i > 0 && trend === prevTrend ? cm + dm : prevDm + dm;
|
|
27335
|
+
const v = b.volume;
|
|
27336
|
+
force[i] = v == null || !Number.isFinite(v) ? Number.NaN : cm === 0 ? 0 : v * Math.abs(2 * (dm / cm) - 1) * trend * 100;
|
|
27337
|
+
prevTrend = trend;
|
|
27338
|
+
prevDm = dm;
|
|
27339
|
+
}
|
|
27340
|
+
const line = sub(ema(force, num3(inputs, "fastLength", 34)), ema(force, num3(inputs, "slowLength", 55)));
|
|
27341
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
27342
|
+
return centeredOscillator({
|
|
27343
|
+
key: "kvo",
|
|
27344
|
+
title: "KVO",
|
|
27345
|
+
values: line,
|
|
27346
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
27347
|
+
zeroInk: transp(NEUTRAL, 50),
|
|
27348
|
+
valueTransparency: 60,
|
|
27349
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: str3(inputs, "signalColor", WARNING) }]
|
|
27350
|
+
});
|
|
26157
27351
|
}
|
|
26158
27352
|
};
|
|
26159
27353
|
var mfi = {
|
|
@@ -26161,7 +27355,15 @@ var mfi = {
|
|
|
26161
27355
|
title: "Money Flow Index",
|
|
26162
27356
|
shortTitle: "MFI",
|
|
26163
27357
|
overlay: false,
|
|
26164
|
-
inputs: [
|
|
27358
|
+
inputs: [
|
|
27359
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to sum positive and negative money flow."),
|
|
27360
|
+
floatInput("overbought", "Overbought Level", 80, 0, 100, 1, "Level above which the MFI is considered overbought."),
|
|
27361
|
+
floatInput("oversold", "Oversold Level", 20, 0, 100, 1, "Level below which the MFI is considered oversold."),
|
|
27362
|
+
colorInput(NEUTRAL, "MFI", "color", "Color of the MFI line between the levels. Toward either level it blends into that level\u2019s color."),
|
|
27363
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
27364
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level."),
|
|
27365
|
+
colorInput(transp(NEUTRAL, 94), "OB/OS Zone Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
27366
|
+
],
|
|
26165
27367
|
compute: (bars, inputs) => {
|
|
26166
27368
|
const len = num3(inputs, "length", 14);
|
|
26167
27369
|
const tp = sourceValues(bars, "HLC3");
|
|
@@ -26169,48 +27371,57 @@ var mfi = {
|
|
|
26169
27371
|
const d = change(tp);
|
|
26170
27372
|
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
26171
27373
|
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
26172
|
-
const values = zip(pos, neg, (p,
|
|
26173
|
-
return {
|
|
26174
|
-
|
|
26175
|
-
|
|
26176
|
-
|
|
26177
|
-
|
|
26178
|
-
|
|
26179
|
-
|
|
27374
|
+
const values = zip(pos, neg, (p, w) => p + w > 0 ? 100 * p / (p + w) : Number.NaN);
|
|
27375
|
+
return boundedOscillator({
|
|
27376
|
+
key: "mfi",
|
|
27377
|
+
title: "MFI",
|
|
27378
|
+
values,
|
|
27379
|
+
scale: { top: 100, bottom: 0 },
|
|
27380
|
+
overbought: num3(inputs, "overbought", 80),
|
|
27381
|
+
oversold: num3(inputs, "oversold", 20),
|
|
27382
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
27383
|
+
levelInk: NEUTRAL,
|
|
27384
|
+
zoneTransparency: 91,
|
|
27385
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 94))
|
|
27386
|
+
});
|
|
26180
27387
|
}
|
|
26181
27388
|
};
|
|
26182
|
-
function
|
|
26183
|
-
|
|
26184
|
-
|
|
26185
|
-
|
|
26186
|
-
|
|
26187
|
-
|
|
26188
|
-
|
|
26189
|
-
|
|
26190
|
-
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
26191
|
-
if (started) out[i] = index;
|
|
26192
|
-
continue;
|
|
26193
|
-
}
|
|
26194
|
-
started = true;
|
|
26195
|
-
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
26196
|
-
out[i] = index;
|
|
26197
|
-
}
|
|
26198
|
-
return out;
|
|
27389
|
+
function regimeStyleInputs(signalTitle) {
|
|
27390
|
+
return [
|
|
27391
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the line and gradient fill while it holds above its signal EMA."),
|
|
27392
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line and gradient fill while it holds below its signal EMA."),
|
|
27393
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor", "Color used while the signal EMA is still forming, or the line sits exactly on it."),
|
|
27394
|
+
colorInput(NEUTRAL, signalTitle, "signalColor", "Color of the signal EMA overlay."),
|
|
27395
|
+
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.")
|
|
27396
|
+
];
|
|
26199
27397
|
}
|
|
27398
|
+
var NVI_POSITION = "Position vs Average";
|
|
27399
|
+
var NVI_SINGLE = "Single Color";
|
|
26200
27400
|
var nvi = {
|
|
26201
27401
|
type: "negative-volume-index",
|
|
26202
27402
|
title: "Negative Volume Index",
|
|
26203
27403
|
shortTitle: "Negative Volume Index",
|
|
26204
27404
|
overlay: false,
|
|
26205
|
-
inputs: [
|
|
27405
|
+
inputs: [
|
|
27406
|
+
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."),
|
|
27407
|
+
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."),
|
|
27408
|
+
lengthInput(255, "signalLength", "Signal Length", 5e3, "Length of the signal EMA. The convention is one trading year \u2014 255 sessions on daily charts."),
|
|
27409
|
+
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."),
|
|
27410
|
+
...regimeStyleInputs("Signal EMA")
|
|
27411
|
+
],
|
|
26206
27412
|
compute: (bars, inputs) => {
|
|
26207
|
-
const
|
|
26208
|
-
|
|
26209
|
-
|
|
26210
|
-
|
|
26211
|
-
|
|
26212
|
-
|
|
26213
|
-
|
|
27413
|
+
const values = volumeIndex(bars, closes(bars), num3(inputs, "base", 1e3), (v, pv) => v < pv);
|
|
27414
|
+
const signal = ema(values, num3(inputs, "signalLength", 255));
|
|
27415
|
+
return regimeIndex(
|
|
27416
|
+
"nvi",
|
|
27417
|
+
"NVI",
|
|
27418
|
+
values,
|
|
27419
|
+
signal,
|
|
27420
|
+
{ bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "neutralColor", NEUTRAL), signal: str3(inputs, "signalColor", NEUTRAL) },
|
|
27421
|
+
bool(inputs, "showSignal", true),
|
|
27422
|
+
bool(inputs, "gradientFill", true),
|
|
27423
|
+
str3(inputs, "colorMode", NVI_POSITION) === NVI_SINGLE
|
|
27424
|
+
);
|
|
26214
27425
|
}
|
|
26215
27426
|
};
|
|
26216
27427
|
var pvi = {
|
|
@@ -26218,15 +27429,26 @@ var pvi = {
|
|
|
26218
27429
|
title: "Positive Volume Index",
|
|
26219
27430
|
shortTitle: "Positive Volume Index",
|
|
26220
27431
|
overlay: false,
|
|
26221
|
-
inputs: [
|
|
27432
|
+
inputs: [
|
|
27433
|
+
sourceInput("Close", "source", "Source", "Price series whose one-bar percentage change moves PVI on its active sessions."),
|
|
27434
|
+
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."),
|
|
27435
|
+
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."),
|
|
27436
|
+
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."),
|
|
27437
|
+
...regimeStyleInputs("Signal Line")
|
|
27438
|
+
],
|
|
26222
27439
|
compute: (bars, inputs) => {
|
|
26223
|
-
const
|
|
26224
|
-
|
|
26225
|
-
|
|
26226
|
-
|
|
26227
|
-
|
|
26228
|
-
|
|
26229
|
-
|
|
27440
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
27441
|
+
const values = volumeIndex(bars, src, num3(inputs, "base", 1e3), (v, pv) => v > pv);
|
|
27442
|
+
const signal = ema(values, num3(inputs, "signalLength", 255));
|
|
27443
|
+
return regimeIndex(
|
|
27444
|
+
"pvi",
|
|
27445
|
+
"PVI",
|
|
27446
|
+
values,
|
|
27447
|
+
signal,
|
|
27448
|
+
{ bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "neutralColor", NEUTRAL), signal: str3(inputs, "signalColor", NEUTRAL) },
|
|
27449
|
+
bool(inputs, "showSignal", true),
|
|
27450
|
+
bool(inputs, "gradientFill", true)
|
|
27451
|
+
);
|
|
26230
27452
|
}
|
|
26231
27453
|
};
|
|
26232
27454
|
var pvt = {
|
|
@@ -26234,7 +27456,15 @@ var pvt = {
|
|
|
26234
27456
|
title: "Price Volume Trend",
|
|
26235
27457
|
shortTitle: "PVT",
|
|
26236
27458
|
overlay: false,
|
|
26237
|
-
inputs: [
|
|
27459
|
+
inputs: [
|
|
27460
|
+
boolInput("showSignal", "Signal Line", false, SETTINGS, "Display a moving average of PVT acting as a signal line."),
|
|
27461
|
+
optionInput("signalType", "Signal Type", "SMA", ["SMA", "EMA"], "Moving average type used for the signal line."),
|
|
27462
|
+
lengthInput(21, "signalLength", "Signal Length", 5e3, "Number of bars used to compute the signal line."),
|
|
27463
|
+
colorInput(NEUTRAL, "PVT Color", "color", "Color of the PVT line while it is flat."),
|
|
27464
|
+
colorInput(BULLISH, "Rising", "bullColor", "Color of the PVT line while it rises bar to bar."),
|
|
27465
|
+
colorInput(BEARISH, "Falling", "bearColor", "Color of the PVT line while it falls bar to bar."),
|
|
27466
|
+
colorInput(WARNING, "Signal Color", "signalColor", "Color of the signal line.")
|
|
27467
|
+
],
|
|
26238
27468
|
compute: (bars, inputs) => {
|
|
26239
27469
|
const c = closes(bars);
|
|
26240
27470
|
const raw = bars.map((b, i) => {
|
|
@@ -26242,7 +27472,17 @@ var pvt = {
|
|
|
26242
27472
|
const pc = c[i - 1];
|
|
26243
27473
|
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
26244
27474
|
});
|
|
26245
|
-
|
|
27475
|
+
const values = cumSum(raw);
|
|
27476
|
+
const flat = str3(inputs, "color", NEUTRAL);
|
|
27477
|
+
const plots = [
|
|
27478
|
+
{ key: "pvt", title: "PVT", values, color: flat, width: 2, colors: slopeColors2(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), flat) }
|
|
27479
|
+
];
|
|
27480
|
+
if (bool(inputs, "showSignal", false)) {
|
|
27481
|
+
const len = num3(inputs, "signalLength", 21);
|
|
27482
|
+
const signal = str3(inputs, "signalType", "SMA") === "EMA" ? ema(values, len) : sma(values, len);
|
|
27483
|
+
plots.push({ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) });
|
|
27484
|
+
}
|
|
27485
|
+
return { plots };
|
|
26246
27486
|
}
|
|
26247
27487
|
};
|
|
26248
27488
|
var volumeOscillator = {
|
|
@@ -26250,39 +27490,75 @@ var volumeOscillator = {
|
|
|
26250
27490
|
title: "Volume Oscillator",
|
|
26251
27491
|
shortTitle: "Vol Osc",
|
|
26252
27492
|
overlay: false,
|
|
26253
|
-
inputs: [
|
|
27493
|
+
inputs: [
|
|
27494
|
+
lengthInput(5, "fastLength", "Fast Length", 5e3, "Length of the fast EMA of volume."),
|
|
27495
|
+
lengthInput(10, "slowLength", "Slow Length", 5e3, "Length of the slow EMA of volume."),
|
|
27496
|
+
colorInput(BULLISH, "Above Zero", "color", "Color of the line and fill while it holds above zero \u2014 volume expanding."),
|
|
27497
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while it holds below zero \u2014 volume contracting.")
|
|
27498
|
+
],
|
|
26254
27499
|
compute: (bars, inputs) => {
|
|
26255
27500
|
const vol = volumes(bars);
|
|
26256
27501
|
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
26257
27502
|
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
26258
|
-
const values = zip(fast, slow, (f, s) => s === 0 ?
|
|
26259
|
-
|
|
26260
|
-
|
|
26261
|
-
|
|
26262
|
-
|
|
27503
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? 0 : 100 * (f - s) / s);
|
|
27504
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
27505
|
+
return centeredOscillator({
|
|
27506
|
+
key: "vo",
|
|
27507
|
+
title: "Volume Oscillator",
|
|
27508
|
+
values,
|
|
27509
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
27510
|
+
zeroInk: NEUTRAL,
|
|
27511
|
+
valueTransparency: 60
|
|
27512
|
+
});
|
|
26263
27513
|
}
|
|
26264
27514
|
};
|
|
27515
|
+
var OSCILLATOR = "Oscillator";
|
|
26265
27516
|
var pvo = {
|
|
26266
27517
|
type: "pvo",
|
|
26267
27518
|
title: "Percentage Volume Oscillator",
|
|
26268
27519
|
shortTitle: "PVO",
|
|
26269
27520
|
overlay: false,
|
|
26270
|
-
inputs: [
|
|
27521
|
+
inputs: [
|
|
27522
|
+
{ ...lengthInput(12, "fastLength", "Fast Length", 5e3, "Period of the fast EMA of volume. The conventional setting is 12."), group: OSCILLATOR },
|
|
27523
|
+
{ ...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 },
|
|
27524
|
+
{ ...lengthInput(9, "signalLength", "Signal Length", 500, "Period of the EMA applied to the PVO to form the signal line."), group: OSCILLATOR },
|
|
27525
|
+
colorInput(BULLISH, "Expansion Color", "color", "Color of the oscillator and fill while participation expands."),
|
|
27526
|
+
colorInput(BEARISH, "Contraction Color", "bearColor", "Color of the oscillator and fill while participation contracts."),
|
|
27527
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
27528
|
+
colorInput(transp(NEUTRAL, 50), "Zero Line", "zeroColor", "Color of the zero reference line."),
|
|
27529
|
+
boolInput("showSignal", "Show Signal Line", true, STYLE, "Plot the signal line, an EMA of the PVO."),
|
|
27530
|
+
boolInput("showHistogram", "Show Histogram", true, STYLE, "Plot the difference between the PVO and its signal line as columns behind the oscillator.")
|
|
27531
|
+
],
|
|
26271
27532
|
compute: (bars, inputs) => {
|
|
26272
27533
|
const vol = volumes(bars);
|
|
26273
27534
|
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
26274
27535
|
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
26275
|
-
const
|
|
26276
|
-
const signal = ema(
|
|
26277
|
-
const
|
|
26278
|
-
|
|
26279
|
-
|
|
26280
|
-
|
|
26281
|
-
|
|
26282
|
-
|
|
26283
|
-
|
|
26284
|
-
|
|
26285
|
-
|
|
27536
|
+
const values = zip(fast, slow, (f, s) => s > 0 ? 100 * (f - s) / s : Number.NaN);
|
|
27537
|
+
const signal = ema(values, num3(inputs, "signalLength", 9));
|
|
27538
|
+
const expansion = str3(inputs, "color", BULLISH);
|
|
27539
|
+
const contraction = str3(inputs, "bearColor", BEARISH);
|
|
27540
|
+
const extraPlots = [];
|
|
27541
|
+
if (bool(inputs, "showHistogram", true)) {
|
|
27542
|
+
const hist = sub(values, signal);
|
|
27543
|
+
extraPlots.push({
|
|
27544
|
+
key: "hist",
|
|
27545
|
+
title: "Histogram",
|
|
27546
|
+
values: hist,
|
|
27547
|
+
kind: "columns",
|
|
27548
|
+
color: transp(expansion, 60),
|
|
27549
|
+
colors: hist.map((x) => Number.isFinite(x) ? transp(x > 0 ? expansion : contraction, 60) : null),
|
|
27550
|
+
base: 0
|
|
27551
|
+
});
|
|
27552
|
+
}
|
|
27553
|
+
if (bool(inputs, "showSignal", true)) extraPlots.push({ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) });
|
|
27554
|
+
return centeredOscillator({
|
|
27555
|
+
key: "pvo",
|
|
27556
|
+
title: "PVO",
|
|
27557
|
+
values,
|
|
27558
|
+
ink: { bull: expansion, bear: contraction, neutral: contraction },
|
|
27559
|
+
zeroInk: str3(inputs, "zeroColor", transp(NEUTRAL, 50)),
|
|
27560
|
+
extraPlots
|
|
27561
|
+
});
|
|
26286
27562
|
}
|
|
26287
27563
|
};
|
|
26288
27564
|
var vfi = {
|
|
@@ -26291,55 +27567,86 @@ var vfi = {
|
|
|
26291
27567
|
shortTitle: "Volume Flow Indicator",
|
|
26292
27568
|
overlay: false,
|
|
26293
27569
|
inputs: [
|
|
26294
|
-
lengthInput(130),
|
|
26295
|
-
|
|
26296
|
-
|
|
26297
|
-
lengthInput(
|
|
27570
|
+
{ ...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 },
|
|
27571
|
+
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."),
|
|
27572
|
+
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."),
|
|
27573
|
+
{ ...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 },
|
|
27574
|
+
boolInput("smooth", "Smooth VFI", true, SETTINGS, "Applies the published light smoothing \u2014 an EMA of the summed line \u2014 before plotting."),
|
|
27575
|
+
lengthInput(3, "smoothLength", "Smoothing Length", 500, "Length of the smoothing EMA. The published version uses 3 bars."),
|
|
27576
|
+
boolInput("showSignal", "Signal Line", true, SETTINGS, "Plots an EMA of VFI as a signal line."),
|
|
27577
|
+
lengthInput(5, "signalLength", "Signal Length", 500, "Length of the signal-line EMA."),
|
|
27578
|
+
colorInput(BULLISH, "Bullish", "color", "Color of the VFI line and gradient fill above the zero line \u2014 the net accumulation regime."),
|
|
27579
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the VFI line and gradient fill below the zero line \u2014 the net distribution regime."),
|
|
27580
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
27581
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the zero line and the VFI line with a vertical gradient.")
|
|
26298
27582
|
],
|
|
26299
27583
|
compute: (bars, inputs) => {
|
|
26300
27584
|
const len = num3(inputs, "length", 130);
|
|
27585
|
+
const typical = sourceValues(bars, "HLC3");
|
|
27586
|
+
const logChange = sub(map(typical, Math.log), shift(map(typical, Math.log), 1));
|
|
27587
|
+
const volatility = stdev(logChange, num3(inputs, "volLength", 30));
|
|
26301
27588
|
const coef = num3(inputs, "coef", 0.2);
|
|
26302
27589
|
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
26303
|
-
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
26304
|
-
const inter = change(typical);
|
|
26305
|
-
const vInter = stdev(inter, 30);
|
|
26306
27590
|
const vol = volumes(bars);
|
|
26307
|
-
const
|
|
26308
|
-
const
|
|
26309
|
-
const
|
|
26310
|
-
const
|
|
27591
|
+
const avgVolume = shift(sma(vol, len), 1);
|
|
27592
|
+
const typicalChange = change(typical);
|
|
27593
|
+
const signed2 = bars.map((b, i) => {
|
|
27594
|
+
const sd = volatility[i];
|
|
27595
|
+
const m = typicalChange[i];
|
|
26311
27596
|
const v = vol[i];
|
|
26312
|
-
|
|
26313
|
-
const
|
|
26314
|
-
|
|
26315
|
-
const capped = Math.min(v,
|
|
27597
|
+
if (!Number.isFinite(sd) || !Number.isFinite(m) || !Number.isFinite(v)) return Number.NaN;
|
|
27598
|
+
const cutoff = coef * sd * b.close;
|
|
27599
|
+
const avg = avgVolume[i];
|
|
27600
|
+
const capped = Number.isFinite(avg) ? Math.min(v, avg * volCoef) : v;
|
|
26316
27601
|
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
26317
27602
|
});
|
|
26318
|
-
const
|
|
26319
|
-
|
|
26320
|
-
|
|
27603
|
+
const raw = zip(sum(signed2, len), avgVolume, (f, a) => a > 0 ? f / a : Number.NaN);
|
|
27604
|
+
const values = bool(inputs, "smooth", true) ? ema(raw, num3(inputs, "smoothLength", 3)) : raw;
|
|
27605
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
27606
|
+
const extraPlots = bool(inputs, "showSignal", true) ? [{ key: "signal", title: "Signal Line", values: ema(values, num3(inputs, "signalLength", 5)), color: str3(inputs, "signalColor", WARNING) }] : [];
|
|
27607
|
+
return centeredOscillator({
|
|
27608
|
+
key: "vfi",
|
|
27609
|
+
title: "VFI",
|
|
27610
|
+
values,
|
|
27611
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "bearColor", BEARISH) },
|
|
27612
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
27613
|
+
fill: bool(inputs, "gradientFill", true),
|
|
27614
|
+
extraPlots
|
|
26321
27615
|
});
|
|
26322
|
-
const flowSum = sum(raw, len);
|
|
26323
|
-
const line = zip(flowSum, values, (f, a) => f / a);
|
|
26324
|
-
return {
|
|
26325
|
-
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
26326
|
-
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
26327
|
-
};
|
|
26328
27616
|
}
|
|
26329
27617
|
};
|
|
27618
|
+
var II_OSCILLATOR = "Normalized Oscillator (II%)";
|
|
27619
|
+
var II_CUMULATIVE = "Cumulative Line";
|
|
26330
27620
|
var intradayIntensity = {
|
|
26331
27621
|
type: "intraday-intensity",
|
|
26332
27622
|
title: "Intraday Intensity",
|
|
26333
27623
|
shortTitle: "Intraday Intensity",
|
|
26334
27624
|
overlay: false,
|
|
26335
|
-
inputs: [
|
|
27625
|
+
inputs: [
|
|
27626
|
+
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."),
|
|
27627
|
+
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."),
|
|
27628
|
+
colorInput(BULLISH, "Bullish", "color", "Color of positive readings, the rising cumulative line and the fill above zero \u2014 the accumulation side."),
|
|
27629
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of negative readings, the falling cumulative line and the fill below zero \u2014 the distribution side."),
|
|
27630
|
+
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.")
|
|
27631
|
+
],
|
|
26336
27632
|
compute: (bars, inputs) => {
|
|
27633
|
+
const intensity = moneyFlowVolume(bars);
|
|
27634
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
27635
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
27636
|
+
if (str3(inputs, "mode", II_OSCILLATOR) === II_CUMULATIVE) {
|
|
27637
|
+
const values2 = cumSum(intensity);
|
|
27638
|
+
return { plots: [{ key: "ii", title: "Intraday Intensity", values: values2, color: bull, colors: slopeColors2(values2, bull, bear, bull) }] };
|
|
27639
|
+
}
|
|
26337
27640
|
const len = num3(inputs, "length", 21);
|
|
26338
|
-
const values = zip(sum(
|
|
26339
|
-
return {
|
|
26340
|
-
|
|
26341
|
-
|
|
26342
|
-
|
|
27641
|
+
const values = zip(sum(intensity, len), sum(volumes(bars), len), (m, v) => v > 0 ? 100 * m / v : Number.NaN);
|
|
27642
|
+
return centeredOscillator({
|
|
27643
|
+
key: "ii",
|
|
27644
|
+
title: "Intraday Intensity",
|
|
27645
|
+
values,
|
|
27646
|
+
ink: { bull, bear, neutral: bear },
|
|
27647
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
27648
|
+
fill: bool(inputs, "gradientFill", true)
|
|
27649
|
+
});
|
|
26343
27650
|
}
|
|
26344
27651
|
};
|
|
26345
27652
|
var volumeSpecs = [
|
|
@@ -26372,15 +27679,24 @@ function periodKey(anchor, time) {
|
|
|
26372
27679
|
}
|
|
26373
27680
|
return Math.floor(time / DAY_MS2);
|
|
26374
27681
|
}
|
|
27682
|
+
function barInterval(bars) {
|
|
27683
|
+
if (bars.length < 2) return 0;
|
|
27684
|
+
const gaps = [];
|
|
27685
|
+
for (let i = 1; i < bars.length; i++) gaps.push(bars[i].time - bars[i - 1].time);
|
|
27686
|
+
gaps.sort((a, b) => a - b);
|
|
27687
|
+
return gaps[Math.floor(gaps.length / 2)] ?? 0;
|
|
27688
|
+
}
|
|
26375
27689
|
var parabolicSar = {
|
|
26376
27690
|
type: "parabolic-sar",
|
|
26377
27691
|
title: "Parabolic SAR",
|
|
26378
27692
|
shortTitle: "SAR",
|
|
26379
27693
|
overlay: true,
|
|
26380
27694
|
inputs: [
|
|
26381
|
-
|
|
26382
|
-
|
|
26383
|
-
|
|
27695
|
+
floatInput("start", "Start", 0.02, 0, 1, 0.01, "Initial acceleration factor applied when a new trend begins."),
|
|
27696
|
+
floatInput("increment", "Increment", 0.02, 0, 1, 0.01, "Amount added to the acceleration factor each time the trend makes a new extreme point."),
|
|
27697
|
+
floatInput("maximum", "Maximum", 0.2, 0.01, 1, 0.01, "Upper cap on the acceleration factor."),
|
|
27698
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the SAR crosses while they trail below price \u2014 uptrend, the stop sits under the market."),
|
|
27699
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the SAR crosses while they sit above price \u2014 downtrend, the stop sits over the market.")
|
|
26384
27700
|
],
|
|
26385
27701
|
compute: (bars, inputs) => {
|
|
26386
27702
|
const start = num3(inputs, "start", 0.02);
|
|
@@ -26388,7 +27704,6 @@ var parabolicSar = {
|
|
|
26388
27704
|
const max = num3(inputs, "maximum", 0.2);
|
|
26389
27705
|
const n = bars.length;
|
|
26390
27706
|
const values = new Array(n).fill(Number.NaN);
|
|
26391
|
-
const colors = new Array(n).fill(null);
|
|
26392
27707
|
if (n >= 2) {
|
|
26393
27708
|
let long = bars[1].close >= bars[0].close;
|
|
26394
27709
|
let sar = long ? bars[0].low : bars[0].high;
|
|
@@ -26413,18 +27728,21 @@ var parabolicSar = {
|
|
|
26413
27728
|
af = Math.min(max, af + inc);
|
|
26414
27729
|
}
|
|
26415
27730
|
values[i] = sar;
|
|
26416
|
-
colors[i] = long ? BULLISH : BEARISH;
|
|
26417
27731
|
}
|
|
26418
27732
|
}
|
|
26419
|
-
|
|
27733
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
27734
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
27735
|
+
const colors = values.map((x, i) => Number.isFinite(x) ? x < bars[i].close ? bull : bear : null);
|
|
27736
|
+
return { plots: [{ key: "sar", title: "SAR", values, kind: "cross", color: bull, colors }] };
|
|
26420
27737
|
}
|
|
26421
27738
|
};
|
|
26422
|
-
var
|
|
26423
|
-
var
|
|
27739
|
+
var VWAP_ANCHORS = ["Session", "Week", "Month", "Quarter", "Year"];
|
|
27740
|
+
var BANDS_STDEV = "Standard Deviation";
|
|
27741
|
+
var BANDS_PERCENT = "Percentage";
|
|
26424
27742
|
var VWAP_BANDS = [
|
|
26425
|
-
{
|
|
26426
|
-
{
|
|
26427
|
-
{
|
|
27743
|
+
{ show: "band1", mult: "band1Mult", defMult: 1, defOn: true },
|
|
27744
|
+
{ show: "band2", mult: "band2Mult", defMult: 2, defOn: false },
|
|
27745
|
+
{ show: "band3", mult: "band3Mult", defMult: 3, defOn: false }
|
|
26428
27746
|
];
|
|
26429
27747
|
var vwap = {
|
|
26430
27748
|
type: "vwap",
|
|
@@ -26432,39 +27750,36 @@ var vwap = {
|
|
|
26432
27750
|
shortTitle: "VWAP",
|
|
26433
27751
|
overlay: true,
|
|
26434
27752
|
inputs: [
|
|
26435
|
-
|
|
26436
|
-
sourceInput("HLC3"),
|
|
26437
|
-
|
|
27753
|
+
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)."),
|
|
27754
|
+
sourceInput("HLC3", "source", "Source", "Price used in the volume weighted average. Typical price (HLC3) is the classic choice."),
|
|
27755
|
+
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."),
|
|
26438
27756
|
...VWAP_BANDS.flatMap((b, i) => [
|
|
26439
|
-
|
|
26440
|
-
|
|
27757
|
+
boolInput(b.show, `Band #${i + 1}`, b.defOn, SETTINGS, `Show the ${["first", "second", "third"][i]} band pair.`),
|
|
27758
|
+
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.")
|
|
26441
27759
|
]),
|
|
26442
|
-
|
|
26443
|
-
|
|
26444
|
-
|
|
26445
|
-
|
|
26446
|
-
|
|
26447
|
-
{ ...colorInput(withAlpha2(b.defColor), "", b.fillColor), inline: b.color, group: VWAP_STYLE }
|
|
26448
|
-
])
|
|
27760
|
+
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."),
|
|
27761
|
+
colorInput(BULLISH, "Bullish", "bullColor", "VWAP line color while price closes above it, and the color of the lower (support) bands."),
|
|
27762
|
+
colorInput(BEARISH, "Bearish", "bearColor", "VWAP line color while price closes below it, and the color of the upper (resistance) bands."),
|
|
27763
|
+
colorInput(NEUTRAL, "Bands", "bandsColor", "Deviation bands fill color."),
|
|
27764
|
+
boolInput("fill", "Bands Fill", true, STYLE, "Fill the area between each visible band pair.")
|
|
26449
27765
|
],
|
|
26450
27766
|
compute: (bars, inputs) => {
|
|
26451
|
-
const anchor = str3(inputs, "anchor", "
|
|
27767
|
+
const anchor = str3(inputs, "anchor", "Session");
|
|
26452
27768
|
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
26453
27769
|
const n = bars.length;
|
|
27770
|
+
const hidden = bool(inputs, "hideDwm", true) && barInterval(bars) >= DAY_MS2;
|
|
26454
27771
|
const values = new Array(n).fill(Number.NaN);
|
|
26455
|
-
const bands = VWAP_BANDS.filter((b) => bool(inputs, b.
|
|
27772
|
+
const bands = VWAP_BANDS.filter((b) => bool(inputs, b.show, b.defOn)).map((b) => ({
|
|
26456
27773
|
mult: Math.max(0, num3(inputs, b.mult, b.defMult)),
|
|
26457
|
-
color: str3(inputs, b.color, b.defColor),
|
|
26458
|
-
fill: bool(inputs, b.fill, true),
|
|
26459
|
-
fillColor: str3(inputs, b.fillColor, withAlpha2(b.defColor)),
|
|
26460
27774
|
up: new Array(n).fill(Number.NaN),
|
|
26461
27775
|
down: new Array(n).fill(Number.NaN)
|
|
26462
27776
|
}));
|
|
27777
|
+
const percentMode = str3(inputs, "bandsMode", BANDS_STDEV) === BANDS_PERCENT;
|
|
26463
27778
|
let period = Number.NaN;
|
|
26464
27779
|
let cumPV = 0;
|
|
26465
27780
|
let cumPV2 = 0;
|
|
26466
27781
|
let cumV = 0;
|
|
26467
|
-
for (let i = 0; i < n; i++) {
|
|
27782
|
+
for (let i = 0; i < n && !hidden; i++) {
|
|
26468
27783
|
const b = bars[i];
|
|
26469
27784
|
const key = periodKey(anchor, b.time);
|
|
26470
27785
|
if (key !== period) {
|
|
@@ -26482,38 +27797,58 @@ var vwap = {
|
|
|
26482
27797
|
}
|
|
26483
27798
|
if (cumV <= 0) continue;
|
|
26484
27799
|
const mean = cumPV / cumV;
|
|
26485
|
-
const
|
|
27800
|
+
const offset = percentMode ? mean / 100 : Math.sqrt(Math.max(0, cumPV2 / cumV - mean * mean));
|
|
26486
27801
|
values[i] = mean;
|
|
26487
27802
|
for (const band of bands) {
|
|
26488
|
-
band.up[i] = mean + band.mult *
|
|
26489
|
-
band.down[i] = mean - band.mult *
|
|
27803
|
+
band.up[i] = mean + band.mult * offset;
|
|
27804
|
+
band.down[i] = mean - band.mult * offset;
|
|
26490
27805
|
}
|
|
26491
27806
|
}
|
|
26492
|
-
const
|
|
27807
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
27808
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
27809
|
+
const fillColor = transp(str3(inputs, "bandsColor", NEUTRAL), 95);
|
|
27810
|
+
const plots = [
|
|
27811
|
+
{ key: "vwap", title: "VWAP", values, color: bull, colors: values.map((x, i) => Number.isFinite(x) ? bars[i].close >= x ? bull : bear : null) }
|
|
27812
|
+
];
|
|
26493
27813
|
const fills = [];
|
|
26494
27814
|
bands.forEach((band, i) => {
|
|
26495
27815
|
plots.push(
|
|
26496
|
-
{ key: `up${i}`, title: `Upper
|
|
26497
|
-
{ key: `down${i}`, title: `Lower
|
|
27816
|
+
{ key: `up${i}`, title: `Upper Band #${i + 1}`, values: band.up, color: transp(bear, 40) },
|
|
27817
|
+
{ key: `down${i}`, title: `Lower Band #${i + 1}`, values: band.down, color: transp(bull, 40) }
|
|
26498
27818
|
);
|
|
26499
|
-
if (
|
|
27819
|
+
if (bool(inputs, "fill", true)) fills.push({ key: `band${i}`, from: `up${i}`, to: `down${i}`, color: fillColor });
|
|
26500
27820
|
});
|
|
26501
27821
|
return { plots, bands: fills };
|
|
26502
27822
|
}
|
|
26503
27823
|
};
|
|
27824
|
+
var PIVOT_TRADITIONAL = "Traditional";
|
|
27825
|
+
var PIVOT_FIBONACCI = "Fibonacci";
|
|
27826
|
+
var PIVOT_CAMARILLA = "Camarilla";
|
|
27827
|
+
var PIVOT_WOODIE = "Woodie";
|
|
27828
|
+
var PIVOT_AUTO = "Auto";
|
|
26504
27829
|
var pivotPoints = {
|
|
26505
27830
|
type: "pivot-points",
|
|
26506
|
-
title: "Pivot Points",
|
|
27831
|
+
title: "Pivot Points Standard",
|
|
26507
27832
|
shortTitle: "Pivots",
|
|
26508
27833
|
overlay: true,
|
|
26509
|
-
inputs: [
|
|
27834
|
+
inputs: [
|
|
27835
|
+
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."),
|
|
27836
|
+
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."),
|
|
27837
|
+
colorInput(WARNING, "Pivot", "pivotColor", "Color of the central pivot (P) level."),
|
|
27838
|
+
colorInput(BEARISH, "Resistances", "resistanceColor", "Color of the resistance (R) levels."),
|
|
27839
|
+
colorInput(BULLISH, "Supports", "supportColor", "Color of the support (S) levels.")
|
|
27840
|
+
],
|
|
26510
27841
|
compute: (bars, inputs) => {
|
|
26511
|
-
const
|
|
27842
|
+
const interval = barInterval(bars);
|
|
27843
|
+
const requested = str3(inputs, "anchor", PIVOT_AUTO);
|
|
27844
|
+
const auto = interval <= 15 * 6e4 ? "Session" : interval < DAY_MS2 ? "Week" : interval <= DAY_MS2 ? "Month" : "Year";
|
|
27845
|
+
const anchor = requested === "Daily" ? "Session" : requested === "Weekly" ? "Week" : requested === "Monthly" ? "Month" : requested === "Yearly" ? "Year" : auto;
|
|
27846
|
+
const kind = str3(inputs, "kind", PIVOT_TRADITIONAL);
|
|
26512
27847
|
const aggs = /* @__PURE__ */ new Map();
|
|
26513
27848
|
for (const b of bars) {
|
|
26514
27849
|
const key = periodKey(anchor, b.time);
|
|
26515
27850
|
const a = aggs.get(key);
|
|
26516
|
-
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
27851
|
+
if (!a) aggs.set(key, { open: b.open, high: b.high, low: b.low, close: b.close });
|
|
26517
27852
|
else {
|
|
26518
27853
|
a.high = Math.max(a.high, b.high);
|
|
26519
27854
|
a.low = Math.min(a.low, b.low);
|
|
@@ -26522,31 +27857,61 @@ var pivotPoints = {
|
|
|
26522
27857
|
}
|
|
26523
27858
|
const n = bars.length;
|
|
26524
27859
|
const mk = () => new Array(n).fill(Number.NaN);
|
|
26525
|
-
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
27860
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk(), r4: mk(), s4: mk() };
|
|
27861
|
+
let lastKey = Number.NaN;
|
|
26526
27862
|
for (let i = 0; i < n; i++) {
|
|
26527
|
-
const
|
|
26528
|
-
|
|
26529
|
-
|
|
27863
|
+
const key = periodKey(anchor, bars[i].time);
|
|
27864
|
+
const fresh = key !== lastKey;
|
|
27865
|
+
lastKey = key;
|
|
27866
|
+
if (fresh) continue;
|
|
27867
|
+
const prev2 = aggs.get(key - 1);
|
|
27868
|
+
const curr = aggs.get(key);
|
|
27869
|
+
if (!prev2 || !curr) continue;
|
|
27870
|
+
const range = prev2.high - prev2.low;
|
|
27871
|
+
const traditional = (prev2.high + prev2.low + prev2.close) / 3;
|
|
27872
|
+
const p = kind === PIVOT_WOODIE ? (prev2.high + prev2.low + 2 * curr.open) / 4 : traditional;
|
|
26530
27873
|
levels.p[i] = p;
|
|
26531
|
-
|
|
26532
|
-
|
|
26533
|
-
|
|
26534
|
-
|
|
26535
|
-
|
|
26536
|
-
|
|
26537
|
-
|
|
26538
|
-
|
|
26539
|
-
|
|
26540
|
-
|
|
26541
|
-
|
|
26542
|
-
|
|
26543
|
-
|
|
26544
|
-
|
|
26545
|
-
|
|
26546
|
-
|
|
26547
|
-
|
|
26548
|
-
|
|
26549
|
-
|
|
27874
|
+
if (kind === PIVOT_FIBONACCI) {
|
|
27875
|
+
levels.r1[i] = traditional + 0.382 * range;
|
|
27876
|
+
levels.s1[i] = traditional - 0.382 * range;
|
|
27877
|
+
levels.r2[i] = traditional + 0.618 * range;
|
|
27878
|
+
levels.s2[i] = traditional - 0.618 * range;
|
|
27879
|
+
levels.r3[i] = traditional + range;
|
|
27880
|
+
levels.s3[i] = traditional - range;
|
|
27881
|
+
} else if (kind === PIVOT_CAMARILLA) {
|
|
27882
|
+
const cam = 1.1 * range;
|
|
27883
|
+
levels.r1[i] = prev2.close + cam / 12;
|
|
27884
|
+
levels.s1[i] = prev2.close - cam / 12;
|
|
27885
|
+
levels.r2[i] = prev2.close + cam / 6;
|
|
27886
|
+
levels.s2[i] = prev2.close - cam / 6;
|
|
27887
|
+
levels.r3[i] = prev2.close + cam / 4;
|
|
27888
|
+
levels.s3[i] = prev2.close - cam / 4;
|
|
27889
|
+
levels.r4[i] = prev2.close + cam / 2;
|
|
27890
|
+
levels.s4[i] = prev2.close - cam / 2;
|
|
27891
|
+
} else {
|
|
27892
|
+
levels.r1[i] = 2 * p - prev2.low;
|
|
27893
|
+
levels.s1[i] = 2 * p - prev2.high;
|
|
27894
|
+
levels.r2[i] = p + range;
|
|
27895
|
+
levels.s2[i] = p - range;
|
|
27896
|
+
if (kind !== PIVOT_WOODIE) {
|
|
27897
|
+
levels.r3[i] = prev2.high + 2 * (p - prev2.low);
|
|
27898
|
+
levels.s3[i] = prev2.low - 2 * (prev2.high - p);
|
|
27899
|
+
}
|
|
27900
|
+
}
|
|
27901
|
+
}
|
|
27902
|
+
const resistance = str3(inputs, "resistanceColor", BEARISH);
|
|
27903
|
+
const support = str3(inputs, "supportColor", BULLISH);
|
|
27904
|
+
const plot = (key, title, color) => ({ key, title, values: levels[key], color });
|
|
27905
|
+
const plots = [
|
|
27906
|
+
plot("p", "P", str3(inputs, "pivotColor", WARNING)),
|
|
27907
|
+
plot("r1", "R1", resistance),
|
|
27908
|
+
plot("s1", "S1", support),
|
|
27909
|
+
plot("r2", "R2", resistance),
|
|
27910
|
+
plot("s2", "S2", support)
|
|
27911
|
+
];
|
|
27912
|
+
if (kind !== PIVOT_WOODIE) plots.push(plot("r3", "R3", resistance), plot("s3", "S3", support));
|
|
27913
|
+
if (kind === PIVOT_CAMARILLA) plots.push(plot("r4", "R4", resistance), plot("s4", "S4", support));
|
|
27914
|
+
return { plots };
|
|
26550
27915
|
}
|
|
26551
27916
|
};
|
|
26552
27917
|
var fiftyTwoWeek = {
|
|
@@ -26554,15 +27919,25 @@ var fiftyTwoWeek = {
|
|
|
26554
27919
|
title: "52 Week High/Low",
|
|
26555
27920
|
shortTitle: "52W H/L",
|
|
26556
27921
|
overlay: true,
|
|
26557
|
-
inputs: [
|
|
27922
|
+
inputs: [
|
|
27923
|
+
intInput("weeks", "Weeks", 52, 1, 520, "Length of the rolling window, in weeks."),
|
|
27924
|
+
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."),
|
|
27925
|
+
colorInput(BULLISH, "52 Week High", "highColor", "Color of the 52 week high level."),
|
|
27926
|
+
colorInput(BEARISH, "52 Week Low", "lowColor", "Color of the 52 week low level."),
|
|
27927
|
+
colorInput(NEUTRAL, "All-Time High/Low", "allTimeColor", "Color of the all-time high and all-time low levels.")
|
|
27928
|
+
],
|
|
26558
27929
|
compute: (bars, inputs) => {
|
|
26559
27930
|
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
26560
27931
|
const n = bars.length;
|
|
26561
27932
|
const hi = new Array(n).fill(Number.NaN);
|
|
26562
27933
|
const lo = new Array(n).fill(Number.NaN);
|
|
27934
|
+
const ath = new Array(n).fill(Number.NaN);
|
|
27935
|
+
const atl = new Array(n).fill(Number.NaN);
|
|
26563
27936
|
const maxIdx = [];
|
|
26564
27937
|
const minIdx = [];
|
|
26565
27938
|
let from = 0;
|
|
27939
|
+
let runHigh = -Infinity;
|
|
27940
|
+
let runLow = Infinity;
|
|
26566
27941
|
for (let i = 0; i < n; i++) {
|
|
26567
27942
|
const b = bars[i];
|
|
26568
27943
|
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
@@ -26574,13 +27949,20 @@ var fiftyTwoWeek = {
|
|
|
26574
27949
|
while (minIdx[0] < from) minIdx.shift();
|
|
26575
27950
|
hi[i] = bars[maxIdx[0]].high;
|
|
26576
27951
|
lo[i] = bars[minIdx[0]].low;
|
|
27952
|
+
runHigh = Math.max(runHigh, b.high);
|
|
27953
|
+
runLow = Math.min(runLow, b.low);
|
|
27954
|
+
ath[i] = runHigh;
|
|
27955
|
+
atl[i] = runLow;
|
|
27956
|
+
}
|
|
27957
|
+
const plots = [
|
|
27958
|
+
{ key: "high", title: "52 Week High", values: hi, kind: "step", color: str3(inputs, "highColor", BULLISH), width: 2 },
|
|
27959
|
+
{ key: "low", title: "52 Week Low", values: lo, kind: "step", color: str3(inputs, "lowColor", BEARISH), width: 2 }
|
|
27960
|
+
];
|
|
27961
|
+
if (bool(inputs, "showAllTime", false)) {
|
|
27962
|
+
const ink = str3(inputs, "allTimeColor", NEUTRAL);
|
|
27963
|
+
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 });
|
|
26577
27964
|
}
|
|
26578
|
-
return {
|
|
26579
|
-
plots: [
|
|
26580
|
-
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
26581
|
-
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
26582
|
-
]
|
|
26583
|
-
};
|
|
27965
|
+
return { plots };
|
|
26584
27966
|
}
|
|
26585
27967
|
};
|
|
26586
27968
|
var zigzag = {
|
|
@@ -26588,9 +27970,11 @@ var zigzag = {
|
|
|
26588
27970
|
title: "ZigZag",
|
|
26589
27971
|
overlay: true,
|
|
26590
27972
|
inputs: [
|
|
26591
|
-
|
|
26592
|
-
{
|
|
26593
|
-
colorInput()
|
|
27973
|
+
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."),
|
|
27974
|
+
{ ...intInput("depth", "Depth", 10, 2, 500, "Minimum number of bars required between two consecutive pivots.") },
|
|
27975
|
+
colorInput(BULLISH, "Up Leg", "bullColor", "Color of rising zigzag segments."),
|
|
27976
|
+
colorInput(BEARISH, "Down Leg", "bearColor", "Color of falling zigzag segments."),
|
|
27977
|
+
{ key: "lineWidth", title: "Width", type: "int", defval: 2, min: 1, max: 10, step: 1, group: STYLE, tooltip: "Width of the zigzag segments." }
|
|
26594
27978
|
],
|
|
26595
27979
|
compute: (bars, inputs) => {
|
|
26596
27980
|
const dev = num3(inputs, "deviation", 5) / 100;
|
|
@@ -26603,14 +27987,14 @@ var zigzag = {
|
|
|
26603
27987
|
const b = bars[i];
|
|
26604
27988
|
if (up) {
|
|
26605
27989
|
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
26606
|
-
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
27990
|
+
else if (b.low <= ext.price * (1 - dev) && ext.i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
26607
27991
|
pivots.push(ext);
|
|
26608
27992
|
up = false;
|
|
26609
27993
|
ext = { i, price: b.low };
|
|
26610
27994
|
}
|
|
26611
27995
|
} else if (b.low <= ext.price) {
|
|
26612
27996
|
ext = { i, price: b.low };
|
|
26613
|
-
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
27997
|
+
} else if (b.high >= ext.price * (1 + dev) && ext.i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
26614
27998
|
pivots.push(ext);
|
|
26615
27999
|
up = true;
|
|
26616
28000
|
ext = { i, price: b.high };
|
|
@@ -26618,22 +28002,29 @@ var zigzag = {
|
|
|
26618
28002
|
}
|
|
26619
28003
|
pivots.push(ext);
|
|
26620
28004
|
}
|
|
26621
|
-
|
|
26622
|
-
|
|
26623
|
-
|
|
26624
|
-
|
|
26625
|
-
|
|
26626
|
-
|
|
26627
|
-
|
|
26628
|
-
|
|
26629
|
-
|
|
26630
|
-
|
|
26631
|
-
|
|
26632
|
-
|
|
26633
|
-
|
|
26634
|
-
|
|
26635
|
-
|
|
26636
|
-
|
|
28005
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
28006
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
28007
|
+
const width = num3(inputs, "lineWidth", 2);
|
|
28008
|
+
const polylines = [];
|
|
28009
|
+
for (let k = 1; k < pivots.length; k++) {
|
|
28010
|
+
const a = pivots[k - 1];
|
|
28011
|
+
const b = pivots[k];
|
|
28012
|
+
polylines.push({
|
|
28013
|
+
key: `leg${k}`,
|
|
28014
|
+
points: [
|
|
28015
|
+
{ xloc: "bar_time", x: bars[a.i].time, price: a.price },
|
|
28016
|
+
{ xloc: "bar_time", x: bars[b.i].time, price: b.price }
|
|
28017
|
+
],
|
|
28018
|
+
curved: false,
|
|
28019
|
+
closed: false,
|
|
28020
|
+
lineColor: b.price >= a.price ? bull : bear,
|
|
28021
|
+
lineWidth: width,
|
|
28022
|
+
lineStyle: "solid",
|
|
28023
|
+
arrowLeft: false,
|
|
28024
|
+
arrowRight: false
|
|
28025
|
+
});
|
|
28026
|
+
}
|
|
28027
|
+
return { plots: [], polylines };
|
|
26637
28028
|
}
|
|
26638
28029
|
};
|
|
26639
28030
|
var williamsFractal = {
|
|
@@ -26641,10 +28032,16 @@ var williamsFractal = {
|
|
|
26641
28032
|
title: "Williams Fractal",
|
|
26642
28033
|
shortTitle: "Fractals",
|
|
26643
28034
|
overlay: true,
|
|
26644
|
-
inputs: [
|
|
28035
|
+
inputs: [
|
|
28036
|
+
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."),
|
|
28037
|
+
colorInput(BEARISH, "Up Fractal", "upColor", "Color of the up fractal marker (swing high), drawn above the fractal bar."),
|
|
28038
|
+
colorInput(BULLISH, "Down Fractal", "downColor", "Color of the down fractal marker (swing low), drawn below the fractal bar.")
|
|
28039
|
+
],
|
|
26645
28040
|
compute: (bars, inputs) => {
|
|
26646
28041
|
const p = num3(inputs, "periods", 2);
|
|
26647
28042
|
const markers = [];
|
|
28043
|
+
const up = str3(inputs, "upColor", BEARISH);
|
|
28044
|
+
const down = str3(inputs, "downColor", BULLISH);
|
|
26648
28045
|
const isExtreme = (i, pick, better) => {
|
|
26649
28046
|
const v = pick(bars[i]);
|
|
26650
28047
|
for (let k = i - p; k <= i + p; k++) {
|
|
@@ -26654,12 +28051,8 @@ var williamsFractal = {
|
|
|
26654
28051
|
return true;
|
|
26655
28052
|
};
|
|
26656
28053
|
for (let i = p; i < bars.length - p; i++) {
|
|
26657
|
-
if (isExtreme(i, (b) => b.high, (a, b) => a
|
|
26658
|
-
|
|
26659
|
-
}
|
|
26660
|
-
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
26661
|
-
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
26662
|
-
}
|
|
28054
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a > b)) markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: up });
|
|
28055
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a < b)) markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: down });
|
|
26663
28056
|
}
|
|
26664
28057
|
return { plots: [], markers };
|
|
26665
28058
|
}
|
|
@@ -27031,4 +28424,4 @@ function resolveElement(container) {
|
|
|
27031
28424
|
return element;
|
|
27032
28425
|
}
|
|
27033
28426
|
|
|
27034
|
-
export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MarksControl, MultiProviderFeed, NativeRenderer, RendererControl, SecondClock, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultMemberOf, defaultToolbar, groupKeyOf, groupMembers, isGroupRow, normalizeSession, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, resolveTimezone, sharedBarStore, timeframeToMs, timezoneMenuRows, tzButtonLabel, tzMenuLabel, tzOffset };
|
|
28427
|
+
export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MarksControl, MultiProviderFeed, NativeRenderer, RendererControl, SecondClock, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultMemberOf, defaultToolbar, groupKeyOf, groupMembers, isExchangeTimezone, isGroupRow, normalizeSession, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, resolveTimezone, sharedBarStore, timeframeToMs, timezoneMenuRows, tzButtonLabel, tzMenuLabel, tzOffset };
|