@luxalgo/vela 0.7.6 → 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-EMB53WNQ.js → chunk-M6GP7DAX.js} +123 -36
- package/dist/{chunk-JSVCWYBF.js → chunk-N4ZZYLMP.js} +2409 -896
- package/dist/{chunk-ZADTVRUO.js → chunk-PVGWNF7V.js} +1 -1
- package/dist/{chunk-BKHSQ4YM.js → chunk-WTWGEB7K.js} +23 -9
- package/dist/{contributions-D6p1RB38.d.cts → contributions-DF_Ea07m.d.cts} +17 -0
- package/dist/{contributions-C7Wsm_S9.d.ts → contributions-nKOSeLy8.d.ts} +17 -0
- package/dist/index.cjs +2416 -902
- package/dist/index.d.cts +37 -6
- package/dist/index.d.ts +37 -6
- package/dist/index.js +2 -2
- package/dist/{plugin-CxWfg6vK.d.cts → plugin-6F04gjQe.d.cts} +23 -23
- package/dist/{plugin-SodaQhjQ.d.ts → plugin-BLsNipkc.d.ts} +23 -23
- package/dist/plugin.d.cts +2 -2
- package/dist/plugin.d.ts +2 -2
- package/dist/{statusline-model-gWHrsOy3.d.cts → statusline-model-BoWJ3cWQ.d.cts} +16 -3
- package/dist/{statusline-model-xBiVsKoE.d.ts → statusline-model-DAFyqVmR.d.ts} +16 -3
- package/dist/ui.cjs +23 -9
- package/dist/ui.d.cts +19 -2
- package/dist/ui.d.ts +19 -2
- package/dist/ui.js +2 -2
- package/dist/vela.global.js +2420 -906
- package/dist/vela.global.min.js +65 -55
- package/dist/widget.cjs +2549 -935
- package/dist/widget.d.cts +5 -5
- package/dist/widget.d.ts +5 -5
- package/dist/widget.js +5 -5
- package/dist/workspace.cjs +2549 -935
- package/dist/workspace.d.cts +35 -6
- package/dist/workspace.d.ts +35 -6
- package/dist/workspace.js +4 -4
- package/package.json +21 -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
|
-
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-
|
|
3
|
-
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL,
|
|
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, 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);
|
|
@@ -1845,9 +1869,22 @@ var TIMEZONES = [
|
|
|
1845
1869
|
{ value: "Pacific/Apia", label: "Apia" },
|
|
1846
1870
|
{ value: "Pacific/Kiritimati", label: "Kiritimati" }
|
|
1847
1871
|
];
|
|
1872
|
+
var EXCHANGE_TIMEZONE = "exchange";
|
|
1873
|
+
function isExchangeTimezone(zone) {
|
|
1874
|
+
return zone === EXCHANGE_TIMEZONE;
|
|
1875
|
+
}
|
|
1876
|
+
function resolveTimezone(zone, exchangeZone) {
|
|
1877
|
+
if (!isExchangeTimezone(zone)) return zone;
|
|
1878
|
+
return exchangeZone && exchangeZone !== "" ? exchangeZone : "Etc/UTC";
|
|
1879
|
+
}
|
|
1848
1880
|
function normalizeTimezone(zone) {
|
|
1849
1881
|
return zone === "UTC" || zone === "Etc/UTC" || zone === "Etc/GMT" ? "Etc/UTC" : zone;
|
|
1850
1882
|
}
|
|
1883
|
+
function timezoneMenuRows(current) {
|
|
1884
|
+
const active = normalizeTimezone(current);
|
|
1885
|
+
const [utc, ...zones] = TIMEZONES.map((t) => ({ value: t.value, label: tzMenuLabel(t.value, t.label), checked: t.value === active }));
|
|
1886
|
+
return [utc, { value: EXCHANGE_TIMEZONE, label: "Exchange", checked: isExchangeTimezone(current) }, ...zones];
|
|
1887
|
+
}
|
|
1851
1888
|
function tzOffset(zone, date = /* @__PURE__ */ new Date()) {
|
|
1852
1889
|
try {
|
|
1853
1890
|
const parts = new Intl.DateTimeFormat("en-US", { timeZone: zone, timeZoneName: "shortOffset" }).formatToParts(date);
|
|
@@ -3503,7 +3540,10 @@ var IndicatorInputsDialog = class {
|
|
|
3503
3540
|
id,
|
|
3504
3541
|
theme: this.host.theme(),
|
|
3505
3542
|
get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
|
|
3506
|
-
onChange: (v) => emit(v)
|
|
3543
|
+
onChange: (v) => emit(v),
|
|
3544
|
+
// An input change re-executes the script over its whole history: commit the
|
|
3545
|
+
// opacity drag once on release, not once per pointer move.
|
|
3546
|
+
commit: "release"
|
|
3507
3547
|
}).el;
|
|
3508
3548
|
}
|
|
3509
3549
|
if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
|
|
@@ -3750,10 +3790,6 @@ var IndicatorInputsDialog = class {
|
|
|
3750
3790
|
}
|
|
3751
3791
|
/** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
|
|
3752
3792
|
openCalendar(anchor, current, onPick) {
|
|
3753
|
-
const parsed = parseIsoDate(current);
|
|
3754
|
-
let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
3755
|
-
let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
|
|
3756
|
-
const selected = parsed ? isoDate(parsed) : current;
|
|
3757
3793
|
const pop = new Popover({
|
|
3758
3794
|
trigger: anchor,
|
|
3759
3795
|
theme: this.host.theme(),
|
|
@@ -3765,80 +3801,10 @@ var IndicatorInputsDialog = class {
|
|
|
3765
3801
|
this.calendarPop = null;
|
|
3766
3802
|
this.calendarAnchor = null;
|
|
3767
3803
|
},
|
|
3768
|
-
content: (el) => {
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
prev2.type = "button";
|
|
3773
|
-
prev2.className = "vela-ind-cal-nav";
|
|
3774
|
-
prev2.setAttribute("aria-label", "Previous month");
|
|
3775
|
-
prev2.innerHTML = iconAt("chevron-left", 14);
|
|
3776
|
-
const next = document.createElement("button");
|
|
3777
|
-
next.type = "button";
|
|
3778
|
-
next.className = "vela-ind-cal-nav";
|
|
3779
|
-
next.setAttribute("aria-label", "Next month");
|
|
3780
|
-
next.innerHTML = iconAt("chevron-right", 14);
|
|
3781
|
-
const head = document.createElement("div");
|
|
3782
|
-
head.className = "vela-ind-cal-head";
|
|
3783
|
-
head.append(prev2, title, next);
|
|
3784
|
-
const week = document.createElement("div");
|
|
3785
|
-
week.className = "vela-ind-cal-week";
|
|
3786
|
-
for (const d of WEEKDAY_LABELS) {
|
|
3787
|
-
const cell = document.createElement("span");
|
|
3788
|
-
cell.textContent = d;
|
|
3789
|
-
week.appendChild(cell);
|
|
3790
|
-
}
|
|
3791
|
-
const grid = document.createElement("div");
|
|
3792
|
-
grid.className = "vela-ind-cal-grid";
|
|
3793
|
-
const paint = () => {
|
|
3794
|
-
title.textContent = `${MONTH_LABELS[month]} ${year}`;
|
|
3795
|
-
grid.replaceChildren();
|
|
3796
|
-
const first = new Date(year, month, 1);
|
|
3797
|
-
const startPad = first.getDay();
|
|
3798
|
-
const days = new Date(year, month + 1, 0).getDate();
|
|
3799
|
-
const today = isoDate(/* @__PURE__ */ new Date());
|
|
3800
|
-
for (let i = 0; i < startPad; i++) {
|
|
3801
|
-
const blank = document.createElement("span");
|
|
3802
|
-
blank.className = "vela-ind-cal-blank";
|
|
3803
|
-
grid.appendChild(blank);
|
|
3804
|
-
}
|
|
3805
|
-
for (let day = 1; day <= days; day++) {
|
|
3806
|
-
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
3807
|
-
const b = document.createElement("button");
|
|
3808
|
-
b.type = "button";
|
|
3809
|
-
b.className = "vela-ind-cal-day";
|
|
3810
|
-
b.textContent = String(day);
|
|
3811
|
-
if (iso === selected) b.dataset.checked = "1";
|
|
3812
|
-
if (iso === today) b.dataset.today = "1";
|
|
3813
|
-
b.addEventListener("click", (e) => {
|
|
3814
|
-
e.stopPropagation();
|
|
3815
|
-
pop.hide();
|
|
3816
|
-
onPick(iso);
|
|
3817
|
-
});
|
|
3818
|
-
grid.appendChild(b);
|
|
3819
|
-
}
|
|
3820
|
-
};
|
|
3821
|
-
prev2.addEventListener("click", (e) => {
|
|
3822
|
-
e.stopPropagation();
|
|
3823
|
-
month -= 1;
|
|
3824
|
-
if (month < 0) {
|
|
3825
|
-
month = 11;
|
|
3826
|
-
year -= 1;
|
|
3827
|
-
}
|
|
3828
|
-
paint();
|
|
3829
|
-
});
|
|
3830
|
-
next.addEventListener("click", (e) => {
|
|
3831
|
-
e.stopPropagation();
|
|
3832
|
-
month += 1;
|
|
3833
|
-
if (month > 11) {
|
|
3834
|
-
month = 0;
|
|
3835
|
-
year += 1;
|
|
3836
|
-
}
|
|
3837
|
-
paint();
|
|
3838
|
-
});
|
|
3839
|
-
paint();
|
|
3840
|
-
el.append(head, week, grid);
|
|
3841
|
-
}
|
|
3804
|
+
content: (el) => fillCalendar(el, current, (iso) => {
|
|
3805
|
+
pop.hide();
|
|
3806
|
+
onPick(iso);
|
|
3807
|
+
})
|
|
3842
3808
|
});
|
|
3843
3809
|
this.calendarPop = pop;
|
|
3844
3810
|
this.calendarAnchor = anchor;
|
|
@@ -3947,7 +3913,7 @@ function groupInputs(inputs) {
|
|
|
3947
3913
|
var CALENDAR_SVG = iconAt("calendar", 14);
|
|
3948
3914
|
var CLOCK_SVG = iconAt("clock", 14);
|
|
3949
3915
|
var DIALOG_STYLE_ID = "vela-ind-dialog-styles";
|
|
3950
|
-
var DIALOG_STYLE_REV = "
|
|
3916
|
+
var DIALOG_STYLE_REV = "30";
|
|
3951
3917
|
var LEGEND_ICON_PX = 16;
|
|
3952
3918
|
function ensureDialogStyles() {
|
|
3953
3919
|
if (typeof document === "undefined") return;
|
|
@@ -3963,8 +3929,12 @@ function ensureDialogStyles() {
|
|
|
3963
3929
|
.vela-ind-combo-chevron{position:absolute;right:0;top:0;bottom:0;width:26px;border:none;background:transparent;color:inherit;opacity:0.55;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;}
|
|
3964
3930
|
.vela-ind-combo-chevron:hover{opacity:0.9;}
|
|
3965
3931
|
.vela-ind-cal{background:var(--vela-bg);color:var(--vela-fg);border:none;border-radius:6px;box-shadow:var(--vela-shadow);font:14px var(--vela-font);padding:10px 12px;user-select:none;}
|
|
3932
|
+
.vela-ind-cal [hidden]{display:none !important;}
|
|
3966
3933
|
.vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
|
|
3967
|
-
.vela-ind-cal-title{flex:1;
|
|
3934
|
+
.vela-ind-cal-title{flex:1;display:flex;align-items:center;justify-content:center;gap:2px;min-width:0;}
|
|
3935
|
+
.vela-ind-cal-switch{border:none;background:transparent;color:var(--vela-fg-bright);font:inherit;font-weight:600;font-size:14px;padding:2px 6px;border-radius:4px;cursor:pointer;}
|
|
3936
|
+
.vela-ind-cal-switch:not(:disabled):hover{background:var(--vela-hover);}
|
|
3937
|
+
.vela-ind-cal-switch:disabled{cursor:default;}
|
|
3968
3938
|
.vela-ind-cal-nav{width:24px;height:24px;border:none;background:transparent;color:var(--vela-fg-muted);border-radius:4px;padding:0;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;}
|
|
3969
3939
|
.vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
3970
3940
|
.vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
|
|
@@ -3975,6 +3945,12 @@ function ensureDialogStyles() {
|
|
|
3975
3945
|
.vela-ind-cal-day:hover{background:var(--vela-hover);}
|
|
3976
3946
|
.vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
3977
3947
|
.vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
3948
|
+
.vela-ind-cal-cells{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;width:calc(7 * 28px + 6 * 2px);}
|
|
3949
|
+
.vela-ind-cal-cell{height:36px;border:none;background:transparent;color:inherit;border-radius:4px;padding:0 4px;cursor:pointer;font:inherit;font-size:14px;}
|
|
3950
|
+
.vela-ind-cal-cell:hover{background:var(--vela-hover);}
|
|
3951
|
+
.vela-ind-cal-cell[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
3952
|
+
.vela-ind-cal-cell[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
3953
|
+
.vela-ind-cal-cell[data-outside]{opacity:0.45;}
|
|
3978
3954
|
${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
3979
3955
|
.vela-ind-tab{font-weight:600;font-size:13px;line-height:20px;transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
3980
3956
|
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
@@ -4009,6 +3985,7 @@ var TIME_OPTIONS = Array.from({ length: 48 }, (_, i) => {
|
|
|
4009
3985
|
return { value: v, label: v };
|
|
4010
3986
|
});
|
|
4011
3987
|
var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
3988
|
+
var MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
4012
3989
|
var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
4013
3990
|
function normalizeDateInput(raw) {
|
|
4014
3991
|
const t = raw.trim();
|
|
@@ -4030,6 +4007,151 @@ function parseIsoDate(raw) {
|
|
|
4030
4007
|
function isoDate(d) {
|
|
4031
4008
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
4032
4009
|
}
|
|
4010
|
+
function fillCalendar(el, current, onPick) {
|
|
4011
|
+
const today = /* @__PURE__ */ new Date();
|
|
4012
|
+
const selected = parseIsoDate(current);
|
|
4013
|
+
let year = selected?.getFullYear() ?? today.getFullYear();
|
|
4014
|
+
let month = selected?.getMonth() ?? today.getMonth();
|
|
4015
|
+
let mode = "date";
|
|
4016
|
+
const prev2 = document.createElement("button");
|
|
4017
|
+
prev2.type = "button";
|
|
4018
|
+
prev2.className = "vela-ind-cal-nav";
|
|
4019
|
+
prev2.innerHTML = iconAt("chevron-left", 14);
|
|
4020
|
+
const next = document.createElement("button");
|
|
4021
|
+
next.type = "button";
|
|
4022
|
+
next.className = "vela-ind-cal-nav";
|
|
4023
|
+
next.innerHTML = iconAt("chevron-right", 14);
|
|
4024
|
+
const monthBtn = document.createElement("button");
|
|
4025
|
+
monthBtn.type = "button";
|
|
4026
|
+
monthBtn.className = "vela-ind-cal-switch";
|
|
4027
|
+
monthBtn.setAttribute("aria-label", "Choose month");
|
|
4028
|
+
const yearBtn = document.createElement("button");
|
|
4029
|
+
yearBtn.type = "button";
|
|
4030
|
+
yearBtn.className = "vela-ind-cal-switch";
|
|
4031
|
+
yearBtn.setAttribute("aria-label", "Choose year");
|
|
4032
|
+
const title = document.createElement("div");
|
|
4033
|
+
title.className = "vela-ind-cal-title";
|
|
4034
|
+
title.append(monthBtn, yearBtn);
|
|
4035
|
+
const head = document.createElement("div");
|
|
4036
|
+
head.className = "vela-ind-cal-head";
|
|
4037
|
+
head.append(prev2, title, next);
|
|
4038
|
+
const week = document.createElement("div");
|
|
4039
|
+
week.className = "vela-ind-cal-week";
|
|
4040
|
+
for (const d of WEEKDAY_LABELS) {
|
|
4041
|
+
const cell = document.createElement("span");
|
|
4042
|
+
cell.textContent = d;
|
|
4043
|
+
week.appendChild(cell);
|
|
4044
|
+
}
|
|
4045
|
+
const grid = document.createElement("div");
|
|
4046
|
+
const paint = () => {
|
|
4047
|
+
const decade = Math.floor(year / 10) * 10;
|
|
4048
|
+
monthBtn.hidden = mode !== "date";
|
|
4049
|
+
monthBtn.textContent = MONTH_LABELS[month] ?? "";
|
|
4050
|
+
yearBtn.textContent = mode === "year" ? `${decade}-${decade + 9}` : String(year);
|
|
4051
|
+
yearBtn.disabled = mode === "year";
|
|
4052
|
+
week.hidden = mode !== "date";
|
|
4053
|
+
prev2.setAttribute("aria-label", mode === "date" ? "Previous month" : mode === "month" ? "Previous year" : "Previous decade");
|
|
4054
|
+
next.setAttribute("aria-label", mode === "date" ? "Next month" : mode === "month" ? "Next year" : "Next decade");
|
|
4055
|
+
grid.className = mode === "date" ? "vela-ind-cal-grid" : "vela-ind-cal-cells";
|
|
4056
|
+
grid.replaceChildren();
|
|
4057
|
+
if (mode === "date") {
|
|
4058
|
+
const startPad = new Date(year, month, 1).getDay();
|
|
4059
|
+
const days = new Date(year, month + 1, 0).getDate();
|
|
4060
|
+
const todayIso = isoDate(today);
|
|
4061
|
+
const selectedIso = selected ? isoDate(selected) : "";
|
|
4062
|
+
for (let i = 0; i < startPad; i++) {
|
|
4063
|
+
const blank = document.createElement("span");
|
|
4064
|
+
blank.className = "vela-ind-cal-blank";
|
|
4065
|
+
grid.appendChild(blank);
|
|
4066
|
+
}
|
|
4067
|
+
for (let day = 1; day <= days; day++) {
|
|
4068
|
+
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
4069
|
+
const b = document.createElement("button");
|
|
4070
|
+
b.type = "button";
|
|
4071
|
+
b.className = "vela-ind-cal-day";
|
|
4072
|
+
b.textContent = String(day);
|
|
4073
|
+
if (iso === selectedIso) b.dataset.checked = "1";
|
|
4074
|
+
if (iso === todayIso) b.dataset.today = "1";
|
|
4075
|
+
b.addEventListener("click", (e) => {
|
|
4076
|
+
e.stopPropagation();
|
|
4077
|
+
onPick(iso);
|
|
4078
|
+
});
|
|
4079
|
+
grid.appendChild(b);
|
|
4080
|
+
}
|
|
4081
|
+
return;
|
|
4082
|
+
}
|
|
4083
|
+
if (mode === "month") {
|
|
4084
|
+
for (let m = 0; m < 12; m++) {
|
|
4085
|
+
const b = document.createElement("button");
|
|
4086
|
+
b.type = "button";
|
|
4087
|
+
b.className = "vela-ind-cal-cell";
|
|
4088
|
+
b.textContent = MONTH_SHORT[m] ?? "";
|
|
4089
|
+
if (selected?.getFullYear() === year && selected.getMonth() === m) b.dataset.checked = "1";
|
|
4090
|
+
if (today.getFullYear() === year && today.getMonth() === m) b.dataset.today = "1";
|
|
4091
|
+
b.addEventListener("click", (e) => {
|
|
4092
|
+
e.stopPropagation();
|
|
4093
|
+
month = m;
|
|
4094
|
+
mode = "date";
|
|
4095
|
+
paint();
|
|
4096
|
+
});
|
|
4097
|
+
grid.appendChild(b);
|
|
4098
|
+
}
|
|
4099
|
+
return;
|
|
4100
|
+
}
|
|
4101
|
+
for (let y = decade - 1; y <= decade + 10; y++) {
|
|
4102
|
+
const b = document.createElement("button");
|
|
4103
|
+
b.type = "button";
|
|
4104
|
+
b.className = "vela-ind-cal-cell";
|
|
4105
|
+
b.textContent = String(y);
|
|
4106
|
+
if (y < decade || y > decade + 9) b.dataset.outside = "1";
|
|
4107
|
+
if (selected?.getFullYear() === y) b.dataset.checked = "1";
|
|
4108
|
+
if (today.getFullYear() === y) b.dataset.today = "1";
|
|
4109
|
+
b.addEventListener("click", (e) => {
|
|
4110
|
+
e.stopPropagation();
|
|
4111
|
+
year = y;
|
|
4112
|
+
mode = "month";
|
|
4113
|
+
paint();
|
|
4114
|
+
});
|
|
4115
|
+
grid.appendChild(b);
|
|
4116
|
+
}
|
|
4117
|
+
};
|
|
4118
|
+
const step = (dir) => {
|
|
4119
|
+
if (mode === "date") {
|
|
4120
|
+
month += dir;
|
|
4121
|
+
if (month < 0) {
|
|
4122
|
+
month = 11;
|
|
4123
|
+
year -= 1;
|
|
4124
|
+
}
|
|
4125
|
+
if (month > 11) {
|
|
4126
|
+
month = 0;
|
|
4127
|
+
year += 1;
|
|
4128
|
+
}
|
|
4129
|
+
} else {
|
|
4130
|
+
year += mode === "month" ? dir : dir * 10;
|
|
4131
|
+
}
|
|
4132
|
+
paint();
|
|
4133
|
+
};
|
|
4134
|
+
prev2.addEventListener("click", (e) => {
|
|
4135
|
+
e.stopPropagation();
|
|
4136
|
+
step(-1);
|
|
4137
|
+
});
|
|
4138
|
+
next.addEventListener("click", (e) => {
|
|
4139
|
+
e.stopPropagation();
|
|
4140
|
+
step(1);
|
|
4141
|
+
});
|
|
4142
|
+
monthBtn.addEventListener("click", (e) => {
|
|
4143
|
+
e.stopPropagation();
|
|
4144
|
+
mode = "month";
|
|
4145
|
+
paint();
|
|
4146
|
+
});
|
|
4147
|
+
yearBtn.addEventListener("click", (e) => {
|
|
4148
|
+
e.stopPropagation();
|
|
4149
|
+
mode = "year";
|
|
4150
|
+
paint();
|
|
4151
|
+
});
|
|
4152
|
+
paint();
|
|
4153
|
+
el.append(head, week, grid);
|
|
4154
|
+
}
|
|
4033
4155
|
function normalizeTimeInput(raw) {
|
|
4034
4156
|
const t = raw.trim();
|
|
4035
4157
|
const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
|
|
@@ -5311,10 +5433,12 @@ var BASELINE_LEVEL_DEFAULT = 50;
|
|
|
5311
5433
|
var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
|
|
5312
5434
|
var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
|
|
5313
5435
|
var EXTENDED_SHADE = POSTMARKET_SHADE;
|
|
5436
|
+
var DEFAULT_MARGINS = { top: 10, bottom: 10, right: 10 };
|
|
5314
5437
|
function defaultChartStyle() {
|
|
5315
5438
|
return {
|
|
5316
5439
|
chartTypes: {},
|
|
5317
5440
|
fontSize: 11,
|
|
5441
|
+
margins: { ...DEFAULT_MARGINS },
|
|
5318
5442
|
gridVert: { visible: true, color: null },
|
|
5319
5443
|
gridHorz: { visible: true, color: null },
|
|
5320
5444
|
borderColor: null,
|
|
@@ -5438,6 +5562,12 @@ function clampLevel(v) {
|
|
|
5438
5562
|
function clampSpacing(v) {
|
|
5439
5563
|
return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
|
|
5440
5564
|
}
|
|
5565
|
+
function clampMarginPct(v) {
|
|
5566
|
+
return v < 0 ? 0 : v > 40 ? 40 : v;
|
|
5567
|
+
}
|
|
5568
|
+
function clampMarginBars(v) {
|
|
5569
|
+
return Math.round(v < 0 ? 0 : v > 200 ? 200 : v);
|
|
5570
|
+
}
|
|
5441
5571
|
function factoryResetConfig(factory, priceStyle = factory.series.style) {
|
|
5442
5572
|
const bag = {};
|
|
5443
5573
|
for (const t of chartTypes()) {
|
|
@@ -5486,6 +5616,7 @@ function mergeConfig(base, patch) {
|
|
|
5486
5616
|
const ps = asObject(p.priceScale);
|
|
5487
5617
|
const anim = asObject(p.animations);
|
|
5488
5618
|
const panes = asObject(p.panes);
|
|
5619
|
+
const margins = asObject(p.margins);
|
|
5489
5620
|
const trades = asObject(p.trades);
|
|
5490
5621
|
const ts = asObject(p.timeScale);
|
|
5491
5622
|
const marks = asObject(p.marks);
|
|
@@ -5545,6 +5676,11 @@ function mergeConfig(base, patch) {
|
|
|
5545
5676
|
panes: {
|
|
5546
5677
|
separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
|
|
5547
5678
|
},
|
|
5679
|
+
margins: {
|
|
5680
|
+
top: isNum(margins.top) ? clampMarginPct(margins.top) : base.margins.top,
|
|
5681
|
+
bottom: isNum(margins.bottom) ? clampMarginPct(margins.bottom) : base.margins.bottom,
|
|
5682
|
+
right: isNum(margins.right) ? clampMarginBars(margins.right) : base.margins.right
|
|
5683
|
+
},
|
|
5548
5684
|
trades: {
|
|
5549
5685
|
visible: isBool(trades.visible) ? trades.visible : base.trades.visible,
|
|
5550
5686
|
labels: isBool(trades.labels) ? trades.labels : base.trades.labels,
|
|
@@ -10874,6 +11010,10 @@ var BUILTIN_SETTINGS_IDS = [
|
|
|
10874
11010
|
"canvas.grid",
|
|
10875
11011
|
"canvas.grid.vertical",
|
|
10876
11012
|
"canvas.grid.horizontal",
|
|
11013
|
+
"canvas.margins",
|
|
11014
|
+
"canvas.margins.top",
|
|
11015
|
+
"canvas.margins.bottom",
|
|
11016
|
+
"canvas.margins.right",
|
|
10877
11017
|
"canvas.theme"
|
|
10878
11018
|
];
|
|
10879
11019
|
function settingsIdCatalog(hostSections, markGroups = []) {
|
|
@@ -11354,7 +11494,7 @@ var SettingsDialog = class {
|
|
|
11354
11494
|
if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
|
|
11355
11495
|
else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
11356
11496
|
else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
11357
|
-
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)));
|
|
11358
11498
|
}
|
|
11359
11499
|
}
|
|
11360
11500
|
};
|
|
@@ -11404,6 +11544,10 @@ var SettingsDialog = class {
|
|
|
11404
11544
|
body.append(sid(this.toggleRow("Horizontal", config.grid.horzLines.visible, (v) => this.emit({ grid: { horzLines: { visible: v } } }), [
|
|
11405
11545
|
this.swatch(config.grid.horzLines.color, (v) => this.emit({ grid: { horzLines: { color: v } } }))
|
|
11406
11546
|
]), "canvas.grid.horizontal"));
|
|
11547
|
+
body.append(sid(this.sectionTitle("Margins"), "canvas.margins"));
|
|
11548
|
+
body.append(sid(this.numberRow("Top", config.margins.top, 0, 40, 1, (v) => this.emit({ margins: { top: v } }), "%"), "canvas.margins.top"));
|
|
11549
|
+
body.append(sid(this.numberRow("Bottom", config.margins.bottom, 0, 40, 1, (v) => this.emit({ margins: { bottom: v } }), "%"), "canvas.margins.bottom"));
|
|
11550
|
+
body.append(sid(this.numberRow("Right", config.margins.right, 0, 200, 1, (v) => this.emit({ margins: { right: v } }), "bars"), "canvas.margins.right"));
|
|
11407
11551
|
if (this.themeControl) {
|
|
11408
11552
|
const tc = this.themeControl;
|
|
11409
11553
|
body.append(sid(this.sectionTitle("Theme"), "canvas.theme"));
|
|
@@ -11975,23 +12119,16 @@ var SettingsDialog = class {
|
|
|
11975
12119
|
this.hintTips.push(dispose);
|
|
11976
12120
|
return el;
|
|
11977
12121
|
}
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
step,
|
|
11989
|
-
fill: false,
|
|
11990
|
-
commit: "live",
|
|
11991
|
-
steppers: true,
|
|
11992
|
-
onChange
|
|
11993
|
-
}).el
|
|
11994
|
-
});
|
|
12122
|
+
/** `unit` (optional) trails the input as muted text — "%", "bars". */
|
|
12123
|
+
numberRow(label, value, min, max, step, onChange, unit) {
|
|
12124
|
+
const controls = [buildFieldControl({ kind: "number", value, min, max, step, fill: false, commit: "live", steppers: true, onChange }).el];
|
|
12125
|
+
if (unit) {
|
|
12126
|
+
const u = document.createElement("span");
|
|
12127
|
+
u.textContent = unit;
|
|
12128
|
+
u.style.cssText = "color:var(--vela-fg-muted);";
|
|
12129
|
+
controls.push(u);
|
|
12130
|
+
}
|
|
12131
|
+
return this.rowWith(label, controls);
|
|
11995
12132
|
}
|
|
11996
12133
|
/** A dropdown whose option values differ from their display labels. */
|
|
11997
12134
|
selectRowLabeled(label, value, options, onChange) {
|
|
@@ -16877,9 +17014,7 @@ function createProjector(coords, paneOf, paneIdAtY, barsInRange, seriesInRange)
|
|
|
16877
17014
|
}
|
|
16878
17015
|
|
|
16879
17016
|
// src/renderers/native/core/autoscale.ts
|
|
16880
|
-
|
|
16881
|
-
var MARGIN_BOTTOM = 1 / 7;
|
|
16882
|
-
function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log = false, offsetOf = () => 0) {
|
|
17017
|
+
function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log = false, offsetOf = () => 0, margins = DEFAULT_MARGINS) {
|
|
16883
17018
|
let min = Infinity;
|
|
16884
17019
|
let max = -Infinity;
|
|
16885
17020
|
const consider = (v) => {
|
|
@@ -16914,14 +17049,17 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
16914
17049
|
const pad = Math.abs(min) * 0.1 || 1;
|
|
16915
17050
|
return { min: min - pad, max: max + pad, log: log && min - pad > 0 };
|
|
16916
17051
|
}
|
|
17052
|
+
const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
|
|
17053
|
+
const above = margins.top / 100 / content;
|
|
17054
|
+
const below = margins.bottom / 100 / content;
|
|
16917
17055
|
if (log && min > 0) {
|
|
16918
17056
|
const lmin = Math.log(min);
|
|
16919
17057
|
const lmax = Math.log(max);
|
|
16920
17058
|
const lspan = lmax - lmin;
|
|
16921
|
-
return { min: Math.exp(lmin - lspan *
|
|
17059
|
+
return { min: Math.exp(lmin - lspan * below), max: Math.exp(lmax + lspan * above), log: true };
|
|
16922
17060
|
}
|
|
16923
17061
|
const span = max - min;
|
|
16924
|
-
return { min: min - span *
|
|
17062
|
+
return { min: min - span * below, max: max + span * above };
|
|
16925
17063
|
}
|
|
16926
17064
|
function considerSeries(s, i0, i1, off, consider) {
|
|
16927
17065
|
if (!seriesInScale(s)) return;
|
|
@@ -18722,6 +18860,7 @@ var NativeRenderer = class {
|
|
|
18722
18860
|
intro: this.intro.style !== false
|
|
18723
18861
|
},
|
|
18724
18862
|
panes: { separatorColor: s.separatorColor ?? t.borderColor },
|
|
18863
|
+
margins: { ...s.margins },
|
|
18725
18864
|
trades: {
|
|
18726
18865
|
visible: this.scene.tradeMarkers.visible,
|
|
18727
18866
|
labels: this.scene.tradeMarkers.labels,
|
|
@@ -18834,6 +18973,10 @@ var NativeRenderer = class {
|
|
|
18834
18973
|
this.animAutoscale.toggle(next.animations.autoscale);
|
|
18835
18974
|
this.intro = { style: next.animations.intro ? this.introOnStyle : false, duration: this.intro.duration || INTRO_DURATION_DEFAULT_MS };
|
|
18836
18975
|
s.separatorColor = keepInherit(s.separatorColor, next.panes.separatorColor, prevTheme.borderColor);
|
|
18976
|
+
if (next.margins.right !== s.margins.right && this.coords.barCount > 0) {
|
|
18977
|
+
this.applyViewport({ barSpacing: this.coords.getViewport().barSpacing, rightOffset: next.margins.right });
|
|
18978
|
+
}
|
|
18979
|
+
s.margins = { ...next.margins };
|
|
18837
18980
|
this.scene.tradeMarkers = {
|
|
18838
18981
|
visible: next.trades.visible,
|
|
18839
18982
|
labels: next.trades.labels,
|
|
@@ -19077,7 +19220,7 @@ var NativeRenderer = class {
|
|
|
19077
19220
|
/** Glide the view back to the most recent bars, keeping the current zoom (barSpacing). */
|
|
19078
19221
|
scrollToRealtime() {
|
|
19079
19222
|
if (this.coords.barCount === 0) return;
|
|
19080
|
-
this.glideRightOffset(
|
|
19223
|
+
this.glideRightOffset(this.scene.style.margins.right);
|
|
19081
19224
|
}
|
|
19082
19225
|
/** Ease rightOffset to `target` at constant zoom (see animTick's scroll glide);
|
|
19083
19226
|
* instant when the scroll glide is off. Shared by scroll-to-latest and panBy. */
|
|
@@ -21019,6 +21162,7 @@ var NativeRenderer = class {
|
|
|
21019
21162
|
const pricePane = panes.find((p) => p.kind === "price") ?? null;
|
|
21020
21163
|
this.chrome.prepare(this.scene, this.coords, this.theme);
|
|
21021
21164
|
const animating = this.animator.active;
|
|
21165
|
+
const margins = this.scene.style.margins;
|
|
21022
21166
|
for (const pane of panes) {
|
|
21023
21167
|
if (pane.manualScale) {
|
|
21024
21168
|
pane.scaleTarget = pane.manualScale;
|
|
@@ -21034,7 +21178,7 @@ var NativeRenderer = class {
|
|
|
21034
21178
|
if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
|
|
21035
21179
|
}
|
|
21036
21180
|
const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels) || !this.paneHasMeasurableContent(masterModels, dr));
|
|
21037
|
-
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
21181
|
+
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
|
|
21038
21182
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
21039
21183
|
pane.axisFormat = void 0;
|
|
21040
21184
|
pane.axisBands = void 0;
|
|
@@ -21045,7 +21189,7 @@ var NativeRenderer = class {
|
|
|
21045
21189
|
pane.axisFormat = "volume";
|
|
21046
21190
|
}
|
|
21047
21191
|
} else if (this.layerNativesOwnPane(pane, masterModels)) {
|
|
21048
|
-
pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
21192
|
+
pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
|
|
21049
21193
|
pane.percentBaseline = this.bars[i0]?.close ?? 0;
|
|
21050
21194
|
if (masterModels.every((m) => m.paneAxis != null)) {
|
|
21051
21195
|
pane.axisFormat = "none";
|
|
@@ -21074,7 +21218,7 @@ var NativeRenderer = class {
|
|
|
21074
21218
|
continue;
|
|
21075
21219
|
}
|
|
21076
21220
|
const mdr = this.chrome.paneDrawingsRange([model], this.scene, false, vr);
|
|
21077
|
-
sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id));
|
|
21221
|
+
sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id), margins);
|
|
21078
21222
|
if (!animating || !sl.initialized) {
|
|
21079
21223
|
sl.scale = { ...sl.scaleTarget };
|
|
21080
21224
|
sl.initialized = true;
|
|
@@ -21115,13 +21259,13 @@ var NativeRenderer = class {
|
|
|
21115
21259
|
for (const pane of this.scene.panes.values()) pane.manualScale = null;
|
|
21116
21260
|
for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
|
|
21117
21261
|
const visibleBars = Math.min(n, 200);
|
|
21118
|
-
const rightOffset =
|
|
21262
|
+
const rightOffset = this.scene.style.margins.right;
|
|
21119
21263
|
const v = this.clampViewport(w / ((visibleBars + rightOffset) * this.coords.spacingScale), rightOffset);
|
|
21120
21264
|
this.coords.setViewport(v);
|
|
21121
21265
|
this.targetBarSpacing = v.barSpacing;
|
|
21122
21266
|
}
|
|
21123
21267
|
/** Re-frame after a series replacement (a symbol/timeframe switch): keep the user's
|
|
21124
|
-
* zoom (bar spacing), re-anchor the newest bars at the
|
|
21268
|
+
* zoom (bar spacing), re-anchor the newest bars at the configured right margin.
|
|
21125
21269
|
* `clampViewport`'s fit-all-bars floor deliberately does NOT apply — a progressive
|
|
21126
21270
|
* head may still be backfilling toward the previous depth, and raising the spacing
|
|
21127
21271
|
* to its temporary bar count would lose the zoom this exists to keep. */
|
|
@@ -21130,7 +21274,7 @@ var NativeRenderer = class {
|
|
|
21130
21274
|
this.panVelocity = 0;
|
|
21131
21275
|
for (const pane of this.scene.panes.values()) pane.manualScale = null;
|
|
21132
21276
|
for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
|
|
21133
|
-
const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset:
|
|
21277
|
+
const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset: this.scene.style.margins.right };
|
|
21134
21278
|
this.coords.setViewport(v);
|
|
21135
21279
|
this.targetBarSpacing = v.barSpacing;
|
|
21136
21280
|
}
|
|
@@ -24277,7 +24421,8 @@ var ClassicIndicator = class {
|
|
|
24277
24421
|
lineStyle: plot.lineStyle ?? "solid",
|
|
24278
24422
|
...plot.base != null ? { base: plot.base } : {}
|
|
24279
24423
|
},
|
|
24280
|
-
...plot.overlay ? { overlay: true } : {}
|
|
24424
|
+
...plot.overlay ? { overlay: true } : {},
|
|
24425
|
+
...plot.display ? { display: plot.display } : {}
|
|
24281
24426
|
});
|
|
24282
24427
|
});
|
|
24283
24428
|
if (out.markers && out.markers.length > 0) {
|
|
@@ -24299,7 +24444,9 @@ var ClassicIndicator = class {
|
|
|
24299
24444
|
paneId: "",
|
|
24300
24445
|
fromSeriesId: from,
|
|
24301
24446
|
toSeriesId: to,
|
|
24302
|
-
color: band.color
|
|
24447
|
+
color: band.color,
|
|
24448
|
+
...band.colors ? { colors: [...band.colors] } : {},
|
|
24449
|
+
...band.gradient ? { gradient: [...band.gradient] } : {}
|
|
24303
24450
|
}
|
|
24304
24451
|
];
|
|
24305
24452
|
});
|
|
@@ -24457,6 +24604,16 @@ function stdev(v, len) {
|
|
|
24457
24604
|
return Math.sqrt(sq / len);
|
|
24458
24605
|
});
|
|
24459
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
|
+
}
|
|
24460
24617
|
function highest(v, len) {
|
|
24461
24618
|
return windowed(v, len, (w, from, to) => {
|
|
24462
24619
|
let m = -Infinity;
|
|
@@ -24588,7 +24745,7 @@ function stoch(v, hi, lo, len) {
|
|
|
24588
24745
|
}
|
|
24589
24746
|
return out;
|
|
24590
24747
|
}
|
|
24591
|
-
function linreg(v, len) {
|
|
24748
|
+
function linreg(v, len, offset = 0) {
|
|
24592
24749
|
const sx = (len - 1) * len / 2;
|
|
24593
24750
|
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
24594
24751
|
const denom = len * sxx - sx * sx;
|
|
@@ -24602,9 +24759,101 @@ function linreg(v, len) {
|
|
|
24602
24759
|
}
|
|
24603
24760
|
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
24604
24761
|
const intercept = (sy - slope * sx) / len;
|
|
24605
|
-
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;
|
|
24606
24849
|
});
|
|
24607
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
|
+
}
|
|
24608
24857
|
function swma(v) {
|
|
24609
24858
|
const out = new Array(v.length).fill(Number.NaN);
|
|
24610
24859
|
for (let i = 3; i < v.length; i++) {
|
|
@@ -24654,38 +24903,253 @@ function cumSum(v) {
|
|
|
24654
24903
|
}
|
|
24655
24904
|
|
|
24656
24905
|
// src/core/native-indicators/classics/shared.ts
|
|
24657
|
-
|
|
24658
|
-
|
|
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 } : {} };
|
|
24916
|
+
}
|
|
24917
|
+
function boolInput(key, title, defval, group = SETTINGS, tooltip) {
|
|
24918
|
+
return { key, title, type: "bool", defval, group, ...tooltip != null ? { tooltip } : {} };
|
|
24659
24919
|
}
|
|
24660
|
-
function
|
|
24661
|
-
return { key
|
|
24920
|
+
function optionInput(key, title, defval, options, tooltip) {
|
|
24921
|
+
return { key, title, type: "string", defval, options, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
24662
24922
|
}
|
|
24663
|
-
function
|
|
24664
|
-
return { key, title, type: "
|
|
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);
|
|
24665
24934
|
}
|
|
24666
24935
|
function withAlpha2(hex, alpha = 0.08) {
|
|
24667
24936
|
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
24668
24937
|
if (!m) return hex;
|
|
24669
|
-
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");
|
|
24670
24939
|
return `#${m[1]}${a}`;
|
|
24671
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
|
+
}
|
|
24672
25075
|
|
|
24673
25076
|
// src/core/native-indicators/classics/averages.ts
|
|
24674
|
-
var
|
|
24675
|
-
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." };
|
|
24676
25140
|
function fixedAverage(type, title, shortTitle, smooth) {
|
|
24677
25141
|
return {
|
|
24678
25142
|
type,
|
|
24679
25143
|
title,
|
|
24680
25144
|
shortTitle,
|
|
24681
25145
|
overlay: true,
|
|
24682
|
-
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()],
|
|
24683
25147
|
compute: (bars, inputs) => {
|
|
24684
25148
|
const len = num3(inputs, "length", 20);
|
|
24685
25149
|
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
24686
25150
|
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
24687
25151
|
if (offset !== 0) values = shift(values, offset);
|
|
24688
|
-
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 }] };
|
|
24689
25153
|
}
|
|
24690
25154
|
};
|
|
24691
25155
|
}
|
|
@@ -24697,36 +25161,37 @@ var movingAverage = {
|
|
|
24697
25161
|
shortTitle: "MA",
|
|
24698
25162
|
overlay: true,
|
|
24699
25163
|
inputs: [
|
|
24700
|
-
|
|
24701
|
-
lengthInput(20),
|
|
24702
|
-
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."),
|
|
24703
25174
|
offsetInput,
|
|
24704
|
-
colorInput()
|
|
25175
|
+
colorInput(INFO, "MA 1", "color", "MA 1 line color."),
|
|
25176
|
+
colorInput(WARNING, "MA 2", "ma2Color", "MA 2 line color.")
|
|
24705
25177
|
],
|
|
24706
25178
|
compute: (bars, inputs) => {
|
|
24707
|
-
const
|
|
24708
|
-
|
|
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);
|
|
24709
25186
|
const kind = str3(inputs, "maType", "SMA");
|
|
24710
|
-
|
|
24711
|
-
|
|
24712
|
-
|
|
24713
|
-
|
|
24714
|
-
|
|
24715
|
-
|
|
24716
|
-
values = wma(src, len);
|
|
24717
|
-
break;
|
|
24718
|
-
case "RMA":
|
|
24719
|
-
values = rma(src, len);
|
|
24720
|
-
break;
|
|
24721
|
-
case "VWMA":
|
|
24722
|
-
values = vwma(src, volumes(bars), len);
|
|
24723
|
-
break;
|
|
24724
|
-
default:
|
|
24725
|
-
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 });
|
|
24726
25193
|
}
|
|
24727
|
-
|
|
24728
|
-
if (offset !== 0) values = shift(values, offset);
|
|
24729
|
-
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
25194
|
+
return { plots };
|
|
24730
25195
|
}
|
|
24731
25196
|
};
|
|
24732
25197
|
var smoothedMa = {
|
|
@@ -24734,17 +25199,13 @@ var smoothedMa = {
|
|
|
24734
25199
|
title: "Smoothed Moving Average",
|
|
24735
25200
|
shortTitle: "RMA",
|
|
24736
25201
|
overlay: true,
|
|
24737
|
-
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
|
+
],
|
|
24738
25207
|
compute: (bars, inputs) => ({
|
|
24739
|
-
plots: [
|
|
24740
|
-
{
|
|
24741
|
-
key: "rma",
|
|
24742
|
-
title: "RMA",
|
|
24743
|
-
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
24744
|
-
color: str3(inputs, "color", SERIES_LINE),
|
|
24745
|
-
width: 2
|
|
24746
|
-
}
|
|
24747
|
-
]
|
|
25208
|
+
plots: [slopeColoredPlot("rma", "RMA", rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)), inputs)]
|
|
24748
25209
|
})
|
|
24749
25210
|
};
|
|
24750
25211
|
var zlema = {
|
|
@@ -24752,61 +25213,86 @@ var zlema = {
|
|
|
24752
25213
|
title: "Zero-Lag Exponential Moving Average",
|
|
24753
25214
|
shortTitle: "ZLEMA",
|
|
24754
25215
|
overlay: true,
|
|
24755
|
-
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
|
+
],
|
|
24756
25221
|
compute: (bars, inputs) => {
|
|
24757
|
-
const len = num3(inputs, "length",
|
|
25222
|
+
const len = num3(inputs, "length", 21);
|
|
24758
25223
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
24759
|
-
const lag = Math.
|
|
24760
|
-
const
|
|
24761
|
-
|
|
24762
|
-
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)] };
|
|
24763
25227
|
}
|
|
24764
25228
|
};
|
|
25229
|
+
var MOMENTUM_SCALER = "Momentum (|CMO|)";
|
|
25230
|
+
var VOLATILITY_SCALER = "Volatility (StDev Ratio)";
|
|
25231
|
+
var REGIME = "Trend Regime";
|
|
24765
25232
|
var vidya = {
|
|
24766
25233
|
type: "vidya",
|
|
24767
25234
|
title: "Variable Index Dynamic Average",
|
|
24768
25235
|
shortTitle: "VIDYA",
|
|
24769
25236
|
overlay: true,
|
|
24770
|
-
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
|
+
],
|
|
24771
25253
|
compute: (bars, inputs) => {
|
|
24772
|
-
const len = num3(inputs, "length",
|
|
24773
|
-
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
25254
|
+
const len = num3(inputs, "length", 12);
|
|
24774
25255
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
24775
|
-
const
|
|
24776
|
-
const
|
|
24777
|
-
const
|
|
24778
|
-
|
|
24779
|
-
|
|
24780
|
-
|
|
24781
|
-
|
|
24782
|
-
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
24783
|
-
const x = d[k];
|
|
24784
|
-
if (!Number.isFinite(x)) {
|
|
24785
|
-
ok = false;
|
|
24786
|
-
break;
|
|
24787
|
-
}
|
|
24788
|
-
if (x > 0) u += x;
|
|
24789
|
-
else w -= x;
|
|
24790
|
-
}
|
|
24791
|
-
if (ok) {
|
|
24792
|
-
su[i] = u;
|
|
24793
|
-
sd[i] = w;
|
|
24794
|
-
}
|
|
24795
|
-
}
|
|
24796
|
-
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);
|
|
24797
25263
|
const out = new Array(src.length).fill(Number.NaN);
|
|
24798
25264
|
let prev2 = Number.NaN;
|
|
24799
25265
|
for (let i = 0; i < src.length; i++) {
|
|
24800
25266
|
const x = src[i];
|
|
24801
|
-
|
|
24802
|
-
const
|
|
24803
|
-
if (!Number.isFinite(
|
|
24804
|
-
|
|
24805
|
-
|
|
24806
|
-
|
|
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
|
+
}
|
|
24807
25274
|
out[i] = prev2;
|
|
24808
25275
|
}
|
|
24809
|
-
|
|
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 }] };
|
|
24810
25296
|
}
|
|
24811
25297
|
};
|
|
24812
25298
|
var maEnvelope = {
|
|
@@ -24815,115 +25301,218 @@ var maEnvelope = {
|
|
|
24815
25301
|
shortTitle: "MA Env",
|
|
24816
25302
|
overlay: true,
|
|
24817
25303
|
inputs: [
|
|
24818
|
-
|
|
24819
|
-
lengthInput(20),
|
|
24820
|
-
sourceInput(),
|
|
24821
|
-
|
|
24822
|
-
|
|
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()
|
|
24823
25309
|
],
|
|
24824
25310
|
compute: (bars, inputs) => {
|
|
24825
|
-
const len = num3(inputs, "length", 20);
|
|
24826
25311
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
24827
|
-
const basis = str3(inputs, "maType", "SMA")
|
|
25312
|
+
const basis = movingAverageOf(str3(inputs, "maType", "SMA"), src, bars, num3(inputs, "length", 20));
|
|
24828
25313
|
const k = num3(inputs, "percent", 2.5) / 100;
|
|
24829
|
-
const ink = str3(inputs, "
|
|
24830
|
-
return
|
|
24831
|
-
plots: [
|
|
24832
|
-
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
24833
|
-
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
24834
|
-
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
24835
|
-
],
|
|
24836
|
-
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
24837
|
-
};
|
|
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");
|
|
24838
25316
|
}
|
|
24839
25317
|
};
|
|
25318
|
+
var LINREG_CURVE = "Curve";
|
|
25319
|
+
var LINREG_SLOPE = "Slope";
|
|
24840
25320
|
var linearRegression = {
|
|
24841
25321
|
type: "linear-regression",
|
|
24842
25322
|
title: "Linear Regression Curve",
|
|
24843
25323
|
shortTitle: "LinReg",
|
|
24844
|
-
|
|
24845
|
-
|
|
24846
|
-
|
|
24847
|
-
plots
|
|
24848
|
-
|
|
24849
|
-
|
|
24850
|
-
|
|
24851
|
-
|
|
24852
|
-
|
|
24853
|
-
|
|
24854
|
-
|
|
24855
|
-
|
|
24856
|
-
|
|
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
|
+
}
|
|
24857
25351
|
};
|
|
24858
25352
|
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
24859
25353
|
|
|
24860
25354
|
// src/core/native-indicators/classics/bands.ts
|
|
25355
|
+
var JAW_COLOR = "#ab47bc";
|
|
25356
|
+
var TEETH_COLOR = WARNING;
|
|
25357
|
+
var LIPS_COLOR = "#ffd700";
|
|
24861
25358
|
function bollinger(bars, inputs) {
|
|
24862
25359
|
const len = num3(inputs, "length", 20);
|
|
24863
25360
|
const mult = num3(inputs, "mult", 2);
|
|
24864
25361
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
24865
25362
|
const basis = sma(src, len);
|
|
24866
25363
|
const dev = map(stdev(src, len), (x) => x * mult);
|
|
24867
|
-
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
|
+
];
|
|
24868
25372
|
}
|
|
24869
|
-
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
24870
25373
|
var bollingerBands = {
|
|
24871
25374
|
type: "bollinger-bands",
|
|
24872
25375
|
title: "Bollinger Bands",
|
|
24873
25376
|
shortTitle: "BB",
|
|
24874
25377
|
overlay: true,
|
|
24875
|
-
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
|
+
],
|
|
24876
25386
|
compute: (bars, inputs) => {
|
|
24877
25387
|
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
24878
|
-
const ink = str3(inputs, "color",
|
|
25388
|
+
const ink = str3(inputs, "color", INFO);
|
|
24879
25389
|
return {
|
|
24880
25390
|
plots: [
|
|
24881
|
-
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
25391
|
+
{ key: "basis", title: "Basis", values: basis, color: str3(inputs, "basisColor", WARNING) },
|
|
24882
25392
|
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
24883
25393
|
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
24884
25394
|
],
|
|
24885
|
-
bands: [{ key: "bb", from: "upper", to: "lower", color:
|
|
25395
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: str3(inputs, "fillColor", transp(INFO, 92)) }]
|
|
24886
25396
|
};
|
|
24887
25397
|
}
|
|
24888
25398
|
};
|
|
25399
|
+
var PERCENT_B_MID = 0.5;
|
|
25400
|
+
var THRESHOLDS = "Thresholds";
|
|
24889
25401
|
var percentB = {
|
|
24890
25402
|
type: "percent-b",
|
|
24891
25403
|
title: "Bollinger Bands %B",
|
|
24892
25404
|
shortTitle: "%B",
|
|
24893
25405
|
overlay: false,
|
|
24894
|
-
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
|
+
],
|
|
24895
25415
|
compute: (bars, inputs) => {
|
|
24896
|
-
const { upper, lower } = bollinger(bars, inputs);
|
|
24897
|
-
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
25416
|
+
const { src, upper, lower } = bollinger(bars, inputs);
|
|
24898
25417
|
const values = src.map((x, i) => {
|
|
24899
25418
|
const u = upper[i];
|
|
24900
25419
|
const l = lower[i];
|
|
24901
25420
|
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
24902
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
|
+
] : [];
|
|
24903
25450
|
return {
|
|
24904
|
-
plots
|
|
25451
|
+
plots,
|
|
25452
|
+
bands,
|
|
24905
25453
|
levels: [
|
|
24906
|
-
{ key: "
|
|
24907
|
-
{ key: "
|
|
24908
|
-
{ 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" }
|
|
24909
25457
|
]
|
|
24910
25458
|
};
|
|
24911
25459
|
}
|
|
24912
25460
|
};
|
|
25461
|
+
var SQUEEZE_BULGE = "Squeeze & Bulge";
|
|
24913
25462
|
var bandwidth = {
|
|
24914
25463
|
type: "bandwidth",
|
|
24915
25464
|
title: "Bollinger Bands Width",
|
|
24916
25465
|
shortTitle: "BandWidth",
|
|
24917
25466
|
overlay: false,
|
|
24918
|
-
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
|
+
],
|
|
24919
25477
|
compute: (bars, inputs) => {
|
|
24920
25478
|
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
24921
25479
|
const values = basis.map((b, i) => {
|
|
24922
25480
|
const u = upper[i];
|
|
24923
25481
|
const l = lower[i];
|
|
24924
|
-
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;
|
|
25483
|
+
});
|
|
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;
|
|
24925
25495
|
});
|
|
24926
|
-
|
|
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 };
|
|
24927
25516
|
}
|
|
24928
25517
|
};
|
|
24929
25518
|
var keltner = {
|
|
@@ -24932,26 +25521,17 @@ var keltner = {
|
|
|
24932
25521
|
shortTitle: "KC",
|
|
24933
25522
|
overlay: true,
|
|
24934
25523
|
inputs: [
|
|
24935
|
-
lengthInput(20),
|
|
24936
|
-
|
|
24937
|
-
|
|
24938
|
-
sourceInput(),
|
|
24939
|
-
|
|
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")
|
|
24940
25529
|
],
|
|
24941
25530
|
compute: (bars, inputs) => {
|
|
24942
|
-
const
|
|
24943
|
-
const
|
|
24944
|
-
const
|
|
24945
|
-
|
|
24946
|
-
const ink = str3(inputs, "color", SERIES_LINE);
|
|
24947
|
-
return {
|
|
24948
|
-
plots: [
|
|
24949
|
-
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
24950
|
-
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
24951
|
-
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
24952
|
-
],
|
|
24953
|
-
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
24954
|
-
};
|
|
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");
|
|
24955
25535
|
}
|
|
24956
25536
|
};
|
|
24957
25537
|
var donchian = {
|
|
@@ -24959,19 +25539,24 @@ var donchian = {
|
|
|
24959
25539
|
title: "Donchian Channels",
|
|
24960
25540
|
shortTitle: "DC",
|
|
24961
25541
|
overlay: true,
|
|
24962
|
-
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
|
+
],
|
|
24963
25549
|
compute: (bars, inputs) => {
|
|
24964
25550
|
const len = num3(inputs, "length", 20);
|
|
24965
25551
|
const upper = highest(highs(bars), len);
|
|
24966
25552
|
const lower = lowest(lows(bars), len);
|
|
24967
|
-
const ink = str3(inputs, "color", SERIES_LINE);
|
|
24968
25553
|
return {
|
|
24969
25554
|
plots: [
|
|
24970
|
-
{ key: "
|
|
24971
|
-
{ key: "
|
|
24972
|
-
{ 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) }
|
|
24973
25558
|
],
|
|
24974
|
-
bands: [{ key: "dc", from: "upper", to: "lower", color:
|
|
25559
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: str3(inputs, "fillColor", transp(NEUTRAL, 92)) }]
|
|
24975
25560
|
};
|
|
24976
25561
|
}
|
|
24977
25562
|
};
|
|
@@ -24980,16 +25565,37 @@ var chandelier = {
|
|
|
24980
25565
|
title: "Chandelier Exit",
|
|
24981
25566
|
shortTitle: "CE",
|
|
24982
25567
|
overlay: true,
|
|
24983
|
-
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
|
+
],
|
|
24984
25575
|
compute: (bars, inputs) => {
|
|
24985
25576
|
const len = num3(inputs, "length", 22);
|
|
24986
25577
|
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
24987
|
-
const
|
|
24988
|
-
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
|
+
}
|
|
24989
25595
|
return {
|
|
24990
25596
|
plots: [
|
|
24991
|
-
{ key: "long", title: "Long
|
|
24992
|
-
{ 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) }
|
|
24993
25599
|
]
|
|
24994
25600
|
};
|
|
24995
25601
|
}
|
|
@@ -25000,21 +25606,22 @@ var chandeKroll = {
|
|
|
25000
25606
|
shortTitle: "CKS",
|
|
25001
25607
|
overlay: true,
|
|
25002
25608
|
inputs: [
|
|
25003
|
-
|
|
25004
|
-
|
|
25005
|
-
|
|
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.")
|
|
25006
25614
|
],
|
|
25007
25615
|
compute: (bars, inputs) => {
|
|
25008
25616
|
const p = num3(inputs, "p", 10);
|
|
25009
|
-
const x = num3(inputs, "x", 1);
|
|
25010
25617
|
const q = num3(inputs, "q", 9);
|
|
25011
|
-
const range = map(atr(bars, p), (v) => v * x);
|
|
25618
|
+
const range = map(atr(bars, p), (v) => v * num3(inputs, "x", 1));
|
|
25012
25619
|
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
25013
25620
|
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
25014
25621
|
return {
|
|
25015
25622
|
plots: [
|
|
25016
|
-
{ key: "stopShort", title: "Stop
|
|
25017
|
-
{ 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) }
|
|
25018
25625
|
]
|
|
25019
25626
|
};
|
|
25020
25627
|
}
|
|
@@ -25025,36 +25632,42 @@ var supertrend = {
|
|
|
25025
25632
|
shortTitle: "SuperTrend",
|
|
25026
25633
|
overlay: true,
|
|
25027
25634
|
inputs: [
|
|
25028
|
-
|
|
25029
|
-
|
|
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.")
|
|
25030
25639
|
],
|
|
25031
25640
|
compute: (bars, inputs) => {
|
|
25032
|
-
const len = num3(inputs, "atrLength", 10);
|
|
25033
25641
|
const mult = num3(inputs, "mult", 3);
|
|
25034
|
-
const range = atr(bars,
|
|
25642
|
+
const range = atr(bars, num3(inputs, "atrLength", 10));
|
|
25035
25643
|
const n = bars.length;
|
|
25036
|
-
const
|
|
25037
|
-
const
|
|
25038
|
-
let
|
|
25039
|
-
let
|
|
25040
|
-
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;
|
|
25041
25649
|
for (let i = 0; i < n; i++) {
|
|
25042
25650
|
const r = range[i];
|
|
25043
25651
|
if (!Number.isFinite(r)) continue;
|
|
25044
25652
|
const b = bars[i];
|
|
25045
25653
|
const mid = (b.high + b.low) / 2;
|
|
25046
|
-
const
|
|
25047
|
-
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;
|
|
25048
25658
|
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
25049
|
-
|
|
25050
|
-
|
|
25051
|
-
|
|
25052
|
-
|
|
25053
|
-
|
|
25054
|
-
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;
|
|
25055
25664
|
}
|
|
25056
|
-
|
|
25057
|
-
|
|
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
|
+
};
|
|
25058
25671
|
}
|
|
25059
25672
|
};
|
|
25060
25673
|
var alligator = {
|
|
@@ -25063,12 +25676,15 @@ var alligator = {
|
|
|
25063
25676
|
shortTitle: "Alligator",
|
|
25064
25677
|
overlay: true,
|
|
25065
25678
|
inputs: [
|
|
25066
|
-
|
|
25067
|
-
|
|
25068
|
-
|
|
25069
|
-
|
|
25070
|
-
|
|
25071
|
-
|
|
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.")
|
|
25072
25688
|
],
|
|
25073
25689
|
compute: (bars, inputs) => {
|
|
25074
25690
|
const hl2 = sourceValues(bars, "HL2");
|
|
@@ -25077,99 +25693,134 @@ var alligator = {
|
|
|
25077
25693
|
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
25078
25694
|
return {
|
|
25079
25695
|
plots: [
|
|
25080
|
-
{ key: "jaw", title: "Jaw", values: jaw, color:
|
|
25081
|
-
{ key: "teeth", title: "Teeth", values: teeth, color:
|
|
25082
|
-
{ 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) }
|
|
25083
25699
|
]
|
|
25084
25700
|
};
|
|
25085
25701
|
}
|
|
25086
25702
|
};
|
|
25703
|
+
var GATOR_LINES = "Alligator Lines";
|
|
25704
|
+
var SMMA_TYPE = "SMMA (Wilder)";
|
|
25087
25705
|
var gator = {
|
|
25088
25706
|
type: "gator-oscillator",
|
|
25089
25707
|
title: "Gator Oscillator",
|
|
25090
25708
|
shortTitle: "Gator Oscillator",
|
|
25091
25709
|
overlay: false,
|
|
25092
|
-
inputs: [
|
|
25093
|
-
|
|
25094
|
-
|
|
25095
|
-
|
|
25096
|
-
|
|
25097
|
-
|
|
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));
|
|
25098
25730
|
const upper = map(sub(jaw, teeth), Math.abs);
|
|
25099
|
-
const lower = map(sub(teeth, lips),
|
|
25100
|
-
const
|
|
25101
|
-
|
|
25102
|
-
|
|
25103
|
-
|
|
25104
|
-
|
|
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
|
+
};
|
|
25105
25743
|
return {
|
|
25106
25744
|
plots: [
|
|
25107
|
-
{ key: "upper", title: "Upper", values: upper, kind: "
|
|
25108
|
-
{ key: "lower", title: "Lower", values: lower, kind: "
|
|
25109
|
-
]
|
|
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" }]
|
|
25110
25749
|
};
|
|
25111
25750
|
}
|
|
25112
25751
|
};
|
|
25113
25752
|
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
25114
25753
|
|
|
25115
25754
|
// src/core/native-indicators/classics/oscillators.ts
|
|
25116
|
-
function
|
|
25117
|
-
|
|
25118
|
-
|
|
25119
|
-
|
|
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.")
|
|
25120
25759
|
];
|
|
25121
|
-
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
25122
|
-
return levels;
|
|
25123
|
-
}
|
|
25124
|
-
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
25125
|
-
function histColors(values) {
|
|
25126
|
-
return values.map((x, i) => {
|
|
25127
|
-
if (!Number.isFinite(x)) return null;
|
|
25128
|
-
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
25129
|
-
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
25130
|
-
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
25131
|
-
return rising ? `${BEARISH}80` : BEARISH;
|
|
25132
|
-
});
|
|
25133
25760
|
}
|
|
25134
25761
|
var rsiSpec = {
|
|
25135
25762
|
type: "rsi",
|
|
25136
25763
|
title: "Relative Strength Index",
|
|
25137
25764
|
shortTitle: "RSI",
|
|
25138
25765
|
overlay: false,
|
|
25139
|
-
inputs: [
|
|
25140
|
-
|
|
25141
|
-
|
|
25142
|
-
|
|
25143
|
-
|
|
25144
|
-
|
|
25145
|
-
|
|
25146
|
-
|
|
25147
|
-
|
|
25148
|
-
|
|
25149
|
-
|
|
25150
|
-
|
|
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))
|
|
25151
25788
|
})
|
|
25152
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
|
+
}
|
|
25153
25807
|
var stochastic = {
|
|
25154
25808
|
type: "stochastic",
|
|
25155
25809
|
title: "Stochastic",
|
|
25156
25810
|
shortTitle: "Stoch",
|
|
25157
25811
|
overlay: false,
|
|
25158
25812
|
inputs: [
|
|
25159
|
-
lengthInput(14, "kLength", "%K
|
|
25160
|
-
lengthInput(
|
|
25161
|
-
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.")
|
|
25162
25820
|
],
|
|
25163
25821
|
compute: (bars, inputs) => {
|
|
25164
|
-
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing",
|
|
25165
|
-
|
|
25166
|
-
return {
|
|
25167
|
-
plots: [
|
|
25168
|
-
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
25169
|
-
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
25170
|
-
],
|
|
25171
|
-
levels: guideLevels(20, 80)
|
|
25172
|
-
};
|
|
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);
|
|
25173
25824
|
}
|
|
25174
25825
|
};
|
|
25175
25826
|
var stochasticRsi = {
|
|
@@ -25178,23 +25829,20 @@ var stochasticRsi = {
|
|
|
25178
25829
|
shortTitle: "Stoch RSI",
|
|
25179
25830
|
overlay: false,
|
|
25180
25831
|
inputs: [
|
|
25181
|
-
|
|
25182
|
-
lengthInput(14, "
|
|
25183
|
-
lengthInput(
|
|
25184
|
-
lengthInput(3, "
|
|
25185
|
-
|
|
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.")
|
|
25186
25841
|
],
|
|
25187
25842
|
compute: (bars, inputs) => {
|
|
25188
25843
|
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
25189
25844
|
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
25190
|
-
|
|
25191
|
-
return {
|
|
25192
|
-
plots: [
|
|
25193
|
-
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
25194
|
-
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
25195
|
-
],
|
|
25196
|
-
levels: guideLevels(20, 80)
|
|
25197
|
-
};
|
|
25845
|
+
return stochasticOutput(k, sma(k, num3(inputs, "dLength", 3)), inputs);
|
|
25198
25846
|
}
|
|
25199
25847
|
};
|
|
25200
25848
|
var macd = {
|
|
@@ -25202,23 +25850,35 @@ var macd = {
|
|
|
25202
25850
|
title: "MACD",
|
|
25203
25851
|
overlay: false,
|
|
25204
25852
|
inputs: [
|
|
25205
|
-
lengthInput(12, "fastLength", "Fast
|
|
25206
|
-
lengthInput(26, "slowLength", "Slow
|
|
25207
|
-
lengthInput(9, "signalLength", "Signal
|
|
25208
|
-
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.")
|
|
25209
25863
|
],
|
|
25210
25864
|
compute: (bars, inputs) => {
|
|
25211
25865
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
25212
25866
|
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
25213
25867
|
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
25214
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
|
+
});
|
|
25215
25875
|
return {
|
|
25216
25876
|
plots: [
|
|
25217
|
-
{ key: "hist", title: "Histogram", values: hist, kind: "
|
|
25218
|
-
{ key: "macd", title: "MACD", values: line, color:
|
|
25219
|
-
{ 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) }
|
|
25220
25880
|
],
|
|
25221
|
-
levels:
|
|
25881
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL, lineStyle: "dashed", title: "Zero Line" }]
|
|
25222
25882
|
};
|
|
25223
25883
|
}
|
|
25224
25884
|
};
|
|
@@ -25228,26 +25888,49 @@ var ppo = {
|
|
|
25228
25888
|
shortTitle: "PPO",
|
|
25229
25889
|
overlay: false,
|
|
25230
25890
|
inputs: [
|
|
25231
|
-
|
|
25232
|
-
lengthInput(
|
|
25233
|
-
lengthInput(
|
|
25234
|
-
|
|
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.")
|
|
25235
25902
|
],
|
|
25236
25903
|
compute: (bars, inputs) => {
|
|
25237
25904
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
25238
|
-
const
|
|
25239
|
-
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));
|
|
25240
25909
|
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
25241
|
-
const signal =
|
|
25910
|
+
const signal = average(str3(inputs, "signalType", "EMA"), line, num3(inputs, "signalLength", 9));
|
|
25242
25911
|
const hist = sub(line, signal);
|
|
25243
|
-
|
|
25244
|
-
|
|
25245
|
-
|
|
25246
|
-
|
|
25247
|
-
|
|
25248
|
-
|
|
25249
|
-
|
|
25250
|
-
|
|
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;
|
|
25251
25934
|
}
|
|
25252
25935
|
};
|
|
25253
25936
|
var awesome = {
|
|
@@ -25255,48 +25938,67 @@ var awesome = {
|
|
|
25255
25938
|
title: "Awesome Oscillator",
|
|
25256
25939
|
shortTitle: "AO",
|
|
25257
25940
|
overlay: false,
|
|
25258
|
-
inputs: [
|
|
25259
|
-
|
|
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) => {
|
|
25260
25947
|
const hl2 = sourceValues(bars, "HL2");
|
|
25261
25948
|
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
25949
|
+
const rising = str3(inputs, "risingColor", BULLISH);
|
|
25950
|
+
const falling = str3(inputs, "fallingColor", BEARISH);
|
|
25262
25951
|
const colors = ao.map((x, i) => {
|
|
25263
25952
|
if (!Number.isFinite(x)) return null;
|
|
25264
25953
|
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
25265
|
-
return Number.isFinite(prev2) && x
|
|
25954
|
+
return Number.isFinite(prev2) && x > prev2 ? rising : falling;
|
|
25266
25955
|
});
|
|
25267
25956
|
return {
|
|
25268
|
-
plots: [{ key: "ao", title: "AO", values: ao, kind: "
|
|
25269
|
-
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" }]
|
|
25270
25959
|
};
|
|
25271
25960
|
}
|
|
25272
25961
|
};
|
|
25962
|
+
var CCI_SCALING = 0.015;
|
|
25273
25963
|
var cci = {
|
|
25274
25964
|
type: "commodity-channel-index",
|
|
25275
25965
|
title: "Commodity Channel Index",
|
|
25276
25966
|
shortTitle: "CCI",
|
|
25277
25967
|
overlay: false,
|
|
25278
|
-
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
|
+
],
|
|
25279
25978
|
compute: (bars, inputs) => {
|
|
25280
25979
|
const len = num3(inputs, "length", 20);
|
|
25281
25980
|
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
25282
25981
|
const basis = sma(src, len);
|
|
25283
|
-
const dev =
|
|
25284
|
-
for (let i = len - 1; i < src.length; i++) {
|
|
25285
|
-
const mean = basis[i];
|
|
25286
|
-
if (!Number.isFinite(mean)) continue;
|
|
25287
|
-
let d = 0;
|
|
25288
|
-
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
25289
|
-
dev[i] = d / len;
|
|
25290
|
-
}
|
|
25982
|
+
const dev = meanDev(src, len);
|
|
25291
25983
|
const values = src.map((x, i) => {
|
|
25292
25984
|
const b = basis[i];
|
|
25293
25985
|
const d = dev[i];
|
|
25294
|
-
|
|
25986
|
+
if (!Number.isFinite(b) || !Number.isFinite(d)) return Number.NaN;
|
|
25987
|
+
return d === 0 ? 0 : (x - b) / (CCI_SCALING * d);
|
|
25988
|
+
});
|
|
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
|
+
]
|
|
25295
26001
|
});
|
|
25296
|
-
return {
|
|
25297
|
-
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
25298
|
-
levels: guideLevels(-100, 100, 0)
|
|
25299
|
-
};
|
|
25300
26002
|
}
|
|
25301
26003
|
};
|
|
25302
26004
|
var williamsR = {
|
|
@@ -25304,31 +26006,78 @@ var williamsR = {
|
|
|
25304
26006
|
title: "Williams %R",
|
|
25305
26007
|
shortTitle: "%R",
|
|
25306
26008
|
overlay: false,
|
|
25307
|
-
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
|
+
],
|
|
25308
26017
|
compute: (bars, inputs) => {
|
|
25309
|
-
const
|
|
25310
|
-
|
|
25311
|
-
|
|
25312
|
-
|
|
25313
|
-
|
|
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
|
+
});
|
|
25314
26037
|
}
|
|
25315
26038
|
};
|
|
25316
|
-
var
|
|
26039
|
+
var cmoSpec = {
|
|
25317
26040
|
type: "chande-momentum-oscillator",
|
|
25318
26041
|
title: "Chande Momentum Oscillator",
|
|
25319
26042
|
shortTitle: "CMO",
|
|
25320
26043
|
overlay: false,
|
|
25321
|
-
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
|
+
],
|
|
25322
26054
|
compute: (bars, inputs) => {
|
|
25323
26055
|
const len = num3(inputs, "length", 9);
|
|
25324
26056
|
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
25325
|
-
const
|
|
25326
|
-
const
|
|
25327
|
-
const values = zip(
|
|
25328
|
-
|
|
25329
|
-
|
|
25330
|
-
|
|
25331
|
-
|
|
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;
|
|
25332
26081
|
}
|
|
25333
26082
|
};
|
|
25334
26083
|
var connorsRsi = {
|
|
@@ -25337,10 +26086,14 @@ var connorsRsi = {
|
|
|
25337
26086
|
shortTitle: "CRSI",
|
|
25338
26087
|
overlay: false,
|
|
25339
26088
|
inputs: [
|
|
25340
|
-
lengthInput(3, "rsiLength", "RSI
|
|
25341
|
-
lengthInput(2, "streakLength", "Streak RSI
|
|
25342
|
-
lengthInput(100, "rankLength", "Percent
|
|
25343
|
-
|
|
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.")
|
|
25344
26097
|
],
|
|
25345
26098
|
compute: (bars, inputs) => {
|
|
25346
26099
|
const c = closes(bars);
|
|
@@ -25358,10 +26111,17 @@ var connorsRsi = {
|
|
|
25358
26111
|
const z = r[i];
|
|
25359
26112
|
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
25360
26113
|
});
|
|
25361
|
-
return {
|
|
25362
|
-
|
|
25363
|
-
|
|
25364
|
-
|
|
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
|
+
});
|
|
25365
26125
|
}
|
|
25366
26126
|
};
|
|
25367
26127
|
var fisher = {
|
|
@@ -25369,7 +26129,13 @@ var fisher = {
|
|
|
25369
26129
|
title: "Fisher Transform",
|
|
25370
26130
|
shortTitle: "Fisher",
|
|
25371
26131
|
overlay: false,
|
|
25372
|
-
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
|
+
],
|
|
25373
26139
|
compute: (bars, inputs) => {
|
|
25374
26140
|
const len = num3(inputs, "length", 9);
|
|
25375
26141
|
const hl2 = sourceValues(bars, "HL2");
|
|
@@ -25377,23 +26143,39 @@ var fisher = {
|
|
|
25377
26143
|
const lo = lowest(hl2, len);
|
|
25378
26144
|
const n = bars.length;
|
|
25379
26145
|
const out = new Array(n).fill(Number.NaN);
|
|
26146
|
+
let position = 0;
|
|
25380
26147
|
let value = 0;
|
|
25381
|
-
let fish = 0;
|
|
25382
26148
|
for (let i = 0; i < n; i++) {
|
|
25383
26149
|
const h = hi[i];
|
|
25384
26150
|
const l = lo[i];
|
|
25385
26151
|
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
25386
|
-
const
|
|
25387
|
-
|
|
25388
|
-
|
|
25389
|
-
|
|
25390
|
-
|
|
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
|
+
};
|
|
25391
26168
|
return {
|
|
25392
26169
|
plots: [
|
|
25393
|
-
{ key: "fisher", title: "Fisher", values: out, color:
|
|
25394
|
-
{ 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) }
|
|
25395
26172
|
],
|
|
25396
|
-
|
|
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
|
+
]
|
|
25397
26179
|
};
|
|
25398
26180
|
}
|
|
25399
26181
|
};
|
|
@@ -25401,93 +26183,180 @@ var trix = {
|
|
|
25401
26183
|
type: "trix",
|
|
25402
26184
|
title: "TRIX",
|
|
25403
26185
|
overlay: false,
|
|
25404
|
-
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
|
+
],
|
|
25405
26194
|
compute: (bars, inputs) => {
|
|
25406
26195
|
const len = num3(inputs, "length", 18);
|
|
25407
|
-
const
|
|
25408
|
-
const
|
|
25409
|
-
|
|
25410
|
-
|
|
25411
|
-
|
|
25412
|
-
|
|
25413
|
-
|
|
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
|
+
});
|
|
25414
26206
|
}
|
|
25415
26207
|
};
|
|
25416
|
-
function
|
|
26208
|
+
function tsiRatio(src, long, short) {
|
|
25417
26209
|
const mom = change(src);
|
|
25418
26210
|
const numer = ema(ema(mom, long), short);
|
|
25419
26211
|
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
25420
|
-
return zip(numer, denom, (a, b) => b === 0 ? 0 :
|
|
26212
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : a / b);
|
|
25421
26213
|
}
|
|
25422
26214
|
var tsi = {
|
|
25423
26215
|
type: "true-strength-index",
|
|
25424
26216
|
title: "True Strength Index",
|
|
25425
26217
|
shortTitle: "TSI",
|
|
25426
26218
|
overlay: false,
|
|
25427
|
-
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
|
+
],
|
|
25428
26228
|
compute: (bars, inputs) => {
|
|
25429
|
-
const
|
|
25430
|
-
return {
|
|
25431
|
-
|
|
25432
|
-
|
|
25433
|
-
|
|
25434
|
-
|
|
25435
|
-
|
|
25436
|
-
|
|
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
|
+
});
|
|
25437
26238
|
}
|
|
25438
26239
|
};
|
|
26240
|
+
var SMI_INDICATOR = "Indicator";
|
|
26241
|
+
var SMI_OSCILLATOR = "Oscillator";
|
|
25439
26242
|
var smiErgodic = {
|
|
25440
26243
|
type: "smi-ergodic",
|
|
25441
26244
|
title: "SMI Ergodic Oscillator",
|
|
25442
26245
|
shortTitle: "SMIE",
|
|
25443
26246
|
overlay: false,
|
|
25444
|
-
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
|
+
],
|
|
25445
26257
|
compute: (bars, inputs) => {
|
|
25446
|
-
const
|
|
25447
|
-
const signal = ema(
|
|
25448
|
-
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
|
+
};
|
|
25449
26279
|
return {
|
|
25450
26280
|
plots: [
|
|
25451
|
-
{ key: "
|
|
25452
|
-
{ key: "
|
|
25453
|
-
{ 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) }
|
|
25454
26283
|
],
|
|
25455
|
-
|
|
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
|
|
25456
26286
|
};
|
|
25457
26287
|
}
|
|
25458
26288
|
};
|
|
26289
|
+
var STC_MID = 50;
|
|
26290
|
+
var TRIGGERS = "Trigger Levels";
|
|
25459
26291
|
var stc = {
|
|
25460
26292
|
type: "schaff-trend-cycle",
|
|
25461
26293
|
title: "Schaff Trend Cycle",
|
|
25462
26294
|
shortTitle: "Schaff Trend Cycle",
|
|
25463
26295
|
overlay: false,
|
|
25464
|
-
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
|
+
],
|
|
25465
26309
|
compute: (bars, inputs) => {
|
|
25466
26310
|
const len = num3(inputs, "cycleLength", 10);
|
|
25467
|
-
const
|
|
26311
|
+
const factor = num3(inputs, "factor", 0.5);
|
|
26312
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
25468
26313
|
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
25469
|
-
const
|
|
26314
|
+
const stochNorm = (v) => {
|
|
25470
26315
|
const lo = lowest(v, len);
|
|
25471
26316
|
const hi = highest(v, len);
|
|
25472
26317
|
const out = new Array(v.length).fill(Number.NaN);
|
|
25473
|
-
let
|
|
25474
|
-
let smoothed = Number.NaN;
|
|
26318
|
+
let pct = Number.NaN;
|
|
25475
26319
|
for (let i = 0; i < v.length; i++) {
|
|
25476
26320
|
const x = v[i];
|
|
25477
26321
|
const l = lo[i];
|
|
25478
26322
|
const h = hi[i];
|
|
25479
26323
|
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
25480
|
-
|
|
25481
|
-
|
|
25482
|
-
|
|
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;
|
|
25483
26336
|
out[i] = smoothed;
|
|
25484
26337
|
}
|
|
25485
26338
|
return out;
|
|
25486
26339
|
};
|
|
25487
|
-
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
|
+
}
|
|
25488
26352
|
return {
|
|
25489
|
-
plots
|
|
25490
|
-
|
|
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
|
+
]
|
|
25491
26360
|
};
|
|
25492
26361
|
}
|
|
25493
26362
|
};
|
|
@@ -25496,22 +26365,37 @@ var kst = {
|
|
|
25496
26365
|
title: "Know Sure Thing",
|
|
25497
26366
|
shortTitle: "KST",
|
|
25498
26367
|
overlay: false,
|
|
25499
|
-
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
|
+
],
|
|
25500
26382
|
compute: (bars, inputs) => {
|
|
25501
|
-
const
|
|
25502
|
-
const
|
|
25503
|
-
|
|
25504
|
-
|
|
25505
|
-
|
|
25506
|
-
|
|
25507
|
-
|
|
25508
|
-
return {
|
|
25509
|
-
|
|
25510
|
-
|
|
25511
|
-
|
|
25512
|
-
|
|
25513
|
-
|
|
25514
|
-
|
|
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
|
+
});
|
|
25515
26399
|
}
|
|
25516
26400
|
};
|
|
25517
26401
|
var coppock = {
|
|
@@ -25520,19 +26404,25 @@ var coppock = {
|
|
|
25520
26404
|
shortTitle: "Coppock",
|
|
25521
26405
|
overlay: false,
|
|
25522
26406
|
inputs: [
|
|
25523
|
-
lengthInput(10, "wmaLength", "WMA
|
|
25524
|
-
lengthInput(14, "longRoc", "Long RoC
|
|
25525
|
-
lengthInput(11, "shortRoc", "Short RoC
|
|
25526
|
-
sourceInput(),
|
|
25527
|
-
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.")
|
|
25528
26415
|
],
|
|
25529
26416
|
compute: (bars, inputs) => {
|
|
25530
26417
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
25531
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));
|
|
25532
|
-
return {
|
|
25533
|
-
|
|
25534
|
-
|
|
25535
|
-
|
|
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
|
+
});
|
|
25536
26426
|
}
|
|
25537
26427
|
};
|
|
25538
26428
|
var dpo = {
|
|
@@ -25540,42 +26430,76 @@ var dpo = {
|
|
|
25540
26430
|
title: "Detrended Price Oscillator",
|
|
25541
26431
|
shortTitle: "DPO",
|
|
25542
26432
|
overlay: false,
|
|
25543
|
-
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
|
+
],
|
|
25544
26442
|
compute: (bars, inputs) => {
|
|
25545
26443
|
const len = num3(inputs, "length", 21);
|
|
25546
|
-
const
|
|
25547
|
-
const
|
|
25548
|
-
|
|
25549
|
-
|
|
25550
|
-
|
|
25551
|
-
|
|
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
|
+
});
|
|
25552
26456
|
}
|
|
25553
26457
|
};
|
|
26458
|
+
var ROC_PERCENT = "ROC %";
|
|
26459
|
+
var ROC_MOMENTUM = "Momentum";
|
|
25554
26460
|
var rocSpec = {
|
|
25555
26461
|
type: "rate-of-change",
|
|
25556
26462
|
title: "Rate of Change",
|
|
25557
26463
|
shortTitle: "ROC",
|
|
25558
26464
|
overlay: false,
|
|
25559
|
-
inputs: [
|
|
25560
|
-
|
|
25561
|
-
|
|
25562
|
-
|
|
25563
|
-
|
|
25564
|
-
|
|
25565
|
-
|
|
25566
|
-
|
|
25567
|
-
|
|
25568
|
-
|
|
25569
|
-
|
|
25570
|
-
|
|
25571
|
-
|
|
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
|
+
}
|
|
25572
26487
|
};
|
|
25573
26488
|
var ultimate = {
|
|
25574
26489
|
type: "ultimate-oscillator",
|
|
25575
26490
|
title: "Ultimate Oscillator",
|
|
25576
26491
|
shortTitle: "UO",
|
|
25577
26492
|
overlay: false,
|
|
25578
|
-
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
|
+
],
|
|
25579
26503
|
compute: (bars, inputs) => {
|
|
25580
26504
|
const n = bars.length;
|
|
25581
26505
|
const bp = new Array(n).fill(Number.NaN);
|
|
@@ -25583,10 +26507,9 @@ var ultimate = {
|
|
|
25583
26507
|
for (let i = 0; i < n; i++) {
|
|
25584
26508
|
const b = bars[i];
|
|
25585
26509
|
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
25586
|
-
const
|
|
25587
|
-
|
|
25588
|
-
|
|
25589
|
-
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;
|
|
25590
26513
|
}
|
|
25591
26514
|
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
25592
26515
|
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
@@ -25597,10 +26520,17 @@ var ultimate = {
|
|
|
25597
26520
|
const z = a28[i];
|
|
25598
26521
|
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
25599
26522
|
});
|
|
25600
|
-
return {
|
|
25601
|
-
|
|
25602
|
-
|
|
25603
|
-
|
|
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
|
+
});
|
|
25604
26534
|
}
|
|
25605
26535
|
};
|
|
25606
26536
|
var rvgi = {
|
|
@@ -25608,7 +26538,11 @@ var rvgi = {
|
|
|
25608
26538
|
title: "Relative Vigor Index",
|
|
25609
26539
|
shortTitle: "RVGI",
|
|
25610
26540
|
overlay: false,
|
|
25611
|
-
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
|
+
],
|
|
25612
26546
|
compute: (bars, inputs) => {
|
|
25613
26547
|
const len = num3(inputs, "length", 10);
|
|
25614
26548
|
const co = swma(sub(closes(bars), opens(bars)));
|
|
@@ -25616,31 +26550,49 @@ var rvgi = {
|
|
|
25616
26550
|
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
25617
26551
|
return {
|
|
25618
26552
|
plots: [
|
|
25619
|
-
{ key: "rvgi", title: "RVGI", values: line, color:
|
|
25620
|
-
{ 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) }
|
|
25621
26555
|
],
|
|
25622
|
-
levels:
|
|
26556
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed", title: "Zero" }]
|
|
25623
26557
|
};
|
|
25624
26558
|
}
|
|
25625
26559
|
};
|
|
26560
|
+
var RVI_EMA = "EMA";
|
|
26561
|
+
var RVI_RMA = "Wilder (RMA)";
|
|
25626
26562
|
var relVolatility = {
|
|
25627
26563
|
type: "relative-volatility-index",
|
|
25628
26564
|
title: "Relative Volatility Index",
|
|
25629
26565
|
shortTitle: "RVI",
|
|
25630
26566
|
overlay: false,
|
|
25631
|
-
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
|
+
],
|
|
25632
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);
|
|
25633
26582
|
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
25634
|
-
const
|
|
25635
|
-
const
|
|
25636
|
-
|
|
25637
|
-
|
|
25638
|
-
|
|
25639
|
-
|
|
25640
|
-
|
|
25641
|
-
|
|
25642
|
-
|
|
25643
|
-
|
|
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
|
+
});
|
|
25644
26596
|
}
|
|
25645
26597
|
};
|
|
25646
26598
|
var bop = {
|
|
@@ -25648,28 +26600,43 @@ var bop = {
|
|
|
25648
26600
|
title: "Balance of Power",
|
|
25649
26601
|
shortTitle: "BOP",
|
|
25650
26602
|
overlay: false,
|
|
25651
|
-
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
|
+
],
|
|
25652
26610
|
compute: (bars, inputs) => {
|
|
25653
|
-
const
|
|
25654
|
-
|
|
25655
|
-
|
|
25656
|
-
|
|
25657
|
-
|
|
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
|
+
});
|
|
25658
26621
|
}
|
|
25659
26622
|
};
|
|
25660
26623
|
var elderRay = {
|
|
25661
26624
|
type: "elder-ray",
|
|
25662
26625
|
title: "Elder Ray",
|
|
25663
26626
|
overlay: false,
|
|
25664
|
-
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
|
+
],
|
|
25665
26632
|
compute: (bars, inputs) => {
|
|
25666
26633
|
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
25667
26634
|
return {
|
|
25668
26635
|
plots: [
|
|
25669
|
-
{ key: "bull", title: "Bull
|
|
25670
|
-
{ 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 }
|
|
25671
26638
|
],
|
|
25672
|
-
levels:
|
|
26639
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 30), lineStyle: "dashed", title: "Zero Line" }]
|
|
25673
26640
|
};
|
|
25674
26641
|
}
|
|
25675
26642
|
};
|
|
@@ -25678,31 +26645,66 @@ var ttmSqueeze = {
|
|
|
25678
26645
|
title: "TTM Squeeze",
|
|
25679
26646
|
overlay: false,
|
|
25680
26647
|
inputs: [
|
|
25681
|
-
lengthInput(20),
|
|
25682
|
-
|
|
25683
|
-
|
|
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.")
|
|
25684
26657
|
],
|
|
25685
26658
|
compute: (bars, inputs) => {
|
|
25686
26659
|
const len = num3(inputs, "length", 20);
|
|
25687
|
-
const
|
|
25688
|
-
const basis = sma(
|
|
25689
|
-
const dev = map(stdev(
|
|
25690
|
-
const kcRange = map(
|
|
25691
|
-
const
|
|
25692
|
-
const anchor = zip(
|
|
25693
|
-
const mom = linreg(sub(
|
|
25694
|
-
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) => {
|
|
25695
26668
|
const d = dev[i];
|
|
25696
26669
|
const r = kcRange[i];
|
|
25697
|
-
|
|
26670
|
+
if (!Number.isFinite(b) || !Number.isFinite(d) || !Number.isFinite(r)) return null;
|
|
26671
|
+
return d < r;
|
|
25698
26672
|
});
|
|
25699
|
-
const
|
|
25700
|
-
|
|
25701
|
-
|
|
25702
|
-
|
|
25703
|
-
|
|
25704
|
-
|
|
25705
|
-
|
|
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 };
|
|
25706
26708
|
}
|
|
25707
26709
|
};
|
|
25708
26710
|
var oscillatorSpecs = [
|
|
@@ -25714,7 +26716,7 @@ var oscillatorSpecs = [
|
|
|
25714
26716
|
awesome,
|
|
25715
26717
|
cci,
|
|
25716
26718
|
williamsR,
|
|
25717
|
-
|
|
26719
|
+
cmoSpec,
|
|
25718
26720
|
connorsRsi,
|
|
25719
26721
|
fisher,
|
|
25720
26722
|
trix,
|
|
@@ -25738,15 +26740,19 @@ var aroon = {
|
|
|
25738
26740
|
type: "aroon",
|
|
25739
26741
|
title: "Aroon",
|
|
25740
26742
|
overlay: false,
|
|
25741
|
-
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
|
+
],
|
|
25742
26748
|
compute: (bars, inputs) => {
|
|
25743
26749
|
const len = num3(inputs, "length", 14);
|
|
25744
|
-
const up = map(barsSinceHighest(highs(bars), len
|
|
25745
|
-
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);
|
|
25746
26752
|
return {
|
|
25747
26753
|
plots: [
|
|
25748
|
-
{ key: "up", title: "Aroon
|
|
25749
|
-
{ 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) }
|
|
25750
26756
|
]
|
|
25751
26757
|
};
|
|
25752
26758
|
}
|
|
@@ -25756,10 +26762,16 @@ var adx = {
|
|
|
25756
26762
|
title: "Average Directional Index",
|
|
25757
26763
|
shortTitle: "ADX",
|
|
25758
26764
|
overlay: false,
|
|
25759
|
-
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
|
+
],
|
|
25760
26773
|
compute: (bars, inputs) => {
|
|
25761
26774
|
const diLen = num3(inputs, "diLength", 14);
|
|
25762
|
-
const adxLen = num3(inputs, "adxLength", 14);
|
|
25763
26775
|
const n = bars.length;
|
|
25764
26776
|
const plusDm = new Array(n).fill(Number.NaN);
|
|
25765
26777
|
const minusDm = new Array(n).fill(Number.NaN);
|
|
@@ -25773,13 +26785,23 @@ var adx = {
|
|
|
25773
26785
|
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
25774
26786
|
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
25775
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
|
+
});
|
|
25776
26798
|
return {
|
|
25777
26799
|
plots: [
|
|
25778
|
-
{ key: "
|
|
25779
|
-
{ key: "
|
|
25780
|
-
{ 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 }
|
|
25781
26803
|
],
|
|
25782
|
-
levels: [{ key: "
|
|
26804
|
+
levels: [{ key: "keyLevel", price: keyLevel, color: NEUTRAL, lineStyle: "dashed", title: "Key Level" }]
|
|
25783
26805
|
};
|
|
25784
26806
|
}
|
|
25785
26807
|
};
|
|
@@ -25788,7 +26810,12 @@ var vortex = {
|
|
|
25788
26810
|
title: "Vortex Indicator",
|
|
25789
26811
|
shortTitle: "VI",
|
|
25790
26812
|
overlay: false,
|
|
25791
|
-
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
|
+
],
|
|
25792
26819
|
compute: (bars, inputs) => {
|
|
25793
26820
|
const len = num3(inputs, "length", 14);
|
|
25794
26821
|
const n = bars.length;
|
|
@@ -25801,25 +26828,48 @@ var vortex = {
|
|
|
25801
26828
|
const trSum = sum(trueRange(bars), len);
|
|
25802
26829
|
return {
|
|
25803
26830
|
plots: [
|
|
25804
|
-
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color:
|
|
25805
|
-
{ 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) }
|
|
25806
26833
|
],
|
|
25807
|
-
levels: [{ key: "
|
|
26834
|
+
levels: [{ key: "baseline", price: 1, color: str3(inputs, "baselineColor", NEUTRAL), lineStyle: "dotted", title: "Baseline" }]
|
|
25808
26835
|
};
|
|
25809
26836
|
}
|
|
25810
26837
|
};
|
|
25811
26838
|
var trendSpecs = [aroon, adx, vortex];
|
|
25812
26839
|
|
|
25813
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
|
+
}
|
|
25814
26854
|
var atrSpec = {
|
|
25815
26855
|
type: "average-true-range",
|
|
25816
26856
|
title: "Average True Range",
|
|
25817
26857
|
shortTitle: "ATR",
|
|
25818
26858
|
overlay: false,
|
|
25819
|
-
inputs: [
|
|
25820
|
-
|
|
25821
|
-
|
|
25822
|
-
|
|
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
|
+
}
|
|
25823
26873
|
};
|
|
25824
26874
|
var historicalVolatility = {
|
|
25825
26875
|
type: "historical-volatility",
|
|
@@ -25827,14 +26877,23 @@ var historicalVolatility = {
|
|
|
25827
26877
|
shortTitle: "HV",
|
|
25828
26878
|
overlay: false,
|
|
25829
26879
|
inputs: [
|
|
25830
|
-
lengthInput(10),
|
|
25831
|
-
|
|
25832
|
-
|
|
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.")
|
|
25833
26884
|
],
|
|
25834
26885
|
compute: (bars, inputs) => {
|
|
25835
|
-
const
|
|
25836
|
-
const
|
|
25837
|
-
|
|
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);
|
|
25838
26897
|
}
|
|
25839
26898
|
};
|
|
25840
26899
|
var chaikinVolatility = {
|
|
@@ -25842,13 +26901,26 @@ var chaikinVolatility = {
|
|
|
25842
26901
|
title: "Chaikin Volatility",
|
|
25843
26902
|
shortTitle: "CHV",
|
|
25844
26903
|
overlay: false,
|
|
25845
|
-
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
|
+
],
|
|
25846
26910
|
compute: (bars, inputs) => {
|
|
25847
26911
|
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
25848
|
-
|
|
25849
|
-
|
|
25850
|
-
|
|
25851
|
-
|
|
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
|
+
});
|
|
25852
26924
|
}
|
|
25853
26925
|
};
|
|
25854
26926
|
var massIndex = {
|
|
@@ -25856,14 +26928,42 @@ var massIndex = {
|
|
|
25856
26928
|
title: "Mass Index",
|
|
25857
26929
|
shortTitle: "MI",
|
|
25858
26930
|
overlay: false,
|
|
25859
|
-
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
|
+
],
|
|
25860
26940
|
compute: (bars, inputs) => {
|
|
25861
|
-
const
|
|
25862
|
-
const single = ema(
|
|
25863
|
-
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
|
+
});
|
|
25864
26956
|
return {
|
|
25865
|
-
plots: [
|
|
25866
|
-
|
|
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
|
+
]
|
|
25867
26967
|
};
|
|
25868
26968
|
}
|
|
25869
26969
|
};
|
|
@@ -25872,30 +26972,83 @@ var standardDeviation = {
|
|
|
25872
26972
|
title: "Standard Deviation",
|
|
25873
26973
|
shortTitle: "StdDev",
|
|
25874
26974
|
overlay: false,
|
|
25875
|
-
inputs: [
|
|
25876
|
-
|
|
25877
|
-
|
|
25878
|
-
|
|
25879
|
-
|
|
25880
|
-
|
|
25881
|
-
|
|
25882
|
-
|
|
25883
|
-
|
|
25884
|
-
|
|
25885
|
-
|
|
25886
|
-
})
|
|
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
|
+
}
|
|
25887
26986
|
};
|
|
25888
26987
|
var ulcerIndex = {
|
|
25889
26988
|
type: "ulcer-index",
|
|
25890
26989
|
title: "Ulcer Index",
|
|
25891
26990
|
overlay: false,
|
|
25892
|
-
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
|
+
],
|
|
25893
27002
|
compute: (bars, inputs) => {
|
|
25894
27003
|
const len = num3(inputs, "length", 14);
|
|
25895
|
-
const
|
|
25896
|
-
const
|
|
25897
|
-
const values =
|
|
25898
|
-
|
|
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
|
+
};
|
|
25899
27052
|
}
|
|
25900
27053
|
};
|
|
25901
27054
|
var choppiness = {
|
|
@@ -25903,19 +27056,33 @@ var choppiness = {
|
|
|
25903
27056
|
title: "Choppiness Index",
|
|
25904
27057
|
shortTitle: "CHOP",
|
|
25905
27058
|
overlay: false,
|
|
25906
|
-
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
|
+
],
|
|
25907
27068
|
compute: (bars, inputs) => {
|
|
25908
27069
|
const len = num3(inputs, "length", 14);
|
|
25909
27070
|
const trSum = sum(trueRange(bars), len);
|
|
25910
27071
|
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
25911
27072
|
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
25912
|
-
return {
|
|
25913
|
-
|
|
25914
|
-
|
|
25915
|
-
|
|
25916
|
-
|
|
25917
|
-
|
|
25918
|
-
|
|
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
|
+
});
|
|
25919
27086
|
}
|
|
25920
27087
|
};
|
|
25921
27088
|
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
@@ -25928,17 +27095,85 @@ function moneyFlowVolume(bars) {
|
|
|
25928
27095
|
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
25929
27096
|
});
|
|
25930
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
|
+
}
|
|
25931
27153
|
var obv = {
|
|
25932
27154
|
type: "on-balance-volume",
|
|
25933
27155
|
title: "On Balance Volume",
|
|
25934
27156
|
shortTitle: "OBV",
|
|
25935
27157
|
overlay: false,
|
|
25936
|
-
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
|
+
],
|
|
25937
27165
|
compute: (bars, inputs) => {
|
|
25938
27166
|
const vol = volumes(bars);
|
|
25939
27167
|
const d = change(closes(bars));
|
|
25940
|
-
const
|
|
25941
|
-
|
|
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 };
|
|
25942
27177
|
}
|
|
25943
27178
|
};
|
|
25944
27179
|
var accDist = {
|
|
@@ -25946,24 +27181,50 @@ var accDist = {
|
|
|
25946
27181
|
title: "Accumulation / Distribution",
|
|
25947
27182
|
shortTitle: "A/D",
|
|
25948
27183
|
overlay: false,
|
|
25949
|
-
inputs: [
|
|
25950
|
-
|
|
25951
|
-
|
|
25952
|
-
|
|
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
|
+
}
|
|
25953
27205
|
};
|
|
25954
27206
|
var cmf = {
|
|
25955
27207
|
type: "chaikin-money-flow",
|
|
25956
27208
|
title: "Chaikin Money Flow",
|
|
25957
27209
|
shortTitle: "CMF",
|
|
25958
27210
|
overlay: false,
|
|
25959
|
-
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
|
+
],
|
|
25960
27218
|
compute: (bars, inputs) => {
|
|
25961
27219
|
const len = num3(inputs, "length", 20);
|
|
25962
27220
|
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
25963
|
-
return {
|
|
25964
|
-
|
|
25965
|
-
|
|
25966
|
-
|
|
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
|
+
});
|
|
25967
27228
|
}
|
|
25968
27229
|
};
|
|
25969
27230
|
var chaikinOscillator = {
|
|
@@ -25971,14 +27232,23 @@ var chaikinOscillator = {
|
|
|
25971
27232
|
title: "Chaikin Oscillator",
|
|
25972
27233
|
shortTitle: "Chaikin Osc",
|
|
25973
27234
|
overlay: false,
|
|
25974
|
-
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
|
+
],
|
|
25975
27242
|
compute: (bars, inputs) => {
|
|
25976
27243
|
const ad = cumSum(moneyFlowVolume(bars));
|
|
25977
27244
|
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
25978
|
-
return {
|
|
25979
|
-
|
|
25980
|
-
|
|
25981
|
-
|
|
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
|
+
});
|
|
25982
27252
|
}
|
|
25983
27253
|
};
|
|
25984
27254
|
var eom = {
|
|
@@ -25986,20 +27256,28 @@ var eom = {
|
|
|
25986
27256
|
title: "Ease of Movement",
|
|
25987
27257
|
shortTitle: "EOM",
|
|
25988
27258
|
overlay: false,
|
|
25989
|
-
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
|
+
],
|
|
25990
27266
|
compute: (bars, inputs) => {
|
|
25991
27267
|
const div = num3(inputs, "divisor", 1e4);
|
|
25992
|
-
const
|
|
25993
|
-
const move = change(hl2);
|
|
27268
|
+
const move = change(sourceValues(bars, "HL2"));
|
|
25994
27269
|
const raw = bars.map((b, i) => {
|
|
25995
27270
|
const m = move[i];
|
|
25996
|
-
if (b.volume == null || !Number.isFinite(b.volume) ||
|
|
25997
|
-
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
|
|
25998
27280
|
});
|
|
25999
|
-
return {
|
|
26000
|
-
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
26001
|
-
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
26002
|
-
};
|
|
26003
27281
|
}
|
|
26004
27282
|
};
|
|
26005
27283
|
var forceIndex = {
|
|
@@ -26007,13 +27285,24 @@ var forceIndex = {
|
|
|
26007
27285
|
title: "Force Index",
|
|
26008
27286
|
shortTitle: "FI",
|
|
26009
27287
|
overlay: false,
|
|
26010
|
-
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
|
+
],
|
|
26011
27293
|
compute: (bars, inputs) => {
|
|
26012
27294
|
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
26013
|
-
|
|
26014
|
-
|
|
26015
|
-
|
|
26016
|
-
|
|
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
|
+
});
|
|
26017
27306
|
}
|
|
26018
27307
|
};
|
|
26019
27308
|
var klinger = {
|
|
@@ -26021,19 +27310,44 @@ var klinger = {
|
|
|
26021
27310
|
title: "Klinger Oscillator",
|
|
26022
27311
|
shortTitle: "KVO",
|
|
26023
27312
|
overlay: false,
|
|
26024
|
-
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
|
+
],
|
|
26025
27321
|
compute: (bars, inputs) => {
|
|
26026
|
-
const
|
|
26027
|
-
const
|
|
26028
|
-
|
|
26029
|
-
|
|
26030
|
-
|
|
26031
|
-
|
|
26032
|
-
|
|
26033
|
-
|
|
26034
|
-
|
|
26035
|
-
|
|
26036
|
-
|
|
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
|
+
});
|
|
26037
27351
|
}
|
|
26038
27352
|
};
|
|
26039
27353
|
var mfi = {
|
|
@@ -26041,7 +27355,15 @@ var mfi = {
|
|
|
26041
27355
|
title: "Money Flow Index",
|
|
26042
27356
|
shortTitle: "MFI",
|
|
26043
27357
|
overlay: false,
|
|
26044
|
-
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
|
+
],
|
|
26045
27367
|
compute: (bars, inputs) => {
|
|
26046
27368
|
const len = num3(inputs, "length", 14);
|
|
26047
27369
|
const tp = sourceValues(bars, "HLC3");
|
|
@@ -26049,48 +27371,57 @@ var mfi = {
|
|
|
26049
27371
|
const d = change(tp);
|
|
26050
27372
|
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
26051
27373
|
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
26052
|
-
const values = zip(pos, neg, (p,
|
|
26053
|
-
return {
|
|
26054
|
-
|
|
26055
|
-
|
|
26056
|
-
|
|
26057
|
-
|
|
26058
|
-
|
|
26059
|
-
|
|
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
|
+
});
|
|
26060
27387
|
}
|
|
26061
27388
|
};
|
|
26062
|
-
function
|
|
26063
|
-
|
|
26064
|
-
|
|
26065
|
-
|
|
26066
|
-
|
|
26067
|
-
|
|
26068
|
-
|
|
26069
|
-
|
|
26070
|
-
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
26071
|
-
if (started) out[i] = index;
|
|
26072
|
-
continue;
|
|
26073
|
-
}
|
|
26074
|
-
started = true;
|
|
26075
|
-
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
26076
|
-
out[i] = index;
|
|
26077
|
-
}
|
|
26078
|
-
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
|
+
];
|
|
26079
27397
|
}
|
|
27398
|
+
var NVI_POSITION = "Position vs Average";
|
|
27399
|
+
var NVI_SINGLE = "Single Color";
|
|
26080
27400
|
var nvi = {
|
|
26081
27401
|
type: "negative-volume-index",
|
|
26082
27402
|
title: "Negative Volume Index",
|
|
26083
27403
|
shortTitle: "Negative Volume Index",
|
|
26084
27404
|
overlay: false,
|
|
26085
|
-
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
|
+
],
|
|
26086
27412
|
compute: (bars, inputs) => {
|
|
26087
|
-
const
|
|
26088
|
-
|
|
26089
|
-
|
|
26090
|
-
|
|
26091
|
-
|
|
26092
|
-
|
|
26093
|
-
|
|
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
|
+
);
|
|
26094
27425
|
}
|
|
26095
27426
|
};
|
|
26096
27427
|
var pvi = {
|
|
@@ -26098,15 +27429,26 @@ var pvi = {
|
|
|
26098
27429
|
title: "Positive Volume Index",
|
|
26099
27430
|
shortTitle: "Positive Volume Index",
|
|
26100
27431
|
overlay: false,
|
|
26101
|
-
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
|
+
],
|
|
26102
27439
|
compute: (bars, inputs) => {
|
|
26103
|
-
const
|
|
26104
|
-
|
|
26105
|
-
|
|
26106
|
-
|
|
26107
|
-
|
|
26108
|
-
|
|
26109
|
-
|
|
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
|
+
);
|
|
26110
27452
|
}
|
|
26111
27453
|
};
|
|
26112
27454
|
var pvt = {
|
|
@@ -26114,7 +27456,15 @@ var pvt = {
|
|
|
26114
27456
|
title: "Price Volume Trend",
|
|
26115
27457
|
shortTitle: "PVT",
|
|
26116
27458
|
overlay: false,
|
|
26117
|
-
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
|
+
],
|
|
26118
27468
|
compute: (bars, inputs) => {
|
|
26119
27469
|
const c = closes(bars);
|
|
26120
27470
|
const raw = bars.map((b, i) => {
|
|
@@ -26122,7 +27472,17 @@ var pvt = {
|
|
|
26122
27472
|
const pc = c[i - 1];
|
|
26123
27473
|
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
26124
27474
|
});
|
|
26125
|
-
|
|
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 };
|
|
26126
27486
|
}
|
|
26127
27487
|
};
|
|
26128
27488
|
var volumeOscillator = {
|
|
@@ -26130,39 +27490,75 @@ var volumeOscillator = {
|
|
|
26130
27490
|
title: "Volume Oscillator",
|
|
26131
27491
|
shortTitle: "Vol Osc",
|
|
26132
27492
|
overlay: false,
|
|
26133
|
-
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
|
+
],
|
|
26134
27499
|
compute: (bars, inputs) => {
|
|
26135
27500
|
const vol = volumes(bars);
|
|
26136
27501
|
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
26137
27502
|
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
26138
|
-
const values = zip(fast, slow, (f, s) => s === 0 ?
|
|
26139
|
-
|
|
26140
|
-
|
|
26141
|
-
|
|
26142
|
-
|
|
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
|
+
});
|
|
26143
27513
|
}
|
|
26144
27514
|
};
|
|
27515
|
+
var OSCILLATOR = "Oscillator";
|
|
26145
27516
|
var pvo = {
|
|
26146
27517
|
type: "pvo",
|
|
26147
27518
|
title: "Percentage Volume Oscillator",
|
|
26148
27519
|
shortTitle: "PVO",
|
|
26149
27520
|
overlay: false,
|
|
26150
|
-
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
|
+
],
|
|
26151
27532
|
compute: (bars, inputs) => {
|
|
26152
27533
|
const vol = volumes(bars);
|
|
26153
27534
|
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
26154
27535
|
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
26155
|
-
const
|
|
26156
|
-
const signal = ema(
|
|
26157
|
-
const
|
|
26158
|
-
|
|
26159
|
-
|
|
26160
|
-
|
|
26161
|
-
|
|
26162
|
-
|
|
26163
|
-
|
|
26164
|
-
|
|
26165
|
-
|
|
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
|
+
});
|
|
26166
27562
|
}
|
|
26167
27563
|
};
|
|
26168
27564
|
var vfi = {
|
|
@@ -26171,55 +27567,86 @@ var vfi = {
|
|
|
26171
27567
|
shortTitle: "Volume Flow Indicator",
|
|
26172
27568
|
overlay: false,
|
|
26173
27569
|
inputs: [
|
|
26174
|
-
lengthInput(130),
|
|
26175
|
-
|
|
26176
|
-
|
|
26177
|
-
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.")
|
|
26178
27582
|
],
|
|
26179
27583
|
compute: (bars, inputs) => {
|
|
26180
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));
|
|
26181
27588
|
const coef = num3(inputs, "coef", 0.2);
|
|
26182
27589
|
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
26183
|
-
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
26184
|
-
const inter = change(typical);
|
|
26185
|
-
const vInter = stdev(inter, 30);
|
|
26186
27590
|
const vol = volumes(bars);
|
|
26187
|
-
const
|
|
26188
|
-
const
|
|
26189
|
-
const
|
|
26190
|
-
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];
|
|
26191
27596
|
const v = vol[i];
|
|
26192
|
-
|
|
26193
|
-
const
|
|
26194
|
-
|
|
26195
|
-
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;
|
|
26196
27601
|
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
26197
27602
|
});
|
|
26198
|
-
const
|
|
26199
|
-
|
|
26200
|
-
|
|
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
|
|
26201
27615
|
});
|
|
26202
|
-
const flowSum = sum(raw, len);
|
|
26203
|
-
const line = zip(flowSum, values, (f, a) => f / a);
|
|
26204
|
-
return {
|
|
26205
|
-
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
26206
|
-
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
26207
|
-
};
|
|
26208
27616
|
}
|
|
26209
27617
|
};
|
|
27618
|
+
var II_OSCILLATOR = "Normalized Oscillator (II%)";
|
|
27619
|
+
var II_CUMULATIVE = "Cumulative Line";
|
|
26210
27620
|
var intradayIntensity = {
|
|
26211
27621
|
type: "intraday-intensity",
|
|
26212
27622
|
title: "Intraday Intensity",
|
|
26213
27623
|
shortTitle: "Intraday Intensity",
|
|
26214
27624
|
overlay: false,
|
|
26215
|
-
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
|
+
],
|
|
26216
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
|
+
}
|
|
26217
27640
|
const len = num3(inputs, "length", 21);
|
|
26218
|
-
const values = zip(sum(
|
|
26219
|
-
return {
|
|
26220
|
-
|
|
26221
|
-
|
|
26222
|
-
|
|
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
|
+
});
|
|
26223
27650
|
}
|
|
26224
27651
|
};
|
|
26225
27652
|
var volumeSpecs = [
|
|
@@ -26252,15 +27679,24 @@ function periodKey(anchor, time) {
|
|
|
26252
27679
|
}
|
|
26253
27680
|
return Math.floor(time / DAY_MS2);
|
|
26254
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
|
+
}
|
|
26255
27689
|
var parabolicSar = {
|
|
26256
27690
|
type: "parabolic-sar",
|
|
26257
27691
|
title: "Parabolic SAR",
|
|
26258
27692
|
shortTitle: "SAR",
|
|
26259
27693
|
overlay: true,
|
|
26260
27694
|
inputs: [
|
|
26261
|
-
|
|
26262
|
-
|
|
26263
|
-
|
|
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.")
|
|
26264
27700
|
],
|
|
26265
27701
|
compute: (bars, inputs) => {
|
|
26266
27702
|
const start = num3(inputs, "start", 0.02);
|
|
@@ -26268,7 +27704,6 @@ var parabolicSar = {
|
|
|
26268
27704
|
const max = num3(inputs, "maximum", 0.2);
|
|
26269
27705
|
const n = bars.length;
|
|
26270
27706
|
const values = new Array(n).fill(Number.NaN);
|
|
26271
|
-
const colors = new Array(n).fill(null);
|
|
26272
27707
|
if (n >= 2) {
|
|
26273
27708
|
let long = bars[1].close >= bars[0].close;
|
|
26274
27709
|
let sar = long ? bars[0].low : bars[0].high;
|
|
@@ -26293,18 +27728,21 @@ var parabolicSar = {
|
|
|
26293
27728
|
af = Math.min(max, af + inc);
|
|
26294
27729
|
}
|
|
26295
27730
|
values[i] = sar;
|
|
26296
|
-
colors[i] = long ? BULLISH : BEARISH;
|
|
26297
27731
|
}
|
|
26298
27732
|
}
|
|
26299
|
-
|
|
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 }] };
|
|
26300
27737
|
}
|
|
26301
27738
|
};
|
|
26302
|
-
var
|
|
26303
|
-
var
|
|
27739
|
+
var VWAP_ANCHORS = ["Session", "Week", "Month", "Quarter", "Year"];
|
|
27740
|
+
var BANDS_STDEV = "Standard Deviation";
|
|
27741
|
+
var BANDS_PERCENT = "Percentage";
|
|
26304
27742
|
var VWAP_BANDS = [
|
|
26305
|
-
{
|
|
26306
|
-
{
|
|
26307
|
-
{
|
|
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 }
|
|
26308
27746
|
];
|
|
26309
27747
|
var vwap = {
|
|
26310
27748
|
type: "vwap",
|
|
@@ -26312,39 +27750,36 @@ var vwap = {
|
|
|
26312
27750
|
shortTitle: "VWAP",
|
|
26313
27751
|
overlay: true,
|
|
26314
27752
|
inputs: [
|
|
26315
|
-
|
|
26316
|
-
sourceInput("HLC3"),
|
|
26317
|
-
|
|
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."),
|
|
26318
27756
|
...VWAP_BANDS.flatMap((b, i) => [
|
|
26319
|
-
|
|
26320
|
-
|
|
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.")
|
|
26321
27759
|
]),
|
|
26322
|
-
|
|
26323
|
-
|
|
26324
|
-
|
|
26325
|
-
|
|
26326
|
-
|
|
26327
|
-
{ ...colorInput(withAlpha2(b.defColor), "", b.fillColor), inline: b.color, group: VWAP_STYLE }
|
|
26328
|
-
])
|
|
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.")
|
|
26329
27765
|
],
|
|
26330
27766
|
compute: (bars, inputs) => {
|
|
26331
|
-
const anchor = str3(inputs, "anchor", "
|
|
27767
|
+
const anchor = str3(inputs, "anchor", "Session");
|
|
26332
27768
|
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
26333
27769
|
const n = bars.length;
|
|
27770
|
+
const hidden = bool(inputs, "hideDwm", true) && barInterval(bars) >= DAY_MS2;
|
|
26334
27771
|
const values = new Array(n).fill(Number.NaN);
|
|
26335
|
-
const bands = VWAP_BANDS.filter((b) => bool(inputs, b.
|
|
27772
|
+
const bands = VWAP_BANDS.filter((b) => bool(inputs, b.show, b.defOn)).map((b) => ({
|
|
26336
27773
|
mult: Math.max(0, num3(inputs, b.mult, b.defMult)),
|
|
26337
|
-
color: str3(inputs, b.color, b.defColor),
|
|
26338
|
-
fill: bool(inputs, b.fill, true),
|
|
26339
|
-
fillColor: str3(inputs, b.fillColor, withAlpha2(b.defColor)),
|
|
26340
27774
|
up: new Array(n).fill(Number.NaN),
|
|
26341
27775
|
down: new Array(n).fill(Number.NaN)
|
|
26342
27776
|
}));
|
|
27777
|
+
const percentMode = str3(inputs, "bandsMode", BANDS_STDEV) === BANDS_PERCENT;
|
|
26343
27778
|
let period = Number.NaN;
|
|
26344
27779
|
let cumPV = 0;
|
|
26345
27780
|
let cumPV2 = 0;
|
|
26346
27781
|
let cumV = 0;
|
|
26347
|
-
for (let i = 0; i < n; i++) {
|
|
27782
|
+
for (let i = 0; i < n && !hidden; i++) {
|
|
26348
27783
|
const b = bars[i];
|
|
26349
27784
|
const key = periodKey(anchor, b.time);
|
|
26350
27785
|
if (key !== period) {
|
|
@@ -26362,38 +27797,58 @@ var vwap = {
|
|
|
26362
27797
|
}
|
|
26363
27798
|
if (cumV <= 0) continue;
|
|
26364
27799
|
const mean = cumPV / cumV;
|
|
26365
|
-
const
|
|
27800
|
+
const offset = percentMode ? mean / 100 : Math.sqrt(Math.max(0, cumPV2 / cumV - mean * mean));
|
|
26366
27801
|
values[i] = mean;
|
|
26367
27802
|
for (const band of bands) {
|
|
26368
|
-
band.up[i] = mean + band.mult *
|
|
26369
|
-
band.down[i] = mean - band.mult *
|
|
27803
|
+
band.up[i] = mean + band.mult * offset;
|
|
27804
|
+
band.down[i] = mean - band.mult * offset;
|
|
26370
27805
|
}
|
|
26371
27806
|
}
|
|
26372
|
-
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
|
+
];
|
|
26373
27813
|
const fills = [];
|
|
26374
27814
|
bands.forEach((band, i) => {
|
|
26375
27815
|
plots.push(
|
|
26376
|
-
{ key: `up${i}`, title: `Upper
|
|
26377
|
-
{ 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) }
|
|
26378
27818
|
);
|
|
26379
|
-
if (
|
|
27819
|
+
if (bool(inputs, "fill", true)) fills.push({ key: `band${i}`, from: `up${i}`, to: `down${i}`, color: fillColor });
|
|
26380
27820
|
});
|
|
26381
27821
|
return { plots, bands: fills };
|
|
26382
27822
|
}
|
|
26383
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";
|
|
26384
27829
|
var pivotPoints = {
|
|
26385
27830
|
type: "pivot-points",
|
|
26386
|
-
title: "Pivot Points",
|
|
27831
|
+
title: "Pivot Points Standard",
|
|
26387
27832
|
shortTitle: "Pivots",
|
|
26388
27833
|
overlay: true,
|
|
26389
|
-
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
|
+
],
|
|
26390
27841
|
compute: (bars, inputs) => {
|
|
26391
|
-
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);
|
|
26392
27847
|
const aggs = /* @__PURE__ */ new Map();
|
|
26393
27848
|
for (const b of bars) {
|
|
26394
27849
|
const key = periodKey(anchor, b.time);
|
|
26395
27850
|
const a = aggs.get(key);
|
|
26396
|
-
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 });
|
|
26397
27852
|
else {
|
|
26398
27853
|
a.high = Math.max(a.high, b.high);
|
|
26399
27854
|
a.low = Math.min(a.low, b.low);
|
|
@@ -26402,31 +27857,61 @@ var pivotPoints = {
|
|
|
26402
27857
|
}
|
|
26403
27858
|
const n = bars.length;
|
|
26404
27859
|
const mk = () => new Array(n).fill(Number.NaN);
|
|
26405
|
-
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;
|
|
26406
27862
|
for (let i = 0; i < n; i++) {
|
|
26407
|
-
const
|
|
26408
|
-
|
|
26409
|
-
|
|
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;
|
|
26410
27873
|
levels.p[i] = p;
|
|
26411
|
-
|
|
26412
|
-
|
|
26413
|
-
|
|
26414
|
-
|
|
26415
|
-
|
|
26416
|
-
|
|
26417
|
-
|
|
26418
|
-
|
|
26419
|
-
|
|
26420
|
-
|
|
26421
|
-
|
|
26422
|
-
|
|
26423
|
-
|
|
26424
|
-
|
|
26425
|
-
|
|
26426
|
-
|
|
26427
|
-
|
|
26428
|
-
|
|
26429
|
-
|
|
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 };
|
|
26430
27915
|
}
|
|
26431
27916
|
};
|
|
26432
27917
|
var fiftyTwoWeek = {
|
|
@@ -26434,15 +27919,25 @@ var fiftyTwoWeek = {
|
|
|
26434
27919
|
title: "52 Week High/Low",
|
|
26435
27920
|
shortTitle: "52W H/L",
|
|
26436
27921
|
overlay: true,
|
|
26437
|
-
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
|
+
],
|
|
26438
27929
|
compute: (bars, inputs) => {
|
|
26439
27930
|
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
26440
27931
|
const n = bars.length;
|
|
26441
27932
|
const hi = new Array(n).fill(Number.NaN);
|
|
26442
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);
|
|
26443
27936
|
const maxIdx = [];
|
|
26444
27937
|
const minIdx = [];
|
|
26445
27938
|
let from = 0;
|
|
27939
|
+
let runHigh = -Infinity;
|
|
27940
|
+
let runLow = Infinity;
|
|
26446
27941
|
for (let i = 0; i < n; i++) {
|
|
26447
27942
|
const b = bars[i];
|
|
26448
27943
|
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
@@ -26454,13 +27949,20 @@ var fiftyTwoWeek = {
|
|
|
26454
27949
|
while (minIdx[0] < from) minIdx.shift();
|
|
26455
27950
|
hi[i] = bars[maxIdx[0]].high;
|
|
26456
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 });
|
|
26457
27964
|
}
|
|
26458
|
-
return {
|
|
26459
|
-
plots: [
|
|
26460
|
-
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
26461
|
-
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
26462
|
-
]
|
|
26463
|
-
};
|
|
27965
|
+
return { plots };
|
|
26464
27966
|
}
|
|
26465
27967
|
};
|
|
26466
27968
|
var zigzag = {
|
|
@@ -26468,9 +27970,11 @@ var zigzag = {
|
|
|
26468
27970
|
title: "ZigZag",
|
|
26469
27971
|
overlay: true,
|
|
26470
27972
|
inputs: [
|
|
26471
|
-
|
|
26472
|
-
{
|
|
26473
|
-
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." }
|
|
26474
27978
|
],
|
|
26475
27979
|
compute: (bars, inputs) => {
|
|
26476
27980
|
const dev = num3(inputs, "deviation", 5) / 100;
|
|
@@ -26483,14 +27987,14 @@ var zigzag = {
|
|
|
26483
27987
|
const b = bars[i];
|
|
26484
27988
|
if (up) {
|
|
26485
27989
|
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
26486
|
-
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) {
|
|
26487
27991
|
pivots.push(ext);
|
|
26488
27992
|
up = false;
|
|
26489
27993
|
ext = { i, price: b.low };
|
|
26490
27994
|
}
|
|
26491
27995
|
} else if (b.low <= ext.price) {
|
|
26492
27996
|
ext = { i, price: b.low };
|
|
26493
|
-
} 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) {
|
|
26494
27998
|
pivots.push(ext);
|
|
26495
27999
|
up = true;
|
|
26496
28000
|
ext = { i, price: b.high };
|
|
@@ -26498,22 +28002,29 @@ var zigzag = {
|
|
|
26498
28002
|
}
|
|
26499
28003
|
pivots.push(ext);
|
|
26500
28004
|
}
|
|
26501
|
-
|
|
26502
|
-
|
|
26503
|
-
|
|
26504
|
-
|
|
26505
|
-
|
|
26506
|
-
|
|
26507
|
-
|
|
26508
|
-
|
|
26509
|
-
|
|
26510
|
-
|
|
26511
|
-
|
|
26512
|
-
|
|
26513
|
-
|
|
26514
|
-
|
|
26515
|
-
|
|
26516
|
-
|
|
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 };
|
|
26517
28028
|
}
|
|
26518
28029
|
};
|
|
26519
28030
|
var williamsFractal = {
|
|
@@ -26521,10 +28032,16 @@ var williamsFractal = {
|
|
|
26521
28032
|
title: "Williams Fractal",
|
|
26522
28033
|
shortTitle: "Fractals",
|
|
26523
28034
|
overlay: true,
|
|
26524
|
-
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
|
+
],
|
|
26525
28040
|
compute: (bars, inputs) => {
|
|
26526
28041
|
const p = num3(inputs, "periods", 2);
|
|
26527
28042
|
const markers = [];
|
|
28043
|
+
const up = str3(inputs, "upColor", BEARISH);
|
|
28044
|
+
const down = str3(inputs, "downColor", BULLISH);
|
|
26528
28045
|
const isExtreme = (i, pick, better) => {
|
|
26529
28046
|
const v = pick(bars[i]);
|
|
26530
28047
|
for (let k = i - p; k <= i + p; k++) {
|
|
@@ -26534,12 +28051,8 @@ var williamsFractal = {
|
|
|
26534
28051
|
return true;
|
|
26535
28052
|
};
|
|
26536
28053
|
for (let i = p; i < bars.length - p; i++) {
|
|
26537
|
-
if (isExtreme(i, (b) => b.high, (a, b) => a
|
|
26538
|
-
|
|
26539
|
-
}
|
|
26540
|
-
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
26541
|
-
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
26542
|
-
}
|
|
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 });
|
|
26543
28056
|
}
|
|
26544
28057
|
return { plots: [], markers };
|
|
26545
28058
|
}
|
|
@@ -26911,4 +28424,4 @@ function resolveElement(container) {
|
|
|
26911
28424
|
return element;
|
|
26912
28425
|
}
|
|
26913
28426
|
|
|
26914
|
-
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, sharedBarStore, timeframeToMs, 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 };
|