@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
package/dist/index.cjs
CHANGED
|
@@ -10229,6 +10229,8 @@ var MultiProviderFeed = class {
|
|
|
10229
10229
|
this.liveBars = [];
|
|
10230
10230
|
/** Sync-accessible symbol metadata, warmed by load()/symbolInfoFor (the engine reads it synchronously). */
|
|
10231
10231
|
this.symInfoCache = /* @__PURE__ */ new Map();
|
|
10232
|
+
/** Requests in flight, keyed like the cache — every concurrent asker shares one round trip. */
|
|
10233
|
+
this.symInfoInflight = /* @__PURE__ */ new Map();
|
|
10232
10234
|
this.cache = new CachingDataFeed(new RegistryFetchFeed(this.registry), store);
|
|
10233
10235
|
}
|
|
10234
10236
|
// ── Registry surface (driven by chart.data / DataControl) ──────────────
|
|
@@ -10289,9 +10291,36 @@ var MultiProviderFeed = class {
|
|
|
10289
10291
|
async symbolInfoFor(raw) {
|
|
10290
10292
|
const resolved = this.registry.resolve(raw, { default: this.primaryProvider });
|
|
10291
10293
|
if (!resolved) return void 0;
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10294
|
+
return this.fetchSymbolInfo(resolved);
|
|
10295
|
+
}
|
|
10296
|
+
/**
|
|
10297
|
+
* One round trip per symbol, shared. A market switch probes this metadata from five
|
|
10298
|
+
* places at once (the bar load's prefetch, the renderer's tick size, the market-status
|
|
10299
|
+
* badge, the session shading, the session toggle) — without the in-flight map each
|
|
10300
|
+
* would open its own request for the same answer, and the last one to land decided how
|
|
10301
|
+
* late the chrome settled.
|
|
10302
|
+
*
|
|
10303
|
+
* Cached for {@link SYMBOL_INFO_TTL_MS}: this metadata is stable within a session
|
|
10304
|
+
* (tick size, session vocabulary, listing prefix), but not immutable — a continuous
|
|
10305
|
+
* futures row rolls its current contract — so the entry expires rather than pinning the
|
|
10306
|
+
* session's first answer forever. A failed probe is NOT remembered: the next asker retries.
|
|
10307
|
+
*/
|
|
10308
|
+
fetchSymbolInfo(resolved) {
|
|
10309
|
+
const key = symKey(resolved);
|
|
10310
|
+
const hit = this.symInfoCache.get(key);
|
|
10311
|
+
if (hit && Date.now() - hit.at < SYMBOL_INFO_TTL_MS) return Promise.resolve(hit.info);
|
|
10312
|
+
const inflight = this.symInfoInflight.get(key);
|
|
10313
|
+
if (inflight) return inflight;
|
|
10314
|
+
const getSymbolInfo = this.registry.get(resolved.provider)?.getSymbolInfo;
|
|
10315
|
+
if (!getSymbolInfo) return Promise.resolve(void 0);
|
|
10316
|
+
const p = Promise.resolve(getSymbolInfo.call(this.registry.get(resolved.provider), resolved.ticker)).then((info) => {
|
|
10317
|
+
if (info) this.symInfoCache.set(key, { info, at: Date.now() });
|
|
10318
|
+
return info;
|
|
10319
|
+
}).finally(() => {
|
|
10320
|
+
this.symInfoInflight.delete(key);
|
|
10321
|
+
});
|
|
10322
|
+
this.symInfoInflight.set(key, p);
|
|
10323
|
+
return p;
|
|
10295
10324
|
}
|
|
10296
10325
|
/**
|
|
10297
10326
|
* Per-symbol capabilities, resolved through the owning provider's `capabilitiesFor`
|
|
@@ -10335,16 +10364,10 @@ var MultiProviderFeed = class {
|
|
|
10335
10364
|
symbolInfo(cfg) {
|
|
10336
10365
|
if (cfg.data && cfg.data.length > 0) return void 0;
|
|
10337
10366
|
const resolved = this.registry.resolve(rawSymbol(cfg), { default: this.primaryProvider });
|
|
10338
|
-
return resolved ? this.symInfoCache.get(symKey(resolved)) : void 0;
|
|
10367
|
+
return resolved ? this.symInfoCache.get(symKey(resolved))?.info : void 0;
|
|
10339
10368
|
}
|
|
10340
10369
|
prefetchSymbolInfo(resolved) {
|
|
10341
|
-
|
|
10342
|
-
if (this.symInfoCache.has(key)) return;
|
|
10343
|
-
const provider = this.registry.get(resolved.provider);
|
|
10344
|
-
if (!provider?.getSymbolInfo) return;
|
|
10345
|
-
void provider.getSymbolInfo(resolved.ticker).then((info) => {
|
|
10346
|
-
if (info) this.symInfoCache.set(key, info);
|
|
10347
|
-
}).catch(() => {
|
|
10370
|
+
void this.fetchSymbolInfo(resolved).catch(() => {
|
|
10348
10371
|
});
|
|
10349
10372
|
}
|
|
10350
10373
|
async loadRange(cfg, range) {
|
|
@@ -10394,8 +10417,9 @@ function canonical(cfg, resolved) {
|
|
|
10394
10417
|
return { ...cfg, symbol: `${resolved.provider}:${resolved.ticker}` };
|
|
10395
10418
|
}
|
|
10396
10419
|
function symKey(resolved) {
|
|
10397
|
-
return `${resolved.provider}|${resolved.ticker}`;
|
|
10420
|
+
return `${resolved.provider}|${resolved.ticker.trim().toUpperCase()}`;
|
|
10398
10421
|
}
|
|
10422
|
+
var SYMBOL_INFO_TTL_MS = 10 * 6e4;
|
|
10399
10423
|
async function safeBars(provider, ticker, tf, range) {
|
|
10400
10424
|
try {
|
|
10401
10425
|
return await provider.getBars(ticker, tf, range);
|
|
@@ -13145,7 +13169,8 @@ function addRecent(hex6) {
|
|
|
13145
13169
|
recents.unshift(hex6);
|
|
13146
13170
|
if (recents.length > 10) recents.length = 10;
|
|
13147
13171
|
}
|
|
13148
|
-
function buildColorPicker(color, theme, onChange) {
|
|
13172
|
+
function buildColorPicker(color, theme, onChange, options = {}) {
|
|
13173
|
+
const commit = options.commit ?? "live";
|
|
13149
13174
|
const parsed = splitColor(color);
|
|
13150
13175
|
let curHex = parsed.hex6;
|
|
13151
13176
|
let curAlpha = parsed.alpha;
|
|
@@ -13228,7 +13253,12 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
13228
13253
|
const r = track.getBoundingClientRect();
|
|
13229
13254
|
curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
|
|
13230
13255
|
paintOpacity();
|
|
13231
|
-
emit();
|
|
13256
|
+
if (commit === "live") emit();
|
|
13257
|
+
};
|
|
13258
|
+
const endDrag = () => {
|
|
13259
|
+
if (!dragging) return;
|
|
13260
|
+
dragging = false;
|
|
13261
|
+
if (commit === "release") emit();
|
|
13232
13262
|
};
|
|
13233
13263
|
track.addEventListener("pointerdown", (e) => {
|
|
13234
13264
|
e.stopPropagation();
|
|
@@ -13239,7 +13269,8 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
13239
13269
|
track.addEventListener("pointermove", (e) => {
|
|
13240
13270
|
if (dragging) onDrag(e.clientX);
|
|
13241
13271
|
});
|
|
13242
|
-
track.addEventListener("pointerup",
|
|
13272
|
+
track.addEventListener("pointerup", endDrag);
|
|
13273
|
+
track.addEventListener("pointercancel", endDrag);
|
|
13243
13274
|
function emit() {
|
|
13244
13275
|
onChange(combineColor(curHex, curAlpha));
|
|
13245
13276
|
}
|
|
@@ -13262,7 +13293,7 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
13262
13293
|
return root;
|
|
13263
13294
|
}
|
|
13264
13295
|
function colorField(theme, getVal, onVal, opts) {
|
|
13265
|
-
return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover }).el;
|
|
13296
|
+
return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover, commit: opts?.commit }).el;
|
|
13266
13297
|
}
|
|
13267
13298
|
var ColorField = class {
|
|
13268
13299
|
constructor(opts) {
|
|
@@ -13271,6 +13302,7 @@ var ColorField = class {
|
|
|
13271
13302
|
this.getVal = opts.getVal;
|
|
13272
13303
|
this.onVal = opts.onVal;
|
|
13273
13304
|
this.popoverOpts = opts.popover;
|
|
13305
|
+
this.commit = opts.commit;
|
|
13274
13306
|
const trigger = document.createElement("button");
|
|
13275
13307
|
trigger.type = "button";
|
|
13276
13308
|
if (opts.id) trigger.id = opts.id;
|
|
@@ -13310,10 +13342,15 @@ var ColorField = class {
|
|
|
13310
13342
|
boundary: this.popoverOpts?.boundary,
|
|
13311
13343
|
zIndex: this.popoverOpts?.zIndex,
|
|
13312
13344
|
className: "vela-color-field-pop",
|
|
13313
|
-
content: buildColorPicker(
|
|
13314
|
-
this.
|
|
13315
|
-
this.
|
|
13316
|
-
|
|
13345
|
+
content: buildColorPicker(
|
|
13346
|
+
this.getVal(),
|
|
13347
|
+
this.theme,
|
|
13348
|
+
(val) => {
|
|
13349
|
+
this.onVal(val);
|
|
13350
|
+
this.paint();
|
|
13351
|
+
},
|
|
13352
|
+
{ commit: this.commit }
|
|
13353
|
+
)
|
|
13317
13354
|
});
|
|
13318
13355
|
pop.show();
|
|
13319
13356
|
}
|
|
@@ -13794,7 +13831,8 @@ function buildFieldControl(desc) {
|
|
|
13794
13831
|
const el2 = colorField(desc.theme, desc.get, desc.onChange, {
|
|
13795
13832
|
shape: "circle",
|
|
13796
13833
|
id: desc.id,
|
|
13797
|
-
popover: desc.popover
|
|
13834
|
+
popover: desc.popover,
|
|
13835
|
+
commit: desc.commit
|
|
13798
13836
|
});
|
|
13799
13837
|
if (desc.title) el2.title = desc.title;
|
|
13800
13838
|
return { el: el2 };
|
|
@@ -14172,7 +14210,10 @@ var IndicatorInputsDialog = class {
|
|
|
14172
14210
|
id,
|
|
14173
14211
|
theme: this.host.theme(),
|
|
14174
14212
|
get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
|
|
14175
|
-
onChange: (v) => emit(v)
|
|
14213
|
+
onChange: (v) => emit(v),
|
|
14214
|
+
// An input change re-executes the script over its whole history: commit the
|
|
14215
|
+
// opacity drag once on release, not once per pointer move.
|
|
14216
|
+
commit: "release"
|
|
14176
14217
|
}).el;
|
|
14177
14218
|
}
|
|
14178
14219
|
if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
|
|
@@ -14419,10 +14460,6 @@ var IndicatorInputsDialog = class {
|
|
|
14419
14460
|
}
|
|
14420
14461
|
/** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
|
|
14421
14462
|
openCalendar(anchor, current, onPick) {
|
|
14422
|
-
const parsed = parseIsoDate(current);
|
|
14423
|
-
let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
14424
|
-
let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
|
|
14425
|
-
const selected = parsed ? isoDate(parsed) : current;
|
|
14426
14463
|
const pop = new Popover({
|
|
14427
14464
|
trigger: anchor,
|
|
14428
14465
|
theme: this.host.theme(),
|
|
@@ -14434,80 +14471,10 @@ var IndicatorInputsDialog = class {
|
|
|
14434
14471
|
this.calendarPop = null;
|
|
14435
14472
|
this.calendarAnchor = null;
|
|
14436
14473
|
},
|
|
14437
|
-
content: (el) => {
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
prev2.type = "button";
|
|
14442
|
-
prev2.className = "vela-ind-cal-nav";
|
|
14443
|
-
prev2.setAttribute("aria-label", "Previous month");
|
|
14444
|
-
prev2.innerHTML = iconAt("chevron-left", 14);
|
|
14445
|
-
const next = document.createElement("button");
|
|
14446
|
-
next.type = "button";
|
|
14447
|
-
next.className = "vela-ind-cal-nav";
|
|
14448
|
-
next.setAttribute("aria-label", "Next month");
|
|
14449
|
-
next.innerHTML = iconAt("chevron-right", 14);
|
|
14450
|
-
const head = document.createElement("div");
|
|
14451
|
-
head.className = "vela-ind-cal-head";
|
|
14452
|
-
head.append(prev2, title, next);
|
|
14453
|
-
const week = document.createElement("div");
|
|
14454
|
-
week.className = "vela-ind-cal-week";
|
|
14455
|
-
for (const d of WEEKDAY_LABELS) {
|
|
14456
|
-
const cell = document.createElement("span");
|
|
14457
|
-
cell.textContent = d;
|
|
14458
|
-
week.appendChild(cell);
|
|
14459
|
-
}
|
|
14460
|
-
const grid = document.createElement("div");
|
|
14461
|
-
grid.className = "vela-ind-cal-grid";
|
|
14462
|
-
const paint = () => {
|
|
14463
|
-
title.textContent = `${MONTH_LABELS[month]} ${year}`;
|
|
14464
|
-
grid.replaceChildren();
|
|
14465
|
-
const first = new Date(year, month, 1);
|
|
14466
|
-
const startPad = first.getDay();
|
|
14467
|
-
const days = new Date(year, month + 1, 0).getDate();
|
|
14468
|
-
const today = isoDate(/* @__PURE__ */ new Date());
|
|
14469
|
-
for (let i = 0; i < startPad; i++) {
|
|
14470
|
-
const blank = document.createElement("span");
|
|
14471
|
-
blank.className = "vela-ind-cal-blank";
|
|
14472
|
-
grid.appendChild(blank);
|
|
14473
|
-
}
|
|
14474
|
-
for (let day = 1; day <= days; day++) {
|
|
14475
|
-
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
14476
|
-
const b = document.createElement("button");
|
|
14477
|
-
b.type = "button";
|
|
14478
|
-
b.className = "vela-ind-cal-day";
|
|
14479
|
-
b.textContent = String(day);
|
|
14480
|
-
if (iso === selected) b.dataset.checked = "1";
|
|
14481
|
-
if (iso === today) b.dataset.today = "1";
|
|
14482
|
-
b.addEventListener("click", (e) => {
|
|
14483
|
-
e.stopPropagation();
|
|
14484
|
-
pop.hide();
|
|
14485
|
-
onPick(iso);
|
|
14486
|
-
});
|
|
14487
|
-
grid.appendChild(b);
|
|
14488
|
-
}
|
|
14489
|
-
};
|
|
14490
|
-
prev2.addEventListener("click", (e) => {
|
|
14491
|
-
e.stopPropagation();
|
|
14492
|
-
month -= 1;
|
|
14493
|
-
if (month < 0) {
|
|
14494
|
-
month = 11;
|
|
14495
|
-
year -= 1;
|
|
14496
|
-
}
|
|
14497
|
-
paint();
|
|
14498
|
-
});
|
|
14499
|
-
next.addEventListener("click", (e) => {
|
|
14500
|
-
e.stopPropagation();
|
|
14501
|
-
month += 1;
|
|
14502
|
-
if (month > 11) {
|
|
14503
|
-
month = 0;
|
|
14504
|
-
year += 1;
|
|
14505
|
-
}
|
|
14506
|
-
paint();
|
|
14507
|
-
});
|
|
14508
|
-
paint();
|
|
14509
|
-
el.append(head, week, grid);
|
|
14510
|
-
}
|
|
14474
|
+
content: (el) => fillCalendar(el, current, (iso) => {
|
|
14475
|
+
pop.hide();
|
|
14476
|
+
onPick(iso);
|
|
14477
|
+
})
|
|
14511
14478
|
});
|
|
14512
14479
|
this.calendarPop = pop;
|
|
14513
14480
|
this.calendarAnchor = anchor;
|
|
@@ -14616,7 +14583,7 @@ function groupInputs(inputs) {
|
|
|
14616
14583
|
var CALENDAR_SVG = iconAt("calendar", 14);
|
|
14617
14584
|
var CLOCK_SVG = iconAt("clock", 14);
|
|
14618
14585
|
var DIALOG_STYLE_ID2 = "vela-ind-dialog-styles";
|
|
14619
|
-
var DIALOG_STYLE_REV = "
|
|
14586
|
+
var DIALOG_STYLE_REV = "30";
|
|
14620
14587
|
var LEGEND_ICON_PX = 16;
|
|
14621
14588
|
function ensureDialogStyles() {
|
|
14622
14589
|
if (typeof document === "undefined") return;
|
|
@@ -14632,8 +14599,12 @@ function ensureDialogStyles() {
|
|
|
14632
14599
|
.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;}
|
|
14633
14600
|
.vela-ind-combo-chevron:hover{opacity:0.9;}
|
|
14634
14601
|
.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;}
|
|
14602
|
+
.vela-ind-cal [hidden]{display:none !important;}
|
|
14635
14603
|
.vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
|
|
14636
|
-
.vela-ind-cal-title{flex:1;
|
|
14604
|
+
.vela-ind-cal-title{flex:1;display:flex;align-items:center;justify-content:center;gap:2px;min-width:0;}
|
|
14605
|
+
.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;}
|
|
14606
|
+
.vela-ind-cal-switch:not(:disabled):hover{background:var(--vela-hover);}
|
|
14607
|
+
.vela-ind-cal-switch:disabled{cursor:default;}
|
|
14637
14608
|
.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;}
|
|
14638
14609
|
.vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
14639
14610
|
.vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
|
|
@@ -14644,6 +14615,12 @@ function ensureDialogStyles() {
|
|
|
14644
14615
|
.vela-ind-cal-day:hover{background:var(--vela-hover);}
|
|
14645
14616
|
.vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
14646
14617
|
.vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
14618
|
+
.vela-ind-cal-cells{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;width:calc(7 * 28px + 6 * 2px);}
|
|
14619
|
+
.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;}
|
|
14620
|
+
.vela-ind-cal-cell:hover{background:var(--vela-hover);}
|
|
14621
|
+
.vela-ind-cal-cell[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
14622
|
+
.vela-ind-cal-cell[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
14623
|
+
.vela-ind-cal-cell[data-outside]{opacity:0.45;}
|
|
14647
14624
|
${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
14648
14625
|
.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;}
|
|
14649
14626
|
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
@@ -14678,6 +14655,7 @@ var TIME_OPTIONS = Array.from({ length: 48 }, (_, i) => {
|
|
|
14678
14655
|
return { value: v, label: v };
|
|
14679
14656
|
});
|
|
14680
14657
|
var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
14658
|
+
var MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
14681
14659
|
var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
14682
14660
|
function normalizeDateInput(raw) {
|
|
14683
14661
|
const t = raw.trim();
|
|
@@ -14699,6 +14677,151 @@ function parseIsoDate(raw) {
|
|
|
14699
14677
|
function isoDate(d) {
|
|
14700
14678
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
14701
14679
|
}
|
|
14680
|
+
function fillCalendar(el, current, onPick) {
|
|
14681
|
+
const today = /* @__PURE__ */ new Date();
|
|
14682
|
+
const selected = parseIsoDate(current);
|
|
14683
|
+
let year = selected?.getFullYear() ?? today.getFullYear();
|
|
14684
|
+
let month = selected?.getMonth() ?? today.getMonth();
|
|
14685
|
+
let mode = "date";
|
|
14686
|
+
const prev2 = document.createElement("button");
|
|
14687
|
+
prev2.type = "button";
|
|
14688
|
+
prev2.className = "vela-ind-cal-nav";
|
|
14689
|
+
prev2.innerHTML = iconAt("chevron-left", 14);
|
|
14690
|
+
const next = document.createElement("button");
|
|
14691
|
+
next.type = "button";
|
|
14692
|
+
next.className = "vela-ind-cal-nav";
|
|
14693
|
+
next.innerHTML = iconAt("chevron-right", 14);
|
|
14694
|
+
const monthBtn = document.createElement("button");
|
|
14695
|
+
monthBtn.type = "button";
|
|
14696
|
+
monthBtn.className = "vela-ind-cal-switch";
|
|
14697
|
+
monthBtn.setAttribute("aria-label", "Choose month");
|
|
14698
|
+
const yearBtn = document.createElement("button");
|
|
14699
|
+
yearBtn.type = "button";
|
|
14700
|
+
yearBtn.className = "vela-ind-cal-switch";
|
|
14701
|
+
yearBtn.setAttribute("aria-label", "Choose year");
|
|
14702
|
+
const title = document.createElement("div");
|
|
14703
|
+
title.className = "vela-ind-cal-title";
|
|
14704
|
+
title.append(monthBtn, yearBtn);
|
|
14705
|
+
const head = document.createElement("div");
|
|
14706
|
+
head.className = "vela-ind-cal-head";
|
|
14707
|
+
head.append(prev2, title, next);
|
|
14708
|
+
const week = document.createElement("div");
|
|
14709
|
+
week.className = "vela-ind-cal-week";
|
|
14710
|
+
for (const d of WEEKDAY_LABELS) {
|
|
14711
|
+
const cell = document.createElement("span");
|
|
14712
|
+
cell.textContent = d;
|
|
14713
|
+
week.appendChild(cell);
|
|
14714
|
+
}
|
|
14715
|
+
const grid = document.createElement("div");
|
|
14716
|
+
const paint = () => {
|
|
14717
|
+
const decade = Math.floor(year / 10) * 10;
|
|
14718
|
+
monthBtn.hidden = mode !== "date";
|
|
14719
|
+
monthBtn.textContent = MONTH_LABELS[month] ?? "";
|
|
14720
|
+
yearBtn.textContent = mode === "year" ? `${decade}-${decade + 9}` : String(year);
|
|
14721
|
+
yearBtn.disabled = mode === "year";
|
|
14722
|
+
week.hidden = mode !== "date";
|
|
14723
|
+
prev2.setAttribute("aria-label", mode === "date" ? "Previous month" : mode === "month" ? "Previous year" : "Previous decade");
|
|
14724
|
+
next.setAttribute("aria-label", mode === "date" ? "Next month" : mode === "month" ? "Next year" : "Next decade");
|
|
14725
|
+
grid.className = mode === "date" ? "vela-ind-cal-grid" : "vela-ind-cal-cells";
|
|
14726
|
+
grid.replaceChildren();
|
|
14727
|
+
if (mode === "date") {
|
|
14728
|
+
const startPad = new Date(year, month, 1).getDay();
|
|
14729
|
+
const days = new Date(year, month + 1, 0).getDate();
|
|
14730
|
+
const todayIso = isoDate(today);
|
|
14731
|
+
const selectedIso = selected ? isoDate(selected) : "";
|
|
14732
|
+
for (let i = 0; i < startPad; i++) {
|
|
14733
|
+
const blank = document.createElement("span");
|
|
14734
|
+
blank.className = "vela-ind-cal-blank";
|
|
14735
|
+
grid.appendChild(blank);
|
|
14736
|
+
}
|
|
14737
|
+
for (let day = 1; day <= days; day++) {
|
|
14738
|
+
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
14739
|
+
const b = document.createElement("button");
|
|
14740
|
+
b.type = "button";
|
|
14741
|
+
b.className = "vela-ind-cal-day";
|
|
14742
|
+
b.textContent = String(day);
|
|
14743
|
+
if (iso === selectedIso) b.dataset.checked = "1";
|
|
14744
|
+
if (iso === todayIso) b.dataset.today = "1";
|
|
14745
|
+
b.addEventListener("click", (e) => {
|
|
14746
|
+
e.stopPropagation();
|
|
14747
|
+
onPick(iso);
|
|
14748
|
+
});
|
|
14749
|
+
grid.appendChild(b);
|
|
14750
|
+
}
|
|
14751
|
+
return;
|
|
14752
|
+
}
|
|
14753
|
+
if (mode === "month") {
|
|
14754
|
+
for (let m = 0; m < 12; m++) {
|
|
14755
|
+
const b = document.createElement("button");
|
|
14756
|
+
b.type = "button";
|
|
14757
|
+
b.className = "vela-ind-cal-cell";
|
|
14758
|
+
b.textContent = MONTH_SHORT[m] ?? "";
|
|
14759
|
+
if (selected?.getFullYear() === year && selected.getMonth() === m) b.dataset.checked = "1";
|
|
14760
|
+
if (today.getFullYear() === year && today.getMonth() === m) b.dataset.today = "1";
|
|
14761
|
+
b.addEventListener("click", (e) => {
|
|
14762
|
+
e.stopPropagation();
|
|
14763
|
+
month = m;
|
|
14764
|
+
mode = "date";
|
|
14765
|
+
paint();
|
|
14766
|
+
});
|
|
14767
|
+
grid.appendChild(b);
|
|
14768
|
+
}
|
|
14769
|
+
return;
|
|
14770
|
+
}
|
|
14771
|
+
for (let y = decade - 1; y <= decade + 10; y++) {
|
|
14772
|
+
const b = document.createElement("button");
|
|
14773
|
+
b.type = "button";
|
|
14774
|
+
b.className = "vela-ind-cal-cell";
|
|
14775
|
+
b.textContent = String(y);
|
|
14776
|
+
if (y < decade || y > decade + 9) b.dataset.outside = "1";
|
|
14777
|
+
if (selected?.getFullYear() === y) b.dataset.checked = "1";
|
|
14778
|
+
if (today.getFullYear() === y) b.dataset.today = "1";
|
|
14779
|
+
b.addEventListener("click", (e) => {
|
|
14780
|
+
e.stopPropagation();
|
|
14781
|
+
year = y;
|
|
14782
|
+
mode = "month";
|
|
14783
|
+
paint();
|
|
14784
|
+
});
|
|
14785
|
+
grid.appendChild(b);
|
|
14786
|
+
}
|
|
14787
|
+
};
|
|
14788
|
+
const step = (dir) => {
|
|
14789
|
+
if (mode === "date") {
|
|
14790
|
+
month += dir;
|
|
14791
|
+
if (month < 0) {
|
|
14792
|
+
month = 11;
|
|
14793
|
+
year -= 1;
|
|
14794
|
+
}
|
|
14795
|
+
if (month > 11) {
|
|
14796
|
+
month = 0;
|
|
14797
|
+
year += 1;
|
|
14798
|
+
}
|
|
14799
|
+
} else {
|
|
14800
|
+
year += mode === "month" ? dir : dir * 10;
|
|
14801
|
+
}
|
|
14802
|
+
paint();
|
|
14803
|
+
};
|
|
14804
|
+
prev2.addEventListener("click", (e) => {
|
|
14805
|
+
e.stopPropagation();
|
|
14806
|
+
step(-1);
|
|
14807
|
+
});
|
|
14808
|
+
next.addEventListener("click", (e) => {
|
|
14809
|
+
e.stopPropagation();
|
|
14810
|
+
step(1);
|
|
14811
|
+
});
|
|
14812
|
+
monthBtn.addEventListener("click", (e) => {
|
|
14813
|
+
e.stopPropagation();
|
|
14814
|
+
mode = "month";
|
|
14815
|
+
paint();
|
|
14816
|
+
});
|
|
14817
|
+
yearBtn.addEventListener("click", (e) => {
|
|
14818
|
+
e.stopPropagation();
|
|
14819
|
+
mode = "year";
|
|
14820
|
+
paint();
|
|
14821
|
+
});
|
|
14822
|
+
paint();
|
|
14823
|
+
el.append(head, week, grid);
|
|
14824
|
+
}
|
|
14702
14825
|
function normalizeTimeInput(raw) {
|
|
14703
14826
|
const t = raw.trim();
|
|
14704
14827
|
const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
|
|
@@ -15980,10 +16103,12 @@ var BASELINE_LEVEL_DEFAULT = 50;
|
|
|
15980
16103
|
var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
|
|
15981
16104
|
var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
|
|
15982
16105
|
var EXTENDED_SHADE = POSTMARKET_SHADE;
|
|
16106
|
+
var DEFAULT_MARGINS = { top: 10, bottom: 10, right: 10 };
|
|
15983
16107
|
function defaultChartStyle() {
|
|
15984
16108
|
return {
|
|
15985
16109
|
chartTypes: {},
|
|
15986
16110
|
fontSize: 11,
|
|
16111
|
+
margins: { ...DEFAULT_MARGINS },
|
|
15987
16112
|
gridVert: { visible: true, color: null },
|
|
15988
16113
|
gridHorz: { visible: true, color: null },
|
|
15989
16114
|
borderColor: null,
|
|
@@ -16107,6 +16232,12 @@ function clampLevel(v) {
|
|
|
16107
16232
|
function clampSpacing(v) {
|
|
16108
16233
|
return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
|
|
16109
16234
|
}
|
|
16235
|
+
function clampMarginPct(v) {
|
|
16236
|
+
return v < 0 ? 0 : v > 40 ? 40 : v;
|
|
16237
|
+
}
|
|
16238
|
+
function clampMarginBars(v) {
|
|
16239
|
+
return Math.round(v < 0 ? 0 : v > 200 ? 200 : v);
|
|
16240
|
+
}
|
|
16110
16241
|
function factoryResetConfig(factory, priceStyle = factory.series.style) {
|
|
16111
16242
|
const bag = {};
|
|
16112
16243
|
for (const t of chartTypes()) {
|
|
@@ -16155,6 +16286,7 @@ function mergeConfig(base, patch) {
|
|
|
16155
16286
|
const ps = asObject(p.priceScale);
|
|
16156
16287
|
const anim = asObject(p.animations);
|
|
16157
16288
|
const panes = asObject(p.panes);
|
|
16289
|
+
const margins = asObject(p.margins);
|
|
16158
16290
|
const trades = asObject(p.trades);
|
|
16159
16291
|
const ts = asObject(p.timeScale);
|
|
16160
16292
|
const marks = asObject(p.marks);
|
|
@@ -16214,6 +16346,11 @@ function mergeConfig(base, patch) {
|
|
|
16214
16346
|
panes: {
|
|
16215
16347
|
separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
|
|
16216
16348
|
},
|
|
16349
|
+
margins: {
|
|
16350
|
+
top: isNum(margins.top) ? clampMarginPct(margins.top) : base.margins.top,
|
|
16351
|
+
bottom: isNum(margins.bottom) ? clampMarginPct(margins.bottom) : base.margins.bottom,
|
|
16352
|
+
right: isNum(margins.right) ? clampMarginBars(margins.right) : base.margins.right
|
|
16353
|
+
},
|
|
16217
16354
|
trades: {
|
|
16218
16355
|
visible: isBool(trades.visible) ? trades.visible : base.trades.visible,
|
|
16219
16356
|
labels: isBool(trades.labels) ? trades.labels : base.trades.labels,
|
|
@@ -21584,6 +21721,10 @@ var BUILTIN_SETTINGS_IDS = [
|
|
|
21584
21721
|
"canvas.grid",
|
|
21585
21722
|
"canvas.grid.vertical",
|
|
21586
21723
|
"canvas.grid.horizontal",
|
|
21724
|
+
"canvas.margins",
|
|
21725
|
+
"canvas.margins.top",
|
|
21726
|
+
"canvas.margins.bottom",
|
|
21727
|
+
"canvas.margins.right",
|
|
21587
21728
|
"canvas.theme"
|
|
21588
21729
|
];
|
|
21589
21730
|
function settingsIdCatalog(hostSections, markGroups = []) {
|
|
@@ -22064,7 +22205,7 @@ var SettingsDialog = class {
|
|
|
22064
22205
|
if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
|
|
22065
22206
|
else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
22066
22207
|
else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
22067
|
-
else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options
|
|
22208
|
+
else body.append(this.selectRowLabeled(hr.label, hr.get(), normalizeSelectOptions(hr.options), (v) => hr.set(v)));
|
|
22068
22209
|
}
|
|
22069
22210
|
}
|
|
22070
22211
|
};
|
|
@@ -22114,6 +22255,10 @@ var SettingsDialog = class {
|
|
|
22114
22255
|
body.append(sid(this.toggleRow("Horizontal", config.grid.horzLines.visible, (v) => this.emit({ grid: { horzLines: { visible: v } } }), [
|
|
22115
22256
|
this.swatch(config.grid.horzLines.color, (v) => this.emit({ grid: { horzLines: { color: v } } }))
|
|
22116
22257
|
]), "canvas.grid.horizontal"));
|
|
22258
|
+
body.append(sid(this.sectionTitle("Margins"), "canvas.margins"));
|
|
22259
|
+
body.append(sid(this.numberRow("Top", config.margins.top, 0, 40, 1, (v) => this.emit({ margins: { top: v } }), "%"), "canvas.margins.top"));
|
|
22260
|
+
body.append(sid(this.numberRow("Bottom", config.margins.bottom, 0, 40, 1, (v) => this.emit({ margins: { bottom: v } }), "%"), "canvas.margins.bottom"));
|
|
22261
|
+
body.append(sid(this.numberRow("Right", config.margins.right, 0, 200, 1, (v) => this.emit({ margins: { right: v } }), "bars"), "canvas.margins.right"));
|
|
22117
22262
|
if (this.themeControl) {
|
|
22118
22263
|
const tc = this.themeControl;
|
|
22119
22264
|
body.append(sid(this.sectionTitle("Theme"), "canvas.theme"));
|
|
@@ -22685,23 +22830,16 @@ var SettingsDialog = class {
|
|
|
22685
22830
|
this.hintTips.push(dispose);
|
|
22686
22831
|
return el;
|
|
22687
22832
|
}
|
|
22688
|
-
|
|
22689
|
-
|
|
22690
|
-
|
|
22691
|
-
|
|
22692
|
-
|
|
22693
|
-
|
|
22694
|
-
|
|
22695
|
-
|
|
22696
|
-
|
|
22697
|
-
|
|
22698
|
-
step,
|
|
22699
|
-
fill: false,
|
|
22700
|
-
commit: "live",
|
|
22701
|
-
steppers: true,
|
|
22702
|
-
onChange
|
|
22703
|
-
}).el
|
|
22704
|
-
});
|
|
22833
|
+
/** `unit` (optional) trails the input as muted text — "%", "bars". */
|
|
22834
|
+
numberRow(label, value, min, max, step, onChange, unit) {
|
|
22835
|
+
const controls = [buildFieldControl({ kind: "number", value, min, max, step, fill: false, commit: "live", steppers: true, onChange }).el];
|
|
22836
|
+
if (unit) {
|
|
22837
|
+
const u = document.createElement("span");
|
|
22838
|
+
u.textContent = unit;
|
|
22839
|
+
u.style.cssText = "color:var(--vela-fg-muted);";
|
|
22840
|
+
controls.push(u);
|
|
22841
|
+
}
|
|
22842
|
+
return this.rowWith(label, controls);
|
|
22705
22843
|
}
|
|
22706
22844
|
/** A dropdown whose option values differ from their display labels. */
|
|
22707
22845
|
selectRowLabeled(label, value, options, onChange) {
|
|
@@ -28220,9 +28358,7 @@ function createProjector(coords, paneOf, paneIdAtY, barsInRange, seriesInRange)
|
|
|
28220
28358
|
}
|
|
28221
28359
|
|
|
28222
28360
|
// src/renderers/native/core/autoscale.ts
|
|
28223
|
-
|
|
28224
|
-
var MARGIN_BOTTOM = 1 / 7;
|
|
28225
|
-
function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log = false, offsetOf = () => 0) {
|
|
28361
|
+
function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log = false, offsetOf = () => 0, margins = DEFAULT_MARGINS) {
|
|
28226
28362
|
let min = Infinity;
|
|
28227
28363
|
let max = -Infinity;
|
|
28228
28364
|
const consider = (v) => {
|
|
@@ -28257,14 +28393,17 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
28257
28393
|
const pad = Math.abs(min) * 0.1 || 1;
|
|
28258
28394
|
return { min: min - pad, max: max + pad, log: log && min - pad > 0 };
|
|
28259
28395
|
}
|
|
28396
|
+
const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
|
|
28397
|
+
const above = margins.top / 100 / content;
|
|
28398
|
+
const below = margins.bottom / 100 / content;
|
|
28260
28399
|
if (log && min > 0) {
|
|
28261
28400
|
const lmin = Math.log(min);
|
|
28262
28401
|
const lmax = Math.log(max);
|
|
28263
28402
|
const lspan = lmax - lmin;
|
|
28264
|
-
return { min: Math.exp(lmin - lspan *
|
|
28403
|
+
return { min: Math.exp(lmin - lspan * below), max: Math.exp(lmax + lspan * above), log: true };
|
|
28265
28404
|
}
|
|
28266
28405
|
const span = max - min;
|
|
28267
|
-
return { min: min - span *
|
|
28406
|
+
return { min: min - span * below, max: max + span * above };
|
|
28268
28407
|
}
|
|
28269
28408
|
function considerSeries(s, i0, i1, off, consider) {
|
|
28270
28409
|
if (!seriesInScale(s)) return;
|
|
@@ -30091,6 +30230,7 @@ var NativeRenderer = class {
|
|
|
30091
30230
|
intro: this.intro.style !== false
|
|
30092
30231
|
},
|
|
30093
30232
|
panes: { separatorColor: s.separatorColor ?? t.borderColor },
|
|
30233
|
+
margins: { ...s.margins },
|
|
30094
30234
|
trades: {
|
|
30095
30235
|
visible: this.scene.tradeMarkers.visible,
|
|
30096
30236
|
labels: this.scene.tradeMarkers.labels,
|
|
@@ -30203,6 +30343,10 @@ var NativeRenderer = class {
|
|
|
30203
30343
|
this.animAutoscale.toggle(next.animations.autoscale);
|
|
30204
30344
|
this.intro = { style: next.animations.intro ? this.introOnStyle : false, duration: this.intro.duration || INTRO_DURATION_DEFAULT_MS };
|
|
30205
30345
|
s.separatorColor = keepInherit(s.separatorColor, next.panes.separatorColor, prevTheme.borderColor);
|
|
30346
|
+
if (next.margins.right !== s.margins.right && this.coords.barCount > 0) {
|
|
30347
|
+
this.applyViewport({ barSpacing: this.coords.getViewport().barSpacing, rightOffset: next.margins.right });
|
|
30348
|
+
}
|
|
30349
|
+
s.margins = { ...next.margins };
|
|
30206
30350
|
this.scene.tradeMarkers = {
|
|
30207
30351
|
visible: next.trades.visible,
|
|
30208
30352
|
labels: next.trades.labels,
|
|
@@ -30446,7 +30590,7 @@ var NativeRenderer = class {
|
|
|
30446
30590
|
/** Glide the view back to the most recent bars, keeping the current zoom (barSpacing). */
|
|
30447
30591
|
scrollToRealtime() {
|
|
30448
30592
|
if (this.coords.barCount === 0) return;
|
|
30449
|
-
this.glideRightOffset(
|
|
30593
|
+
this.glideRightOffset(this.scene.style.margins.right);
|
|
30450
30594
|
}
|
|
30451
30595
|
/** Ease rightOffset to `target` at constant zoom (see animTick's scroll glide);
|
|
30452
30596
|
* instant when the scroll glide is off. Shared by scroll-to-latest and panBy. */
|
|
@@ -32388,6 +32532,7 @@ var NativeRenderer = class {
|
|
|
32388
32532
|
const pricePane = panes.find((p) => p.kind === "price") ?? null;
|
|
32389
32533
|
this.chrome.prepare(this.scene, this.coords, this.theme);
|
|
32390
32534
|
const animating = this.animator.active;
|
|
32535
|
+
const margins = this.scene.style.margins;
|
|
32391
32536
|
for (const pane of panes) {
|
|
32392
32537
|
if (pane.manualScale) {
|
|
32393
32538
|
pane.scaleTarget = pane.manualScale;
|
|
@@ -32403,7 +32548,7 @@ var NativeRenderer = class {
|
|
|
32403
32548
|
if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
|
|
32404
32549
|
}
|
|
32405
32550
|
const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels) || !this.paneHasMeasurableContent(masterModels, dr));
|
|
32406
|
-
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
32551
|
+
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
|
|
32407
32552
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
32408
32553
|
pane.axisFormat = void 0;
|
|
32409
32554
|
pane.axisBands = void 0;
|
|
@@ -32414,7 +32559,7 @@ var NativeRenderer = class {
|
|
|
32414
32559
|
pane.axisFormat = "volume";
|
|
32415
32560
|
}
|
|
32416
32561
|
} else if (this.layerNativesOwnPane(pane, masterModels)) {
|
|
32417
|
-
pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
32562
|
+
pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
|
|
32418
32563
|
pane.percentBaseline = this.bars[i0]?.close ?? 0;
|
|
32419
32564
|
if (masterModels.every((m) => m.paneAxis != null)) {
|
|
32420
32565
|
pane.axisFormat = "none";
|
|
@@ -32443,7 +32588,7 @@ var NativeRenderer = class {
|
|
|
32443
32588
|
continue;
|
|
32444
32589
|
}
|
|
32445
32590
|
const mdr = this.chrome.paneDrawingsRange([model], this.scene, false, vr);
|
|
32446
|
-
sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id));
|
|
32591
|
+
sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id), margins);
|
|
32447
32592
|
if (!animating || !sl.initialized) {
|
|
32448
32593
|
sl.scale = { ...sl.scaleTarget };
|
|
32449
32594
|
sl.initialized = true;
|
|
@@ -32484,13 +32629,13 @@ var NativeRenderer = class {
|
|
|
32484
32629
|
for (const pane of this.scene.panes.values()) pane.manualScale = null;
|
|
32485
32630
|
for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
|
|
32486
32631
|
const visibleBars = Math.min(n, 200);
|
|
32487
|
-
const rightOffset =
|
|
32632
|
+
const rightOffset = this.scene.style.margins.right;
|
|
32488
32633
|
const v = this.clampViewport(w / ((visibleBars + rightOffset) * this.coords.spacingScale), rightOffset);
|
|
32489
32634
|
this.coords.setViewport(v);
|
|
32490
32635
|
this.targetBarSpacing = v.barSpacing;
|
|
32491
32636
|
}
|
|
32492
32637
|
/** Re-frame after a series replacement (a symbol/timeframe switch): keep the user's
|
|
32493
|
-
* zoom (bar spacing), re-anchor the newest bars at the
|
|
32638
|
+
* zoom (bar spacing), re-anchor the newest bars at the configured right margin.
|
|
32494
32639
|
* `clampViewport`'s fit-all-bars floor deliberately does NOT apply — a progressive
|
|
32495
32640
|
* head may still be backfilling toward the previous depth, and raising the spacing
|
|
32496
32641
|
* to its temporary bar count would lose the zoom this exists to keep. */
|
|
@@ -32499,7 +32644,7 @@ var NativeRenderer = class {
|
|
|
32499
32644
|
this.panVelocity = 0;
|
|
32500
32645
|
for (const pane of this.scene.panes.values()) pane.manualScale = null;
|
|
32501
32646
|
for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
|
|
32502
|
-
const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset:
|
|
32647
|
+
const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset: this.scene.style.margins.right };
|
|
32503
32648
|
this.coords.setViewport(v);
|
|
32504
32649
|
this.targetBarSpacing = v.barSpacing;
|
|
32505
32650
|
}
|
|
@@ -33329,7 +33474,8 @@ var ClassicIndicator = class {
|
|
|
33329
33474
|
lineStyle: plot.lineStyle ?? "solid",
|
|
33330
33475
|
...plot.base != null ? { base: plot.base } : {}
|
|
33331
33476
|
},
|
|
33332
|
-
...plot.overlay ? { overlay: true } : {}
|
|
33477
|
+
...plot.overlay ? { overlay: true } : {},
|
|
33478
|
+
...plot.display ? { display: plot.display } : {}
|
|
33333
33479
|
});
|
|
33334
33480
|
});
|
|
33335
33481
|
if (out.markers && out.markers.length > 0) {
|
|
@@ -33351,7 +33497,9 @@ var ClassicIndicator = class {
|
|
|
33351
33497
|
paneId: "",
|
|
33352
33498
|
fromSeriesId: from,
|
|
33353
33499
|
toSeriesId: to,
|
|
33354
|
-
color: band.color
|
|
33500
|
+
color: band.color,
|
|
33501
|
+
...band.colors ? { colors: [...band.colors] } : {},
|
|
33502
|
+
...band.gradient ? { gradient: [...band.gradient] } : {}
|
|
33355
33503
|
}
|
|
33356
33504
|
];
|
|
33357
33505
|
});
|
|
@@ -33509,6 +33657,16 @@ function stdev(v, len) {
|
|
|
33509
33657
|
return Math.sqrt(sq / len);
|
|
33510
33658
|
});
|
|
33511
33659
|
}
|
|
33660
|
+
function meanDev(v, len) {
|
|
33661
|
+
return windowed(v, len, (w, from, to) => {
|
|
33662
|
+
let s = 0;
|
|
33663
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
33664
|
+
const mean = s / len;
|
|
33665
|
+
let dev = 0;
|
|
33666
|
+
for (let k = from; k <= to; k++) dev += Math.abs(w[k] - mean);
|
|
33667
|
+
return dev / len;
|
|
33668
|
+
});
|
|
33669
|
+
}
|
|
33512
33670
|
function highest(v, len) {
|
|
33513
33671
|
return windowed(v, len, (w, from, to) => {
|
|
33514
33672
|
let m = -Infinity;
|
|
@@ -33640,7 +33798,7 @@ function stoch(v, hi, lo, len) {
|
|
|
33640
33798
|
}
|
|
33641
33799
|
return out;
|
|
33642
33800
|
}
|
|
33643
|
-
function linreg(v, len) {
|
|
33801
|
+
function linreg(v, len, offset = 0) {
|
|
33644
33802
|
const sx = (len - 1) * len / 2;
|
|
33645
33803
|
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
33646
33804
|
const denom = len * sxx - sx * sx;
|
|
@@ -33654,9 +33812,101 @@ function linreg(v, len) {
|
|
|
33654
33812
|
}
|
|
33655
33813
|
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
33656
33814
|
const intercept = (sy - slope * sx) / len;
|
|
33657
|
-
return intercept + slope * (len - 1);
|
|
33815
|
+
return intercept + slope * (len - 1 - offset);
|
|
33658
33816
|
});
|
|
33659
33817
|
}
|
|
33818
|
+
function hma(v, len) {
|
|
33819
|
+
const n = Math.max(2, Math.trunc(len));
|
|
33820
|
+
const raw = sub(map(wma(v, Math.max(1, Math.trunc(n / 2))), (x) => 2 * x), wma(v, n));
|
|
33821
|
+
return wma(raw, Math.max(1, Math.round(Math.sqrt(n))));
|
|
33822
|
+
}
|
|
33823
|
+
function alma(v, len, offset, sigma) {
|
|
33824
|
+
const m = offset * (len - 1);
|
|
33825
|
+
const s = len / Math.max(sigma, 1e-9);
|
|
33826
|
+
const weights = new Array(len);
|
|
33827
|
+
let norm = 0;
|
|
33828
|
+
for (let i = 0; i < len; i++) {
|
|
33829
|
+
const d = i - m;
|
|
33830
|
+
const w = Math.exp(-(d * d) / (2 * s * s));
|
|
33831
|
+
weights[i] = w;
|
|
33832
|
+
norm += w;
|
|
33833
|
+
}
|
|
33834
|
+
return windowed(v, len, (win, from, to) => {
|
|
33835
|
+
let acc = 0;
|
|
33836
|
+
for (let k = from; k <= to; k++) acc += win[k] * weights[k - from];
|
|
33837
|
+
return norm === 0 ? Number.NaN : acc / norm;
|
|
33838
|
+
});
|
|
33839
|
+
}
|
|
33840
|
+
function dema(v, len) {
|
|
33841
|
+
const e1 = ema(v, len);
|
|
33842
|
+
return sub(map(e1, (x) => 2 * x), ema(e1, len));
|
|
33843
|
+
}
|
|
33844
|
+
function tema(v, len) {
|
|
33845
|
+
const e1 = ema(v, len);
|
|
33846
|
+
const e2 = ema(e1, len);
|
|
33847
|
+
const e3 = ema(e2, len);
|
|
33848
|
+
return zip(zip(map(e1, (x) => 3 * x), map(e2, (x) => 3 * x), (a, b) => a - b), e3, (a, b) => a + b);
|
|
33849
|
+
}
|
|
33850
|
+
function kama(v, len, fastAlpha = 2 / 3, slowAlpha = 2 / 31) {
|
|
33851
|
+
const direction = map(change(v, len), Math.abs);
|
|
33852
|
+
const volatility = sum(map(change(v), Math.abs), len);
|
|
33853
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
33854
|
+
let prev2 = Number.NaN;
|
|
33855
|
+
for (let i = 0; i < v.length; i++) {
|
|
33856
|
+
const x = v[i];
|
|
33857
|
+
if (!Number.isFinite(x)) continue;
|
|
33858
|
+
const d = direction[i];
|
|
33859
|
+
const w = volatility[i];
|
|
33860
|
+
if (!Number.isFinite(d) || !Number.isFinite(w)) {
|
|
33861
|
+
prev2 = Number.isFinite(prev2) ? prev2 : x;
|
|
33862
|
+
out[i] = prev2;
|
|
33863
|
+
continue;
|
|
33864
|
+
}
|
|
33865
|
+
const er = w === 0 ? 0 : d / w;
|
|
33866
|
+
const sc = (er * (fastAlpha - slowAlpha) + slowAlpha) ** 2;
|
|
33867
|
+
prev2 = Number.isFinite(prev2) ? prev2 + sc * (x - prev2) : x;
|
|
33868
|
+
out[i] = prev2;
|
|
33869
|
+
}
|
|
33870
|
+
return out;
|
|
33871
|
+
}
|
|
33872
|
+
function mcginley(v, len) {
|
|
33873
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
33874
|
+
let prev2 = Number.NaN;
|
|
33875
|
+
for (let i = 0; i < v.length; i++) {
|
|
33876
|
+
const x = v[i];
|
|
33877
|
+
if (!Number.isFinite(x)) continue;
|
|
33878
|
+
if (!Number.isFinite(prev2)) {
|
|
33879
|
+
prev2 = x;
|
|
33880
|
+
} else {
|
|
33881
|
+
const ratio = prev2 === 0 ? 0 : x / prev2;
|
|
33882
|
+
const denom = len * ratio ** 4;
|
|
33883
|
+
prev2 += denom === 0 ? 0 : (x - prev2) / denom;
|
|
33884
|
+
}
|
|
33885
|
+
out[i] = prev2;
|
|
33886
|
+
}
|
|
33887
|
+
return out;
|
|
33888
|
+
}
|
|
33889
|
+
function hamming(v, len, a0 = 0.54, a1 = 0.46) {
|
|
33890
|
+
if (len <= 1) return v.map((x) => Number.isFinite(x) ? x : Number.NaN);
|
|
33891
|
+
const weights = new Array(len);
|
|
33892
|
+
let norm = 0;
|
|
33893
|
+
for (let i = 0; i < len; i++) {
|
|
33894
|
+
const w = a0 - a1 * Math.cos(2 * Math.PI * i / (len - 1));
|
|
33895
|
+
weights[i] = w;
|
|
33896
|
+
norm += w;
|
|
33897
|
+
}
|
|
33898
|
+
return windowed(v, len, (win, from, to) => {
|
|
33899
|
+
let acc = 0;
|
|
33900
|
+
for (let k = from; k <= to; k++) acc += win[k] * weights[to - k];
|
|
33901
|
+
return norm === 0 ? Number.NaN : acc / norm;
|
|
33902
|
+
});
|
|
33903
|
+
}
|
|
33904
|
+
function cmo(v, len) {
|
|
33905
|
+
const d = change(v);
|
|
33906
|
+
const up = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
33907
|
+
const dn = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
33908
|
+
return zip(up, dn, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
33909
|
+
}
|
|
33660
33910
|
function swma(v) {
|
|
33661
33911
|
const out = new Array(v.length).fill(Number.NaN);
|
|
33662
33912
|
for (let i = 3; i < v.length; i++) {
|
|
@@ -33706,38 +33956,253 @@ function cumSum(v) {
|
|
|
33706
33956
|
}
|
|
33707
33957
|
|
|
33708
33958
|
// src/core/native-indicators/classics/shared.ts
|
|
33709
|
-
|
|
33710
|
-
|
|
33959
|
+
var SETTINGS = "Settings";
|
|
33960
|
+
var STYLE = "Style";
|
|
33961
|
+
function lengthInput(defval, key = "length", title = "Length", max = 5e3, tooltip) {
|
|
33962
|
+
return { key, title, type: "int", defval, min: 1, max, step: 1, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
33963
|
+
}
|
|
33964
|
+
function intInput(key, title, defval, min = 1, max = 5e3, tooltip) {
|
|
33965
|
+
return { key, title, type: "int", defval, min, max, step: 1, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
33711
33966
|
}
|
|
33712
|
-
function
|
|
33713
|
-
return { key
|
|
33967
|
+
function floatInput(key, title, defval, min = 0, max = 1e3, step = 0.1, tooltip) {
|
|
33968
|
+
return { key, title, type: "float", defval, min, max, step, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
33714
33969
|
}
|
|
33715
|
-
function
|
|
33716
|
-
return { key, title, type: "
|
|
33970
|
+
function boolInput(key, title, defval, group = SETTINGS, tooltip) {
|
|
33971
|
+
return { key, title, type: "bool", defval, group, ...tooltip != null ? { tooltip } : {} };
|
|
33972
|
+
}
|
|
33973
|
+
function optionInput(key, title, defval, options, tooltip) {
|
|
33974
|
+
return { key, title, type: "string", defval, options, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
33975
|
+
}
|
|
33976
|
+
function sourceInput(defval = "Close", key = "source", title = "Source", tooltip) {
|
|
33977
|
+
return { key, title, type: "string", defval, options: SOURCES2, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
33978
|
+
}
|
|
33979
|
+
function colorInput(defval = INFO, title = "Color", key = "color", tooltip) {
|
|
33980
|
+
return { key, title, type: "color", defval, group: STYLE, ...tooltip != null ? { tooltip } : {} };
|
|
33981
|
+
}
|
|
33982
|
+
function widthInput(defval = 2, key = "lineWidth", title = "Line Width") {
|
|
33983
|
+
return { key, title, type: "int", defval, min: 1, max: 5, step: 1, group: STYLE, tooltip: "Width of the plotted line, in pixels." };
|
|
33984
|
+
}
|
|
33985
|
+
function transp(hex, transparency) {
|
|
33986
|
+
return withAlpha2(hex, (100 - transparency) / 100);
|
|
33717
33987
|
}
|
|
33718
33988
|
function withAlpha2(hex, alpha = 0.08) {
|
|
33719
33989
|
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
33720
33990
|
if (!m) return hex;
|
|
33721
|
-
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
33991
|
+
const a = Math.round(Math.max(0, Math.min(1, alpha)) * 255).toString(16).padStart(2, "0");
|
|
33722
33992
|
return `#${m[1]}${a}`;
|
|
33723
33993
|
}
|
|
33994
|
+
function channelStyleInputs(basisTitle = "Basis") {
|
|
33995
|
+
return [
|
|
33996
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the lower band and of the basis while price holds above it."),
|
|
33997
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the upper band and of the basis while price sits below it."),
|
|
33998
|
+
colorInput(NEUTRAL, basisTitle, "basisColor", "Color of the basis line while price sits exactly on it.")
|
|
33999
|
+
];
|
|
34000
|
+
}
|
|
34001
|
+
function directionalChannel(bars, basis, upper, lower, ink, key) {
|
|
34002
|
+
const basisColors = basis.map((b, i) => {
|
|
34003
|
+
if (!Number.isFinite(b)) return null;
|
|
34004
|
+
const c = bars[i].close;
|
|
34005
|
+
return c > b ? ink.bull : c < b ? ink.bear : ink.neutral;
|
|
34006
|
+
});
|
|
34007
|
+
const wash = upper.map((u, i) => {
|
|
34008
|
+
const l = lower[i];
|
|
34009
|
+
if (!Number.isFinite(u) || !Number.isFinite(l)) return null;
|
|
34010
|
+
return { topValue: u, bottomValue: l, topColor: transp(ink.bear, 90), bottomColor: transp(ink.bull, 90) };
|
|
34011
|
+
});
|
|
34012
|
+
return {
|
|
34013
|
+
plots: [
|
|
34014
|
+
{ key: "basis", title: "Basis", values: [...basis], color: ink.neutral, colors: basisColors },
|
|
34015
|
+
{ key: "upper", title: "Upper", values: [...upper], color: transp(ink.bear, 30) },
|
|
34016
|
+
{ key: "lower", title: "Lower", values: [...lower], color: transp(ink.bull, 30) }
|
|
34017
|
+
],
|
|
34018
|
+
bands: [{ key, from: "upper", to: "lower", color: transp(NEUTRAL, 90), gradient: wash }]
|
|
34019
|
+
};
|
|
34020
|
+
}
|
|
34021
|
+
function anchorPlot(key, title, length, level) {
|
|
34022
|
+
return {
|
|
34023
|
+
key,
|
|
34024
|
+
title,
|
|
34025
|
+
values: new Array(length).fill(level),
|
|
34026
|
+
color: transp(NEUTRAL, 100),
|
|
34027
|
+
display: { pane: false, legend: false, dataWindow: false }
|
|
34028
|
+
};
|
|
34029
|
+
}
|
|
34030
|
+
function thresholdZones(length, levels, ink, transparency = 92) {
|
|
34031
|
+
return {
|
|
34032
|
+
plots: [
|
|
34033
|
+
anchorPlot("obAnchor", "Overbought Anchor", length, levels.overbought),
|
|
34034
|
+
anchorPlot("osAnchor", "Oversold Anchor", length, levels.oversold),
|
|
34035
|
+
anchorPlot("topAnchor", "Top Anchor", length, levels.top),
|
|
34036
|
+
anchorPlot("bottomAnchor", "Bottom Anchor", length, levels.bottom)
|
|
34037
|
+
],
|
|
34038
|
+
bands: [
|
|
34039
|
+
{ key: "obZone", from: "obAnchor", to: "topAnchor", color: transp(ink.overbought, transparency) },
|
|
34040
|
+
{ key: "osZone", from: "osAnchor", to: "bottomAnchor", color: transp(ink.oversold, transparency) }
|
|
34041
|
+
]
|
|
34042
|
+
};
|
|
34043
|
+
}
|
|
34044
|
+
function rampColors(values, levels, ink) {
|
|
34045
|
+
return values.map((x) => {
|
|
34046
|
+
if (!Number.isFinite(x)) return null;
|
|
34047
|
+
return x <= levels.mid ? gradient(x, levels.low, levels.mid, ink.low, ink.mid) : gradient(x, levels.mid, levels.high, ink.mid, ink.high);
|
|
34048
|
+
});
|
|
34049
|
+
}
|
|
34050
|
+
function baselineGradient(values, baseline, inkOf, valueTransparency = 50, baseTransparency = 100) {
|
|
34051
|
+
return values.map((x, i) => {
|
|
34052
|
+
if (!Number.isFinite(x)) return null;
|
|
34053
|
+
const ink = inkOf(x, i);
|
|
34054
|
+
const above = x > baseline;
|
|
34055
|
+
return {
|
|
34056
|
+
topValue: Math.max(x, baseline),
|
|
34057
|
+
bottomValue: Math.min(x, baseline),
|
|
34058
|
+
topColor: transp(ink, above ? valueTransparency : baseTransparency),
|
|
34059
|
+
bottomColor: transp(ink, above ? baseTransparency : valueTransparency)
|
|
34060
|
+
};
|
|
34061
|
+
});
|
|
34062
|
+
}
|
|
34063
|
+
function momentumColumnColors(values, ink) {
|
|
34064
|
+
return values.map((x, i) => {
|
|
34065
|
+
if (!Number.isFinite(x)) return null;
|
|
34066
|
+
const prev2 = i > 0 && Number.isFinite(values[i - 1]) ? values[i - 1] : x;
|
|
34067
|
+
const rising = x > prev2;
|
|
34068
|
+
if (x >= 0) return rising ? ink.growAbove : ink.fallAbove;
|
|
34069
|
+
return rising ? ink.growBelow : ink.fallBelow;
|
|
34070
|
+
});
|
|
34071
|
+
}
|
|
34072
|
+
function boundedOscillator(opts) {
|
|
34073
|
+
const mid = (opts.overbought + opts.oversold) / 2;
|
|
34074
|
+
const ramp = rampColors(opts.values, { low: opts.oversold, mid, high: opts.overbought }, opts.ink);
|
|
34075
|
+
const zones = thresholdZones(
|
|
34076
|
+
opts.values.length,
|
|
34077
|
+
{ overbought: opts.overbought, oversold: opts.oversold, top: opts.scale.top, bottom: opts.scale.bottom },
|
|
34078
|
+
{ overbought: opts.ink.high, oversold: opts.ink.low },
|
|
34079
|
+
opts.zoneTransparency
|
|
34080
|
+
);
|
|
34081
|
+
const plots = [{ key: opts.key, title: opts.title, values: opts.values, color: opts.ink.mid, width: opts.width ?? 1, colors: ramp }, ...zones.plots];
|
|
34082
|
+
const bands = [...zones.bands];
|
|
34083
|
+
if (opts.bandFill != null) bands.push({ key: "band", from: "obAnchor", to: "osAnchor", color: opts.bandFill });
|
|
34084
|
+
if (opts.midpointWash) {
|
|
34085
|
+
plots.push(anchorPlot("midAnchor", "Midpoint Anchor", opts.values.length, mid));
|
|
34086
|
+
bands.push({ key: "wash", from: opts.key, to: "midAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(opts.values, mid, (_x, i) => ramp[i] ?? opts.ink.mid) });
|
|
34087
|
+
}
|
|
34088
|
+
const levels = [
|
|
34089
|
+
{ key: "overbought", price: opts.overbought, color: opts.levelInk, lineStyle: "dashed", title: opts.levelTitles?.overbought ?? "Overbought" },
|
|
34090
|
+
{ key: "oversold", price: opts.oversold, color: opts.levelInk, lineStyle: "dashed", title: opts.levelTitles?.oversold ?? "Oversold" }
|
|
34091
|
+
];
|
|
34092
|
+
if (opts.midline != null) levels.splice(1, 0, { key: "midline", price: opts.midline, color: opts.levelInk, lineStyle: "dotted", title: "Midline" });
|
|
34093
|
+
return { plots, bands, levels };
|
|
34094
|
+
}
|
|
34095
|
+
function centeredOscillator(opts) {
|
|
34096
|
+
const baseline = opts.baseline ?? 0;
|
|
34097
|
+
const inkOf = (x) => x > baseline ? opts.ink.bull : x < baseline ? opts.ink.bear : opts.ink.neutral;
|
|
34098
|
+
const plots = [
|
|
34099
|
+
{ key: opts.key, title: opts.title, values: opts.values, color: opts.ink.neutral, width: opts.width ?? 1, colors: opts.values.map((x) => Number.isFinite(x) ? inkOf(x) : null) },
|
|
34100
|
+
...opts.extraPlots ?? []
|
|
34101
|
+
];
|
|
34102
|
+
const bands = [];
|
|
34103
|
+
if (opts.fill !== false) {
|
|
34104
|
+
plots.push(anchorPlot("baseline", "Baseline Anchor", opts.values.length, baseline));
|
|
34105
|
+
bands.push({ key: "wash", from: opts.key, to: "baseline", color: transp(NEUTRAL, 100), gradient: baselineGradient(opts.values, baseline, inkOf, opts.valueTransparency) });
|
|
34106
|
+
}
|
|
34107
|
+
return {
|
|
34108
|
+
plots,
|
|
34109
|
+
bands,
|
|
34110
|
+
levels: [{ key: "zero", price: baseline, color: opts.zeroInk, lineStyle: opts.zeroLineStyle ?? "dashed", title: opts.zeroTitle ?? "Zero Line" }, ...opts.extraLevels ?? []]
|
|
34111
|
+
};
|
|
34112
|
+
}
|
|
34113
|
+
function channels(hex) {
|
|
34114
|
+
const m = /^#([0-9a-f]{6})/i.exec(hex.trim());
|
|
34115
|
+
if (!m) return null;
|
|
34116
|
+
const n = parseInt(m[1], 16);
|
|
34117
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
34118
|
+
}
|
|
34119
|
+
function gradient(value, low, high, from, to) {
|
|
34120
|
+
const a = channels(from);
|
|
34121
|
+
const b = channels(to);
|
|
34122
|
+
if (!a || !b || !Number.isFinite(value)) return from;
|
|
34123
|
+
const span = high - low;
|
|
34124
|
+
const t = span === 0 ? 0 : Math.max(0, Math.min(1, (value - low) / span));
|
|
34125
|
+
const mix2 = (i) => Math.round(a[i] + (b[i] - a[i]) * t).toString(16).padStart(2, "0");
|
|
34126
|
+
return `#${mix2(0)}${mix2(1)}${mix2(2)}`;
|
|
34127
|
+
}
|
|
33724
34128
|
|
|
33725
34129
|
// src/core/native-indicators/classics/averages.ts
|
|
33726
|
-
var
|
|
33727
|
-
var
|
|
34130
|
+
var MA_TYPES = ["SMA", "EMA", "WMA", "RMA (SMMA)", "HMA", "ALMA", "VWMA", "DEMA", "TEMA", "KAMA", "LSMA", "McGinley", "Hamming"];
|
|
34131
|
+
var ENVELOPE_MA_TYPES = ["SMA", "EMA", "WMA", "RMA (SMMA)", "HMA", "VWMA"];
|
|
34132
|
+
function movingAverageOf(kind, src, bars, len, opts = {}) {
|
|
34133
|
+
switch (kind) {
|
|
34134
|
+
case "EMA":
|
|
34135
|
+
return ema(src, len);
|
|
34136
|
+
case "WMA":
|
|
34137
|
+
return wma(src, len);
|
|
34138
|
+
case "RMA":
|
|
34139
|
+
case "RMA (SMMA)":
|
|
34140
|
+
return rma(src, len);
|
|
34141
|
+
case "HMA":
|
|
34142
|
+
return hma(src, len);
|
|
34143
|
+
case "ALMA":
|
|
34144
|
+
return alma(src, len, opts.almaOffset ?? 0.85, opts.almaSigma ?? 6);
|
|
34145
|
+
case "VWMA":
|
|
34146
|
+
return vwma(src, volumes(bars), len);
|
|
34147
|
+
case "DEMA":
|
|
34148
|
+
return dema(src, len);
|
|
34149
|
+
case "TEMA":
|
|
34150
|
+
return tema(src, len);
|
|
34151
|
+
case "KAMA":
|
|
34152
|
+
return kama(src, len);
|
|
34153
|
+
case "LSMA":
|
|
34154
|
+
return linreg(src, len, opts.lsmaOffset ?? 0);
|
|
34155
|
+
case "McGinley":
|
|
34156
|
+
return mcginley(src, len);
|
|
34157
|
+
case "Hamming":
|
|
34158
|
+
return hamming(src, len);
|
|
34159
|
+
default:
|
|
34160
|
+
return sma(src, len);
|
|
34161
|
+
}
|
|
34162
|
+
}
|
|
34163
|
+
function slopeColors(values, bull, bear, flat) {
|
|
34164
|
+
return values.map((x, i) => {
|
|
34165
|
+
if (!Number.isFinite(x)) return null;
|
|
34166
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
34167
|
+
if (!Number.isFinite(prev2)) return flat;
|
|
34168
|
+
return x > prev2 ? bull : x < prev2 ? bear : flat;
|
|
34169
|
+
});
|
|
34170
|
+
}
|
|
34171
|
+
function directionInputs() {
|
|
34172
|
+
return [
|
|
34173
|
+
boolInput("trendColor", "Slope Trend Coloring", true, STYLE, "Colors the line by its slope \u2014 bullish while it rises, bearish while it falls. Disable to draw it in the neutral color."),
|
|
34174
|
+
colorInput(BULLISH, "Bullish", "bullColor"),
|
|
34175
|
+
colorInput(BEARISH, "Bearish", "bearColor"),
|
|
34176
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor"),
|
|
34177
|
+
widthInput()
|
|
34178
|
+
];
|
|
34179
|
+
}
|
|
34180
|
+
function slopeColoredPlot(key, title, values, inputs) {
|
|
34181
|
+
const neutral = str3(inputs, "neutralColor", NEUTRAL);
|
|
34182
|
+
const colored = bool(inputs, "trendColor", true);
|
|
34183
|
+
return {
|
|
34184
|
+
key,
|
|
34185
|
+
title,
|
|
34186
|
+
values,
|
|
34187
|
+
color: neutral,
|
|
34188
|
+
width: num3(inputs, "lineWidth", 2),
|
|
34189
|
+
...colored ? { colors: slopeColors(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), neutral) } : {}
|
|
34190
|
+
};
|
|
34191
|
+
}
|
|
34192
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1, group: SETTINGS, tooltip: "Bars the plotted line is displaced by. Positive values push it to the right." };
|
|
33728
34193
|
function fixedAverage(type, title, shortTitle, smooth) {
|
|
33729
34194
|
return {
|
|
33730
34195
|
type,
|
|
33731
34196
|
title,
|
|
33732
34197
|
shortTitle,
|
|
33733
34198
|
overlay: true,
|
|
33734
|
-
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
34199
|
+
inputs: [lengthInput(20, "length", "Length", 5e3, `Number of bars used in the ${shortTitle} calculation.`), sourceInput("Close", "source", "Source", `Price series the ${shortTitle} is calculated on.`), offsetInput, colorInput()],
|
|
33735
34200
|
compute: (bars, inputs) => {
|
|
33736
34201
|
const len = num3(inputs, "length", 20);
|
|
33737
34202
|
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
33738
34203
|
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
33739
34204
|
if (offset !== 0) values = shift(values, offset);
|
|
33740
|
-
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color",
|
|
34205
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
33741
34206
|
}
|
|
33742
34207
|
};
|
|
33743
34208
|
}
|
|
@@ -33749,36 +34214,37 @@ var movingAverage = {
|
|
|
33749
34214
|
shortTitle: "MA",
|
|
33750
34215
|
overlay: true,
|
|
33751
34216
|
inputs: [
|
|
33752
|
-
|
|
33753
|
-
lengthInput(20),
|
|
33754
|
-
sourceInput(),
|
|
34217
|
+
optionInput("maType", "MA 1 Type", "SMA", MA_TYPES, "Moving average calculation used for MA 1."),
|
|
34218
|
+
lengthInput(20, "length", "MA 1 Length", 5e3, "Number of bars used in the MA 1 calculation."),
|
|
34219
|
+
sourceInput("Close", "source", "MA 1 Source", "Price series MA 1 is calculated on."),
|
|
34220
|
+
boolInput("showMa2", "Show MA 2", false, SETTINGS, "Displays the optional second moving average."),
|
|
34221
|
+
optionInput("ma2Type", "MA 2 Type", "SMA", MA_TYPES, "Moving average calculation used for MA 2."),
|
|
34222
|
+
lengthInput(50, "ma2Length", "MA 2 Length", 5e3, "Number of bars used in the MA 2 calculation."),
|
|
34223
|
+
sourceInput("Close", "ma2Source", "MA 2 Source", "Price series MA 2 is calculated on."),
|
|
34224
|
+
floatInput("almaOffset", "ALMA Offset", 0.85, 0, 1, 0.05, "ALMA type only. Values near 1 make the average more responsive, values near 0 make it smoother."),
|
|
34225
|
+
floatInput("almaSigma", "ALMA Sigma", 6, 0.1, 100, 0.5, "ALMA type only. Larger values widen the Gaussian window for a smoother average."),
|
|
34226
|
+
intInput("lsmaOffset", "LSMA Offset", 0, 0, 500, "LSMA type only. Bar offset applied to the linear regression value."),
|
|
33755
34227
|
offsetInput,
|
|
33756
|
-
colorInput()
|
|
34228
|
+
colorInput(INFO, "MA 1", "color", "MA 1 line color."),
|
|
34229
|
+
colorInput(WARNING, "MA 2", "ma2Color", "MA 2 line color.")
|
|
33757
34230
|
],
|
|
33758
34231
|
compute: (bars, inputs) => {
|
|
33759
|
-
const
|
|
33760
|
-
|
|
34232
|
+
const opts = {
|
|
34233
|
+
almaOffset: num3(inputs, "almaOffset", 0.85),
|
|
34234
|
+
almaSigma: num3(inputs, "almaSigma", 6),
|
|
34235
|
+
lsmaOffset: Math.trunc(num3(inputs, "lsmaOffset", 0))
|
|
34236
|
+
};
|
|
34237
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
34238
|
+
const displace = (v) => offset === 0 ? v : shift(v, offset);
|
|
33761
34239
|
const kind = str3(inputs, "maType", "SMA");
|
|
33762
|
-
|
|
33763
|
-
|
|
33764
|
-
|
|
33765
|
-
|
|
33766
|
-
|
|
33767
|
-
|
|
33768
|
-
values = wma(src, len);
|
|
33769
|
-
break;
|
|
33770
|
-
case "RMA":
|
|
33771
|
-
values = rma(src, len);
|
|
33772
|
-
break;
|
|
33773
|
-
case "VWMA":
|
|
33774
|
-
values = vwma(src, volumes(bars), len);
|
|
33775
|
-
break;
|
|
33776
|
-
default:
|
|
33777
|
-
values = sma(src, len);
|
|
34240
|
+
const ma1 = displace(movingAverageOf(kind, sourceValues(bars, str3(inputs, "source", "Close")), bars, num3(inputs, "length", 20), opts));
|
|
34241
|
+
const plots = [{ key: "ma", title: kind, values: ma1, color: str3(inputs, "color", INFO), width: 2 }];
|
|
34242
|
+
if (bool(inputs, "showMa2", false)) {
|
|
34243
|
+
const kind2 = str3(inputs, "ma2Type", "SMA");
|
|
34244
|
+
const ma2 = displace(movingAverageOf(kind2, sourceValues(bars, str3(inputs, "ma2Source", "Close")), bars, num3(inputs, "ma2Length", 50), opts));
|
|
34245
|
+
plots.push({ key: "ma2", title: `${kind2} 2`, values: ma2, color: str3(inputs, "ma2Color", WARNING), width: 2 });
|
|
33778
34246
|
}
|
|
33779
|
-
|
|
33780
|
-
if (offset !== 0) values = shift(values, offset);
|
|
33781
|
-
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
34247
|
+
return { plots };
|
|
33782
34248
|
}
|
|
33783
34249
|
};
|
|
33784
34250
|
var smoothedMa = {
|
|
@@ -33786,17 +34252,13 @@ var smoothedMa = {
|
|
|
33786
34252
|
title: "Smoothed Moving Average",
|
|
33787
34253
|
shortTitle: "RMA",
|
|
33788
34254
|
overlay: true,
|
|
33789
|
-
inputs: [
|
|
34255
|
+
inputs: [
|
|
34256
|
+
sourceInput("Close", "source", "Source", "Price series the average smooths. Wilder computed his 1978 indicators on the close, but the recursion accepts any source."),
|
|
34257
|
+
lengthInput(14, "length", "RMA Length", 5e3, "Number of bars N in Wilder's smoothing: the line keeps (N \u2212 1)/N of its previous value and blends in 1/N of the new price. That alpha is smaller than an EMA's, so an N-period RMA responds like a (2N \u2212 1)-period EMA."),
|
|
34258
|
+
...directionInputs()
|
|
34259
|
+
],
|
|
33790
34260
|
compute: (bars, inputs) => ({
|
|
33791
|
-
plots: [
|
|
33792
|
-
{
|
|
33793
|
-
key: "rma",
|
|
33794
|
-
title: "RMA",
|
|
33795
|
-
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
33796
|
-
color: str3(inputs, "color", SERIES_LINE),
|
|
33797
|
-
width: 2
|
|
33798
|
-
}
|
|
33799
|
-
]
|
|
34261
|
+
plots: [slopeColoredPlot("rma", "RMA", rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)), inputs)]
|
|
33800
34262
|
})
|
|
33801
34263
|
};
|
|
33802
34264
|
var zlema = {
|
|
@@ -33804,61 +34266,86 @@ var zlema = {
|
|
|
33804
34266
|
title: "Zero-Lag Exponential Moving Average",
|
|
33805
34267
|
shortTitle: "ZLEMA",
|
|
33806
34268
|
overlay: true,
|
|
33807
|
-
inputs: [
|
|
34269
|
+
inputs: [
|
|
34270
|
+
sourceInput("Close", "source", "Source", "Price series the average de-lags and then smooths."),
|
|
34271
|
+
lengthInput(21, "length", "ZLEMA Length", 5e3, "EMA length N. It sets both the smoothing (alpha = 2 / (N + 1)) and the correction: an EMA of length N lags by about (N \u2212 1)/2 bars, and the de-lagging step is sized to cancel exactly that."),
|
|
34272
|
+
...directionInputs()
|
|
34273
|
+
],
|
|
33808
34274
|
compute: (bars, inputs) => {
|
|
33809
|
-
const len = num3(inputs, "length",
|
|
34275
|
+
const len = num3(inputs, "length", 21);
|
|
33810
34276
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
33811
|
-
const lag = Math.
|
|
33812
|
-
const
|
|
33813
|
-
|
|
33814
|
-
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
34277
|
+
const lag = Math.round((len - 1) / 2);
|
|
34278
|
+
const delagged = zip(src, shift(src, lag), (a, b) => 2 * a - b);
|
|
34279
|
+
return { plots: [slopeColoredPlot("zlema", "ZLEMA", ema(delagged, len), inputs)] };
|
|
33815
34280
|
}
|
|
33816
34281
|
};
|
|
34282
|
+
var MOMENTUM_SCALER = "Momentum (|CMO|)";
|
|
34283
|
+
var VOLATILITY_SCALER = "Volatility (StDev Ratio)";
|
|
34284
|
+
var REGIME = "Trend Regime";
|
|
33817
34285
|
var vidya = {
|
|
33818
34286
|
type: "vidya",
|
|
33819
34287
|
title: "Variable Index Dynamic Average",
|
|
33820
34288
|
shortTitle: "VIDYA",
|
|
33821
34289
|
overlay: true,
|
|
33822
|
-
inputs: [
|
|
34290
|
+
inputs: [
|
|
34291
|
+
sourceInput("Close", "source", "Source", "Price series the average smooths. VIDYA is classically computed on the close."),
|
|
34292
|
+
lengthInput(12, "length", "VIDYA Length", 5e3, "EMA-equivalent length N of the base smoothing constant, alpha = 2 / (N + 1). The adaptive scaler rescales this base speed bar by bar."),
|
|
34293
|
+
optionInput("scaler", "Adaptive Scaler", MOMENTUM_SCALER, [MOMENTUM_SCALER, VOLATILITY_SCALER], "Bar-by-bar measure that rescales the smoothing constant. Momentum divides the absolute Chande Momentum Oscillator by 100; Volatility is Chande's original short-term over reference standard deviation."),
|
|
34294
|
+
intInput("scalerLength", "Scaler Lookback", 9, 1, 5e3, "Lookback of the adaptive scaler \u2014 the CMO period in momentum mode, or the short-term standard deviation window in volatility mode."),
|
|
34295
|
+
intInput("refLength", "Reference StDev Length", 30, 2, 5e3, "Volatility mode only \u2014 window of the reference standard deviation the short-term reading is divided by."),
|
|
34296
|
+
{ ...floatInput("flatThreshold", "Flat Threshold", 0.05, 0, 10, 0.01, "One-bar change of VIDYA, as a fraction of ATR, below which the line reads as flat."), group: REGIME },
|
|
34297
|
+
{ ...intInput("atrLength", "ATR Normalization Length", 14, 1, 5e3, "ATR window used to normalize VIDYA's one-bar slope, so the flat threshold behaves consistently across symbols and timeframes."), group: REGIME },
|
|
34298
|
+
boolInput("trendColor", "Trend Regime Coloring", true, STYLE, "Colors VIDYA by its regime read \u2014 bullish while sloping up beyond the flat threshold, bearish while sloping down, flat inside congestion."),
|
|
34299
|
+
boolInput("adaptiveGradient", "Adaptive Gradient", true, STYLE, "Blends the trend color toward the flat color according to the adaptive scaler, so the line pales as momentum dies."),
|
|
34300
|
+
colorInput(BULLISH, "Bullish", "bullColor"),
|
|
34301
|
+
colorInput(BEARISH, "Bearish", "bearColor"),
|
|
34302
|
+
colorInput(NEUTRAL, "Flat", "flatColor"),
|
|
34303
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor"),
|
|
34304
|
+
widthInput()
|
|
34305
|
+
],
|
|
33823
34306
|
compute: (bars, inputs) => {
|
|
33824
|
-
const len = num3(inputs, "length",
|
|
33825
|
-
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
34307
|
+
const len = num3(inputs, "length", 12);
|
|
33826
34308
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
33827
|
-
const
|
|
33828
|
-
const
|
|
33829
|
-
const
|
|
33830
|
-
|
|
33831
|
-
|
|
33832
|
-
|
|
33833
|
-
|
|
33834
|
-
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
33835
|
-
const x = d[k];
|
|
33836
|
-
if (!Number.isFinite(x)) {
|
|
33837
|
-
ok = false;
|
|
33838
|
-
break;
|
|
33839
|
-
}
|
|
33840
|
-
if (x > 0) u += x;
|
|
33841
|
-
else w -= x;
|
|
33842
|
-
}
|
|
33843
|
-
if (ok) {
|
|
33844
|
-
su[i] = u;
|
|
33845
|
-
sd[i] = w;
|
|
33846
|
-
}
|
|
33847
|
-
}
|
|
33848
|
-
const alpha = 2 / (len + 1);
|
|
34309
|
+
const scalerLen = num3(inputs, "scalerLength", 9);
|
|
34310
|
+
const momentum = map(cmo(src, scalerLen), (x) => Math.abs(x) / 100);
|
|
34311
|
+
const shortDev = stdev(src, scalerLen);
|
|
34312
|
+
const refDev = stdev(src, num3(inputs, "refLength", 30));
|
|
34313
|
+
const volatility = zip(shortDev, refDev, (s, r) => r > 0 ? s / r : 0);
|
|
34314
|
+
const scale = str3(inputs, "scaler", MOMENTUM_SCALER) === VOLATILITY_SCALER ? volatility : momentum;
|
|
34315
|
+
const alphaBase = 2 / (len + 1);
|
|
33849
34316
|
const out = new Array(src.length).fill(Number.NaN);
|
|
33850
34317
|
let prev2 = Number.NaN;
|
|
33851
34318
|
for (let i = 0; i < src.length; i++) {
|
|
33852
34319
|
const x = src[i];
|
|
33853
|
-
|
|
33854
|
-
const
|
|
33855
|
-
if (!Number.isFinite(
|
|
33856
|
-
|
|
33857
|
-
|
|
33858
|
-
|
|
34320
|
+
if (!Number.isFinite(x)) continue;
|
|
34321
|
+
const k = scale[i];
|
|
34322
|
+
if (!Number.isFinite(prev2) || !Number.isFinite(k)) prev2 = x;
|
|
34323
|
+
else {
|
|
34324
|
+
const alpha = Math.min(alphaBase * k, 1);
|
|
34325
|
+
prev2 = alpha * x + (1 - alpha) * prev2;
|
|
34326
|
+
}
|
|
33859
34327
|
out[i] = prev2;
|
|
33860
34328
|
}
|
|
33861
|
-
|
|
34329
|
+
const neutral = str3(inputs, "neutralColor", NEUTRAL);
|
|
34330
|
+
if (!bool(inputs, "trendColor", true)) {
|
|
34331
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: neutral, width: num3(inputs, "lineWidth", 2) }] };
|
|
34332
|
+
}
|
|
34333
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
34334
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
34335
|
+
const flat = str3(inputs, "flatColor", NEUTRAL);
|
|
34336
|
+
const threshold = num3(inputs, "flatThreshold", 0.05);
|
|
34337
|
+
const range = atr(bars, num3(inputs, "atrLength", 14));
|
|
34338
|
+
const fade = bool(inputs, "adaptiveGradient", true);
|
|
34339
|
+
const colors = out.map((x, i) => {
|
|
34340
|
+
if (!Number.isFinite(x)) return null;
|
|
34341
|
+
const r = range[i];
|
|
34342
|
+
const slope = i > 0 && Number.isFinite(out[i - 1]) ? x - out[i - 1] : 0;
|
|
34343
|
+
const normalized = Number.isFinite(r) && r !== 0 ? slope / r : 0;
|
|
34344
|
+
const ink = normalized > threshold ? bull : normalized < -threshold ? bear : null;
|
|
34345
|
+
if (ink == null) return flat;
|
|
34346
|
+
return fade ? gradient(Math.min(scale[i] ?? 0, 1), 0, 1, flat, ink) : ink;
|
|
34347
|
+
});
|
|
34348
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: flat, width: num3(inputs, "lineWidth", 2), colors }] };
|
|
33862
34349
|
}
|
|
33863
34350
|
};
|
|
33864
34351
|
var maEnvelope = {
|
|
@@ -33867,115 +34354,218 @@ var maEnvelope = {
|
|
|
33867
34354
|
shortTitle: "MA Env",
|
|
33868
34355
|
overlay: true,
|
|
33869
34356
|
inputs: [
|
|
33870
|
-
|
|
33871
|
-
lengthInput(20),
|
|
33872
|
-
sourceInput(),
|
|
33873
|
-
|
|
33874
|
-
|
|
34357
|
+
optionInput("maType", "MA Type", "SMA", ENVELOPE_MA_TYPES, "Moving average type used as the envelope basis."),
|
|
34358
|
+
{ ...lengthInput(20, "length", "Length", 5e3, "Number of bars in the basis moving average."), min: 2 },
|
|
34359
|
+
sourceInput("Close", "source", "Source", "Price series the basis moving average is calculated on."),
|
|
34360
|
+
floatInput("percent", "Percent", 2.5, 0, 50, 0.1, "Envelope offset as a percentage of the basis. Upper = basis \xD7 (1 + percent / 100), lower = basis \xD7 (1 \u2212 percent / 100)."),
|
|
34361
|
+
...channelStyleInputs()
|
|
33875
34362
|
],
|
|
33876
34363
|
compute: (bars, inputs) => {
|
|
33877
|
-
const len = num3(inputs, "length", 20);
|
|
33878
34364
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
33879
|
-
const basis = str3(inputs, "maType", "SMA")
|
|
34365
|
+
const basis = movingAverageOf(str3(inputs, "maType", "SMA"), src, bars, num3(inputs, "length", 20));
|
|
33880
34366
|
const k = num3(inputs, "percent", 2.5) / 100;
|
|
33881
|
-
const ink = str3(inputs, "
|
|
33882
|
-
return
|
|
33883
|
-
plots: [
|
|
33884
|
-
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
33885
|
-
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
33886
|
-
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
33887
|
-
],
|
|
33888
|
-
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
33889
|
-
};
|
|
34367
|
+
const ink = { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "basisColor", NEUTRAL) };
|
|
34368
|
+
return directionalChannel(bars, basis, map(basis, (x) => x * (1 + k)), map(basis, (x) => x * (1 - k)), ink, "envelope");
|
|
33890
34369
|
}
|
|
33891
34370
|
};
|
|
34371
|
+
var LINREG_CURVE = "Curve";
|
|
34372
|
+
var LINREG_SLOPE = "Slope";
|
|
33892
34373
|
var linearRegression = {
|
|
33893
34374
|
type: "linear-regression",
|
|
33894
34375
|
title: "Linear Regression Curve",
|
|
33895
34376
|
shortTitle: "LinReg",
|
|
33896
|
-
|
|
33897
|
-
|
|
33898
|
-
|
|
33899
|
-
plots
|
|
33900
|
-
|
|
33901
|
-
|
|
33902
|
-
|
|
33903
|
-
|
|
33904
|
-
|
|
33905
|
-
|
|
33906
|
-
|
|
33907
|
-
|
|
33908
|
-
|
|
34377
|
+
// Slope lives in its own pane; Curve force-overlays itself onto the price candles.
|
|
34378
|
+
overlay: false,
|
|
34379
|
+
inputs: [
|
|
34380
|
+
optionInput("mode", "Mode", LINREG_CURVE, [LINREG_CURVE, LINREG_SLOPE], "Curve plots the regression value on the price chart; Slope plots the per-bar slope of the fit in the pane."),
|
|
34381
|
+
{ ...lengthInput(100, "length", "Length", 5e3, "Number of bars used to fit the linear regression."), min: 2 },
|
|
34382
|
+
sourceInput("Close", "source", "Source", "Price series the regression is fitted to."),
|
|
34383
|
+
colorInput(BULLISH, "Rising", "bullColor", "Color of the regression curve and slope line while the fit slopes upward."),
|
|
34384
|
+
colorInput(BEARISH, "Falling", "bearColor", "Color of the regression curve and slope line while the fit slopes downward."),
|
|
34385
|
+
colorInput(NEUTRAL, "Regression Line", "lineColor", "Color of the regression curve and slope line while the fit is flat.")
|
|
34386
|
+
],
|
|
34387
|
+
compute: (bars, inputs) => {
|
|
34388
|
+
const len = num3(inputs, "length", 100);
|
|
34389
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
34390
|
+
const curve = linreg(src, len, 0);
|
|
34391
|
+
const slope = zip(curve, linreg(src, len, 1), (a, b) => a - b);
|
|
34392
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
34393
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
34394
|
+
const flat = str3(inputs, "lineColor", NEUTRAL);
|
|
34395
|
+
const colors = slope.map((s) => Number.isFinite(s) ? s > 0 ? bull : s < 0 ? bear : flat : null);
|
|
34396
|
+
if (str3(inputs, "mode", LINREG_CURVE) === LINREG_SLOPE) {
|
|
34397
|
+
return {
|
|
34398
|
+
plots: [{ key: "slope", title: "Slope", values: slope, color: flat, width: 2, colors }],
|
|
34399
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed" }]
|
|
34400
|
+
};
|
|
34401
|
+
}
|
|
34402
|
+
return { plots: [{ key: "curve", title: "Curve", values: curve, color: flat, width: 2, colors, overlay: true }] };
|
|
34403
|
+
}
|
|
33909
34404
|
};
|
|
33910
34405
|
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
33911
34406
|
|
|
33912
34407
|
// src/core/native-indicators/classics/bands.ts
|
|
34408
|
+
var JAW_COLOR = "#ab47bc";
|
|
34409
|
+
var TEETH_COLOR = WARNING;
|
|
34410
|
+
var LIPS_COLOR = "#ffd700";
|
|
33913
34411
|
function bollinger(bars, inputs) {
|
|
33914
34412
|
const len = num3(inputs, "length", 20);
|
|
33915
34413
|
const mult = num3(inputs, "mult", 2);
|
|
33916
34414
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
33917
34415
|
const basis = sma(src, len);
|
|
33918
34416
|
const dev = map(stdev(src, len), (x) => x * mult);
|
|
33919
|
-
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
34417
|
+
return { src, basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
34418
|
+
}
|
|
34419
|
+
function bollingerInputs(multMin, multStep) {
|
|
34420
|
+
return [
|
|
34421
|
+
{ ...lengthInput(20, "length", "Length", 5e3, "Number of bars used for the Bollinger Bands basis (a simple moving average) and for the standard deviation."), min: 2 },
|
|
34422
|
+
sourceInput("Close", "source", "Source", "Price series the bands are computed from."),
|
|
34423
|
+
floatInput("mult", "Multiplier", 2, multMin, 50, multStep, "Standard deviation multiplier for the upper and lower bands.")
|
|
34424
|
+
];
|
|
33920
34425
|
}
|
|
33921
|
-
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
33922
34426
|
var bollingerBands = {
|
|
33923
34427
|
type: "bollinger-bands",
|
|
33924
34428
|
title: "Bollinger Bands",
|
|
33925
34429
|
shortTitle: "BB",
|
|
33926
34430
|
overlay: true,
|
|
33927
|
-
inputs: [
|
|
34431
|
+
inputs: [
|
|
34432
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used to calculate the basis SMA and the standard deviation."),
|
|
34433
|
+
sourceInput("Close", "source", "Source", "Price series the bands are calculated on."),
|
|
34434
|
+
floatInput("mult", "Multiplier", 2, 0, 50, 0.1, "Number of standard deviations added above and below the basis to form the bands."),
|
|
34435
|
+
colorInput(WARNING, "Basis", "basisColor", "Color of the basis (middle band) moving average."),
|
|
34436
|
+
colorInput(INFO, "Bands", "color", "Color of the upper and lower bands."),
|
|
34437
|
+
colorInput(transp(INFO, 92), "Fill", "fillColor", "Fill color of the area between the upper and lower bands.")
|
|
34438
|
+
],
|
|
33928
34439
|
compute: (bars, inputs) => {
|
|
33929
34440
|
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
33930
|
-
const ink = str3(inputs, "color",
|
|
34441
|
+
const ink = str3(inputs, "color", INFO);
|
|
33931
34442
|
return {
|
|
33932
34443
|
plots: [
|
|
33933
|
-
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
34444
|
+
{ key: "basis", title: "Basis", values: basis, color: str3(inputs, "basisColor", WARNING) },
|
|
33934
34445
|
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
33935
34446
|
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
33936
34447
|
],
|
|
33937
|
-
bands: [{ key: "bb", from: "upper", to: "lower", color:
|
|
34448
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: str3(inputs, "fillColor", transp(INFO, 92)) }]
|
|
33938
34449
|
};
|
|
33939
34450
|
}
|
|
33940
34451
|
};
|
|
34452
|
+
var PERCENT_B_MID = 0.5;
|
|
34453
|
+
var THRESHOLDS = "Thresholds";
|
|
33941
34454
|
var percentB = {
|
|
33942
34455
|
type: "percent-b",
|
|
33943
34456
|
title: "Bollinger Bands %B",
|
|
33944
34457
|
shortTitle: "%B",
|
|
33945
34458
|
overlay: false,
|
|
33946
|
-
inputs: [
|
|
34459
|
+
inputs: [
|
|
34460
|
+
...bollingerInputs(1e-3, 0.25),
|
|
34461
|
+
{ ...floatInput("overbought", "Overbought", 1, -10, 10, 0.05, "Level treated as overbought. 1 is the upper band itself, so readings above the default mean price sits outside the bands."), group: THRESHOLDS },
|
|
34462
|
+
{ ...floatInput("oversold", "Oversold", 0, -10, 10, 0.05, "Level treated as oversold. 0 is the lower band itself, so readings below the default mean price sits outside the bands."), group: THRESHOLDS },
|
|
34463
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of %B above the 0.5 midline, where price trades in the upper half of the bands."),
|
|
34464
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of %B below the 0.5 midline, where price trades in the lower half of the bands."),
|
|
34465
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Vertical gradient between %B and the 0.5 midline, fully transparent at the midline and more opaque at the oscillator value."),
|
|
34466
|
+
colorInput(NEUTRAL, "Levels Color", "levelsColor", "Color of the dashed overbought/oversold levels and of the dotted midline.")
|
|
34467
|
+
],
|
|
33947
34468
|
compute: (bars, inputs) => {
|
|
33948
|
-
const { upper, lower } = bollinger(bars, inputs);
|
|
33949
|
-
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
34469
|
+
const { src, upper, lower } = bollinger(bars, inputs);
|
|
33950
34470
|
const values = src.map((x, i) => {
|
|
33951
34471
|
const u = upper[i];
|
|
33952
34472
|
const l = lower[i];
|
|
33953
34473
|
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
33954
34474
|
});
|
|
34475
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
34476
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
34477
|
+
const levels = str3(inputs, "levelsColor", NEUTRAL);
|
|
34478
|
+
const colors = values.map((x) => Number.isFinite(x) ? x > PERCENT_B_MID ? bull : bear : null);
|
|
34479
|
+
const plots = [
|
|
34480
|
+
{ key: "pb", title: "%B", values, color: bear, colors },
|
|
34481
|
+
{ key: "mid", title: "Midline", values: values.map((x) => Number.isFinite(x) ? PERCENT_B_MID : Number.NaN), color: levels, display: { pane: false, legend: false, dataWindow: false } }
|
|
34482
|
+
];
|
|
34483
|
+
const bands = bool(inputs, "gradientFill", true) ? [
|
|
34484
|
+
{
|
|
34485
|
+
key: "pbFill",
|
|
34486
|
+
from: "pb",
|
|
34487
|
+
to: "mid",
|
|
34488
|
+
color: transp(NEUTRAL, 100),
|
|
34489
|
+
// Opaque at the oscillator, invisible where it meets the midline.
|
|
34490
|
+
gradient: values.map((x) => {
|
|
34491
|
+
if (!Number.isFinite(x)) return null;
|
|
34492
|
+
const up = x > PERCENT_B_MID;
|
|
34493
|
+
const ink = up ? bull : bear;
|
|
34494
|
+
return {
|
|
34495
|
+
topValue: Math.max(x, PERCENT_B_MID),
|
|
34496
|
+
bottomValue: Math.min(x, PERCENT_B_MID),
|
|
34497
|
+
topColor: up ? transp(ink, 50) : transp(ink, 100),
|
|
34498
|
+
bottomColor: up ? transp(ink, 100) : transp(ink, 50)
|
|
34499
|
+
};
|
|
34500
|
+
})
|
|
34501
|
+
}
|
|
34502
|
+
] : [];
|
|
33955
34503
|
return {
|
|
33956
|
-
plots
|
|
34504
|
+
plots,
|
|
34505
|
+
bands,
|
|
33957
34506
|
levels: [
|
|
33958
|
-
{ key: "
|
|
33959
|
-
{ key: "
|
|
33960
|
-
{ key: "
|
|
34507
|
+
{ key: "overbought", price: num3(inputs, "overbought", 1), color: levels, lineStyle: "dashed", title: "Overbought" },
|
|
34508
|
+
{ key: "oversold", price: num3(inputs, "oversold", 0), color: levels, lineStyle: "dashed", title: "Oversold" },
|
|
34509
|
+
{ key: "midline", price: PERCENT_B_MID, color: transp(levels, 50), lineStyle: "dotted", title: "Midline" }
|
|
33961
34510
|
]
|
|
33962
34511
|
};
|
|
33963
34512
|
}
|
|
33964
34513
|
};
|
|
34514
|
+
var SQUEEZE_BULGE = "Squeeze & Bulge";
|
|
33965
34515
|
var bandwidth = {
|
|
33966
34516
|
type: "bandwidth",
|
|
33967
34517
|
title: "Bollinger Bands Width",
|
|
33968
34518
|
shortTitle: "BandWidth",
|
|
33969
34519
|
overlay: false,
|
|
33970
|
-
inputs: [
|
|
34520
|
+
inputs: [
|
|
34521
|
+
...bollingerInputs(1e-3, 0.25),
|
|
34522
|
+
{ ...intInput("lookback", "Reference Lookback", 125, 2, 5e3, "Window used to locate the lowest (Squeeze) and highest (Bulge) BandWidth references."), group: SQUEEZE_BULGE },
|
|
34523
|
+
boolInput("showSqueeze", "Show Squeeze Level", true, SQUEEZE_BULGE, "Plots the lowest BandWidth of the reference lookback \u2014 unusually compressed volatility, a condition rather than a directional signal."),
|
|
34524
|
+
boolInput("showBulge", "Show Bulge Level", true, SQUEEZE_BULGE, "Plots the highest BandWidth of the reference lookback \u2014 extreme width that is unlikely to be sustained."),
|
|
34525
|
+
colorInput(NEUTRAL, "BandWidth", "color", "Color of the BandWidth line. At its Squeeze reference the line takes the Squeeze color, at its Bulge reference the Bulge color."),
|
|
34526
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between zero and BandWidth with a vertical gradient, fully transparent at zero."),
|
|
34527
|
+
colorInput(WARNING, "Squeeze Level", "squeezeColor", "Color of the lowest-BandWidth reference line."),
|
|
34528
|
+
colorInput(BEARISH, "Bulge Level", "bulgeColor", "Color of the highest-BandWidth reference line.")
|
|
34529
|
+
],
|
|
33971
34530
|
compute: (bars, inputs) => {
|
|
33972
34531
|
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
33973
34532
|
const values = basis.map((b, i) => {
|
|
33974
34533
|
const u = upper[i];
|
|
33975
34534
|
const l = lower[i];
|
|
33976
|
-
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b
|
|
34535
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b : Number.NaN;
|
|
34536
|
+
});
|
|
34537
|
+
const lookback = num3(inputs, "lookback", 125);
|
|
34538
|
+
const squeezeLevel = lowest(values, lookback);
|
|
34539
|
+
const bulgeLevel = highest(values, lookback);
|
|
34540
|
+
const base = str3(inputs, "color", NEUTRAL);
|
|
34541
|
+
const squeezeInk = str3(inputs, "squeezeColor", WARNING);
|
|
34542
|
+
const bulgeInk = str3(inputs, "bulgeColor", BEARISH);
|
|
34543
|
+
const colors = values.map((x, i) => {
|
|
34544
|
+
if (!Number.isFinite(x)) return null;
|
|
34545
|
+
if (x <= squeezeLevel[i]) return squeezeInk;
|
|
34546
|
+
if (x >= bulgeLevel[i]) return bulgeInk;
|
|
34547
|
+
return base;
|
|
33977
34548
|
});
|
|
33978
|
-
|
|
34549
|
+
const plots = [
|
|
34550
|
+
{ key: "bbw", title: "BandWidth", values, color: base, colors },
|
|
34551
|
+
{ key: "zero", title: "Zero Line", values: values.map((x) => Number.isFinite(x) ? 0 : Number.NaN), color: transp(NEUTRAL, 90) }
|
|
34552
|
+
];
|
|
34553
|
+
if (bool(inputs, "showSqueeze", true)) plots.push({ key: "squeeze", title: "Squeeze Level", values: squeezeLevel, color: squeezeInk });
|
|
34554
|
+
if (bool(inputs, "showBulge", true)) plots.push({ key: "bulge", title: "Bulge Level", values: bulgeLevel, color: bulgeInk });
|
|
34555
|
+
const bands = bool(inputs, "gradientFill", true) ? [
|
|
34556
|
+
{
|
|
34557
|
+
key: "bbwFill",
|
|
34558
|
+
from: "bbw",
|
|
34559
|
+
to: "zero",
|
|
34560
|
+
color: transp(NEUTRAL, 100),
|
|
34561
|
+
gradient: values.map((x, i) => {
|
|
34562
|
+
if (!Number.isFinite(x)) return null;
|
|
34563
|
+
const ink = colors[i] ?? base;
|
|
34564
|
+
return { topValue: x, bottomValue: 0, topColor: transp(ink, 50), bottomColor: transp(ink, 100) };
|
|
34565
|
+
})
|
|
34566
|
+
}
|
|
34567
|
+
] : [];
|
|
34568
|
+
return { plots, bands };
|
|
33979
34569
|
}
|
|
33980
34570
|
};
|
|
33981
34571
|
var keltner = {
|
|
@@ -33984,26 +34574,17 @@ var keltner = {
|
|
|
33984
34574
|
shortTitle: "KC",
|
|
33985
34575
|
overlay: true,
|
|
33986
34576
|
inputs: [
|
|
33987
|
-
lengthInput(20),
|
|
33988
|
-
|
|
33989
|
-
|
|
33990
|
-
sourceInput(),
|
|
33991
|
-
|
|
34577
|
+
lengthInput(20, "length", "EMA Length", 5e3, "Number of bars used to compute the exponential moving average basis line."),
|
|
34578
|
+
intInput("atrLength", "ATR Length", 10, 1, 5e3, "Number of bars used to compute the Average True Range (Wilder smoothing)."),
|
|
34579
|
+
floatInput("mult", "Multiplier", 2, 0, 50, 0.1, "ATR multiple used to offset the upper and lower bands from the basis line."),
|
|
34580
|
+
sourceInput("Close", "source", "Source", "Price series used to compute the basis line."),
|
|
34581
|
+
...channelStyleInputs("Basis Color")
|
|
33992
34582
|
],
|
|
33993
34583
|
compute: (bars, inputs) => {
|
|
33994
|
-
const
|
|
33995
|
-
const
|
|
33996
|
-
const
|
|
33997
|
-
|
|
33998
|
-
const ink = str3(inputs, "color", SERIES_LINE);
|
|
33999
|
-
return {
|
|
34000
|
-
plots: [
|
|
34001
|
-
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
34002
|
-
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
34003
|
-
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
34004
|
-
],
|
|
34005
|
-
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
34006
|
-
};
|
|
34584
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20));
|
|
34585
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * num3(inputs, "mult", 2));
|
|
34586
|
+
const ink = { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "basisColor", NEUTRAL) };
|
|
34587
|
+
return directionalChannel(bars, basis, zip(basis, range, (b, r) => b + r), zip(basis, range, (b, r) => b - r), ink, "kc");
|
|
34007
34588
|
}
|
|
34008
34589
|
};
|
|
34009
34590
|
var donchian = {
|
|
@@ -34011,19 +34592,24 @@ var donchian = {
|
|
|
34011
34592
|
title: "Donchian Channels",
|
|
34012
34593
|
shortTitle: "DC",
|
|
34013
34594
|
overlay: true,
|
|
34014
|
-
inputs: [
|
|
34595
|
+
inputs: [
|
|
34596
|
+
lengthInput(20, "length", "Length", 5e3, "Lookback length. The upper and lower channels are the highest high and lowest low over this many bars."),
|
|
34597
|
+
colorInput(BEARISH, "Upper", "upperColor", "Color of the upper channel line."),
|
|
34598
|
+
colorInput(NEUTRAL, "Middle", "middleColor", "Color of the channel midline."),
|
|
34599
|
+
colorInput(BULLISH, "Lower", "lowerColor", "Color of the lower channel line."),
|
|
34600
|
+
colorInput(transp(NEUTRAL, 92), "Fill", "fillColor", "Color of the channel interior fill.")
|
|
34601
|
+
],
|
|
34015
34602
|
compute: (bars, inputs) => {
|
|
34016
34603
|
const len = num3(inputs, "length", 20);
|
|
34017
34604
|
const upper = highest(highs(bars), len);
|
|
34018
34605
|
const lower = lowest(lows(bars), len);
|
|
34019
|
-
const ink = str3(inputs, "color", SERIES_LINE);
|
|
34020
34606
|
return {
|
|
34021
34607
|
plots: [
|
|
34022
|
-
{ key: "
|
|
34023
|
-
{ key: "
|
|
34024
|
-
{ key: "lower", title: "Lower", values: lower, color:
|
|
34608
|
+
{ key: "upper", title: "Upper", values: upper, color: str3(inputs, "upperColor", BEARISH) },
|
|
34609
|
+
{ key: "middle", title: "Middle", kind: "circles", values: zip(upper, lower, (u, l) => (u + l) / 2), color: str3(inputs, "middleColor", NEUTRAL) },
|
|
34610
|
+
{ key: "lower", title: "Lower", values: lower, color: str3(inputs, "lowerColor", BULLISH) }
|
|
34025
34611
|
],
|
|
34026
|
-
bands: [{ key: "dc", from: "upper", to: "lower", color:
|
|
34612
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: str3(inputs, "fillColor", transp(NEUTRAL, 92)) }]
|
|
34027
34613
|
};
|
|
34028
34614
|
}
|
|
34029
34615
|
};
|
|
@@ -34032,16 +34618,37 @@ var chandelier = {
|
|
|
34032
34618
|
title: "Chandelier Exit",
|
|
34033
34619
|
shortTitle: "CE",
|
|
34034
34620
|
overlay: true,
|
|
34035
|
-
inputs: [
|
|
34621
|
+
inputs: [
|
|
34622
|
+
lengthInput(22, "length", "Length", 5e3, "Lookback used for both the highest high / lowest low extremes and the ATR."),
|
|
34623
|
+
floatInput("mult", "ATR Multiplier", 3, 0, 50, 0.1, "Multiple of ATR subtracted from the highest high (long stop) and added to the lowest low (short stop)."),
|
|
34624
|
+
boolInput("ratchet", "Ratchet Stops", false, void 0, "Prevents the long stop from falling and the short stop from rising, resetting once close crosses through the stop."),
|
|
34625
|
+
colorInput(BULLISH, "Long Stop", "longColor", "Color of the long trailing stop line."),
|
|
34626
|
+
colorInput(BEARISH, "Short Stop", "shortColor", "Color of the short trailing stop line.")
|
|
34627
|
+
],
|
|
34036
34628
|
compute: (bars, inputs) => {
|
|
34037
34629
|
const len = num3(inputs, "length", 22);
|
|
34038
34630
|
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
34039
|
-
const
|
|
34040
|
-
const
|
|
34631
|
+
const basicLong = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
34632
|
+
const basicShort = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
34633
|
+
let long = basicLong;
|
|
34634
|
+
let short = basicShort;
|
|
34635
|
+
if (bool(inputs, "ratchet", false)) {
|
|
34636
|
+
long = new Array(bars.length).fill(Number.NaN);
|
|
34637
|
+
short = new Array(bars.length).fill(Number.NaN);
|
|
34638
|
+
for (let i = 0; i < bars.length; i++) {
|
|
34639
|
+
const bl = basicLong[i];
|
|
34640
|
+
const bs = basicShort[i];
|
|
34641
|
+
const prevClose = i > 0 ? bars[i - 1].close : bars[i].close;
|
|
34642
|
+
const prevLong = i > 0 && Number.isFinite(long[i - 1]) ? long[i - 1] : bl;
|
|
34643
|
+
const prevShort = i > 0 && Number.isFinite(short[i - 1]) ? short[i - 1] : bs;
|
|
34644
|
+
long[i] = Number.isFinite(bl) ? prevClose > prevLong ? Math.max(bl, prevLong) : bl : Number.NaN;
|
|
34645
|
+
short[i] = Number.isFinite(bs) ? prevClose < prevShort ? Math.min(bs, prevShort) : bs : Number.NaN;
|
|
34646
|
+
}
|
|
34647
|
+
}
|
|
34041
34648
|
return {
|
|
34042
34649
|
plots: [
|
|
34043
|
-
{ key: "long", title: "Long
|
|
34044
|
-
{ key: "short", title: "Short
|
|
34650
|
+
{ key: "long", title: "Long Stop", values: long, color: str3(inputs, "longColor", BULLISH) },
|
|
34651
|
+
{ key: "short", title: "Short Stop", values: short, color: str3(inputs, "shortColor", BEARISH) }
|
|
34045
34652
|
]
|
|
34046
34653
|
};
|
|
34047
34654
|
}
|
|
@@ -34052,21 +34659,22 @@ var chandeKroll = {
|
|
|
34052
34659
|
shortTitle: "CKS",
|
|
34053
34660
|
overlay: true,
|
|
34054
34661
|
inputs: [
|
|
34055
|
-
|
|
34056
|
-
|
|
34057
|
-
|
|
34662
|
+
intInput("p", "ATR Length (P)", 10, 1, 5e3, "Lookback used for both the ATR and the highest high / lowest low price extremes."),
|
|
34663
|
+
floatInput("x", "ATR Multiplier (X)", 1, 0, 50, 0.1, "Multiplier applied to the ATR to offset the preliminary stops from the price extremes."),
|
|
34664
|
+
intInput("q", "Stop Length (Q)", 9, 1, 5e3, "Lookback over which the preliminary stops are extended into the final stop levels."),
|
|
34665
|
+
colorInput(BEARISH, "Stop Short", "shortColor", "Color of the protective stop line for short positions."),
|
|
34666
|
+
colorInput(BULLISH, "Stop Long", "longColor", "Color of the protective stop line for long positions.")
|
|
34058
34667
|
],
|
|
34059
34668
|
compute: (bars, inputs) => {
|
|
34060
34669
|
const p = num3(inputs, "p", 10);
|
|
34061
|
-
const x = num3(inputs, "x", 1);
|
|
34062
34670
|
const q = num3(inputs, "q", 9);
|
|
34063
|
-
const range = map(atr(bars, p), (v) => v * x);
|
|
34671
|
+
const range = map(atr(bars, p), (v) => v * num3(inputs, "x", 1));
|
|
34064
34672
|
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
34065
34673
|
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
34066
34674
|
return {
|
|
34067
34675
|
plots: [
|
|
34068
|
-
{ key: "stopShort", title: "Stop
|
|
34069
|
-
{ key: "stopLong", title: "Stop
|
|
34676
|
+
{ key: "stopShort", title: "Stop Short", values: highest(firstHigh, q), color: str3(inputs, "shortColor", BEARISH) },
|
|
34677
|
+
{ key: "stopLong", title: "Stop Long", values: lowest(firstLow, q), color: str3(inputs, "longColor", BULLISH) }
|
|
34070
34678
|
]
|
|
34071
34679
|
};
|
|
34072
34680
|
}
|
|
@@ -34077,36 +34685,42 @@ var supertrend = {
|
|
|
34077
34685
|
shortTitle: "SuperTrend",
|
|
34078
34686
|
overlay: true,
|
|
34079
34687
|
inputs: [
|
|
34080
|
-
|
|
34081
|
-
|
|
34688
|
+
intInput("atrLength", "ATR Length", 10, 1, 5e3, "Number of bars used to compute the Wilder ATR."),
|
|
34689
|
+
floatInput("mult", "Factor", 3, 0, 50, 0.1, "ATR multiplier setting how far the trailing stop sits from the bar midpoint."),
|
|
34690
|
+
colorInput(BULLISH, "Uptrend Color", "upColor", "Line color while the trend is up."),
|
|
34691
|
+
colorInput(BEARISH, "Downtrend Color", "downColor", "Line color while the trend is down.")
|
|
34082
34692
|
],
|
|
34083
34693
|
compute: (bars, inputs) => {
|
|
34084
|
-
const len = num3(inputs, "atrLength", 10);
|
|
34085
34694
|
const mult = num3(inputs, "mult", 3);
|
|
34086
|
-
const range = atr(bars,
|
|
34695
|
+
const range = atr(bars, num3(inputs, "atrLength", 10));
|
|
34087
34696
|
const n = bars.length;
|
|
34088
|
-
const
|
|
34089
|
-
const
|
|
34090
|
-
let
|
|
34091
|
-
let
|
|
34092
|
-
let trend = 1;
|
|
34697
|
+
const up = new Array(n).fill(Number.NaN);
|
|
34698
|
+
const down = new Array(n).fill(Number.NaN);
|
|
34699
|
+
let finalUpper = Number.NaN;
|
|
34700
|
+
let finalLower = Number.NaN;
|
|
34701
|
+
let trend = -1;
|
|
34093
34702
|
for (let i = 0; i < n; i++) {
|
|
34094
34703
|
const r = range[i];
|
|
34095
34704
|
if (!Number.isFinite(r)) continue;
|
|
34096
34705
|
const b = bars[i];
|
|
34097
34706
|
const mid = (b.high + b.low) / 2;
|
|
34098
|
-
const
|
|
34099
|
-
const
|
|
34707
|
+
const basicUpper = mid + mult * r;
|
|
34708
|
+
const basicLower = mid - mult * r;
|
|
34709
|
+
const prevUpper = Number.isFinite(finalUpper) ? finalUpper : basicUpper;
|
|
34710
|
+
const prevLower = Number.isFinite(finalLower) ? finalLower : basicLower;
|
|
34100
34711
|
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
34101
|
-
|
|
34102
|
-
|
|
34103
|
-
|
|
34104
|
-
|
|
34105
|
-
|
|
34106
|
-
st[i] = trend === 1 ? up : dn;
|
|
34712
|
+
finalUpper = prevClose <= prevUpper ? Math.min(basicUpper, prevUpper) : basicUpper;
|
|
34713
|
+
finalLower = prevClose >= prevLower ? Math.max(basicLower, prevLower) : basicLower;
|
|
34714
|
+
trend = b.close > finalUpper ? 1 : b.close < finalLower ? -1 : trend;
|
|
34715
|
+
if (trend === 1) up[i] = finalLower;
|
|
34716
|
+
else down[i] = finalUpper;
|
|
34107
34717
|
}
|
|
34108
|
-
|
|
34109
|
-
|
|
34718
|
+
return {
|
|
34719
|
+
plots: [
|
|
34720
|
+
{ key: "up", title: "Uptrend", values: up, color: str3(inputs, "upColor", BULLISH) },
|
|
34721
|
+
{ key: "down", title: "Downtrend", values: down, color: str3(inputs, "downColor", BEARISH) }
|
|
34722
|
+
]
|
|
34723
|
+
};
|
|
34110
34724
|
}
|
|
34111
34725
|
};
|
|
34112
34726
|
var alligator = {
|
|
@@ -34115,12 +34729,15 @@ var alligator = {
|
|
|
34115
34729
|
shortTitle: "Alligator",
|
|
34116
34730
|
overlay: true,
|
|
34117
34731
|
inputs: [
|
|
34118
|
-
|
|
34119
|
-
|
|
34120
|
-
|
|
34121
|
-
|
|
34122
|
-
|
|
34123
|
-
|
|
34732
|
+
intInput("jawLength", "Jaw Length", 13, 1, 5e3, "Smoothing length of the Jaw SMMA of median price (hl2), 13 in the classic setup."),
|
|
34733
|
+
intInput("teethLength", "Teeth Length", 8, 1, 5e3, "Smoothing length of the Teeth SMMA of median price (hl2), 8 in the classic setup."),
|
|
34734
|
+
intInput("lipsLength", "Lips Length", 5, 1, 5e3, "Smoothing length of the Lips SMMA of median price (hl2), 5 in the classic setup."),
|
|
34735
|
+
intInput("jawOffset", "Jaw Offset", 8, 0, 100, "Number of bars the Jaw plot is shifted into the future, 8 in the classic setup."),
|
|
34736
|
+
intInput("teethOffset", "Teeth Offset", 5, 0, 100, "Number of bars the Teeth plot is shifted into the future, 5 in the classic setup."),
|
|
34737
|
+
intInput("lipsOffset", "Lips Offset", 3, 0, 100, "Number of bars the Lips plot is shifted into the future, 3 in the classic setup."),
|
|
34738
|
+
colorInput(JAW_COLOR, "Jaw", "jawColor", "Color of the Jaw line."),
|
|
34739
|
+
colorInput(TEETH_COLOR, "Teeth", "teethColor", "Color of the Teeth line."),
|
|
34740
|
+
colorInput(LIPS_COLOR, "Lips", "lipsColor", "Color of the Lips line.")
|
|
34124
34741
|
],
|
|
34125
34742
|
compute: (bars, inputs) => {
|
|
34126
34743
|
const hl2 = sourceValues(bars, "HL2");
|
|
@@ -34129,99 +34746,134 @@ var alligator = {
|
|
|
34129
34746
|
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
34130
34747
|
return {
|
|
34131
34748
|
plots: [
|
|
34132
|
-
{ key: "jaw", title: "Jaw", values: jaw, color:
|
|
34133
|
-
{ key: "teeth", title: "Teeth", values: teeth, color:
|
|
34134
|
-
{ key: "lips", title: "Lips", values: lips, color:
|
|
34749
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: str3(inputs, "jawColor", JAW_COLOR) },
|
|
34750
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: str3(inputs, "teethColor", TEETH_COLOR) },
|
|
34751
|
+
{ key: "lips", title: "Lips", values: lips, color: str3(inputs, "lipsColor", LIPS_COLOR) }
|
|
34135
34752
|
]
|
|
34136
34753
|
};
|
|
34137
34754
|
}
|
|
34138
34755
|
};
|
|
34756
|
+
var GATOR_LINES = "Alligator Lines";
|
|
34757
|
+
var SMMA_TYPE = "SMMA (Wilder)";
|
|
34139
34758
|
var gator = {
|
|
34140
34759
|
type: "gator-oscillator",
|
|
34141
34760
|
title: "Gator Oscillator",
|
|
34142
34761
|
shortTitle: "Gator Oscillator",
|
|
34143
34762
|
overlay: false,
|
|
34144
|
-
inputs: [
|
|
34145
|
-
|
|
34146
|
-
|
|
34147
|
-
|
|
34148
|
-
|
|
34149
|
-
|
|
34763
|
+
inputs: [
|
|
34764
|
+
{ ...sourceInput("HL2", "source", "Source", "Price series fed to the three Alligator averages. Bill Williams' original recipe uses the median price (high + low) / 2."), group: GATOR_LINES },
|
|
34765
|
+
{ ...optionInput("maType", "Smoothing", SMMA_TYPE, [SMMA_TYPE, "SMA", "EMA"], "Moving average applied to the source for the jaw, teeth and lips. The original uses the smoothed (Wilder-style) moving average."), group: GATOR_LINES },
|
|
34766
|
+
{ ...intInput("jawLength", "Jaw Length", 13, 1, 500, "Averaging length of the jaw line."), group: GATOR_LINES },
|
|
34767
|
+
{ ...intInput("jawOffset", "Jaw Offset", 8, 0, 100, "Bars the jaw is displaced forward, 8 in the original recipe. The upper histogram measures the absolute spread between the displaced jaw and teeth."), group: GATOR_LINES },
|
|
34768
|
+
{ ...intInput("teethLength", "Teeth Length", 8, 1, 500, "Averaging length of the teeth line."), group: GATOR_LINES },
|
|
34769
|
+
{ ...intInput("teethOffset", "Teeth Offset", 5, 0, 100, "Bars the teeth are displaced forward, 5 in the original recipe. The teeth line enters both spreads."), group: GATOR_LINES },
|
|
34770
|
+
{ ...intInput("lipsLength", "Lips Length", 5, 1, 500, "Averaging length of the lips line."), group: GATOR_LINES },
|
|
34771
|
+
{ ...intInput("lipsOffset", "Lips Offset", 3, 0, 100, "Bars the lips are displaced forward, 3 in the original recipe. The lower histogram measures the negative absolute spread between the displaced teeth and lips."), group: GATOR_LINES },
|
|
34772
|
+
colorInput(BULLISH, "Expanding", "growColor", "Column color while a spread grew against its own previous bar \u2014 the averages are spreading apart."),
|
|
34773
|
+
colorInput(BEARISH, "Contracting", "shrinkColor", "Column color while a spread shrank \u2014 the averages are converging. Color encodes expansion, not trade direction."),
|
|
34774
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero line separating the two histograms.")
|
|
34775
|
+
],
|
|
34776
|
+
compute: (bars, inputs) => {
|
|
34777
|
+
const src = sourceValues(bars, str3(inputs, "source", "HL2"));
|
|
34778
|
+
const kind = str3(inputs, "maType", SMMA_TYPE);
|
|
34779
|
+
const smooth = (len) => kind === "SMA" ? sma(src, len) : kind === "EMA" ? ema(src, len) : rma(src, len);
|
|
34780
|
+
const jaw = shift(smooth(num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
34781
|
+
const teeth = shift(smooth(num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
34782
|
+
const lips = shift(smooth(num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
34150
34783
|
const upper = map(sub(jaw, teeth), Math.abs);
|
|
34151
|
-
const lower = map(sub(teeth, lips),
|
|
34152
|
-
const
|
|
34153
|
-
|
|
34154
|
-
|
|
34155
|
-
|
|
34156
|
-
|
|
34784
|
+
const lower = map(sub(teeth, lips), Math.abs);
|
|
34785
|
+
const grow = str3(inputs, "growColor", BULLISH);
|
|
34786
|
+
const shrink = str3(inputs, "shrinkColor", BEARISH);
|
|
34787
|
+
const expansionColors2 = (v) => {
|
|
34788
|
+
let growing = false;
|
|
34789
|
+
return v.map((x, i) => {
|
|
34790
|
+
if (!Number.isFinite(x)) return null;
|
|
34791
|
+
const prev2 = i > 0 ? v[i - 1] : Number.NaN;
|
|
34792
|
+
if (Number.isFinite(prev2)) growing = x > prev2 ? true : x < prev2 ? false : growing;
|
|
34793
|
+
return growing ? grow : shrink;
|
|
34794
|
+
});
|
|
34795
|
+
};
|
|
34157
34796
|
return {
|
|
34158
34797
|
plots: [
|
|
34159
|
-
{ key: "upper", title: "Upper", values: upper, kind: "
|
|
34160
|
-
{ key: "lower", title: "Lower", values: lower, kind: "
|
|
34161
|
-
]
|
|
34798
|
+
{ key: "upper", title: "Upper Histogram (Jaw - Teeth)", values: upper, kind: "columns", color: grow, colors: expansionColors2(upper), base: 0 },
|
|
34799
|
+
{ key: "lower", title: "Lower Histogram (Teeth - Lips)", values: map(lower, (x) => -x), kind: "columns", color: shrink, colors: expansionColors2(lower), base: 0 }
|
|
34800
|
+
],
|
|
34801
|
+
levels: [{ key: "zero", price: 0, color: str3(inputs, "zeroColor", NEUTRAL), lineStyle: "solid", title: "Zero Line" }]
|
|
34162
34802
|
};
|
|
34163
34803
|
}
|
|
34164
34804
|
};
|
|
34165
34805
|
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
34166
34806
|
|
|
34167
34807
|
// src/core/native-indicators/classics/oscillators.ts
|
|
34168
|
-
function
|
|
34169
|
-
|
|
34170
|
-
|
|
34171
|
-
|
|
34808
|
+
function thresholdInputs(obDefault, osDefault, min = 0, max = 100, obTitle = "Overbought Level", osTitle = "Oversold Level") {
|
|
34809
|
+
return [
|
|
34810
|
+
floatInput("overbought", obTitle, obDefault, min, max, 1, "Level above which the oscillator is considered overbought."),
|
|
34811
|
+
floatInput("oversold", osTitle, osDefault, min, max, 1, "Level below which the oscillator is considered oversold.")
|
|
34172
34812
|
];
|
|
34173
|
-
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
34174
|
-
return levels;
|
|
34175
|
-
}
|
|
34176
|
-
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
34177
|
-
function histColors(values) {
|
|
34178
|
-
return values.map((x, i) => {
|
|
34179
|
-
if (!Number.isFinite(x)) return null;
|
|
34180
|
-
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
34181
|
-
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
34182
|
-
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
34183
|
-
return rising ? `${BEARISH}80` : BEARISH;
|
|
34184
|
-
});
|
|
34185
34813
|
}
|
|
34186
34814
|
var rsiSpec = {
|
|
34187
34815
|
type: "rsi",
|
|
34188
34816
|
title: "Relative Strength Index",
|
|
34189
34817
|
shortTitle: "RSI",
|
|
34190
34818
|
overlay: false,
|
|
34191
|
-
inputs: [
|
|
34192
|
-
|
|
34193
|
-
|
|
34194
|
-
|
|
34195
|
-
|
|
34196
|
-
|
|
34197
|
-
|
|
34198
|
-
|
|
34199
|
-
|
|
34200
|
-
|
|
34201
|
-
|
|
34202
|
-
|
|
34819
|
+
inputs: [
|
|
34820
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used in the Wilder smoothing of average gains and losses."),
|
|
34821
|
+
sourceInput("Close", "source", "Source", "Price series the RSI is calculated on."),
|
|
34822
|
+
...thresholdInputs(70, 30, 0, 100, "Overbought", "Oversold"),
|
|
34823
|
+
colorInput(NEUTRAL, "RSI", "color", "Color of the RSI line at the midline. Toward either threshold it blends into that threshold\u2019s color."),
|
|
34824
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level."),
|
|
34825
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
34826
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought, midline and oversold levels."),
|
|
34827
|
+
colorInput(transp(NEUTRAL, 90), "Band Fill", "fillColor", "Color of the fill between the overbought and oversold levels.")
|
|
34828
|
+
],
|
|
34829
|
+
compute: (bars, inputs) => boundedOscillator({
|
|
34830
|
+
key: "rsi",
|
|
34831
|
+
title: "RSI",
|
|
34832
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
34833
|
+
scale: { top: 100, bottom: 0 },
|
|
34834
|
+
overbought: num3(inputs, "overbought", 70),
|
|
34835
|
+
oversold: num3(inputs, "oversold", 30),
|
|
34836
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
34837
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
34838
|
+
width: 2,
|
|
34839
|
+
midline: 50,
|
|
34840
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 90))
|
|
34203
34841
|
})
|
|
34204
34842
|
};
|
|
34843
|
+
function stochasticOutput(k, d, inputs) {
|
|
34844
|
+
const overbought = num3(inputs, "overbought", 80);
|
|
34845
|
+
const oversold = num3(inputs, "oversold", 20);
|
|
34846
|
+
return {
|
|
34847
|
+
plots: [
|
|
34848
|
+
{ key: "k", title: "%K", values: k, color: str3(inputs, "kColor", INFO) },
|
|
34849
|
+
{ key: "d", title: "%D", values: d, color: str3(inputs, "dColor", WARNING) },
|
|
34850
|
+
anchorPlot("obAnchor", "Overbought Anchor", k.length, overbought),
|
|
34851
|
+
anchorPlot("osAnchor", "Oversold Anchor", k.length, oversold)
|
|
34852
|
+
],
|
|
34853
|
+
bands: [{ key: "band", from: "obAnchor", to: "osAnchor", color: str3(inputs, "fillColor", transp(INFO, 90)) }],
|
|
34854
|
+
levels: [
|
|
34855
|
+
{ key: "overbought", price: overbought, color: NEUTRAL, lineStyle: "dashed", title: "Overbought" },
|
|
34856
|
+
{ key: "oversold", price: oversold, color: NEUTRAL, lineStyle: "dashed", title: "Oversold" }
|
|
34857
|
+
]
|
|
34858
|
+
};
|
|
34859
|
+
}
|
|
34205
34860
|
var stochastic = {
|
|
34206
34861
|
type: "stochastic",
|
|
34207
34862
|
title: "Stochastic",
|
|
34208
34863
|
shortTitle: "Stoch",
|
|
34209
34864
|
overlay: false,
|
|
34210
34865
|
inputs: [
|
|
34211
|
-
lengthInput(14, "kLength", "%K
|
|
34212
|
-
lengthInput(
|
|
34213
|
-
lengthInput(3, "dLength", "%D
|
|
34866
|
+
lengthInput(14, "kLength", "%K Length", 5e3, "Number of bars used for the highest high / lowest low range of the raw %K."),
|
|
34867
|
+
lengthInput(3, "kSmoothing", "%K Smoothing", 500, "Simple moving average length applied to the raw %K. 1 gives the fast stochastic, 3 the classic slow stochastic."),
|
|
34868
|
+
lengthInput(3, "dLength", "%D Smoothing", 500, "Simple moving average length of the %D signal line, applied to the smoothed %K."),
|
|
34869
|
+
...thresholdInputs(80, 20),
|
|
34870
|
+
colorInput(INFO, "%K", "kColor", "Color of the %K line."),
|
|
34871
|
+
colorInput(WARNING, "%D", "dColor", "Color of the %D signal line."),
|
|
34872
|
+
colorInput(transp(INFO, 90), "Band Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
34214
34873
|
],
|
|
34215
34874
|
compute: (bars, inputs) => {
|
|
34216
|
-
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing",
|
|
34217
|
-
|
|
34218
|
-
return {
|
|
34219
|
-
plots: [
|
|
34220
|
-
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
34221
|
-
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
34222
|
-
],
|
|
34223
|
-
levels: guideLevels(20, 80)
|
|
34224
|
-
};
|
|
34875
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
34876
|
+
return stochasticOutput(k, sma(k, num3(inputs, "dLength", 3)), inputs);
|
|
34225
34877
|
}
|
|
34226
34878
|
};
|
|
34227
34879
|
var stochasticRsi = {
|
|
@@ -34230,23 +34882,20 @@ var stochasticRsi = {
|
|
|
34230
34882
|
shortTitle: "Stoch RSI",
|
|
34231
34883
|
overlay: false,
|
|
34232
34884
|
inputs: [
|
|
34233
|
-
|
|
34234
|
-
lengthInput(14, "
|
|
34235
|
-
lengthInput(
|
|
34236
|
-
lengthInput(3, "
|
|
34237
|
-
|
|
34885
|
+
sourceInput("Close", "source", "RSI Source", "Price series the RSI is computed on."),
|
|
34886
|
+
lengthInput(14, "rsiLength", "RSI Length", 5e3, "Lookback length of the Wilder RSI."),
|
|
34887
|
+
lengthInput(14, "stochLength", "Stochastic Length", 5e3, "Lookback over which the highest and lowest RSI values are taken."),
|
|
34888
|
+
lengthInput(3, "kSmoothing", "%K Smoothing", 500, "Simple moving average length applied to the stochastic of RSI to form %K."),
|
|
34889
|
+
lengthInput(3, "dLength", "%D Smoothing", 500, "Simple moving average length applied to %K to form the %D signal line."),
|
|
34890
|
+
...thresholdInputs(80, 20),
|
|
34891
|
+
colorInput(INFO, "%K Color", "kColor", "Color of the %K line."),
|
|
34892
|
+
colorInput(WARNING, "%D Color", "dColor", "Color of the %D signal line."),
|
|
34893
|
+
colorInput(transp(INFO, 90), "Zone Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
34238
34894
|
],
|
|
34239
34895
|
compute: (bars, inputs) => {
|
|
34240
34896
|
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
34241
34897
|
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
34242
|
-
|
|
34243
|
-
return {
|
|
34244
|
-
plots: [
|
|
34245
|
-
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
34246
|
-
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
34247
|
-
],
|
|
34248
|
-
levels: guideLevels(20, 80)
|
|
34249
|
-
};
|
|
34898
|
+
return stochasticOutput(k, sma(k, num3(inputs, "dLength", 3)), inputs);
|
|
34250
34899
|
}
|
|
34251
34900
|
};
|
|
34252
34901
|
var macd = {
|
|
@@ -34254,23 +34903,35 @@ var macd = {
|
|
|
34254
34903
|
title: "MACD",
|
|
34255
34904
|
overlay: false,
|
|
34256
34905
|
inputs: [
|
|
34257
|
-
lengthInput(12, "fastLength", "Fast
|
|
34258
|
-
lengthInput(26, "slowLength", "Slow
|
|
34259
|
-
lengthInput(9, "signalLength", "Signal
|
|
34260
|
-
sourceInput()
|
|
34906
|
+
lengthInput(12, "fastLength", "Fast Length", 5e3, "Length of the fast EMA."),
|
|
34907
|
+
lengthInput(26, "slowLength", "Slow Length", 5e3, "Length of the slow EMA."),
|
|
34908
|
+
lengthInput(9, "signalLength", "Signal Smoothing", 500, "Length of the EMA applied to the MACD line to obtain the signal line."),
|
|
34909
|
+
sourceInput("Close", "source", "Source", "Price series used to compute the fast and slow EMAs."),
|
|
34910
|
+
colorInput(INFO, "MACD Line", "color", "Color of the MACD line."),
|
|
34911
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
34912
|
+
colorInput(BULLISH, "Grow Above", "growAboveColor", "Histogram color when above zero and rising."),
|
|
34913
|
+
colorInput(transp(BULLISH, 50), "Fall Above", "fallAboveColor", "Histogram color when above zero and falling."),
|
|
34914
|
+
colorInput(transp(BEARISH, 50), "Grow Below", "growBelowColor", "Histogram color when below zero and rising."),
|
|
34915
|
+
colorInput(BEARISH, "Fall Below", "fallBelowColor", "Histogram color when below zero and falling.")
|
|
34261
34916
|
],
|
|
34262
34917
|
compute: (bars, inputs) => {
|
|
34263
34918
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
34264
34919
|
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
34265
34920
|
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
34266
34921
|
const hist = sub(line, signal);
|
|
34922
|
+
const colors = momentumColumnColors(hist, {
|
|
34923
|
+
growAbove: str3(inputs, "growAboveColor", BULLISH),
|
|
34924
|
+
fallAbove: str3(inputs, "fallAboveColor", transp(BULLISH, 50)),
|
|
34925
|
+
growBelow: str3(inputs, "growBelowColor", transp(BEARISH, 50)),
|
|
34926
|
+
fallBelow: str3(inputs, "fallBelowColor", BEARISH)
|
|
34927
|
+
});
|
|
34267
34928
|
return {
|
|
34268
34929
|
plots: [
|
|
34269
|
-
{ key: "hist", title: "Histogram", values: hist, kind: "
|
|
34270
|
-
{ key: "macd", title: "MACD", values: line, color:
|
|
34271
|
-
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
34930
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "columns", color: BULLISH, colors, base: 0 },
|
|
34931
|
+
{ key: "macd", title: "MACD", values: line, color: str3(inputs, "color", INFO) },
|
|
34932
|
+
{ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) }
|
|
34272
34933
|
],
|
|
34273
|
-
levels:
|
|
34934
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL, lineStyle: "dashed", title: "Zero Line" }]
|
|
34274
34935
|
};
|
|
34275
34936
|
}
|
|
34276
34937
|
};
|
|
@@ -34280,26 +34941,49 @@ var ppo = {
|
|
|
34280
34941
|
shortTitle: "PPO",
|
|
34281
34942
|
overlay: false,
|
|
34282
34943
|
inputs: [
|
|
34283
|
-
|
|
34284
|
-
lengthInput(
|
|
34285
|
-
lengthInput(
|
|
34286
|
-
|
|
34944
|
+
sourceInput("Close", "source", "Source", "Price series fed into the fast and slow averages."),
|
|
34945
|
+
lengthInput(12, "fastLength", "Fast Length", 5e3, "Length of the fast moving average."),
|
|
34946
|
+
lengthInput(26, "slowLength", "Slow Length", 5e3, "Length of the slow moving average. The spread is divided by it and scaled to a percentage of price."),
|
|
34947
|
+
optionInput("maType", "Average Type", "EMA", ["EMA", "SMA"], "Moving average type used for the fast and slow averages."),
|
|
34948
|
+
lengthInput(9, "signalLength", "Signal Smoothing", 500, "Length of the signal line that smooths the PPO line."),
|
|
34949
|
+
optionInput("signalType", "Signal Type", "EMA", ["EMA", "SMA"], "Moving average type used for the signal line."),
|
|
34950
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the PPO line, gradient fill and histogram above the zero line."),
|
|
34951
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the PPO line, gradient fill and histogram below the zero line."),
|
|
34952
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
34953
|
+
boolInput("showHistogram", "Show Histogram", true, STYLE, "Displays the histogram (PPO minus signal line) as columns. Bright columns mark a spread still widening, faded ones a spread narrowing back."),
|
|
34954
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the zero line and the PPO line with a vertical gradient.")
|
|
34287
34955
|
],
|
|
34288
34956
|
compute: (bars, inputs) => {
|
|
34289
34957
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
34290
|
-
const
|
|
34291
|
-
const
|
|
34958
|
+
const average = (kind2, v, len) => kind2 === "SMA" ? sma(v, len) : ema(v, len);
|
|
34959
|
+
const kind = str3(inputs, "maType", "EMA");
|
|
34960
|
+
const fast = average(kind, src, num3(inputs, "fastLength", 12));
|
|
34961
|
+
const slow = average(kind, src, num3(inputs, "slowLength", 26));
|
|
34292
34962
|
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
34293
|
-
const signal =
|
|
34963
|
+
const signal = average(str3(inputs, "signalType", "EMA"), line, num3(inputs, "signalLength", 9));
|
|
34294
34964
|
const hist = sub(line, signal);
|
|
34295
|
-
|
|
34296
|
-
|
|
34297
|
-
|
|
34298
|
-
|
|
34299
|
-
|
|
34300
|
-
|
|
34301
|
-
|
|
34302
|
-
|
|
34965
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
34966
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
34967
|
+
const out = centeredOscillator({
|
|
34968
|
+
key: "ppo",
|
|
34969
|
+
title: "PPO Line",
|
|
34970
|
+
values: line,
|
|
34971
|
+
ink: { bull, bear, neutral: bear },
|
|
34972
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
34973
|
+
fill: bool(inputs, "gradientFill", true),
|
|
34974
|
+
extraPlots: [{ key: "signal", title: "Signal Line", values: signal, color: str3(inputs, "signalColor", WARNING) }]
|
|
34975
|
+
});
|
|
34976
|
+
if (bool(inputs, "showHistogram", true)) {
|
|
34977
|
+
const colors = hist.map((x, i) => {
|
|
34978
|
+
if (!Number.isFinite(x)) return null;
|
|
34979
|
+
const prev2 = i > 0 && Number.isFinite(hist[i - 1]) ? hist[i - 1] : x;
|
|
34980
|
+
const ink = x >= 0 ? bull : bear;
|
|
34981
|
+
const expanding = x >= 0 ? x > prev2 : x < prev2;
|
|
34982
|
+
return transp(ink, expanding ? 40 : 70);
|
|
34983
|
+
});
|
|
34984
|
+
out.plots.unshift({ key: "hist", title: "Histogram", values: hist, kind: "columns", color: bull, colors, base: 0 });
|
|
34985
|
+
}
|
|
34986
|
+
return out;
|
|
34303
34987
|
}
|
|
34304
34988
|
};
|
|
34305
34989
|
var awesome = {
|
|
@@ -34307,48 +34991,67 @@ var awesome = {
|
|
|
34307
34991
|
title: "Awesome Oscillator",
|
|
34308
34992
|
shortTitle: "AO",
|
|
34309
34993
|
overlay: false,
|
|
34310
|
-
inputs: [
|
|
34311
|
-
|
|
34994
|
+
inputs: [
|
|
34995
|
+
colorInput(BULLISH, "Rising", "risingColor", "Histogram color when the oscillator is higher than on the previous bar."),
|
|
34996
|
+
colorInput(BEARISH, "Falling", "fallingColor", "Histogram color when the oscillator is lower than or equal to the previous bar."),
|
|
34997
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
34998
|
+
],
|
|
34999
|
+
compute: (bars, inputs) => {
|
|
34312
35000
|
const hl2 = sourceValues(bars, "HL2");
|
|
34313
35001
|
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
35002
|
+
const rising = str3(inputs, "risingColor", BULLISH);
|
|
35003
|
+
const falling = str3(inputs, "fallingColor", BEARISH);
|
|
34314
35004
|
const colors = ao.map((x, i) => {
|
|
34315
35005
|
if (!Number.isFinite(x)) return null;
|
|
34316
35006
|
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
34317
|
-
return Number.isFinite(prev2) && x
|
|
35007
|
+
return Number.isFinite(prev2) && x > prev2 ? rising : falling;
|
|
34318
35008
|
});
|
|
34319
35009
|
return {
|
|
34320
|
-
plots: [{ key: "ao", title: "AO", values: ao, kind: "
|
|
34321
|
-
levels:
|
|
35010
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "columns", color: rising, colors, base: 0 }],
|
|
35011
|
+
levels: [{ key: "zero", price: 0, color: str3(inputs, "zeroColor", NEUTRAL), lineStyle: "dashed", title: "Zero Line" }]
|
|
34322
35012
|
};
|
|
34323
35013
|
}
|
|
34324
35014
|
};
|
|
35015
|
+
var CCI_SCALING = 0.015;
|
|
34325
35016
|
var cci = {
|
|
34326
35017
|
type: "commodity-channel-index",
|
|
34327
35018
|
title: "Commodity Channel Index",
|
|
34328
35019
|
shortTitle: "CCI",
|
|
34329
35020
|
overlay: false,
|
|
34330
|
-
inputs: [
|
|
35021
|
+
inputs: [
|
|
35022
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used for the moving average and the mean absolute deviation of the source."),
|
|
35023
|
+
sourceInput("HLC3", "source", "Source", "Price series measured by the CCI; the typical price hlc3 is the classic choice."),
|
|
35024
|
+
floatInput("upper", "Upper Level", 100, -1e3, 1e3, 10, "Overbought reference level, +100 classically."),
|
|
35025
|
+
floatInput("lower", "Lower Level", -100, -1e3, 1e3, 10, "Oversold reference level, \u2212100 classically."),
|
|
35026
|
+
colorInput(NEUTRAL, "CCI Line", "color", "Color of the CCI line."),
|
|
35027
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the CCI reads above zero."),
|
|
35028
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the CCI reads below zero."),
|
|
35029
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the upper, lower and zero reference lines.")
|
|
35030
|
+
],
|
|
34331
35031
|
compute: (bars, inputs) => {
|
|
34332
35032
|
const len = num3(inputs, "length", 20);
|
|
34333
35033
|
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
34334
35034
|
const basis = sma(src, len);
|
|
34335
|
-
const dev =
|
|
34336
|
-
for (let i = len - 1; i < src.length; i++) {
|
|
34337
|
-
const mean = basis[i];
|
|
34338
|
-
if (!Number.isFinite(mean)) continue;
|
|
34339
|
-
let d = 0;
|
|
34340
|
-
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
34341
|
-
dev[i] = d / len;
|
|
34342
|
-
}
|
|
35035
|
+
const dev = meanDev(src, len);
|
|
34343
35036
|
const values = src.map((x, i) => {
|
|
34344
35037
|
const b = basis[i];
|
|
34345
35038
|
const d = dev[i];
|
|
34346
|
-
|
|
35039
|
+
if (!Number.isFinite(b) || !Number.isFinite(d)) return Number.NaN;
|
|
35040
|
+
return d === 0 ? 0 : (x - b) / (CCI_SCALING * d);
|
|
35041
|
+
});
|
|
35042
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
35043
|
+
return centeredOscillator({
|
|
35044
|
+
key: "cci",
|
|
35045
|
+
title: "CCI",
|
|
35046
|
+
values,
|
|
35047
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
35048
|
+
zeroInk: levelInk,
|
|
35049
|
+
zeroLineStyle: "dotted",
|
|
35050
|
+
extraLevels: [
|
|
35051
|
+
{ key: "upper", price: num3(inputs, "upper", 100), color: levelInk, lineStyle: "dashed", title: "Upper Level" },
|
|
35052
|
+
{ key: "lower", price: num3(inputs, "lower", -100), color: levelInk, lineStyle: "dashed", title: "Lower Level" }
|
|
35053
|
+
]
|
|
34347
35054
|
});
|
|
34348
|
-
return {
|
|
34349
|
-
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
34350
|
-
levels: guideLevels(-100, 100, 0)
|
|
34351
|
-
};
|
|
34352
35055
|
}
|
|
34353
35056
|
};
|
|
34354
35057
|
var williamsR = {
|
|
@@ -34356,31 +35059,78 @@ var williamsR = {
|
|
|
34356
35059
|
title: "Williams %R",
|
|
34357
35060
|
shortTitle: "%R",
|
|
34358
35061
|
overlay: false,
|
|
34359
|
-
inputs: [
|
|
35062
|
+
inputs: [
|
|
35063
|
+
lengthInput(14, "length", "Length", 5e3, "Lookback length of the highest high / lowest low range. 14 is the classic setting."),
|
|
35064
|
+
sourceInput("Close", "source", "Source", "Series located within the lookback range. Close is the classic choice."),
|
|
35065
|
+
...thresholdInputs(-20, -80, -100, 0),
|
|
35066
|
+
colorInput(NEUTRAL, "%R Line", "color", "Color of the %R line between the thresholds. Toward either level it blends into that level\u2019s color."),
|
|
35067
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
35068
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level.")
|
|
35069
|
+
],
|
|
34360
35070
|
compute: (bars, inputs) => {
|
|
34361
|
-
const
|
|
34362
|
-
|
|
34363
|
-
|
|
34364
|
-
|
|
34365
|
-
|
|
35071
|
+
const len = num3(inputs, "length", 14);
|
|
35072
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
35073
|
+
const hh = highest(highs(bars), len);
|
|
35074
|
+
const ll = lowest(lows(bars), len);
|
|
35075
|
+
const values = src.map((x, i) => {
|
|
35076
|
+
const h = hh[i];
|
|
35077
|
+
const l = ll[i];
|
|
35078
|
+
return Number.isFinite(h) && Number.isFinite(l) && h > l ? -100 * (h - x) / (h - l) : Number.NaN;
|
|
35079
|
+
});
|
|
35080
|
+
return boundedOscillator({
|
|
35081
|
+
key: "wr",
|
|
35082
|
+
title: "%R",
|
|
35083
|
+
values,
|
|
35084
|
+
scale: { top: 0, bottom: -100 },
|
|
35085
|
+
overbought: num3(inputs, "overbought", -20),
|
|
35086
|
+
oversold: num3(inputs, "oversold", -80),
|
|
35087
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
35088
|
+
levelInk: NEUTRAL
|
|
35089
|
+
});
|
|
34366
35090
|
}
|
|
34367
35091
|
};
|
|
34368
|
-
var
|
|
35092
|
+
var cmoSpec = {
|
|
34369
35093
|
type: "chande-momentum-oscillator",
|
|
34370
35094
|
title: "Chande Momentum Oscillator",
|
|
34371
35095
|
shortTitle: "CMO",
|
|
34372
35096
|
overlay: false,
|
|
34373
|
-
inputs: [
|
|
35097
|
+
inputs: [
|
|
35098
|
+
lengthInput(9, "length", "Length", 5e3, "Number of bars over which up moves and down moves are summed."),
|
|
35099
|
+
sourceInput("Close", "source", "Source", "Price series used to measure bar-to-bar changes."),
|
|
35100
|
+
floatInput("overbought", "Overbought Level", 50, 0, 100, 1, "Level above which the oscillator is considered overbought."),
|
|
35101
|
+
floatInput("oversold", "Oversold Level", -50, -100, 0, 1, "Level below which the oscillator is considered oversold."),
|
|
35102
|
+
colorInput(NEUTRAL, "CMO Line", "color", "Color of the CMO line."),
|
|
35103
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while momentum is positive, and shading of the oversold zone."),
|
|
35104
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while momentum is negative, and shading of the overbought zone."),
|
|
35105
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought, oversold, and zero lines.")
|
|
35106
|
+
],
|
|
34374
35107
|
compute: (bars, inputs) => {
|
|
34375
35108
|
const len = num3(inputs, "length", 9);
|
|
34376
35109
|
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
34377
|
-
const
|
|
34378
|
-
const
|
|
34379
|
-
const values = zip(
|
|
34380
|
-
|
|
34381
|
-
|
|
34382
|
-
|
|
34383
|
-
|
|
35110
|
+
const up = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
35111
|
+
const down = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
35112
|
+
const values = zip(up, down, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
35113
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
35114
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
35115
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
35116
|
+
const overbought = num3(inputs, "overbought", 50);
|
|
35117
|
+
const oversold = num3(inputs, "oversold", -50);
|
|
35118
|
+
const out = centeredOscillator({
|
|
35119
|
+
key: "cmo",
|
|
35120
|
+
title: "CMO",
|
|
35121
|
+
values,
|
|
35122
|
+
ink: { bull, bear, neutral: str3(inputs, "color", NEUTRAL) },
|
|
35123
|
+
zeroInk: levelInk,
|
|
35124
|
+
zeroLineStyle: "dotted",
|
|
35125
|
+
extraLevels: [
|
|
35126
|
+
{ key: "overbought", price: overbought, color: levelInk, lineStyle: "dashed", title: "Overbought" },
|
|
35127
|
+
{ key: "oversold", price: oversold, color: levelInk, lineStyle: "dashed", title: "Oversold" }
|
|
35128
|
+
]
|
|
35129
|
+
});
|
|
35130
|
+
const zones = thresholdZones(values.length, { overbought, oversold, top: 100, bottom: -100 }, { overbought: bear, oversold: bull });
|
|
35131
|
+
out.plots.push(...zones.plots);
|
|
35132
|
+
out.bands = [...out.bands ?? [], ...zones.bands];
|
|
35133
|
+
return out;
|
|
34384
35134
|
}
|
|
34385
35135
|
};
|
|
34386
35136
|
var connorsRsi = {
|
|
@@ -34389,10 +35139,14 @@ var connorsRsi = {
|
|
|
34389
35139
|
shortTitle: "CRSI",
|
|
34390
35140
|
overlay: false,
|
|
34391
35141
|
inputs: [
|
|
34392
|
-
lengthInput(3, "rsiLength", "RSI
|
|
34393
|
-
lengthInput(2, "streakLength", "Streak RSI
|
|
34394
|
-
lengthInput(100, "rankLength", "Percent
|
|
34395
|
-
|
|
35142
|
+
lengthInput(3, "rsiLength", "RSI Length", 5e3, "Length of the Wilder RSI applied to the closing price."),
|
|
35143
|
+
lengthInput(2, "streakLength", "Streak RSI Length", 5e3, "Length of the Wilder RSI applied to the up/down close streak."),
|
|
35144
|
+
lengthInput(100, "rankLength", "Percent Rank Length", 5e3, "Lookback of the percent rank applied to the 1-bar rate of change of the closing price."),
|
|
35145
|
+
...thresholdInputs(90, 10),
|
|
35146
|
+
colorInput(NEUTRAL, "CRSI Line", "color", "Color of the Connors RSI line."),
|
|
35147
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Line, fill and zone color toward and below the oversold level."),
|
|
35148
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Line, fill and zone color toward and above the overbought level."),
|
|
35149
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought and oversold levels.")
|
|
34396
35150
|
],
|
|
34397
35151
|
compute: (bars, inputs) => {
|
|
34398
35152
|
const c = closes(bars);
|
|
@@ -34410,10 +35164,17 @@ var connorsRsi = {
|
|
|
34410
35164
|
const z = r[i];
|
|
34411
35165
|
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
34412
35166
|
});
|
|
34413
|
-
return {
|
|
34414
|
-
|
|
34415
|
-
|
|
34416
|
-
|
|
35167
|
+
return boundedOscillator({
|
|
35168
|
+
key: "crsi",
|
|
35169
|
+
title: "Connors RSI",
|
|
35170
|
+
values,
|
|
35171
|
+
scale: { top: 100, bottom: 0 },
|
|
35172
|
+
overbought: num3(inputs, "overbought", 90),
|
|
35173
|
+
oversold: num3(inputs, "oversold", 10),
|
|
35174
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
35175
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
35176
|
+
midpointWash: true
|
|
35177
|
+
});
|
|
34417
35178
|
}
|
|
34418
35179
|
};
|
|
34419
35180
|
var fisher = {
|
|
@@ -34421,7 +35182,13 @@ var fisher = {
|
|
|
34421
35182
|
title: "Fisher Transform",
|
|
34422
35183
|
shortTitle: "Fisher",
|
|
34423
35184
|
overlay: false,
|
|
34424
|
-
inputs: [
|
|
35185
|
+
inputs: [
|
|
35186
|
+
lengthInput(9, "length", "Length", 5e3, "Lookback period used to locate the median price hl2 within its highest-lowest range. Ehlers' original length is 10; 9 is the common platform default."),
|
|
35187
|
+
colorInput(NEUTRAL, "Fisher Line", "color", "Color of the Fisher line when it sits exactly on its trigger."),
|
|
35188
|
+
colorInput(NEUTRAL, "Trigger Line", "triggerColor", "Color of the trigger line, the Fisher value delayed one bar."),
|
|
35189
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the Fisher line and fill while it is above the trigger."),
|
|
35190
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the Fisher line and fill while it is below the trigger.")
|
|
35191
|
+
],
|
|
34425
35192
|
compute: (bars, inputs) => {
|
|
34426
35193
|
const len = num3(inputs, "length", 9);
|
|
34427
35194
|
const hl2 = sourceValues(bars, "HL2");
|
|
@@ -34429,23 +35196,39 @@ var fisher = {
|
|
|
34429
35196
|
const lo = lowest(hl2, len);
|
|
34430
35197
|
const n = bars.length;
|
|
34431
35198
|
const out = new Array(n).fill(Number.NaN);
|
|
35199
|
+
let position = 0;
|
|
34432
35200
|
let value = 0;
|
|
34433
|
-
let fish = 0;
|
|
34434
35201
|
for (let i = 0; i < n; i++) {
|
|
34435
35202
|
const h = hi[i];
|
|
34436
35203
|
const l = lo[i];
|
|
34437
35204
|
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
34438
|
-
const
|
|
34439
|
-
|
|
34440
|
-
|
|
34441
|
-
|
|
34442
|
-
|
|
35205
|
+
const raw = h > l ? (hl2[i] - l) / (h - l) - 0.5 : 0;
|
|
35206
|
+
position = 0.66 * raw + 0.67 * position;
|
|
35207
|
+
position = position > 0.99 ? 0.999 : position < -0.99 ? -0.999 : position;
|
|
35208
|
+
value = 0.5 * Math.log((1 + position) / (1 - position)) + 0.5 * value;
|
|
35209
|
+
out[i] = value;
|
|
35210
|
+
}
|
|
35211
|
+
const trigger = shift(out, 1);
|
|
35212
|
+
const neutral = str3(inputs, "color", NEUTRAL);
|
|
35213
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
35214
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
35215
|
+
const inkOf = (i) => {
|
|
35216
|
+
const f = out[i];
|
|
35217
|
+
const t = trigger[i];
|
|
35218
|
+
if (!Number.isFinite(t)) return neutral;
|
|
35219
|
+
return f > t ? bull : f < t ? bear : neutral;
|
|
35220
|
+
};
|
|
34443
35221
|
return {
|
|
34444
35222
|
plots: [
|
|
34445
|
-
{ key: "fisher", title: "Fisher", values: out, color:
|
|
34446
|
-
{ key: "trigger", title: "Trigger", values:
|
|
35223
|
+
{ key: "fisher", title: "Fisher", values: out, color: neutral, width: 2, colors: out.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) },
|
|
35224
|
+
{ key: "trigger", title: "Trigger", values: trigger, color: str3(inputs, "triggerColor", NEUTRAL) }
|
|
34447
35225
|
],
|
|
34448
|
-
|
|
35226
|
+
bands: [{ key: "spread", from: "fisher", to: "trigger", color: transp(neutral, 80), colors: out.map((x, i) => Number.isFinite(x) ? transp(inkOf(i), 80) : null) }],
|
|
35227
|
+
levels: [
|
|
35228
|
+
{ key: "upper", price: 1.5, color: NEUTRAL, lineStyle: "dashed", title: "Upper Level" },
|
|
35229
|
+
{ key: "lower", price: -1.5, color: NEUTRAL, lineStyle: "dashed", title: "Lower Level" },
|
|
35230
|
+
{ key: "zero", price: 0, color: NEUTRAL, lineStyle: "dotted", title: "Zero Line" }
|
|
35231
|
+
]
|
|
34449
35232
|
};
|
|
34450
35233
|
}
|
|
34451
35234
|
};
|
|
@@ -34453,93 +35236,180 @@ var trix = {
|
|
|
34453
35236
|
type: "trix",
|
|
34454
35237
|
title: "TRIX",
|
|
34455
35238
|
overlay: false,
|
|
34456
|
-
inputs: [
|
|
35239
|
+
inputs: [
|
|
35240
|
+
lengthInput(18, "length", "Length", 5e3, "Length of each of the three EMA smoothing passes applied to the close price."),
|
|
35241
|
+
lengthInput(9, "signalLength", "Signal Length", 500, "Length of the EMA applied to TRIX to form the signal line."),
|
|
35242
|
+
colorInput(NEUTRAL, "TRIX Color", "color", "Color of the TRIX line."),
|
|
35243
|
+
colorInput(WARNING, "Signal Color", "signalColor", "Color of the signal line."),
|
|
35244
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the TRIX line and its gradient fill while TRIX holds above zero."),
|
|
35245
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the TRIX line and its gradient fill while TRIX holds below zero.")
|
|
35246
|
+
],
|
|
34457
35247
|
compute: (bars, inputs) => {
|
|
34458
35248
|
const len = num3(inputs, "length", 18);
|
|
34459
|
-
const
|
|
34460
|
-
const
|
|
34461
|
-
|
|
34462
|
-
|
|
34463
|
-
|
|
34464
|
-
|
|
34465
|
-
|
|
35249
|
+
const smooth = ema(ema(ema(closes(bars), len), len), len);
|
|
35250
|
+
const values = roc(smooth, 1);
|
|
35251
|
+
return centeredOscillator({
|
|
35252
|
+
key: "trix",
|
|
35253
|
+
title: "TRIX",
|
|
35254
|
+
values,
|
|
35255
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
35256
|
+
zeroInk: NEUTRAL,
|
|
35257
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(values, num3(inputs, "signalLength", 9)), color: str3(inputs, "signalColor", WARNING) }]
|
|
35258
|
+
});
|
|
34466
35259
|
}
|
|
34467
35260
|
};
|
|
34468
|
-
function
|
|
35261
|
+
function tsiRatio(src, long, short) {
|
|
34469
35262
|
const mom = change(src);
|
|
34470
35263
|
const numer = ema(ema(mom, long), short);
|
|
34471
35264
|
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
34472
|
-
return zip(numer, denom, (a, b) => b === 0 ? 0 :
|
|
35265
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : a / b);
|
|
34473
35266
|
}
|
|
34474
35267
|
var tsi = {
|
|
34475
35268
|
type: "true-strength-index",
|
|
34476
35269
|
title: "True Strength Index",
|
|
34477
35270
|
shortTitle: "TSI",
|
|
34478
35271
|
overlay: false,
|
|
34479
|
-
inputs: [
|
|
35272
|
+
inputs: [
|
|
35273
|
+
lengthInput(25, "longLength", "Long Length", 5e3, "Length of the first (long) EMA applied to the 1-bar price change."),
|
|
35274
|
+
lengthInput(13, "shortLength", "Short Length", 5e3, "Length of the second (short) EMA applied to the long-smoothed price change."),
|
|
35275
|
+
lengthInput(13, "signalLength", "Signal Length", 500, "Length of the EMA of TSI used as the signal line."),
|
|
35276
|
+
colorInput(NEUTRAL, "TSI", "color", "Color of the TSI line."),
|
|
35277
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line."),
|
|
35278
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the TSI line and its gradient fill while TSI holds above zero."),
|
|
35279
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the TSI line and its gradient fill while TSI holds below zero.")
|
|
35280
|
+
],
|
|
34480
35281
|
compute: (bars, inputs) => {
|
|
34481
|
-
const
|
|
34482
|
-
return {
|
|
34483
|
-
|
|
34484
|
-
|
|
34485
|
-
|
|
34486
|
-
|
|
34487
|
-
|
|
34488
|
-
|
|
35282
|
+
const values = map(tsiRatio(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13)), (x) => 100 * x);
|
|
35283
|
+
return centeredOscillator({
|
|
35284
|
+
key: "tsi",
|
|
35285
|
+
title: "TSI",
|
|
35286
|
+
values,
|
|
35287
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
35288
|
+
zeroInk: NEUTRAL,
|
|
35289
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(values, num3(inputs, "signalLength", 13)), color: str3(inputs, "signalColor", WARNING) }]
|
|
35290
|
+
});
|
|
34489
35291
|
}
|
|
34490
35292
|
};
|
|
35293
|
+
var SMI_INDICATOR = "Indicator";
|
|
35294
|
+
var SMI_OSCILLATOR = "Oscillator";
|
|
34491
35295
|
var smiErgodic = {
|
|
34492
35296
|
type: "smi-ergodic",
|
|
34493
35297
|
title: "SMI Ergodic Oscillator",
|
|
34494
35298
|
shortTitle: "SMIE",
|
|
34495
35299
|
overlay: false,
|
|
34496
|
-
inputs: [
|
|
35300
|
+
inputs: [
|
|
35301
|
+
lengthInput(20, "longLength", "Long Length", 5e3, "Length of the first EMA smoothing applied to the momentum of the closing price."),
|
|
35302
|
+
lengthInput(5, "shortLength", "Short Length", 5e3, "Length of the second EMA smoothing applied to the momentum of the closing price."),
|
|
35303
|
+
lengthInput(5, "signalLength", "Signal Length", 500, "Length of the EMA of the SMI that forms the signal line."),
|
|
35304
|
+
optionInput("display", "Display", SMI_INDICATOR, [SMI_INDICATOR, SMI_OSCILLATOR], "Indicator plots the SMI and its signal line. Oscillator plots the difference between them as a histogram."),
|
|
35305
|
+
colorInput(NEUTRAL, "SMI", "color", "Color of the SMI line while it sits exactly on its signal line."),
|
|
35306
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line."),
|
|
35307
|
+
colorInput(BULLISH, "Oscillator Grow", "growColor", "Color of the rising oscillator histogram, and of the SMI line while it leads its signal."),
|
|
35308
|
+
colorInput(BEARISH, "Oscillator Fall", "fallColor", "Color of the falling oscillator histogram, and of the SMI line while it lags its signal.")
|
|
35309
|
+
],
|
|
34497
35310
|
compute: (bars, inputs) => {
|
|
34498
|
-
const
|
|
34499
|
-
const signal = ema(
|
|
34500
|
-
const
|
|
35311
|
+
const smi = tsiRatio(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
35312
|
+
const signal = ema(smi, num3(inputs, "signalLength", 5));
|
|
35313
|
+
const grow = str3(inputs, "growColor", BULLISH);
|
|
35314
|
+
const fall = str3(inputs, "fallColor", BEARISH);
|
|
35315
|
+
const zero = [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed", title: "Zero" }];
|
|
35316
|
+
if (str3(inputs, "display", SMI_INDICATOR) === SMI_OSCILLATOR) {
|
|
35317
|
+
const osc = sub(smi, signal);
|
|
35318
|
+
const colors = osc.map((x, i) => {
|
|
35319
|
+
if (!Number.isFinite(x)) return null;
|
|
35320
|
+
const prev2 = i > 0 && Number.isFinite(osc[i - 1]) ? osc[i - 1] : x;
|
|
35321
|
+
return x > prev2 ? grow : fall;
|
|
35322
|
+
});
|
|
35323
|
+
return { plots: [{ key: "osc", title: "Oscillator", values: osc, kind: "columns", color: grow, colors, base: 0 }], levels: zero };
|
|
35324
|
+
}
|
|
35325
|
+
const neutral = str3(inputs, "color", NEUTRAL);
|
|
35326
|
+
const inkOf = (i) => {
|
|
35327
|
+
const a = smi[i];
|
|
35328
|
+
const b = signal[i];
|
|
35329
|
+
if (!Number.isFinite(b)) return neutral;
|
|
35330
|
+
return a > b ? grow : a < b ? fall : neutral;
|
|
35331
|
+
};
|
|
34501
35332
|
return {
|
|
34502
35333
|
plots: [
|
|
34503
|
-
{ key: "
|
|
34504
|
-
{ key: "
|
|
34505
|
-
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
35334
|
+
{ key: "smi", title: "SMI", values: smi, color: neutral, colors: smi.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) },
|
|
35335
|
+
{ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) }
|
|
34506
35336
|
],
|
|
34507
|
-
|
|
35337
|
+
bands: [{ key: "spread", from: "smi", to: "signal", color: transp(neutral, 75), colors: smi.map((x, i) => Number.isFinite(x) ? transp(inkOf(i), 75) : null) }],
|
|
35338
|
+
levels: zero
|
|
34508
35339
|
};
|
|
34509
35340
|
}
|
|
34510
35341
|
};
|
|
35342
|
+
var STC_MID = 50;
|
|
35343
|
+
var TRIGGERS = "Trigger Levels";
|
|
34511
35344
|
var stc = {
|
|
34512
35345
|
type: "schaff-trend-cycle",
|
|
34513
35346
|
title: "Schaff Trend Cycle",
|
|
34514
35347
|
shortTitle: "Schaff Trend Cycle",
|
|
34515
35348
|
overlay: false,
|
|
34516
|
-
inputs: [
|
|
35349
|
+
inputs: [
|
|
35350
|
+
sourceInput("Close", "source", "Source", "Price series used to build the MACD that the trend cycle is measured on."),
|
|
35351
|
+
lengthInput(23, "fastLength", "Fast Length", 5e3, "Fast EMA length of the underlying MACD."),
|
|
35352
|
+
lengthInput(50, "slowLength", "Slow Length", 5e3, "Slow EMA length of the underlying MACD."),
|
|
35353
|
+
{ ...lengthInput(10, "cycleLength", "Cycle Length", 5e3, "Window of the two stochastic passes. Each pass measures where its input sits inside the range of its own values over this many bars."), min: 2 },
|
|
35354
|
+
floatInput("factor", "Smoothing Factor", 0.5, 0.01, 1, 0.05, "%D style recursive smoothing applied after each stochastic pass. 0.5 is the published default."),
|
|
35355
|
+
{ ...floatInput("upper", "Upper Trigger", 75, 0, 100, 1, "Upper trigger line. A cross down through it flags an emerging downtrend."), group: TRIGGERS },
|
|
35356
|
+
{ ...floatInput("lower", "Lower Trigger", 25, 0, 100, 1, "Lower trigger line. A cross up through it flags an emerging uptrend."), group: TRIGGERS },
|
|
35357
|
+
colorInput(BULLISH, "Bullish Color", "bullColor", "Color of the trend cycle line and gradient while the cycle holds the upper half of its 0-100 scale."),
|
|
35358
|
+
colorInput(BEARISH, "Bearish Color", "bearColor", "Color of the trend cycle line and gradient while the cycle holds the lower half of its 0-100 scale."),
|
|
35359
|
+
colorInput(NEUTRAL, "Levels Color", "levelsColor", "Color of the dashed trigger lines and the dotted midline."),
|
|
35360
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Vertical gradient between the midline and the trend cycle, fading out toward the midline.")
|
|
35361
|
+
],
|
|
34517
35362
|
compute: (bars, inputs) => {
|
|
34518
35363
|
const len = num3(inputs, "cycleLength", 10);
|
|
34519
|
-
const
|
|
35364
|
+
const factor = num3(inputs, "factor", 0.5);
|
|
35365
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
34520
35366
|
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
34521
|
-
const
|
|
35367
|
+
const stochNorm = (v) => {
|
|
34522
35368
|
const lo = lowest(v, len);
|
|
34523
35369
|
const hi = highest(v, len);
|
|
34524
35370
|
const out = new Array(v.length).fill(Number.NaN);
|
|
34525
|
-
let
|
|
34526
|
-
let smoothed = Number.NaN;
|
|
35371
|
+
let pct = Number.NaN;
|
|
34527
35372
|
for (let i = 0; i < v.length; i++) {
|
|
34528
35373
|
const x = v[i];
|
|
34529
35374
|
const l = lo[i];
|
|
34530
35375
|
const h = hi[i];
|
|
34531
35376
|
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
34532
|
-
|
|
34533
|
-
|
|
34534
|
-
|
|
35377
|
+
if (h > l) pct = 100 * (x - l) / (h - l);
|
|
35378
|
+
out[i] = pct;
|
|
35379
|
+
}
|
|
35380
|
+
return out;
|
|
35381
|
+
};
|
|
35382
|
+
const smoothStep = (v) => {
|
|
35383
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
35384
|
+
let smoothed = Number.NaN;
|
|
35385
|
+
for (let i = 0; i < v.length; i++) {
|
|
35386
|
+
const x = v[i];
|
|
35387
|
+
if (!Number.isFinite(x)) continue;
|
|
35388
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + factor * (x - smoothed) : x;
|
|
34535
35389
|
out[i] = smoothed;
|
|
34536
35390
|
}
|
|
34537
35391
|
return out;
|
|
34538
35392
|
};
|
|
34539
|
-
const values =
|
|
35393
|
+
const values = smoothStep(stochNorm(smoothStep(stochNorm(macdLine))));
|
|
35394
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
35395
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
35396
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
35397
|
+
const plots = [
|
|
35398
|
+
{ key: "stc", title: "Schaff Trend Cycle", values, color: bear, width: 2, colors: values.map((x) => Number.isFinite(x) ? x > STC_MID ? bull : bear : null) }
|
|
35399
|
+
];
|
|
35400
|
+
const bands = [];
|
|
35401
|
+
if (bool(inputs, "gradientFill", true)) {
|
|
35402
|
+
plots.push(anchorPlot("midAnchor", "Midline Anchor", values.length, STC_MID));
|
|
35403
|
+
bands.push({ key: "wash", from: "stc", to: "midAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, STC_MID, (x) => x > STC_MID ? bull : bear) });
|
|
35404
|
+
}
|
|
34540
35405
|
return {
|
|
34541
|
-
plots
|
|
34542
|
-
|
|
35406
|
+
plots,
|
|
35407
|
+
bands,
|
|
35408
|
+
levels: [
|
|
35409
|
+
{ key: "upper", price: num3(inputs, "upper", 75), color: transp(levelInk, 25), lineStyle: "dashed", title: "Upper Trigger" },
|
|
35410
|
+
{ key: "lower", price: num3(inputs, "lower", 25), color: transp(levelInk, 25), lineStyle: "dashed", title: "Lower Trigger" },
|
|
35411
|
+
{ key: "midline", price: STC_MID, color: transp(levelInk, 60), lineStyle: "dotted", title: "Midline" }
|
|
35412
|
+
]
|
|
34543
35413
|
};
|
|
34544
35414
|
}
|
|
34545
35415
|
};
|
|
@@ -34548,22 +35418,37 @@ var kst = {
|
|
|
34548
35418
|
title: "Know Sure Thing",
|
|
34549
35419
|
shortTitle: "KST",
|
|
34550
35420
|
overlay: false,
|
|
34551
|
-
inputs: [
|
|
35421
|
+
inputs: [
|
|
35422
|
+
lengthInput(10, "rocLength1", "ROC Length #1", 5e3, "Lookback of the first (shortest) rate of change."),
|
|
35423
|
+
lengthInput(15, "rocLength2", "ROC Length #2", 5e3, "Lookback of the second rate of change."),
|
|
35424
|
+
lengthInput(20, "rocLength3", "ROC Length #3", 5e3, "Lookback of the third rate of change."),
|
|
35425
|
+
lengthInput(30, "rocLength4", "ROC Length #4", 5e3, "Lookback of the fourth (longest) rate of change."),
|
|
35426
|
+
lengthInput(10, "smaLength1", "SMA Length #1", 5e3, "Smoothing length applied to the first rate of change (RCMA #1)."),
|
|
35427
|
+
lengthInput(10, "smaLength2", "SMA Length #2", 5e3, "Smoothing length applied to the second rate of change (RCMA #2)."),
|
|
35428
|
+
lengthInput(10, "smaLength3", "SMA Length #3", 5e3, "Smoothing length applied to the third rate of change (RCMA #3)."),
|
|
35429
|
+
lengthInput(15, "smaLength4", "SMA Length #4", 5e3, "Smoothing length applied to the fourth rate of change (RCMA #4)."),
|
|
35430
|
+
lengthInput(9, "signalLength", "Signal Length", 500, "Simple moving average length of the signal line."),
|
|
35431
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the KST line while above zero."),
|
|
35432
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the KST line while below zero."),
|
|
35433
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line.")
|
|
35434
|
+
],
|
|
34552
35435
|
compute: (bars, inputs) => {
|
|
34553
|
-
const
|
|
34554
|
-
const
|
|
34555
|
-
|
|
34556
|
-
|
|
34557
|
-
|
|
34558
|
-
|
|
34559
|
-
|
|
34560
|
-
return {
|
|
34561
|
-
|
|
34562
|
-
|
|
34563
|
-
|
|
34564
|
-
|
|
34565
|
-
|
|
34566
|
-
|
|
35436
|
+
const c = closes(bars);
|
|
35437
|
+
const rcma = (rocKey, rocDefault, smaKey, smaDefault) => sma(roc(c, num3(inputs, rocKey, rocDefault)), num3(inputs, smaKey, smaDefault));
|
|
35438
|
+
const values = [rcma("rocLength1", 10, "smaLength1", 10), rcma("rocLength2", 15, "smaLength2", 10), rcma("rocLength3", 20, "smaLength3", 10), rcma("rocLength4", 30, "smaLength4", 15)].reduce(
|
|
35439
|
+
(acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)),
|
|
35440
|
+
new Array(c.length).fill(0)
|
|
35441
|
+
);
|
|
35442
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
35443
|
+
return centeredOscillator({
|
|
35444
|
+
key: "kst",
|
|
35445
|
+
title: "KST",
|
|
35446
|
+
values,
|
|
35447
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
35448
|
+
zeroInk: NEUTRAL,
|
|
35449
|
+
valueTransparency: 60,
|
|
35450
|
+
extraPlots: [{ key: "signal", title: "Signal", values: sma(values, num3(inputs, "signalLength", 9)), color: str3(inputs, "signalColor", WARNING) }]
|
|
35451
|
+
});
|
|
34567
35452
|
}
|
|
34568
35453
|
};
|
|
34569
35454
|
var coppock = {
|
|
@@ -34572,19 +35457,25 @@ var coppock = {
|
|
|
34572
35457
|
shortTitle: "Coppock",
|
|
34573
35458
|
overlay: false,
|
|
34574
35459
|
inputs: [
|
|
34575
|
-
lengthInput(10, "wmaLength", "WMA
|
|
34576
|
-
lengthInput(14, "longRoc", "Long RoC
|
|
34577
|
-
lengthInput(11, "shortRoc", "Short RoC
|
|
34578
|
-
sourceInput(),
|
|
34579
|
-
colorInput()
|
|
35460
|
+
lengthInput(10, "wmaLength", "WMA Length", 5e3, "Length of the weighted moving average applied to the sum of the two rates of change."),
|
|
35461
|
+
lengthInput(14, "longRoc", "Long RoC Length", 5e3, "Lookback of the long rate of change, in bars. The original definition uses 14 monthly closes."),
|
|
35462
|
+
lengthInput(11, "shortRoc", "Short RoC Length", 5e3, "Lookback of the short rate of change, in bars. The original definition uses 11 monthly closes."),
|
|
35463
|
+
sourceInput("Close", "source", "Source", "Price series used for the rate of change calculations."),
|
|
35464
|
+
colorInput(NEUTRAL, "Coppock Curve", "color", "Color of the Coppock Curve line."),
|
|
35465
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the curve reads above zero."),
|
|
35466
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the curve reads below zero."),
|
|
35467
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
34580
35468
|
],
|
|
34581
35469
|
compute: (bars, inputs) => {
|
|
34582
35470
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
34583
35471
|
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
34584
|
-
return {
|
|
34585
|
-
|
|
34586
|
-
|
|
34587
|
-
|
|
35472
|
+
return centeredOscillator({
|
|
35473
|
+
key: "coppock",
|
|
35474
|
+
title: "Coppock Curve",
|
|
35475
|
+
values,
|
|
35476
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
35477
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
35478
|
+
});
|
|
34588
35479
|
}
|
|
34589
35480
|
};
|
|
34590
35481
|
var dpo = {
|
|
@@ -34592,42 +35483,76 @@ var dpo = {
|
|
|
34592
35483
|
title: "Detrended Price Oscillator",
|
|
34593
35484
|
shortTitle: "DPO",
|
|
34594
35485
|
overlay: false,
|
|
34595
|
-
inputs: [
|
|
35486
|
+
inputs: [
|
|
35487
|
+
lengthInput(21, "length", "Length", 5e3, "Lookback of the simple moving average that price is detrended against. The comparison is displaced by Length / 2 + 1 bars."),
|
|
35488
|
+
sourceInput("Close", "source", "Source", "Price series used in the detrending calculation."),
|
|
35489
|
+
boolInput("centered", "Centered", false, void 0, "Shifts the oscillator back by Length / 2 + 1 bars so it is centered on the prices it detrends. The most recent bars then show no value."),
|
|
35490
|
+
colorInput(NEUTRAL, "DPO", "color", "Color of the line when it sits exactly at zero."),
|
|
35491
|
+
colorInput(BULLISH, "Above Zero", "bullColor", "Color of the line and fill while the oscillator is above zero."),
|
|
35492
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while the oscillator is below zero."),
|
|
35493
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
35494
|
+
],
|
|
34596
35495
|
compute: (bars, inputs) => {
|
|
34597
35496
|
const len = num3(inputs, "length", 21);
|
|
34598
|
-
const
|
|
34599
|
-
const
|
|
34600
|
-
|
|
34601
|
-
|
|
34602
|
-
|
|
34603
|
-
|
|
35497
|
+
const barsBack = Math.trunc(len / 2) + 1;
|
|
35498
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
35499
|
+
const basis = sma(src, len);
|
|
35500
|
+
const centered = bool(inputs, "centered", false);
|
|
35501
|
+
const values = centered ? shift(sub(shift(src, barsBack), basis), -barsBack) : sub(src, shift(basis, barsBack));
|
|
35502
|
+
return centeredOscillator({
|
|
35503
|
+
key: "dpo",
|
|
35504
|
+
title: "DPO",
|
|
35505
|
+
values,
|
|
35506
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
35507
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
35508
|
+
});
|
|
34604
35509
|
}
|
|
34605
35510
|
};
|
|
35511
|
+
var ROC_PERCENT = "ROC %";
|
|
35512
|
+
var ROC_MOMENTUM = "Momentum";
|
|
34606
35513
|
var rocSpec = {
|
|
34607
35514
|
type: "rate-of-change",
|
|
34608
35515
|
title: "Rate of Change",
|
|
34609
35516
|
shortTitle: "ROC",
|
|
34610
35517
|
overlay: false,
|
|
34611
|
-
inputs: [
|
|
34612
|
-
|
|
34613
|
-
|
|
34614
|
-
|
|
34615
|
-
|
|
34616
|
-
|
|
34617
|
-
|
|
34618
|
-
|
|
34619
|
-
|
|
34620
|
-
|
|
34621
|
-
|
|
34622
|
-
|
|
34623
|
-
|
|
35518
|
+
inputs: [
|
|
35519
|
+
lengthInput(9, "length", "Length", 5e3, "Lookback period; the source is compared with its value this many bars ago."),
|
|
35520
|
+
sourceInput("Close", "source", "Source", "Series used as input for the calculation."),
|
|
35521
|
+
optionInput("mode", "Mode", ROC_PERCENT, [ROC_PERCENT, ROC_MOMENTUM], "ROC % plots the change as a percentage of the source value from the lookback; Momentum plots the raw difference."),
|
|
35522
|
+
colorInput(NEUTRAL, "Line", "color", "Color of the line when the reading sits exactly at zero."),
|
|
35523
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the line and fill while the reading is above zero."),
|
|
35524
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line and fill while the reading is below zero."),
|
|
35525
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the line and zero, following the line color.")
|
|
35526
|
+
],
|
|
35527
|
+
compute: (bars, inputs) => {
|
|
35528
|
+
const len = num3(inputs, "length", 9);
|
|
35529
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
35530
|
+
const values = str3(inputs, "mode", ROC_PERCENT) === ROC_MOMENTUM ? change(src, len) : roc(src, len);
|
|
35531
|
+
return centeredOscillator({
|
|
35532
|
+
key: "roc",
|
|
35533
|
+
title: "ROC",
|
|
35534
|
+
values,
|
|
35535
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
35536
|
+
zeroInk: NEUTRAL,
|
|
35537
|
+
fill: bool(inputs, "gradientFill", true)
|
|
35538
|
+
});
|
|
35539
|
+
}
|
|
34624
35540
|
};
|
|
34625
35541
|
var ultimate = {
|
|
34626
35542
|
type: "ultimate-oscillator",
|
|
34627
35543
|
title: "Ultimate Oscillator",
|
|
34628
35544
|
shortTitle: "UO",
|
|
34629
35545
|
overlay: false,
|
|
34630
|
-
inputs: [
|
|
35546
|
+
inputs: [
|
|
35547
|
+
lengthInput(7, "fastLength", "Fast Length", 5e3, "Bars in the fastest averaging window. Weighted 4 in the final blend, so it dominates the oscillator."),
|
|
35548
|
+
lengthInput(14, "middleLength", "Middle Length", 5e3, "Bars in the intermediate averaging window. Weighted 2 in the final blend."),
|
|
35549
|
+
lengthInput(28, "slowLength", "Slow Length", 5e3, "Bars in the slowest averaging window. Weighted 1 in the final blend."),
|
|
35550
|
+
...thresholdInputs(70, 30, 0, 100, "Overbought", "Oversold"),
|
|
35551
|
+
colorInput(NEUTRAL, "Oscillator", "color", "Color of the line midway between the levels. Toward either level it blends into that level\u2019s color."),
|
|
35552
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought and oversold levels."),
|
|
35553
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line at the oversold level and of the oversold zone shading."),
|
|
35554
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line at the overbought level and of the overbought zone shading.")
|
|
35555
|
+
],
|
|
34631
35556
|
compute: (bars, inputs) => {
|
|
34632
35557
|
const n = bars.length;
|
|
34633
35558
|
const bp = new Array(n).fill(Number.NaN);
|
|
@@ -34635,10 +35560,9 @@ var ultimate = {
|
|
|
34635
35560
|
for (let i = 0; i < n; i++) {
|
|
34636
35561
|
const b = bars[i];
|
|
34637
35562
|
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
34638
|
-
const
|
|
34639
|
-
|
|
34640
|
-
|
|
34641
|
-
tr[i] = hi - lo;
|
|
35563
|
+
const trueLow = Math.min(b.low, pc);
|
|
35564
|
+
bp[i] = b.close - trueLow;
|
|
35565
|
+
tr[i] = Math.max(b.high, pc) - trueLow;
|
|
34642
35566
|
}
|
|
34643
35567
|
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
34644
35568
|
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
@@ -34649,10 +35573,17 @@ var ultimate = {
|
|
|
34649
35573
|
const z = a28[i];
|
|
34650
35574
|
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
34651
35575
|
});
|
|
34652
|
-
return {
|
|
34653
|
-
|
|
34654
|
-
|
|
34655
|
-
|
|
35576
|
+
return boundedOscillator({
|
|
35577
|
+
key: "uo",
|
|
35578
|
+
title: "UO",
|
|
35579
|
+
values,
|
|
35580
|
+
scale: { top: 100, bottom: 0 },
|
|
35581
|
+
overbought: num3(inputs, "overbought", 70),
|
|
35582
|
+
oversold: num3(inputs, "oversold", 30),
|
|
35583
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
35584
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
35585
|
+
zoneTransparency: 91
|
|
35586
|
+
});
|
|
34656
35587
|
}
|
|
34657
35588
|
};
|
|
34658
35589
|
var rvgi = {
|
|
@@ -34660,7 +35591,11 @@ var rvgi = {
|
|
|
34660
35591
|
title: "Relative Vigor Index",
|
|
34661
35592
|
shortTitle: "RVGI",
|
|
34662
35593
|
overlay: false,
|
|
34663
|
-
inputs: [
|
|
35594
|
+
inputs: [
|
|
35595
|
+
lengthInput(10, "length", "Length", 5e3, "Number of bars summed for the smoothed numerator (close \u2212 open) and denominator (high \u2212 low)."),
|
|
35596
|
+
colorInput(BULLISH, "RVGI", "color", "Color of the RVGI line."),
|
|
35597
|
+
colorInput(BEARISH, "Signal", "signalColor", "Color of the signal line.")
|
|
35598
|
+
],
|
|
34664
35599
|
compute: (bars, inputs) => {
|
|
34665
35600
|
const len = num3(inputs, "length", 10);
|
|
34666
35601
|
const co = swma(sub(closes(bars), opens(bars)));
|
|
@@ -34668,31 +35603,49 @@ var rvgi = {
|
|
|
34668
35603
|
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
34669
35604
|
return {
|
|
34670
35605
|
plots: [
|
|
34671
|
-
{ key: "rvgi", title: "RVGI", values: line, color:
|
|
34672
|
-
{ key: "signal", title: "Signal", values: swma(line), color:
|
|
35606
|
+
{ key: "rvgi", title: "RVGI", values: line, color: str3(inputs, "color", BULLISH) },
|
|
35607
|
+
{ key: "signal", title: "Signal", values: swma(line), color: str3(inputs, "signalColor", BEARISH) }
|
|
34673
35608
|
],
|
|
34674
|
-
levels:
|
|
35609
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed", title: "Zero" }]
|
|
34675
35610
|
};
|
|
34676
35611
|
}
|
|
34677
35612
|
};
|
|
35613
|
+
var RVI_EMA = "EMA";
|
|
35614
|
+
var RVI_RMA = "Wilder (RMA)";
|
|
34678
35615
|
var relVolatility = {
|
|
34679
35616
|
type: "relative-volatility-index",
|
|
34680
35617
|
title: "Relative Volatility Index",
|
|
34681
35618
|
shortTitle: "RVI",
|
|
34682
35619
|
overlay: false,
|
|
34683
|
-
inputs: [
|
|
35620
|
+
inputs: [
|
|
35621
|
+
lengthInput(10, "stdevLength", "Stdev Length", 5e3, "Number of bars used for the standard deviation of closing prices that serves as each bar's volatility reading."),
|
|
35622
|
+
lengthInput(14, "smoothLength", "Smoothing Length", 5e3, "Number of bars used to smooth the up and down volatility streams."),
|
|
35623
|
+
optionInput("smoothType", "Smoothing Type", RVI_EMA, [RVI_EMA, RVI_RMA], "Moving average applied to the volatility streams. EMA is Dorsey's classic calculation; Wilder (RMA) matches platforms that use Wilder smoothing."),
|
|
35624
|
+
...thresholdInputs(80, 20),
|
|
35625
|
+
colorInput(NEUTRAL, "RVI Line", "color", "Color of the RVI line at the midline. Toward either threshold it blends into that threshold\u2019s color."),
|
|
35626
|
+
colorInput(BULLISH, "Up Volatility", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
35627
|
+
colorInput(BEARISH, "Down Volatility", "oversoldColor", "Color of the line and zone shading at and below the oversold level.")
|
|
35628
|
+
],
|
|
34684
35629
|
compute: (bars, inputs) => {
|
|
35630
|
+
const c = closes(bars);
|
|
35631
|
+
const sigma = stdev(c, num3(inputs, "stdevLength", 10));
|
|
35632
|
+
const d = change(c);
|
|
35633
|
+
const up = zip(sigma, d, (s, x) => x > 0 ? s : 0);
|
|
35634
|
+
const down = zip(sigma, d, (s, x) => x < 0 ? s : 0);
|
|
34685
35635
|
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
34686
|
-
const
|
|
34687
|
-
const
|
|
34688
|
-
|
|
34689
|
-
|
|
34690
|
-
|
|
34691
|
-
|
|
34692
|
-
|
|
34693
|
-
|
|
34694
|
-
|
|
34695
|
-
|
|
35636
|
+
const smooth = str3(inputs, "smoothType", RVI_EMA) === RVI_RMA ? rma : ema;
|
|
35637
|
+
const values = zip(smooth(up, smoothLen), smooth(down, smoothLen), (u, w) => u + w === 0 ? 0 : 100 * u / (u + w));
|
|
35638
|
+
return boundedOscillator({
|
|
35639
|
+
key: "rvi",
|
|
35640
|
+
title: "RVI",
|
|
35641
|
+
values,
|
|
35642
|
+
scale: { top: 100, bottom: 0 },
|
|
35643
|
+
overbought: num3(inputs, "overbought", 80),
|
|
35644
|
+
oversold: num3(inputs, "oversold", 20),
|
|
35645
|
+
ink: { low: str3(inputs, "oversoldColor", BEARISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BULLISH) },
|
|
35646
|
+
levelInk: NEUTRAL,
|
|
35647
|
+
width: 2
|
|
35648
|
+
});
|
|
34696
35649
|
}
|
|
34697
35650
|
};
|
|
34698
35651
|
var bop = {
|
|
@@ -34700,28 +35653,43 @@ var bop = {
|
|
|
34700
35653
|
title: "Balance of Power",
|
|
34701
35654
|
shortTitle: "BOP",
|
|
34702
35655
|
overlay: false,
|
|
34703
|
-
inputs: [
|
|
35656
|
+
inputs: [
|
|
35657
|
+
boolInput("smoothing", "Smoothing", false, void 0, "Smooth the raw Balance of Power with a simple moving average instead of plotting the single-bar value."),
|
|
35658
|
+
lengthInput(14, "smoothLength", "Length", 5e3, "Simple moving average length applied when smoothing is enabled."),
|
|
35659
|
+
colorInput(NEUTRAL, "BOP Color", "color", "Color of the line at exactly zero."),
|
|
35660
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the Balance of Power is above zero \u2014 buyers in control of the bar."),
|
|
35661
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the Balance of Power is below zero \u2014 sellers in control of the bar.")
|
|
35662
|
+
],
|
|
34704
35663
|
compute: (bars, inputs) => {
|
|
34705
|
-
const
|
|
34706
|
-
|
|
34707
|
-
|
|
34708
|
-
|
|
34709
|
-
|
|
35664
|
+
const raw = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
35665
|
+
const values = bool(inputs, "smoothing", false) ? sma(raw, num3(inputs, "smoothLength", 14)) : raw;
|
|
35666
|
+
return centeredOscillator({
|
|
35667
|
+
key: "bop",
|
|
35668
|
+
title: "BOP",
|
|
35669
|
+
values,
|
|
35670
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
35671
|
+
zeroInk: NEUTRAL,
|
|
35672
|
+
valueTransparency: 60
|
|
35673
|
+
});
|
|
34710
35674
|
}
|
|
34711
35675
|
};
|
|
34712
35676
|
var elderRay = {
|
|
34713
35677
|
type: "elder-ray",
|
|
34714
35678
|
title: "Elder Ray",
|
|
34715
35679
|
overlay: false,
|
|
34716
|
-
inputs: [
|
|
35680
|
+
inputs: [
|
|
35681
|
+
lengthInput(13, "length", "EMA Length", 5e3, "Number of bars in the exponential moving average of closing prices used as the reference level for Bull Power and Bear Power. Alexander Elder's classic setting is 13."),
|
|
35682
|
+
colorInput(BULLISH, "Bull Power", "bullColor", "Color of the Bull Power columns."),
|
|
35683
|
+
colorInput(BEARISH, "Bear Power", "bearColor", "Color of the Bear Power columns.")
|
|
35684
|
+
],
|
|
34717
35685
|
compute: (bars, inputs) => {
|
|
34718
35686
|
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
34719
35687
|
return {
|
|
34720
35688
|
plots: [
|
|
34721
|
-
{ key: "bull", title: "Bull
|
|
34722
|
-
{ key: "bear", title: "Bear
|
|
35689
|
+
{ key: "bull", title: "Bull Power", values: sub(highs(bars), basis), kind: "columns", color: str3(inputs, "bullColor", BULLISH), base: 0 },
|
|
35690
|
+
{ key: "bear", title: "Bear Power", values: sub(lows(bars), basis), kind: "columns", color: str3(inputs, "bearColor", BEARISH), base: 0 }
|
|
34723
35691
|
],
|
|
34724
|
-
levels:
|
|
35692
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 30), lineStyle: "dashed", title: "Zero Line" }]
|
|
34725
35693
|
};
|
|
34726
35694
|
}
|
|
34727
35695
|
};
|
|
@@ -34730,31 +35698,66 @@ var ttmSqueeze = {
|
|
|
34730
35698
|
title: "TTM Squeeze",
|
|
34731
35699
|
overlay: false,
|
|
34732
35700
|
inputs: [
|
|
34733
|
-
lengthInput(20),
|
|
34734
|
-
|
|
34735
|
-
|
|
35701
|
+
{ ...lengthInput(20, "length", "Length", 5e3, "Shared lookback for the Bollinger Bands, the Keltner Channels, and the momentum regression. John Carter's original setting is 20."), min: 2 },
|
|
35702
|
+
sourceInput("Close", "source", "Source", "Price series used for the shared moving average basis, the standard deviation, and the momentum displacement."),
|
|
35703
|
+
floatInput("bbMult", "Bollinger Bands Multiplier", 2, 0.1, 50, 0.1, "Standard deviation multiplier for the Bollinger Bands."),
|
|
35704
|
+
floatInput("kcMult", "Keltner Channels Multiplier", 1.5, 0.1, 50, 0.1, "ATR multiplier for the Keltner Channels. A smaller multiplier demands a tighter compression before the squeeze turns on."),
|
|
35705
|
+
colorInput(BULLISH, "Momentum Up", "bullColor", "Histogram color while momentum is positive. Bars building away from zero print solid, bars fading back print dimmed."),
|
|
35706
|
+
colorInput(BEARISH, "Momentum Down", "bearColor", "Histogram color while momentum is negative. Bars building away from zero print solid, bars fading back print dimmed."),
|
|
35707
|
+
colorInput(WARNING, "Squeeze On", "squeezeOnColor", "Zero-line dot color while both Bollinger Bands sit fully inside the Keltner Channels."),
|
|
35708
|
+
colorInput(NEUTRAL, "Squeeze Off", "squeezeOffColor", "Zero-line dot color while the Bollinger Bands trade outside the Keltner Channels."),
|
|
35709
|
+
boolInput("markFires", "Mark Squeeze Fires", true, STYLE, "Draws a cross on the zero line on the first bar where the bands re-emerge outside the channels, colored by the side of the momentum histogram.")
|
|
34736
35710
|
],
|
|
34737
35711
|
compute: (bars, inputs) => {
|
|
34738
35712
|
const len = num3(inputs, "length", 20);
|
|
34739
|
-
const
|
|
34740
|
-
const basis = sma(
|
|
34741
|
-
const dev = map(stdev(
|
|
34742
|
-
const kcRange = map(
|
|
34743
|
-
const
|
|
34744
|
-
const anchor = zip(
|
|
34745
|
-
const mom = linreg(sub(
|
|
34746
|
-
const
|
|
35713
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
35714
|
+
const basis = sma(src, len);
|
|
35715
|
+
const dev = map(stdev(src, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
35716
|
+
const kcRange = map(atr(bars, len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
35717
|
+
const rangeMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
35718
|
+
const anchor = zip(rangeMid, basis, (d, b) => (d + b) / 2);
|
|
35719
|
+
const mom = linreg(sub(src, anchor), len, 0);
|
|
35720
|
+
const squeezeOn = basis.map((b, i) => {
|
|
34747
35721
|
const d = dev[i];
|
|
34748
35722
|
const r = kcRange[i];
|
|
34749
|
-
|
|
35723
|
+
if (!Number.isFinite(b) || !Number.isFinite(d) || !Number.isFinite(r)) return null;
|
|
35724
|
+
return d < r;
|
|
34750
35725
|
});
|
|
34751
|
-
const
|
|
34752
|
-
|
|
34753
|
-
|
|
34754
|
-
|
|
34755
|
-
|
|
34756
|
-
|
|
34757
|
-
|
|
35726
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
35727
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
35728
|
+
const onInk = str3(inputs, "squeezeOnColor", WARNING);
|
|
35729
|
+
const offInk = str3(inputs, "squeezeOffColor", NEUTRAL);
|
|
35730
|
+
const momColors = mom.map((x, i) => {
|
|
35731
|
+
if (!Number.isFinite(x)) return null;
|
|
35732
|
+
const prev2 = i > 0 && Number.isFinite(mom[i - 1]) ? mom[i - 1] : x;
|
|
35733
|
+
const rising = x > prev2;
|
|
35734
|
+
return x >= 0 ? rising ? bull : transp(bull, 60) : rising ? transp(bear, 60) : bear;
|
|
35735
|
+
});
|
|
35736
|
+
const plots = [
|
|
35737
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "columns", color: bull, colors: momColors, base: 0 },
|
|
35738
|
+
{
|
|
35739
|
+
key: "state",
|
|
35740
|
+
title: "Squeeze State",
|
|
35741
|
+
values: squeezeOn.map((s) => s == null ? Number.NaN : 0),
|
|
35742
|
+
kind: "circles",
|
|
35743
|
+
color: offInk,
|
|
35744
|
+
width: 2,
|
|
35745
|
+
colors: squeezeOn.map((s) => s == null ? null : s ? onInk : offInk)
|
|
35746
|
+
}
|
|
35747
|
+
];
|
|
35748
|
+
if (bool(inputs, "markFires", true)) {
|
|
35749
|
+
const fires = squeezeOn.map((s, i) => s === false && squeezeOn[i - 1] === true ? 0 : Number.NaN);
|
|
35750
|
+
plots.push({
|
|
35751
|
+
key: "fire",
|
|
35752
|
+
title: "Squeeze Fire",
|
|
35753
|
+
values: fires,
|
|
35754
|
+
kind: "cross",
|
|
35755
|
+
color: offInk,
|
|
35756
|
+
width: 3,
|
|
35757
|
+
colors: fires.map((x, i) => Number.isFinite(x) ? Number.isFinite(mom[i]) ? mom[i] >= 0 ? bull : bear : offInk : null)
|
|
35758
|
+
});
|
|
35759
|
+
}
|
|
35760
|
+
return { plots };
|
|
34758
35761
|
}
|
|
34759
35762
|
};
|
|
34760
35763
|
var oscillatorSpecs = [
|
|
@@ -34766,7 +35769,7 @@ var oscillatorSpecs = [
|
|
|
34766
35769
|
awesome,
|
|
34767
35770
|
cci,
|
|
34768
35771
|
williamsR,
|
|
34769
|
-
|
|
35772
|
+
cmoSpec,
|
|
34770
35773
|
connorsRsi,
|
|
34771
35774
|
fisher,
|
|
34772
35775
|
trix,
|
|
@@ -34790,15 +35793,19 @@ var aroon = {
|
|
|
34790
35793
|
type: "aroon",
|
|
34791
35794
|
title: "Aroon",
|
|
34792
35795
|
overlay: false,
|
|
34793
|
-
inputs: [
|
|
35796
|
+
inputs: [
|
|
35797
|
+
lengthInput(14, "length", "Length", 5e3, "Lookback length used to locate the highest high and lowest low."),
|
|
35798
|
+
colorInput(BULLISH, "Aroon Up", "upColor", "Color of the Aroon Up line."),
|
|
35799
|
+
colorInput(BEARISH, "Aroon Down", "downColor", "Color of the Aroon Down line.")
|
|
35800
|
+
],
|
|
34794
35801
|
compute: (bars, inputs) => {
|
|
34795
35802
|
const len = num3(inputs, "length", 14);
|
|
34796
|
-
const up = map(barsSinceHighest(highs(bars), len
|
|
34797
|
-
const down = map(barsSinceLowest(lows(bars), len
|
|
35803
|
+
const up = map(barsSinceHighest(highs(bars), len), (x) => 100 * (len - x) / len);
|
|
35804
|
+
const down = map(barsSinceLowest(lows(bars), len), (x) => 100 * (len - x) / len);
|
|
34798
35805
|
return {
|
|
34799
35806
|
plots: [
|
|
34800
|
-
{ key: "up", title: "Aroon
|
|
34801
|
-
{ key: "down", title: "Aroon
|
|
35807
|
+
{ key: "up", title: "Aroon Up", values: up, color: str3(inputs, "upColor", BULLISH) },
|
|
35808
|
+
{ key: "down", title: "Aroon Down", values: down, color: str3(inputs, "downColor", BEARISH) }
|
|
34802
35809
|
]
|
|
34803
35810
|
};
|
|
34804
35811
|
}
|
|
@@ -34808,10 +35815,16 @@ var adx = {
|
|
|
34808
35815
|
title: "Average Directional Index",
|
|
34809
35816
|
shortTitle: "ADX",
|
|
34810
35817
|
overlay: false,
|
|
34811
|
-
inputs: [
|
|
35818
|
+
inputs: [
|
|
35819
|
+
lengthInput(14, "diLength", "DI Length", 5e3, "Wilder RMA smoothing length applied to directional movement and true range when computing +DI and \u2212DI."),
|
|
35820
|
+
lengthInput(14, "adxLength", "ADX Smoothing", 5e3, "Wilder RMA smoothing length applied to DX to obtain the ADX line."),
|
|
35821
|
+
floatInput("keyLevel", "Key Level", 25, 0, 100, 1, "Trend strength reference level; ADX above it is commonly read as a trending market."),
|
|
35822
|
+
colorInput(BULLISH, "+DI Color", "plusDiColor", "Color of the plus directional indicator, and of the ADX line above the key level while +DI leads."),
|
|
35823
|
+
colorInput(BEARISH, "-DI Color", "minusDiColor", "Color of the minus directional indicator, and of the ADX line above the key level while \u2212DI leads."),
|
|
35824
|
+
colorInput(NEUTRAL, "ADX Color", "adxColor", "Color of the ADX line while it sits below the key level \u2014 no trend strong enough to trade.")
|
|
35825
|
+
],
|
|
34812
35826
|
compute: (bars, inputs) => {
|
|
34813
35827
|
const diLen = num3(inputs, "diLength", 14);
|
|
34814
|
-
const adxLen = num3(inputs, "adxLength", 14);
|
|
34815
35828
|
const n = bars.length;
|
|
34816
35829
|
const plusDm = new Array(n).fill(Number.NaN);
|
|
34817
35830
|
const minusDm = new Array(n).fill(Number.NaN);
|
|
@@ -34825,13 +35838,23 @@ var adx = {
|
|
|
34825
35838
|
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
34826
35839
|
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
34827
35840
|
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
35841
|
+
const adxLine = rma(dx, num3(inputs, "adxLength", 14));
|
|
35842
|
+
const plusInk = str3(inputs, "plusDiColor", BULLISH);
|
|
35843
|
+
const minusInk = str3(inputs, "minusDiColor", BEARISH);
|
|
35844
|
+
const neutralInk = str3(inputs, "adxColor", NEUTRAL);
|
|
35845
|
+
const keyLevel = num3(inputs, "keyLevel", 25);
|
|
35846
|
+
const adxColors = adxLine.map((x, i) => {
|
|
35847
|
+
if (!Number.isFinite(x)) return null;
|
|
35848
|
+
if (x <= keyLevel) return neutralInk;
|
|
35849
|
+
return (plusDi[i] ?? 0) > (minusDi[i] ?? 0) ? plusInk : minusInk;
|
|
35850
|
+
});
|
|
34828
35851
|
return {
|
|
34829
35852
|
plots: [
|
|
34830
|
-
{ key: "
|
|
34831
|
-
{ key: "
|
|
34832
|
-
{ key: "
|
|
35853
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: transp(plusInk, 30) },
|
|
35854
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: transp(minusInk, 30) },
|
|
35855
|
+
{ key: "adx", title: "ADX", values: adxLine, color: neutralInk, width: 2, colors: adxColors }
|
|
34833
35856
|
],
|
|
34834
|
-
levels: [{ key: "
|
|
35857
|
+
levels: [{ key: "keyLevel", price: keyLevel, color: NEUTRAL, lineStyle: "dashed", title: "Key Level" }]
|
|
34835
35858
|
};
|
|
34836
35859
|
}
|
|
34837
35860
|
};
|
|
@@ -34840,7 +35863,12 @@ var vortex = {
|
|
|
34840
35863
|
title: "Vortex Indicator",
|
|
34841
35864
|
shortTitle: "VI",
|
|
34842
35865
|
overlay: false,
|
|
34843
|
-
inputs: [
|
|
35866
|
+
inputs: [
|
|
35867
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to sum the vortex movements (VM+ and VM\u2212) and the true range."),
|
|
35868
|
+
colorInput(BULLISH, "VI+", "plusColor", "Color of the positive vortex line (VI+)."),
|
|
35869
|
+
colorInput(BEARISH, "VI-", "minusColor", "Color of the negative vortex line (VI\u2212)."),
|
|
35870
|
+
colorInput(NEUTRAL, "Baseline", "baselineColor", "Color of the dotted baseline drawn at 1.")
|
|
35871
|
+
],
|
|
34844
35872
|
compute: (bars, inputs) => {
|
|
34845
35873
|
const len = num3(inputs, "length", 14);
|
|
34846
35874
|
const n = bars.length;
|
|
@@ -34853,25 +35881,48 @@ var vortex = {
|
|
|
34853
35881
|
const trSum = sum(trueRange(bars), len);
|
|
34854
35882
|
return {
|
|
34855
35883
|
plots: [
|
|
34856
|
-
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color:
|
|
34857
|
-
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color:
|
|
35884
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: str3(inputs, "plusColor", BULLISH) },
|
|
35885
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: str3(inputs, "minusColor", BEARISH) }
|
|
34858
35886
|
],
|
|
34859
|
-
levels: [{ key: "
|
|
35887
|
+
levels: [{ key: "baseline", price: 1, color: str3(inputs, "baselineColor", NEUTRAL), lineStyle: "dotted", title: "Baseline" }]
|
|
34860
35888
|
};
|
|
34861
35889
|
}
|
|
34862
35890
|
};
|
|
34863
35891
|
var trendSpecs = [aroon, adx, vortex];
|
|
34864
35892
|
|
|
34865
35893
|
// src/core/native-indicators/classics/volatility.ts
|
|
35894
|
+
function expansionColors(values, calm, expanding) {
|
|
35895
|
+
return values.map((x, i) => {
|
|
35896
|
+
if (!Number.isFinite(x)) return null;
|
|
35897
|
+
const prev2 = i > 0 && Number.isFinite(values[i - 1]) ? values[i - 1] : x;
|
|
35898
|
+
return x > prev2 ? expanding : calm;
|
|
35899
|
+
});
|
|
35900
|
+
}
|
|
35901
|
+
function washedToZero(key, title, values, colors, fallback, valueTransparency, width = 1) {
|
|
35902
|
+
return {
|
|
35903
|
+
plots: [{ key, title, values, color: fallback, width, colors }, anchorPlot("zeroAnchor", "Zero Anchor", values.length, 0)],
|
|
35904
|
+
bands: [{ key: "wash", from: key, to: "zeroAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, 0, (_x, i) => colors[i] ?? fallback, valueTransparency) }]
|
|
35905
|
+
};
|
|
35906
|
+
}
|
|
34866
35907
|
var atrSpec = {
|
|
34867
35908
|
type: "average-true-range",
|
|
34868
35909
|
title: "Average True Range",
|
|
34869
35910
|
shortTitle: "ATR",
|
|
34870
35911
|
overlay: false,
|
|
34871
|
-
inputs: [
|
|
34872
|
-
|
|
34873
|
-
|
|
34874
|
-
|
|
35912
|
+
inputs: [
|
|
35913
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to average the true range."),
|
|
35914
|
+
optionInput("smoothing", "Smoothing", "RMA", ["RMA", "SMA", "EMA", "WMA"], "Moving average applied to the true range. RMA is Wilder's original smoothing used by the classic ATR."),
|
|
35915
|
+
colorInput(NEUTRAL, "ATR Line", "color", "Color of the ATR line while volatility contracts or holds flat."),
|
|
35916
|
+
colorInput(WARNING, "Expanding", "expandColor", "Color of the ATR line while it rises \u2014 volatility expanding bar to bar.")
|
|
35917
|
+
],
|
|
35918
|
+
compute: (bars, inputs) => {
|
|
35919
|
+
const len = num3(inputs, "length", 14);
|
|
35920
|
+
const tr = trueRange(bars);
|
|
35921
|
+
const kind = str3(inputs, "smoothing", "RMA");
|
|
35922
|
+
const values = kind === "SMA" ? sma(tr, len) : kind === "EMA" ? ema(tr, len) : kind === "WMA" ? wma(tr, len) : rma(tr, len);
|
|
35923
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
35924
|
+
return washedToZero("atr", "ATR", values, expansionColors(values, calm, str3(inputs, "expandColor", WARNING)), calm, 75);
|
|
35925
|
+
}
|
|
34875
35926
|
};
|
|
34876
35927
|
var historicalVolatility = {
|
|
34877
35928
|
type: "historical-volatility",
|
|
@@ -34879,14 +35930,23 @@ var historicalVolatility = {
|
|
|
34879
35930
|
shortTitle: "HV",
|
|
34880
35931
|
overlay: false,
|
|
34881
35932
|
inputs: [
|
|
34882
|
-
lengthInput(10),
|
|
34883
|
-
|
|
34884
|
-
|
|
35933
|
+
{ ...lengthInput(10, "length", "Length", 5e3, "Lookback window in bars for the standard deviation of close-to-close log returns."), min: 2 },
|
|
35934
|
+
boolInput("annualize", "Annualize", true, void 0, "Scale the per-bar volatility to an annual figure by multiplying by the square root of the periods per year."),
|
|
35935
|
+
intInput("periodsPerYear", "Periods Per Year", 252, 1, 1e5, "Number of periods in a year used for annualization: 252 for daily bars, 52 for weekly, 12 for monthly."),
|
|
35936
|
+
colorInput(WARNING, "Volatility Line", "color", "Color of the line at its recent highs; it fades toward grey as volatility contracts.")
|
|
34885
35937
|
],
|
|
34886
35938
|
compute: (bars, inputs) => {
|
|
34887
|
-
const
|
|
34888
|
-
const
|
|
34889
|
-
|
|
35939
|
+
const len = num3(inputs, "length", 10);
|
|
35940
|
+
const logReturns = sub(map(closes(bars), Math.log), shift(map(closes(bars), Math.log), 1));
|
|
35941
|
+
const meanReturn = map(sum(logReturns, len), (s) => s / len);
|
|
35942
|
+
const squaredSum = sum(map(logReturns, (r) => r * r), len);
|
|
35943
|
+
const factor = bool(inputs, "annualize", true) ? Math.sqrt(num3(inputs, "periodsPerYear", 252)) : 1;
|
|
35944
|
+
const values = zip(squaredSum, meanReturn, (sq, m) => Math.sqrt(Math.max(sq - len * m * m, 0) / (len - 1)) * factor * 100);
|
|
35945
|
+
const low = lowest(values, 100);
|
|
35946
|
+
const high = highest(values, 100);
|
|
35947
|
+
const accent = str3(inputs, "color", WARNING);
|
|
35948
|
+
const colors = values.map((x, i) => Number.isFinite(x) ? gradient(x, low[i] ?? 0, high[i] ?? 0, NEUTRAL, accent) : null);
|
|
35949
|
+
return washedToZero("hv", "Historical Volatility", values, colors, accent, 75, 2);
|
|
34890
35950
|
}
|
|
34891
35951
|
};
|
|
34892
35952
|
var chaikinVolatility = {
|
|
@@ -34894,13 +35954,26 @@ var chaikinVolatility = {
|
|
|
34894
35954
|
title: "Chaikin Volatility",
|
|
34895
35955
|
shortTitle: "CHV",
|
|
34896
35956
|
overlay: false,
|
|
34897
|
-
inputs: [
|
|
35957
|
+
inputs: [
|
|
35958
|
+
lengthInput(10, "emaLength", "EMA Length", 5e3, "Length of the exponential moving average applied to the bar high-low range."),
|
|
35959
|
+
lengthInput(10, "rocLength", "ROC Length", 5e3, "Number of bars used for the rate of change of the smoothed high-low range."),
|
|
35960
|
+
colorInput(NEUTRAL, "CHV Color", "color", "Color of the Chaikin Volatility line."),
|
|
35961
|
+
colorInput(WARNING, "Expansion", "expansionColor", "Line and fill color while the reading is above zero \u2014 the smoothed range is wider than it was ROC Length bars ago.")
|
|
35962
|
+
],
|
|
34898
35963
|
compute: (bars, inputs) => {
|
|
34899
35964
|
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
34900
|
-
|
|
34901
|
-
|
|
34902
|
-
|
|
34903
|
-
|
|
35965
|
+
const prior = shift(range, num3(inputs, "rocLength", 10));
|
|
35966
|
+
const values = zip(range, prior, (r, p) => p === 0 ? Number.NaN : 100 * (r - p) / p);
|
|
35967
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
35968
|
+
const expansion = str3(inputs, "expansionColor", WARNING);
|
|
35969
|
+
return centeredOscillator({
|
|
35970
|
+
key: "chv",
|
|
35971
|
+
title: "Chaikin Volatility",
|
|
35972
|
+
values,
|
|
35973
|
+
// Only expansion is a state worth accenting; contraction stays structural grey.
|
|
35974
|
+
ink: { bull: expansion, bear: calm, neutral: calm },
|
|
35975
|
+
zeroInk: NEUTRAL
|
|
35976
|
+
});
|
|
34904
35977
|
}
|
|
34905
35978
|
};
|
|
34906
35979
|
var massIndex = {
|
|
@@ -34908,14 +35981,42 @@ var massIndex = {
|
|
|
34908
35981
|
title: "Mass Index",
|
|
34909
35982
|
shortTitle: "MI",
|
|
34910
35983
|
overlay: false,
|
|
34911
|
-
inputs: [
|
|
35984
|
+
inputs: [
|
|
35985
|
+
lengthInput(25, "length", "Sum Length", 5e3, "Number of bars over which the single/double EMA ratio is summed."),
|
|
35986
|
+
lengthInput(9, "emaLength", "EMA Length", 5e3, "Length of the single EMA of the high-low range and of its double smoothing."),
|
|
35987
|
+
floatInput("setupLevel", "Setup Level", 27, 0, 1e3, 0.1, "Reversal bulge set-up level; a rise above it arms the pattern."),
|
|
35988
|
+
floatInput("triggerLevel", "Trigger Level", 26.5, 0, 1e3, 0.1, "Reversal bulge trigger level; a decline below it after a set-up completes the pattern."),
|
|
35989
|
+
colorInput(NEUTRAL, "Mass Index", "color", "Line color while no reversal bulge is armed."),
|
|
35990
|
+
colorInput(WARNING, "Armed", "armedColor", "Line color from the rise above the setup level until the decline below the trigger level."),
|
|
35991
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Reversal bulge levels color.")
|
|
35992
|
+
],
|
|
34912
35993
|
compute: (bars, inputs) => {
|
|
34913
|
-
const
|
|
34914
|
-
const single = ema(
|
|
34915
|
-
const ratio = zip(single, ema(single,
|
|
35994
|
+
const emaLen = num3(inputs, "emaLength", 9);
|
|
35995
|
+
const single = ema(sub(highs(bars), lows(bars)), emaLen);
|
|
35996
|
+
const ratio = zip(single, ema(single, emaLen), (a, b) => b === 0 ? 1 : a / b);
|
|
35997
|
+
const values = sum(ratio, num3(inputs, "length", 25));
|
|
35998
|
+
const setup = num3(inputs, "setupLevel", 27);
|
|
35999
|
+
const trigger = num3(inputs, "triggerLevel", 26.5);
|
|
36000
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
36001
|
+
const armedInk = str3(inputs, "armedColor", WARNING);
|
|
36002
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
36003
|
+
let armed = false;
|
|
36004
|
+
const colors = values.map((x) => {
|
|
36005
|
+
if (!Number.isFinite(x)) return null;
|
|
36006
|
+
armed = x > setup ? true : x < trigger ? false : armed;
|
|
36007
|
+
return armed ? armedInk : calm;
|
|
36008
|
+
});
|
|
34916
36009
|
return {
|
|
34917
|
-
plots: [
|
|
34918
|
-
|
|
36010
|
+
plots: [
|
|
36011
|
+
{ key: "mi", title: "Mass Index", values, color: calm, colors },
|
|
36012
|
+
anchorPlot("setupAnchor", "Setup Anchor", values.length, setup),
|
|
36013
|
+
anchorPlot("triggerAnchor", "Trigger Anchor", values.length, trigger)
|
|
36014
|
+
],
|
|
36015
|
+
bands: [{ key: "bulge", from: "setupAnchor", to: "triggerAnchor", color: transp(armedInk, 90) }],
|
|
36016
|
+
levels: [
|
|
36017
|
+
{ key: "setup", price: setup, color: levelInk, lineStyle: "dashed", title: "Setup Level" },
|
|
36018
|
+
{ key: "trigger", price: trigger, color: levelInk, lineStyle: "dashed", title: "Trigger Level" }
|
|
36019
|
+
]
|
|
34919
36020
|
};
|
|
34920
36021
|
}
|
|
34921
36022
|
};
|
|
@@ -34924,30 +36025,83 @@ var standardDeviation = {
|
|
|
34924
36025
|
title: "Standard Deviation",
|
|
34925
36026
|
shortTitle: "StdDev",
|
|
34926
36027
|
overlay: false,
|
|
34927
|
-
inputs: [
|
|
34928
|
-
|
|
34929
|
-
|
|
34930
|
-
|
|
34931
|
-
|
|
34932
|
-
|
|
34933
|
-
|
|
34934
|
-
|
|
34935
|
-
|
|
34936
|
-
|
|
34937
|
-
|
|
34938
|
-
})
|
|
36028
|
+
inputs: [
|
|
36029
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used to compute the standard deviation."),
|
|
36030
|
+
sourceInput("Close", "source", "Source", "Price series the standard deviation is calculated on."),
|
|
36031
|
+
colorInput(NEUTRAL, "Standard Deviation", "color", "Line color while volatility contracts or holds flat."),
|
|
36032
|
+
colorInput(WARNING, "Expanding", "expandColor", "Line and fill color while volatility expands bar to bar.")
|
|
36033
|
+
],
|
|
36034
|
+
compute: (bars, inputs) => {
|
|
36035
|
+
const values = stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20));
|
|
36036
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
36037
|
+
return washedToZero("stdev", "Standard Deviation", values, expansionColors(values, calm, str3(inputs, "expandColor", WARNING)), calm, 80);
|
|
36038
|
+
}
|
|
34939
36039
|
};
|
|
34940
36040
|
var ulcerIndex = {
|
|
34941
36041
|
type: "ulcer-index",
|
|
34942
36042
|
title: "Ulcer Index",
|
|
34943
36043
|
overlay: false,
|
|
34944
|
-
inputs: [
|
|
36044
|
+
inputs: [
|
|
36045
|
+
{ ...lengthInput(14, "length", "Length", 500, "Lookback window. At each bar the percentage drawdown from the window's running maximum is squared; the squares are averaged and the root taken."), min: 2 },
|
|
36046
|
+
sourceInput("Close", "source", "Source", "Price series the Ulcer Index is computed on. Close is the standard choice."),
|
|
36047
|
+
floatInput("threshold", "Alert Threshold", 5, 0, 1e3, 0.25, "Reference level drawn as a dashed line. The index scales with the instrument's volatility and the window length, so set it relative to its own history."),
|
|
36048
|
+
colorInput(NEUTRAL, "Ulcer Index", "color", "Color of the index near zero; it blends toward the stress color as the reading approaches the threshold."),
|
|
36049
|
+
colorInput(BEARISH, "Stress", "stressColor", "Color of the index at and beyond the alert threshold."),
|
|
36050
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Vertical gradient between zero and the Ulcer Index, fully transparent at zero."),
|
|
36051
|
+
boolInput("showDrawdown", "Drawdown %", false, STYLE, "Plot the current percentage drawdown from the window high as columns below zero \u2014 the raw ingredient the index averages."),
|
|
36052
|
+
colorInput(transp(BEARISH, 50), "Drawdown Color", "drawdownColor", "Color of the drawdown columns."),
|
|
36053
|
+
colorInput(NEUTRAL, "Threshold", "thresholdColor", "Color of the alert threshold line.")
|
|
36054
|
+
],
|
|
34945
36055
|
compute: (bars, inputs) => {
|
|
34946
36056
|
const len = num3(inputs, "length", 14);
|
|
34947
|
-
const
|
|
34948
|
-
const
|
|
34949
|
-
const values =
|
|
34950
|
-
|
|
36057
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
36058
|
+
const n = src.length;
|
|
36059
|
+
const values = new Array(n).fill(Number.NaN);
|
|
36060
|
+
const drawdown = new Array(n).fill(Number.NaN);
|
|
36061
|
+
for (let i = len - 1; i < n; i++) {
|
|
36062
|
+
let runMax = Number.NaN;
|
|
36063
|
+
let sumSq = 0;
|
|
36064
|
+
let valid = true;
|
|
36065
|
+
let last = Number.NaN;
|
|
36066
|
+
for (let k = i - len + 1; k <= i; k++) {
|
|
36067
|
+
const price = src[k];
|
|
36068
|
+
if (!Number.isFinite(price)) {
|
|
36069
|
+
valid = false;
|
|
36070
|
+
break;
|
|
36071
|
+
}
|
|
36072
|
+
runMax = Number.isFinite(runMax) ? Math.max(runMax, price) : price;
|
|
36073
|
+
if (runMax <= 0) {
|
|
36074
|
+
valid = false;
|
|
36075
|
+
break;
|
|
36076
|
+
}
|
|
36077
|
+
last = 100 * (price - runMax) / runMax;
|
|
36078
|
+
sumSq += last * last;
|
|
36079
|
+
}
|
|
36080
|
+
if (!valid) continue;
|
|
36081
|
+
values[i] = Math.sqrt(sumSq / len);
|
|
36082
|
+
drawdown[i] = last;
|
|
36083
|
+
}
|
|
36084
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
36085
|
+
const stress = str3(inputs, "stressColor", BEARISH);
|
|
36086
|
+
const threshold = num3(inputs, "threshold", 5);
|
|
36087
|
+
const colors = values.map((x) => Number.isFinite(x) ? threshold > 0 ? gradient(x, 0, threshold, calm, stress) : stress : null);
|
|
36088
|
+
const plots = [{ key: "ui", title: "Ulcer Index", values, color: calm, colors }];
|
|
36089
|
+
const bands = [];
|
|
36090
|
+
if (bool(inputs, "gradientFill", true)) {
|
|
36091
|
+
plots.push(anchorPlot("zeroAnchor", "Zero Base", n, 0));
|
|
36092
|
+
bands.push({ key: "wash", from: "ui", to: "zeroAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, 0, (_x, i) => colors[i] ?? calm) });
|
|
36093
|
+
}
|
|
36094
|
+
if (bool(inputs, "showDrawdown", false)) {
|
|
36095
|
+
plots.push({ key: "dd", title: "Drawdown %", values: drawdown, kind: "columns", color: str3(inputs, "drawdownColor", transp(BEARISH, 50)), base: 0 });
|
|
36096
|
+
}
|
|
36097
|
+
return {
|
|
36098
|
+
plots,
|
|
36099
|
+
bands,
|
|
36100
|
+
levels: [
|
|
36101
|
+
{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dotted", title: "Zero Level" },
|
|
36102
|
+
{ key: "threshold", price: threshold, color: str3(inputs, "thresholdColor", NEUTRAL), lineStyle: "dashed", title: "Alert Threshold" }
|
|
36103
|
+
]
|
|
36104
|
+
};
|
|
34951
36105
|
}
|
|
34952
36106
|
};
|
|
34953
36107
|
var choppiness = {
|
|
@@ -34955,19 +36109,33 @@ var choppiness = {
|
|
|
34955
36109
|
title: "Choppiness Index",
|
|
34956
36110
|
shortTitle: "CHOP",
|
|
34957
36111
|
overlay: false,
|
|
34958
|
-
inputs: [
|
|
36112
|
+
inputs: [
|
|
36113
|
+
{ ...lengthInput(14, "length", "Length", 5e3, "Number of bars used to sum true ranges and to measure the highest high \u2212 lowest low range."), min: 2 },
|
|
36114
|
+
floatInput("upperBand", "Upper Band", 61.8, 0, 100, 0.1, "Readings above this level indicate a choppy, range-bound market."),
|
|
36115
|
+
floatInput("lowerBand", "Lower Band", 38.2, 0, 100, 0.1, "Readings below this level indicate a strong trend."),
|
|
36116
|
+
colorInput(NEUTRAL, "CHOP Line", "color", "Color of the Choppiness Index line."),
|
|
36117
|
+
colorInput(BULLISH, "Trending", "trendingColor", "Line, fill and zone color toward and below the lower band \u2014 a trending market."),
|
|
36118
|
+
colorInput(BEARISH, "Choppy", "choppyColor", "Line, fill and zone color toward and above the upper band \u2014 a choppy, range-bound market."),
|
|
36119
|
+
colorInput(transp(NEUTRAL, 90), "Band Fill", "fillColor", "Fill color of the zone between the upper and lower bands.")
|
|
36120
|
+
],
|
|
34959
36121
|
compute: (bars, inputs) => {
|
|
34960
36122
|
const len = num3(inputs, "length", 14);
|
|
34961
36123
|
const trSum = sum(trueRange(bars), len);
|
|
34962
36124
|
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
34963
36125
|
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
34964
|
-
return {
|
|
34965
|
-
|
|
34966
|
-
|
|
34967
|
-
|
|
34968
|
-
|
|
34969
|
-
|
|
34970
|
-
|
|
36126
|
+
return boundedOscillator({
|
|
36127
|
+
key: "chop",
|
|
36128
|
+
title: "CHOP",
|
|
36129
|
+
values,
|
|
36130
|
+
scale: { top: 100, bottom: 0 },
|
|
36131
|
+
overbought: num3(inputs, "upperBand", 61.8),
|
|
36132
|
+
oversold: num3(inputs, "lowerBand", 38.2),
|
|
36133
|
+
ink: { low: str3(inputs, "trendingColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "choppyColor", BEARISH) },
|
|
36134
|
+
levelInk: NEUTRAL,
|
|
36135
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 90)),
|
|
36136
|
+
midpointWash: true,
|
|
36137
|
+
levelTitles: { overbought: "Upper Band", oversold: "Lower Band" }
|
|
36138
|
+
});
|
|
34971
36139
|
}
|
|
34972
36140
|
};
|
|
34973
36141
|
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
@@ -34980,17 +36148,85 @@ function moneyFlowVolume(bars) {
|
|
|
34980
36148
|
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
34981
36149
|
});
|
|
34982
36150
|
}
|
|
36151
|
+
function slopeColors2(values, bull, bear, flat) {
|
|
36152
|
+
return values.map((x, i) => {
|
|
36153
|
+
if (!Number.isFinite(x)) return null;
|
|
36154
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
36155
|
+
if (!Number.isFinite(prev2)) return flat;
|
|
36156
|
+
return x > prev2 ? bull : x < prev2 ? bear : flat;
|
|
36157
|
+
});
|
|
36158
|
+
}
|
|
36159
|
+
function volumeIndex(bars, src, base, when) {
|
|
36160
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
36161
|
+
let index = base;
|
|
36162
|
+
for (let i = 0; i < bars.length; i++) {
|
|
36163
|
+
const v = bars[i].volume;
|
|
36164
|
+
const pv = i > 0 ? bars[i - 1].volume : null;
|
|
36165
|
+
const prev2 = i > 0 ? src[i - 1] : Number.NaN;
|
|
36166
|
+
const active = v != null && pv != null && Number.isFinite(v) && Number.isFinite(pv) && when(v, pv);
|
|
36167
|
+
if (active && Number.isFinite(prev2) && prev2 !== 0) index *= 1 + (src[i] - prev2) / prev2;
|
|
36168
|
+
out[i] = index;
|
|
36169
|
+
}
|
|
36170
|
+
return out;
|
|
36171
|
+
}
|
|
36172
|
+
function regimeIndex(key, title, values, signal, ink, showSignal, gradientFill, singleColor = false) {
|
|
36173
|
+
const inkOf = (i) => {
|
|
36174
|
+
const s = signal[i];
|
|
36175
|
+
const x = values[i];
|
|
36176
|
+
if (singleColor || !Number.isFinite(s)) return ink.neutral;
|
|
36177
|
+
return x > s ? ink.bull : x < s ? ink.bear : ink.neutral;
|
|
36178
|
+
};
|
|
36179
|
+
const plots = [
|
|
36180
|
+
{
|
|
36181
|
+
key: "signal",
|
|
36182
|
+
title: "Signal EMA",
|
|
36183
|
+
values: signal,
|
|
36184
|
+
color: ink.signal,
|
|
36185
|
+
...showSignal ? {} : { display: { pane: false } }
|
|
36186
|
+
},
|
|
36187
|
+
{ key, title, values, color: ink.neutral, width: 2, colors: values.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) }
|
|
36188
|
+
];
|
|
36189
|
+
const bands = gradientFill ? [
|
|
36190
|
+
{
|
|
36191
|
+
key: "regime",
|
|
36192
|
+
from: key,
|
|
36193
|
+
to: "signal",
|
|
36194
|
+
color: transp(NEUTRAL, 100),
|
|
36195
|
+
gradient: values.map((x, i) => {
|
|
36196
|
+
const s = signal[i];
|
|
36197
|
+
if (!Number.isFinite(x) || !Number.isFinite(s)) return null;
|
|
36198
|
+
const above = x > s;
|
|
36199
|
+
const c = above ? ink.bull : ink.bear;
|
|
36200
|
+
return { topValue: Math.max(x, s), bottomValue: Math.min(x, s), topColor: transp(c, above ? 50 : 100), bottomColor: transp(c, above ? 100 : 50) };
|
|
36201
|
+
})
|
|
36202
|
+
}
|
|
36203
|
+
] : [];
|
|
36204
|
+
return { plots, bands };
|
|
36205
|
+
}
|
|
34983
36206
|
var obv = {
|
|
34984
36207
|
type: "on-balance-volume",
|
|
34985
36208
|
title: "On Balance Volume",
|
|
34986
36209
|
shortTitle: "OBV",
|
|
34987
36210
|
overlay: false,
|
|
34988
|
-
inputs: [
|
|
36211
|
+
inputs: [
|
|
36212
|
+
boolInput("smoothing", "Smoothing MA", false, SETTINGS, "Display a moving average of OBV to act as a smoothing/signal line."),
|
|
36213
|
+
optionInput("maType", "MA Type", "SMA", ["SMA", "EMA", "WMA", "RMA (SMMA)"], "Moving average type used for the smoothing MA."),
|
|
36214
|
+
lengthInput(20, "maLength", "MA Length", 5e3, "Number of bars used for the smoothing MA."),
|
|
36215
|
+
colorInput(INFO, "OBV", "color", "Color of the OBV line."),
|
|
36216
|
+
colorInput(WARNING, "Smoothing MA", "maColor", "Color of the smoothing MA line.")
|
|
36217
|
+
],
|
|
34989
36218
|
compute: (bars, inputs) => {
|
|
34990
36219
|
const vol = volumes(bars);
|
|
34991
36220
|
const d = change(closes(bars));
|
|
34992
|
-
const
|
|
34993
|
-
|
|
36221
|
+
const values = cumSum(zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0));
|
|
36222
|
+
const plots = [{ key: "obv", title: "OBV", values, color: str3(inputs, "color", INFO) }];
|
|
36223
|
+
if (bool(inputs, "smoothing", false)) {
|
|
36224
|
+
const len = num3(inputs, "maLength", 20);
|
|
36225
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
36226
|
+
const ma = kind === "EMA" ? ema(values, len) : kind === "WMA" ? wma(values, len) : kind === "RMA (SMMA)" ? rma(values, len) : sma(values, len);
|
|
36227
|
+
plots.push({ key: "ma", title: "Smoothing MA", values: ma, color: str3(inputs, "maColor", WARNING) });
|
|
36228
|
+
}
|
|
36229
|
+
return { plots };
|
|
34994
36230
|
}
|
|
34995
36231
|
};
|
|
34996
36232
|
var accDist = {
|
|
@@ -34998,24 +36234,50 @@ var accDist = {
|
|
|
34998
36234
|
title: "Accumulation / Distribution",
|
|
34999
36235
|
shortTitle: "A/D",
|
|
35000
36236
|
overlay: false,
|
|
35001
|
-
inputs: [
|
|
35002
|
-
|
|
35003
|
-
|
|
35004
|
-
|
|
36237
|
+
inputs: [
|
|
36238
|
+
boolInput("slopeColoring", "Slope Coloring", true, STYLE, "Color the line by its slope \u2014 bullish while rising (accumulation), bearish while falling (distribution)."),
|
|
36239
|
+
colorInput(NEUTRAL, "A/D Line", "color", "Color of the line when slope coloring is disabled or the line is flat."),
|
|
36240
|
+
colorInput(BULLISH, "Rising", "bullColor", "Line color while the A/D Line rises."),
|
|
36241
|
+
colorInput(BEARISH, "Falling", "bearColor", "Line color while the A/D Line falls.")
|
|
36242
|
+
],
|
|
36243
|
+
compute: (bars, inputs) => {
|
|
36244
|
+
const values = cumSum(moneyFlowVolume(bars));
|
|
36245
|
+
const flat = str3(inputs, "color", NEUTRAL);
|
|
36246
|
+
return {
|
|
36247
|
+
plots: [
|
|
36248
|
+
{
|
|
36249
|
+
key: "ad",
|
|
36250
|
+
title: "A/D",
|
|
36251
|
+
values,
|
|
36252
|
+
color: flat,
|
|
36253
|
+
...bool(inputs, "slopeColoring", true) ? { colors: slopeColors2(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), flat) } : {}
|
|
36254
|
+
}
|
|
36255
|
+
]
|
|
36256
|
+
};
|
|
36257
|
+
}
|
|
35005
36258
|
};
|
|
35006
36259
|
var cmf = {
|
|
35007
36260
|
type: "chaikin-money-flow",
|
|
35008
36261
|
title: "Chaikin Money Flow",
|
|
35009
36262
|
shortTitle: "CMF",
|
|
35010
36263
|
overlay: false,
|
|
35011
|
-
inputs: [
|
|
36264
|
+
inputs: [
|
|
36265
|
+
lengthInput(20, "length", "Length", 5e3, "Lookback length over which money flow volume and volume are summed."),
|
|
36266
|
+
colorInput(NEUTRAL, "CMF Line", "color", "Color of the Chaikin Money Flow line."),
|
|
36267
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the CMF reads above zero (net money flowing in)."),
|
|
36268
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the CMF reads below zero (net money flowing out)."),
|
|
36269
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
36270
|
+
],
|
|
35012
36271
|
compute: (bars, inputs) => {
|
|
35013
36272
|
const len = num3(inputs, "length", 20);
|
|
35014
36273
|
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
35015
|
-
return {
|
|
35016
|
-
|
|
35017
|
-
|
|
35018
|
-
|
|
36274
|
+
return centeredOscillator({
|
|
36275
|
+
key: "cmf",
|
|
36276
|
+
title: "CMF",
|
|
36277
|
+
values,
|
|
36278
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
36279
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
36280
|
+
});
|
|
35019
36281
|
}
|
|
35020
36282
|
};
|
|
35021
36283
|
var chaikinOscillator = {
|
|
@@ -35023,14 +36285,23 @@ var chaikinOscillator = {
|
|
|
35023
36285
|
title: "Chaikin Oscillator",
|
|
35024
36286
|
shortTitle: "Chaikin Osc",
|
|
35025
36287
|
overlay: false,
|
|
35026
|
-
inputs: [
|
|
36288
|
+
inputs: [
|
|
36289
|
+
lengthInput(3, "fastLength", "Fast Length", 5e3, "Length of the fast EMA applied to the accumulation/distribution line."),
|
|
36290
|
+
lengthInput(10, "slowLength", "Slow Length", 5e3, "Length of the slow EMA applied to the accumulation/distribution line."),
|
|
36291
|
+
colorInput(NEUTRAL, "Oscillator", "color", "Color of the Chaikin Oscillator line."),
|
|
36292
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the oscillator reads above zero (accumulation)."),
|
|
36293
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the oscillator reads below zero (distribution).")
|
|
36294
|
+
],
|
|
35027
36295
|
compute: (bars, inputs) => {
|
|
35028
36296
|
const ad = cumSum(moneyFlowVolume(bars));
|
|
35029
36297
|
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
35030
|
-
return {
|
|
35031
|
-
|
|
35032
|
-
|
|
35033
|
-
|
|
36298
|
+
return centeredOscillator({
|
|
36299
|
+
key: "co",
|
|
36300
|
+
title: "Chaikin Oscillator",
|
|
36301
|
+
values,
|
|
36302
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
36303
|
+
zeroInk: NEUTRAL
|
|
36304
|
+
});
|
|
35034
36305
|
}
|
|
35035
36306
|
};
|
|
35036
36307
|
var eom = {
|
|
@@ -35038,20 +36309,28 @@ var eom = {
|
|
|
35038
36309
|
title: "Ease of Movement",
|
|
35039
36310
|
shortTitle: "EOM",
|
|
35040
36311
|
overlay: false,
|
|
35041
|
-
inputs: [
|
|
36312
|
+
inputs: [
|
|
36313
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars in the simple moving average smoothing the 1-bar Ease of Movement values."),
|
|
36314
|
+
intInput("divisor", "Divisor", 1e4, 1, 1e9, "Volume scaling constant of the box ratio; larger values scale the oscillator up. 10000 is the conventional default."),
|
|
36315
|
+
colorInput(NEUTRAL, "EOM Line", "color", "Color of the line when it sits exactly at zero."),
|
|
36316
|
+
colorInput(BULLISH, "Above Zero", "bullColor", "Color of the line and fill while Ease of Movement is positive."),
|
|
36317
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while Ease of Movement is negative.")
|
|
36318
|
+
],
|
|
35042
36319
|
compute: (bars, inputs) => {
|
|
35043
36320
|
const div = num3(inputs, "divisor", 1e4);
|
|
35044
|
-
const
|
|
35045
|
-
const move = change(hl2);
|
|
36321
|
+
const move = change(sourceValues(bars, "HL2"));
|
|
35046
36322
|
const raw = bars.map((b, i) => {
|
|
35047
36323
|
const m = move[i];
|
|
35048
|
-
if (b.volume == null || !Number.isFinite(b.volume) ||
|
|
35049
|
-
return div * m * (b.high - b.low) / b.volume;
|
|
36324
|
+
if (b.volume == null || !Number.isFinite(b.volume) || !Number.isFinite(m)) return Number.NaN;
|
|
36325
|
+
return b.volume === 0 ? 0 : div * m * (b.high - b.low) / b.volume;
|
|
36326
|
+
});
|
|
36327
|
+
return centeredOscillator({
|
|
36328
|
+
key: "eom",
|
|
36329
|
+
title: "EOM",
|
|
36330
|
+
values: sma(raw, num3(inputs, "length", 14)),
|
|
36331
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
36332
|
+
zeroInk: NEUTRAL
|
|
35050
36333
|
});
|
|
35051
|
-
return {
|
|
35052
|
-
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
35053
|
-
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
35054
|
-
};
|
|
35055
36334
|
}
|
|
35056
36335
|
};
|
|
35057
36336
|
var forceIndex = {
|
|
@@ -35059,13 +36338,24 @@ var forceIndex = {
|
|
|
35059
36338
|
title: "Force Index",
|
|
35060
36339
|
shortTitle: "FI",
|
|
35061
36340
|
overlay: false,
|
|
35062
|
-
inputs: [
|
|
36341
|
+
inputs: [
|
|
36342
|
+
lengthInput(13, "length", "Length", 5e3, "EMA length applied to the raw 1-bar force index, (close \u2212 close[1]) \xD7 volume. Elder used 13 for the intermediate trend and 2 for short-term timing."),
|
|
36343
|
+
colorInput(BULLISH, "Force Index", "color", "Color of the line while above zero (buying pressure)."),
|
|
36344
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line while below zero (selling pressure).")
|
|
36345
|
+
],
|
|
35063
36346
|
compute: (bars, inputs) => {
|
|
35064
36347
|
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
35065
|
-
|
|
35066
|
-
|
|
35067
|
-
|
|
35068
|
-
|
|
36348
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
36349
|
+
return centeredOscillator({
|
|
36350
|
+
key: "force",
|
|
36351
|
+
title: "Force Index",
|
|
36352
|
+
values: ema(raw, num3(inputs, "length", 13)),
|
|
36353
|
+
// The reference reads an exact zero as buying pressure, so neutral shares the bull ink.
|
|
36354
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
36355
|
+
zeroInk: NEUTRAL,
|
|
36356
|
+
width: 2,
|
|
36357
|
+
valueTransparency: 60
|
|
36358
|
+
});
|
|
35069
36359
|
}
|
|
35070
36360
|
};
|
|
35071
36361
|
var klinger = {
|
|
@@ -35073,19 +36363,44 @@ var klinger = {
|
|
|
35073
36363
|
title: "Klinger Oscillator",
|
|
35074
36364
|
shortTitle: "KVO",
|
|
35075
36365
|
overlay: false,
|
|
35076
|
-
inputs: [
|
|
36366
|
+
inputs: [
|
|
36367
|
+
lengthInput(34, "fastLength", "Fast Length", 5e3, "Length of the fast EMA applied to the volume force."),
|
|
36368
|
+
lengthInput(55, "slowLength", "Slow Length", 5e3, "Length of the slow EMA applied to the volume force."),
|
|
36369
|
+
lengthInput(13, "signalLength", "Signal Length", 500, "Length of the EMA applied to the oscillator to obtain the signal line."),
|
|
36370
|
+
colorInput(BULLISH, "Bullish", "color", "Color of the Klinger oscillator while above zero."),
|
|
36371
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the Klinger oscillator while below zero."),
|
|
36372
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line.")
|
|
36373
|
+
],
|
|
35077
36374
|
compute: (bars, inputs) => {
|
|
35078
|
-
const
|
|
35079
|
-
const
|
|
35080
|
-
|
|
35081
|
-
|
|
35082
|
-
|
|
35083
|
-
|
|
35084
|
-
|
|
35085
|
-
|
|
35086
|
-
|
|
35087
|
-
|
|
35088
|
-
|
|
36375
|
+
const n = bars.length;
|
|
36376
|
+
const force = new Array(n).fill(Number.NaN);
|
|
36377
|
+
let trend = 1;
|
|
36378
|
+
let prevTrend = 1;
|
|
36379
|
+
let cm = 0;
|
|
36380
|
+
let prevDm = 0;
|
|
36381
|
+
for (let i = 0; i < n; i++) {
|
|
36382
|
+
const b = bars[i];
|
|
36383
|
+
const hlc = b.high + b.low + b.close;
|
|
36384
|
+
const prevHlc = i > 0 ? bars[i - 1].high + bars[i - 1].low + bars[i - 1].close : hlc;
|
|
36385
|
+
trend = hlc > prevHlc ? 1 : -1;
|
|
36386
|
+
const dm = b.high - b.low;
|
|
36387
|
+
cm = i > 0 && trend === prevTrend ? cm + dm : prevDm + dm;
|
|
36388
|
+
const v = b.volume;
|
|
36389
|
+
force[i] = v == null || !Number.isFinite(v) ? Number.NaN : cm === 0 ? 0 : v * Math.abs(2 * (dm / cm) - 1) * trend * 100;
|
|
36390
|
+
prevTrend = trend;
|
|
36391
|
+
prevDm = dm;
|
|
36392
|
+
}
|
|
36393
|
+
const line = sub(ema(force, num3(inputs, "fastLength", 34)), ema(force, num3(inputs, "slowLength", 55)));
|
|
36394
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
36395
|
+
return centeredOscillator({
|
|
36396
|
+
key: "kvo",
|
|
36397
|
+
title: "KVO",
|
|
36398
|
+
values: line,
|
|
36399
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
36400
|
+
zeroInk: transp(NEUTRAL, 50),
|
|
36401
|
+
valueTransparency: 60,
|
|
36402
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: str3(inputs, "signalColor", WARNING) }]
|
|
36403
|
+
});
|
|
35089
36404
|
}
|
|
35090
36405
|
};
|
|
35091
36406
|
var mfi = {
|
|
@@ -35093,7 +36408,15 @@ var mfi = {
|
|
|
35093
36408
|
title: "Money Flow Index",
|
|
35094
36409
|
shortTitle: "MFI",
|
|
35095
36410
|
overlay: false,
|
|
35096
|
-
inputs: [
|
|
36411
|
+
inputs: [
|
|
36412
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to sum positive and negative money flow."),
|
|
36413
|
+
floatInput("overbought", "Overbought Level", 80, 0, 100, 1, "Level above which the MFI is considered overbought."),
|
|
36414
|
+
floatInput("oversold", "Oversold Level", 20, 0, 100, 1, "Level below which the MFI is considered oversold."),
|
|
36415
|
+
colorInput(NEUTRAL, "MFI", "color", "Color of the MFI line between the levels. Toward either level it blends into that level\u2019s color."),
|
|
36416
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
36417
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level."),
|
|
36418
|
+
colorInput(transp(NEUTRAL, 94), "OB/OS Zone Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
36419
|
+
],
|
|
35097
36420
|
compute: (bars, inputs) => {
|
|
35098
36421
|
const len = num3(inputs, "length", 14);
|
|
35099
36422
|
const tp = sourceValues(bars, "HLC3");
|
|
@@ -35101,48 +36424,57 @@ var mfi = {
|
|
|
35101
36424
|
const d = change(tp);
|
|
35102
36425
|
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
35103
36426
|
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
35104
|
-
const values = zip(pos, neg, (p,
|
|
35105
|
-
return {
|
|
35106
|
-
|
|
35107
|
-
|
|
35108
|
-
|
|
35109
|
-
|
|
35110
|
-
|
|
35111
|
-
|
|
36427
|
+
const values = zip(pos, neg, (p, w) => p + w > 0 ? 100 * p / (p + w) : Number.NaN);
|
|
36428
|
+
return boundedOscillator({
|
|
36429
|
+
key: "mfi",
|
|
36430
|
+
title: "MFI",
|
|
36431
|
+
values,
|
|
36432
|
+
scale: { top: 100, bottom: 0 },
|
|
36433
|
+
overbought: num3(inputs, "overbought", 80),
|
|
36434
|
+
oversold: num3(inputs, "oversold", 20),
|
|
36435
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
36436
|
+
levelInk: NEUTRAL,
|
|
36437
|
+
zoneTransparency: 91,
|
|
36438
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 94))
|
|
36439
|
+
});
|
|
35112
36440
|
}
|
|
35113
36441
|
};
|
|
35114
|
-
function
|
|
35115
|
-
|
|
35116
|
-
|
|
35117
|
-
|
|
35118
|
-
|
|
35119
|
-
|
|
35120
|
-
|
|
35121
|
-
|
|
35122
|
-
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
35123
|
-
if (started) out[i] = index;
|
|
35124
|
-
continue;
|
|
35125
|
-
}
|
|
35126
|
-
started = true;
|
|
35127
|
-
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
35128
|
-
out[i] = index;
|
|
35129
|
-
}
|
|
35130
|
-
return out;
|
|
36442
|
+
function regimeStyleInputs(signalTitle) {
|
|
36443
|
+
return [
|
|
36444
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the line and gradient fill while it holds above its signal EMA."),
|
|
36445
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line and gradient fill while it holds below its signal EMA."),
|
|
36446
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor", "Color used while the signal EMA is still forming, or the line sits exactly on it."),
|
|
36447
|
+
colorInput(NEUTRAL, signalTitle, "signalColor", "Color of the signal EMA overlay."),
|
|
36448
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the gap between the line and its signal EMA with a vertical gradient \u2014 transparent at the average, strongest at the line.")
|
|
36449
|
+
];
|
|
35131
36450
|
}
|
|
36451
|
+
var NVI_POSITION = "Position vs Average";
|
|
36452
|
+
var NVI_SINGLE = "Single Color";
|
|
35132
36453
|
var nvi = {
|
|
35133
36454
|
type: "negative-volume-index",
|
|
35134
36455
|
title: "Negative Volume Index",
|
|
35135
36456
|
shortTitle: "Negative Volume Index",
|
|
35136
36457
|
overlay: false,
|
|
35137
|
-
inputs: [
|
|
36458
|
+
inputs: [
|
|
36459
|
+
floatInput("base", "Base Level", 1e3, 0.01, 1e6, 1, "Arbitrary seed of the cumulative line, conventionally 1000. The absolute level carries no information \u2014 the line is read by slope and by position against the signal EMA."),
|
|
36460
|
+
boolInput("showSignal", "Signal EMA", true, SETTINGS, "Displays the conventional signal overlay. Hiding it only removes the plotted average; the regime coloring and fill still use it."),
|
|
36461
|
+
lengthInput(255, "signalLength", "Signal Length", 5e3, "Length of the signal EMA. The convention is one trading year \u2014 255 sessions on daily charts."),
|
|
36462
|
+
optionInput("colorMode", "Line Coloring", NVI_POSITION, [NVI_POSITION, NVI_SINGLE], "'Position vs Average' colors the line by the classic regime read; 'Single Color' draws the whole line in the neutral color."),
|
|
36463
|
+
...regimeStyleInputs("Signal EMA")
|
|
36464
|
+
],
|
|
35138
36465
|
compute: (bars, inputs) => {
|
|
35139
|
-
const
|
|
35140
|
-
|
|
35141
|
-
|
|
35142
|
-
|
|
35143
|
-
|
|
35144
|
-
|
|
35145
|
-
|
|
36466
|
+
const values = volumeIndex(bars, closes(bars), num3(inputs, "base", 1e3), (v, pv) => v < pv);
|
|
36467
|
+
const signal = ema(values, num3(inputs, "signalLength", 255));
|
|
36468
|
+
return regimeIndex(
|
|
36469
|
+
"nvi",
|
|
36470
|
+
"NVI",
|
|
36471
|
+
values,
|
|
36472
|
+
signal,
|
|
36473
|
+
{ bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "neutralColor", NEUTRAL), signal: str3(inputs, "signalColor", NEUTRAL) },
|
|
36474
|
+
bool(inputs, "showSignal", true),
|
|
36475
|
+
bool(inputs, "gradientFill", true),
|
|
36476
|
+
str3(inputs, "colorMode", NVI_POSITION) === NVI_SINGLE
|
|
36477
|
+
);
|
|
35146
36478
|
}
|
|
35147
36479
|
};
|
|
35148
36480
|
var pvi = {
|
|
@@ -35150,15 +36482,26 @@ var pvi = {
|
|
|
35150
36482
|
title: "Positive Volume Index",
|
|
35151
36483
|
shortTitle: "Positive Volume Index",
|
|
35152
36484
|
overlay: false,
|
|
35153
|
-
inputs: [
|
|
36485
|
+
inputs: [
|
|
36486
|
+
sourceInput("Close", "source", "Source", "Price series whose one-bar percentage change moves PVI on its active sessions."),
|
|
36487
|
+
floatInput("base", "Starting Value", 1e3, 1, 1e6, 1, "Seed of the cumulative line. The absolute level carries no information, so the reads are the slope and the position against the signal EMA."),
|
|
36488
|
+
boolInput("showSignal", "Signal EMA", true, SETTINGS, "Plots the long EMA of PVI that anchors the classic regime read. Hiding it keeps the regime coloring and fill, which are always measured against it."),
|
|
36489
|
+
lengthInput(255, "signalLength", "Signal Length", 5e3, "Length of the signal EMA. The classic reference is a one-year average \u2014 255 trading days on daily charts."),
|
|
36490
|
+
...regimeStyleInputs("Signal Line")
|
|
36491
|
+
],
|
|
35154
36492
|
compute: (bars, inputs) => {
|
|
35155
|
-
const
|
|
35156
|
-
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
|
|
35160
|
-
|
|
35161
|
-
|
|
36493
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
36494
|
+
const values = volumeIndex(bars, src, num3(inputs, "base", 1e3), (v, pv) => v > pv);
|
|
36495
|
+
const signal = ema(values, num3(inputs, "signalLength", 255));
|
|
36496
|
+
return regimeIndex(
|
|
36497
|
+
"pvi",
|
|
36498
|
+
"PVI",
|
|
36499
|
+
values,
|
|
36500
|
+
signal,
|
|
36501
|
+
{ bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "neutralColor", NEUTRAL), signal: str3(inputs, "signalColor", NEUTRAL) },
|
|
36502
|
+
bool(inputs, "showSignal", true),
|
|
36503
|
+
bool(inputs, "gradientFill", true)
|
|
36504
|
+
);
|
|
35162
36505
|
}
|
|
35163
36506
|
};
|
|
35164
36507
|
var pvt = {
|
|
@@ -35166,7 +36509,15 @@ var pvt = {
|
|
|
35166
36509
|
title: "Price Volume Trend",
|
|
35167
36510
|
shortTitle: "PVT",
|
|
35168
36511
|
overlay: false,
|
|
35169
|
-
inputs: [
|
|
36512
|
+
inputs: [
|
|
36513
|
+
boolInput("showSignal", "Signal Line", false, SETTINGS, "Display a moving average of PVT acting as a signal line."),
|
|
36514
|
+
optionInput("signalType", "Signal Type", "SMA", ["SMA", "EMA"], "Moving average type used for the signal line."),
|
|
36515
|
+
lengthInput(21, "signalLength", "Signal Length", 5e3, "Number of bars used to compute the signal line."),
|
|
36516
|
+
colorInput(NEUTRAL, "PVT Color", "color", "Color of the PVT line while it is flat."),
|
|
36517
|
+
colorInput(BULLISH, "Rising", "bullColor", "Color of the PVT line while it rises bar to bar."),
|
|
36518
|
+
colorInput(BEARISH, "Falling", "bearColor", "Color of the PVT line while it falls bar to bar."),
|
|
36519
|
+
colorInput(WARNING, "Signal Color", "signalColor", "Color of the signal line.")
|
|
36520
|
+
],
|
|
35170
36521
|
compute: (bars, inputs) => {
|
|
35171
36522
|
const c = closes(bars);
|
|
35172
36523
|
const raw = bars.map((b, i) => {
|
|
@@ -35174,7 +36525,17 @@ var pvt = {
|
|
|
35174
36525
|
const pc = c[i - 1];
|
|
35175
36526
|
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
35176
36527
|
});
|
|
35177
|
-
|
|
36528
|
+
const values = cumSum(raw);
|
|
36529
|
+
const flat = str3(inputs, "color", NEUTRAL);
|
|
36530
|
+
const plots = [
|
|
36531
|
+
{ key: "pvt", title: "PVT", values, color: flat, width: 2, colors: slopeColors2(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), flat) }
|
|
36532
|
+
];
|
|
36533
|
+
if (bool(inputs, "showSignal", false)) {
|
|
36534
|
+
const len = num3(inputs, "signalLength", 21);
|
|
36535
|
+
const signal = str3(inputs, "signalType", "SMA") === "EMA" ? ema(values, len) : sma(values, len);
|
|
36536
|
+
plots.push({ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) });
|
|
36537
|
+
}
|
|
36538
|
+
return { plots };
|
|
35178
36539
|
}
|
|
35179
36540
|
};
|
|
35180
36541
|
var volumeOscillator = {
|
|
@@ -35182,39 +36543,75 @@ var volumeOscillator = {
|
|
|
35182
36543
|
title: "Volume Oscillator",
|
|
35183
36544
|
shortTitle: "Vol Osc",
|
|
35184
36545
|
overlay: false,
|
|
35185
|
-
inputs: [
|
|
36546
|
+
inputs: [
|
|
36547
|
+
lengthInput(5, "fastLength", "Fast Length", 5e3, "Length of the fast EMA of volume."),
|
|
36548
|
+
lengthInput(10, "slowLength", "Slow Length", 5e3, "Length of the slow EMA of volume."),
|
|
36549
|
+
colorInput(BULLISH, "Above Zero", "color", "Color of the line and fill while it holds above zero \u2014 volume expanding."),
|
|
36550
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while it holds below zero \u2014 volume contracting.")
|
|
36551
|
+
],
|
|
35186
36552
|
compute: (bars, inputs) => {
|
|
35187
36553
|
const vol = volumes(bars);
|
|
35188
36554
|
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
35189
36555
|
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
35190
|
-
const values = zip(fast, slow, (f, s) => s === 0 ?
|
|
35191
|
-
|
|
35192
|
-
|
|
35193
|
-
|
|
35194
|
-
|
|
36556
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? 0 : 100 * (f - s) / s);
|
|
36557
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
36558
|
+
return centeredOscillator({
|
|
36559
|
+
key: "vo",
|
|
36560
|
+
title: "Volume Oscillator",
|
|
36561
|
+
values,
|
|
36562
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
36563
|
+
zeroInk: NEUTRAL,
|
|
36564
|
+
valueTransparency: 60
|
|
36565
|
+
});
|
|
35195
36566
|
}
|
|
35196
36567
|
};
|
|
36568
|
+
var OSCILLATOR = "Oscillator";
|
|
35197
36569
|
var pvo = {
|
|
35198
36570
|
type: "pvo",
|
|
35199
36571
|
title: "Percentage Volume Oscillator",
|
|
35200
36572
|
shortTitle: "PVO",
|
|
35201
36573
|
overlay: false,
|
|
35202
|
-
inputs: [
|
|
36574
|
+
inputs: [
|
|
36575
|
+
{ ...lengthInput(12, "fastLength", "Fast Length", 5e3, "Period of the fast EMA of volume. The conventional setting is 12."), group: OSCILLATOR },
|
|
36576
|
+
{ ...lengthInput(26, "slowLength", "Slow Length", 5e3, "Period of the slow EMA of volume. The PVO expresses the fast/slow spread as a percentage of this slow average."), group: OSCILLATOR },
|
|
36577
|
+
{ ...lengthInput(9, "signalLength", "Signal Length", 500, "Period of the EMA applied to the PVO to form the signal line."), group: OSCILLATOR },
|
|
36578
|
+
colorInput(BULLISH, "Expansion Color", "color", "Color of the oscillator and fill while participation expands."),
|
|
36579
|
+
colorInput(BEARISH, "Contraction Color", "bearColor", "Color of the oscillator and fill while participation contracts."),
|
|
36580
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
36581
|
+
colorInput(transp(NEUTRAL, 50), "Zero Line", "zeroColor", "Color of the zero reference line."),
|
|
36582
|
+
boolInput("showSignal", "Show Signal Line", true, STYLE, "Plot the signal line, an EMA of the PVO."),
|
|
36583
|
+
boolInput("showHistogram", "Show Histogram", true, STYLE, "Plot the difference between the PVO and its signal line as columns behind the oscillator.")
|
|
36584
|
+
],
|
|
35203
36585
|
compute: (bars, inputs) => {
|
|
35204
36586
|
const vol = volumes(bars);
|
|
35205
36587
|
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
35206
36588
|
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
35207
|
-
const
|
|
35208
|
-
const signal = ema(
|
|
35209
|
-
const
|
|
35210
|
-
|
|
35211
|
-
|
|
35212
|
-
|
|
35213
|
-
|
|
35214
|
-
|
|
35215
|
-
|
|
35216
|
-
|
|
35217
|
-
|
|
36589
|
+
const values = zip(fast, slow, (f, s) => s > 0 ? 100 * (f - s) / s : Number.NaN);
|
|
36590
|
+
const signal = ema(values, num3(inputs, "signalLength", 9));
|
|
36591
|
+
const expansion = str3(inputs, "color", BULLISH);
|
|
36592
|
+
const contraction = str3(inputs, "bearColor", BEARISH);
|
|
36593
|
+
const extraPlots = [];
|
|
36594
|
+
if (bool(inputs, "showHistogram", true)) {
|
|
36595
|
+
const hist = sub(values, signal);
|
|
36596
|
+
extraPlots.push({
|
|
36597
|
+
key: "hist",
|
|
36598
|
+
title: "Histogram",
|
|
36599
|
+
values: hist,
|
|
36600
|
+
kind: "columns",
|
|
36601
|
+
color: transp(expansion, 60),
|
|
36602
|
+
colors: hist.map((x) => Number.isFinite(x) ? transp(x > 0 ? expansion : contraction, 60) : null),
|
|
36603
|
+
base: 0
|
|
36604
|
+
});
|
|
36605
|
+
}
|
|
36606
|
+
if (bool(inputs, "showSignal", true)) extraPlots.push({ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) });
|
|
36607
|
+
return centeredOscillator({
|
|
36608
|
+
key: "pvo",
|
|
36609
|
+
title: "PVO",
|
|
36610
|
+
values,
|
|
36611
|
+
ink: { bull: expansion, bear: contraction, neutral: contraction },
|
|
36612
|
+
zeroInk: str3(inputs, "zeroColor", transp(NEUTRAL, 50)),
|
|
36613
|
+
extraPlots
|
|
36614
|
+
});
|
|
35218
36615
|
}
|
|
35219
36616
|
};
|
|
35220
36617
|
var vfi = {
|
|
@@ -35223,55 +36620,86 @@ var vfi = {
|
|
|
35223
36620
|
shortTitle: "Volume Flow Indicator",
|
|
35224
36621
|
overlay: false,
|
|
35225
36622
|
inputs: [
|
|
35226
|
-
lengthInput(130),
|
|
35227
|
-
|
|
35228
|
-
|
|
35229
|
-
lengthInput(
|
|
36623
|
+
{ ...lengthInput(130, "length", "Length", 5e3, "Rolling summation window, also used for the average volume that scales the result. The published default of 130 bars targets position-horizon work on daily charts."), min: 2 },
|
|
36624
|
+
floatInput("coef", "Cutoff Coefficient", 0.2, 0, 10, 0.05, "Scales the volatility-based minimum change in typical price. Moves smaller than the cutoff count as noise and contribute no volume."),
|
|
36625
|
+
floatInput("volCoef", "Volume Cap Multiplier", 2.5, 0.1, 50, 0.1, "Caps each bar's volume contribution at this multiple of the average volume, so a single climactic print stays bounded."),
|
|
36626
|
+
{ ...lengthInput(30, "volLength", "Volatility Length", 5e3, "Window of the standard deviation of the log change in typical price used to build the adaptive cutoff."), min: 2 },
|
|
36627
|
+
boolInput("smooth", "Smooth VFI", true, SETTINGS, "Applies the published light smoothing \u2014 an EMA of the summed line \u2014 before plotting."),
|
|
36628
|
+
lengthInput(3, "smoothLength", "Smoothing Length", 500, "Length of the smoothing EMA. The published version uses 3 bars."),
|
|
36629
|
+
boolInput("showSignal", "Signal Line", true, SETTINGS, "Plots an EMA of VFI as a signal line."),
|
|
36630
|
+
lengthInput(5, "signalLength", "Signal Length", 500, "Length of the signal-line EMA."),
|
|
36631
|
+
colorInput(BULLISH, "Bullish", "color", "Color of the VFI line and gradient fill above the zero line \u2014 the net accumulation regime."),
|
|
36632
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the VFI line and gradient fill below the zero line \u2014 the net distribution regime."),
|
|
36633
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
36634
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the zero line and the VFI line with a vertical gradient.")
|
|
35230
36635
|
],
|
|
35231
36636
|
compute: (bars, inputs) => {
|
|
35232
36637
|
const len = num3(inputs, "length", 130);
|
|
36638
|
+
const typical = sourceValues(bars, "HLC3");
|
|
36639
|
+
const logChange = sub(map(typical, Math.log), shift(map(typical, Math.log), 1));
|
|
36640
|
+
const volatility = stdev(logChange, num3(inputs, "volLength", 30));
|
|
35233
36641
|
const coef = num3(inputs, "coef", 0.2);
|
|
35234
36642
|
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
35235
|
-
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
35236
|
-
const inter = change(typical);
|
|
35237
|
-
const vInter = stdev(inter, 30);
|
|
35238
36643
|
const vol = volumes(bars);
|
|
35239
|
-
const
|
|
35240
|
-
const
|
|
35241
|
-
const
|
|
35242
|
-
const
|
|
36644
|
+
const avgVolume = shift(sma(vol, len), 1);
|
|
36645
|
+
const typicalChange = change(typical);
|
|
36646
|
+
const signed3 = bars.map((b, i) => {
|
|
36647
|
+
const sd = volatility[i];
|
|
36648
|
+
const m = typicalChange[i];
|
|
35243
36649
|
const v = vol[i];
|
|
35244
|
-
|
|
35245
|
-
const
|
|
35246
|
-
|
|
35247
|
-
const capped = Math.min(v,
|
|
36650
|
+
if (!Number.isFinite(sd) || !Number.isFinite(m) || !Number.isFinite(v)) return Number.NaN;
|
|
36651
|
+
const cutoff = coef * sd * b.close;
|
|
36652
|
+
const avg = avgVolume[i];
|
|
36653
|
+
const capped = Number.isFinite(avg) ? Math.min(v, avg * volCoef) : v;
|
|
35248
36654
|
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
35249
36655
|
});
|
|
35250
|
-
const
|
|
35251
|
-
|
|
35252
|
-
|
|
36656
|
+
const raw = zip(sum(signed3, len), avgVolume, (f, a) => a > 0 ? f / a : Number.NaN);
|
|
36657
|
+
const values = bool(inputs, "smooth", true) ? ema(raw, num3(inputs, "smoothLength", 3)) : raw;
|
|
36658
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
36659
|
+
const extraPlots = bool(inputs, "showSignal", true) ? [{ key: "signal", title: "Signal Line", values: ema(values, num3(inputs, "signalLength", 5)), color: str3(inputs, "signalColor", WARNING) }] : [];
|
|
36660
|
+
return centeredOscillator({
|
|
36661
|
+
key: "vfi",
|
|
36662
|
+
title: "VFI",
|
|
36663
|
+
values,
|
|
36664
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "bearColor", BEARISH) },
|
|
36665
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
36666
|
+
fill: bool(inputs, "gradientFill", true),
|
|
36667
|
+
extraPlots
|
|
35253
36668
|
});
|
|
35254
|
-
const flowSum = sum(raw, len);
|
|
35255
|
-
const line = zip(flowSum, values, (f, a) => f / a);
|
|
35256
|
-
return {
|
|
35257
|
-
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
35258
|
-
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
35259
|
-
};
|
|
35260
36669
|
}
|
|
35261
36670
|
};
|
|
36671
|
+
var II_OSCILLATOR = "Normalized Oscillator (II%)";
|
|
36672
|
+
var II_CUMULATIVE = "Cumulative Line";
|
|
35262
36673
|
var intradayIntensity = {
|
|
35263
36674
|
type: "intraday-intensity",
|
|
35264
36675
|
title: "Intraday Intensity",
|
|
35265
36676
|
shortTitle: "Intraday Intensity",
|
|
35266
36677
|
overlay: false,
|
|
35267
|
-
inputs: [
|
|
36678
|
+
inputs: [
|
|
36679
|
+
optionInput("mode", "Display", II_OSCILLATOR, [II_OSCILLATOR, II_CUMULATIVE], "The two published forms. The cumulative line is open-ended and read by slope against price; the normalized oscillator divides a rolling sum of intensity by the rolling sum of volume and oscillates around zero."),
|
|
36680
|
+
lengthInput(21, "length", "Oscillator Length", 5e3, "Window of the rolling sums in the normalized form. The common setting is 21 bars. It has no effect on the cumulative line."),
|
|
36681
|
+
colorInput(BULLISH, "Bullish", "color", "Color of positive readings, the rising cumulative line and the fill above zero \u2014 the accumulation side."),
|
|
36682
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of negative readings, the falling cumulative line and the fill below zero \u2014 the distribution side."),
|
|
36683
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the zero line and the oscillator with a vertical gradient. Applies to the normalized oscillator only.")
|
|
36684
|
+
],
|
|
35268
36685
|
compute: (bars, inputs) => {
|
|
36686
|
+
const intensity = moneyFlowVolume(bars);
|
|
36687
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
36688
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
36689
|
+
if (str3(inputs, "mode", II_OSCILLATOR) === II_CUMULATIVE) {
|
|
36690
|
+
const values2 = cumSum(intensity);
|
|
36691
|
+
return { plots: [{ key: "ii", title: "Intraday Intensity", values: values2, color: bull, colors: slopeColors2(values2, bull, bear, bull) }] };
|
|
36692
|
+
}
|
|
35269
36693
|
const len = num3(inputs, "length", 21);
|
|
35270
|
-
const values = zip(sum(
|
|
35271
|
-
return {
|
|
35272
|
-
|
|
35273
|
-
|
|
35274
|
-
|
|
36694
|
+
const values = zip(sum(intensity, len), sum(volumes(bars), len), (m, v) => v > 0 ? 100 * m / v : Number.NaN);
|
|
36695
|
+
return centeredOscillator({
|
|
36696
|
+
key: "ii",
|
|
36697
|
+
title: "Intraday Intensity",
|
|
36698
|
+
values,
|
|
36699
|
+
ink: { bull, bear, neutral: bear },
|
|
36700
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
36701
|
+
fill: bool(inputs, "gradientFill", true)
|
|
36702
|
+
});
|
|
35275
36703
|
}
|
|
35276
36704
|
};
|
|
35277
36705
|
var volumeSpecs = [
|
|
@@ -35304,15 +36732,24 @@ function periodKey(anchor, time) {
|
|
|
35304
36732
|
}
|
|
35305
36733
|
return Math.floor(time / DAY_MS2);
|
|
35306
36734
|
}
|
|
36735
|
+
function barInterval(bars) {
|
|
36736
|
+
if (bars.length < 2) return 0;
|
|
36737
|
+
const gaps = [];
|
|
36738
|
+
for (let i = 1; i < bars.length; i++) gaps.push(bars[i].time - bars[i - 1].time);
|
|
36739
|
+
gaps.sort((a, b) => a - b);
|
|
36740
|
+
return gaps[Math.floor(gaps.length / 2)] ?? 0;
|
|
36741
|
+
}
|
|
35307
36742
|
var parabolicSar = {
|
|
35308
36743
|
type: "parabolic-sar",
|
|
35309
36744
|
title: "Parabolic SAR",
|
|
35310
36745
|
shortTitle: "SAR",
|
|
35311
36746
|
overlay: true,
|
|
35312
36747
|
inputs: [
|
|
35313
|
-
|
|
35314
|
-
|
|
35315
|
-
|
|
36748
|
+
floatInput("start", "Start", 0.02, 0, 1, 0.01, "Initial acceleration factor applied when a new trend begins."),
|
|
36749
|
+
floatInput("increment", "Increment", 0.02, 0, 1, 0.01, "Amount added to the acceleration factor each time the trend makes a new extreme point."),
|
|
36750
|
+
floatInput("maximum", "Maximum", 0.2, 0.01, 1, 0.01, "Upper cap on the acceleration factor."),
|
|
36751
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the SAR crosses while they trail below price \u2014 uptrend, the stop sits under the market."),
|
|
36752
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the SAR crosses while they sit above price \u2014 downtrend, the stop sits over the market.")
|
|
35316
36753
|
],
|
|
35317
36754
|
compute: (bars, inputs) => {
|
|
35318
36755
|
const start = num3(inputs, "start", 0.02);
|
|
@@ -35320,7 +36757,6 @@ var parabolicSar = {
|
|
|
35320
36757
|
const max = num3(inputs, "maximum", 0.2);
|
|
35321
36758
|
const n = bars.length;
|
|
35322
36759
|
const values = new Array(n).fill(Number.NaN);
|
|
35323
|
-
const colors = new Array(n).fill(null);
|
|
35324
36760
|
if (n >= 2) {
|
|
35325
36761
|
let long = bars[1].close >= bars[0].close;
|
|
35326
36762
|
let sar = long ? bars[0].low : bars[0].high;
|
|
@@ -35345,18 +36781,21 @@ var parabolicSar = {
|
|
|
35345
36781
|
af = Math.min(max, af + inc);
|
|
35346
36782
|
}
|
|
35347
36783
|
values[i] = sar;
|
|
35348
|
-
colors[i] = long ? BULLISH : BEARISH;
|
|
35349
36784
|
}
|
|
35350
36785
|
}
|
|
35351
|
-
|
|
36786
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
36787
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
36788
|
+
const colors = values.map((x, i) => Number.isFinite(x) ? x < bars[i].close ? bull : bear : null);
|
|
36789
|
+
return { plots: [{ key: "sar", title: "SAR", values, kind: "cross", color: bull, colors }] };
|
|
35352
36790
|
}
|
|
35353
36791
|
};
|
|
35354
|
-
var
|
|
35355
|
-
var
|
|
36792
|
+
var VWAP_ANCHORS = ["Session", "Week", "Month", "Quarter", "Year"];
|
|
36793
|
+
var BANDS_STDEV = "Standard Deviation";
|
|
36794
|
+
var BANDS_PERCENT = "Percentage";
|
|
35356
36795
|
var VWAP_BANDS = [
|
|
35357
|
-
{
|
|
35358
|
-
{
|
|
35359
|
-
{
|
|
36796
|
+
{ show: "band1", mult: "band1Mult", defMult: 1, defOn: true },
|
|
36797
|
+
{ show: "band2", mult: "band2Mult", defMult: 2, defOn: false },
|
|
36798
|
+
{ show: "band3", mult: "band3Mult", defMult: 3, defOn: false }
|
|
35360
36799
|
];
|
|
35361
36800
|
var vwap = {
|
|
35362
36801
|
type: "vwap",
|
|
@@ -35364,39 +36803,36 @@ var vwap = {
|
|
|
35364
36803
|
shortTitle: "VWAP",
|
|
35365
36804
|
overlay: true,
|
|
35366
36805
|
inputs: [
|
|
35367
|
-
|
|
35368
|
-
sourceInput("HLC3"),
|
|
35369
|
-
|
|
36806
|
+
optionInput("anchor", "Anchor Period", "Session", VWAP_ANCHORS, "Period anchoring the average. VWAP and its bands reset on the first bar of each new session, week, month, quarter or year (UTC)."),
|
|
36807
|
+
sourceInput("HLC3", "source", "Source", "Price used in the volume weighted average. Typical price (HLC3) is the classic choice."),
|
|
36808
|
+
optionInput("bandsMode", "Bands Mode", BANDS_STDEV, [BANDS_STDEV, BANDS_PERCENT], "Bands offset method: multiples of the volume weighted standard deviation of price around VWAP, or a percentage of the VWAP value."),
|
|
35370
36809
|
...VWAP_BANDS.flatMap((b, i) => [
|
|
35371
|
-
|
|
35372
|
-
|
|
36810
|
+
boolInput(b.show, `Band #${i + 1}`, b.defOn, SETTINGS, `Show the ${["first", "second", "third"][i]} band pair.`),
|
|
36811
|
+
floatInput(b.mult, `Band #${i + 1} Multiplier`, b.defMult, 0, 50, 0.5, "Multiplier for this band pair, in standard deviations or percent depending on the bands mode.")
|
|
35373
36812
|
]),
|
|
35374
|
-
|
|
35375
|
-
|
|
35376
|
-
|
|
35377
|
-
|
|
35378
|
-
|
|
35379
|
-
{ ...colorInput(withAlpha2(b.defColor), "", b.fillColor), inline: b.color, group: VWAP_STYLE }
|
|
35380
|
-
])
|
|
36813
|
+
boolInput("hideDwm", "Hide VWAP on 1D or Above", true, SETTINGS, "Hide the VWAP and its bands on daily and higher timeframes, where an intraday anchored VWAP is not meaningful."),
|
|
36814
|
+
colorInput(BULLISH, "Bullish", "bullColor", "VWAP line color while price closes above it, and the color of the lower (support) bands."),
|
|
36815
|
+
colorInput(BEARISH, "Bearish", "bearColor", "VWAP line color while price closes below it, and the color of the upper (resistance) bands."),
|
|
36816
|
+
colorInput(NEUTRAL, "Bands", "bandsColor", "Deviation bands fill color."),
|
|
36817
|
+
boolInput("fill", "Bands Fill", true, STYLE, "Fill the area between each visible band pair.")
|
|
35381
36818
|
],
|
|
35382
36819
|
compute: (bars, inputs) => {
|
|
35383
|
-
const anchor = str3(inputs, "anchor", "
|
|
36820
|
+
const anchor = str3(inputs, "anchor", "Session");
|
|
35384
36821
|
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
35385
36822
|
const n = bars.length;
|
|
36823
|
+
const hidden = bool(inputs, "hideDwm", true) && barInterval(bars) >= DAY_MS2;
|
|
35386
36824
|
const values = new Array(n).fill(Number.NaN);
|
|
35387
|
-
const bands = VWAP_BANDS.filter((b) => bool(inputs, b.
|
|
36825
|
+
const bands = VWAP_BANDS.filter((b) => bool(inputs, b.show, b.defOn)).map((b) => ({
|
|
35388
36826
|
mult: Math.max(0, num3(inputs, b.mult, b.defMult)),
|
|
35389
|
-
color: str3(inputs, b.color, b.defColor),
|
|
35390
|
-
fill: bool(inputs, b.fill, true),
|
|
35391
|
-
fillColor: str3(inputs, b.fillColor, withAlpha2(b.defColor)),
|
|
35392
36827
|
up: new Array(n).fill(Number.NaN),
|
|
35393
36828
|
down: new Array(n).fill(Number.NaN)
|
|
35394
36829
|
}));
|
|
36830
|
+
const percentMode = str3(inputs, "bandsMode", BANDS_STDEV) === BANDS_PERCENT;
|
|
35395
36831
|
let period = Number.NaN;
|
|
35396
36832
|
let cumPV = 0;
|
|
35397
36833
|
let cumPV2 = 0;
|
|
35398
36834
|
let cumV = 0;
|
|
35399
|
-
for (let i = 0; i < n; i++) {
|
|
36835
|
+
for (let i = 0; i < n && !hidden; i++) {
|
|
35400
36836
|
const b = bars[i];
|
|
35401
36837
|
const key = periodKey(anchor, b.time);
|
|
35402
36838
|
if (key !== period) {
|
|
@@ -35414,38 +36850,58 @@ var vwap = {
|
|
|
35414
36850
|
}
|
|
35415
36851
|
if (cumV <= 0) continue;
|
|
35416
36852
|
const mean = cumPV / cumV;
|
|
35417
|
-
const
|
|
36853
|
+
const offset = percentMode ? mean / 100 : Math.sqrt(Math.max(0, cumPV2 / cumV - mean * mean));
|
|
35418
36854
|
values[i] = mean;
|
|
35419
36855
|
for (const band of bands) {
|
|
35420
|
-
band.up[i] = mean + band.mult *
|
|
35421
|
-
band.down[i] = mean - band.mult *
|
|
36856
|
+
band.up[i] = mean + band.mult * offset;
|
|
36857
|
+
band.down[i] = mean - band.mult * offset;
|
|
35422
36858
|
}
|
|
35423
36859
|
}
|
|
35424
|
-
const
|
|
36860
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
36861
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
36862
|
+
const fillColor = transp(str3(inputs, "bandsColor", NEUTRAL), 95);
|
|
36863
|
+
const plots = [
|
|
36864
|
+
{ key: "vwap", title: "VWAP", values, color: bull, colors: values.map((x, i) => Number.isFinite(x) ? bars[i].close >= x ? bull : bear : null) }
|
|
36865
|
+
];
|
|
35425
36866
|
const fills = [];
|
|
35426
36867
|
bands.forEach((band, i) => {
|
|
35427
36868
|
plots.push(
|
|
35428
|
-
{ key: `up${i}`, title: `Upper
|
|
35429
|
-
{ key: `down${i}`, title: `Lower
|
|
36869
|
+
{ key: `up${i}`, title: `Upper Band #${i + 1}`, values: band.up, color: transp(bear, 40) },
|
|
36870
|
+
{ key: `down${i}`, title: `Lower Band #${i + 1}`, values: band.down, color: transp(bull, 40) }
|
|
35430
36871
|
);
|
|
35431
|
-
if (
|
|
36872
|
+
if (bool(inputs, "fill", true)) fills.push({ key: `band${i}`, from: `up${i}`, to: `down${i}`, color: fillColor });
|
|
35432
36873
|
});
|
|
35433
36874
|
return { plots, bands: fills };
|
|
35434
36875
|
}
|
|
35435
36876
|
};
|
|
36877
|
+
var PIVOT_TRADITIONAL = "Traditional";
|
|
36878
|
+
var PIVOT_FIBONACCI = "Fibonacci";
|
|
36879
|
+
var PIVOT_CAMARILLA = "Camarilla";
|
|
36880
|
+
var PIVOT_WOODIE = "Woodie";
|
|
36881
|
+
var PIVOT_AUTO = "Auto";
|
|
35436
36882
|
var pivotPoints = {
|
|
35437
36883
|
type: "pivot-points",
|
|
35438
|
-
title: "Pivot Points",
|
|
36884
|
+
title: "Pivot Points Standard",
|
|
35439
36885
|
shortTitle: "Pivots",
|
|
35440
36886
|
overlay: true,
|
|
35441
|
-
inputs: [
|
|
36887
|
+
inputs: [
|
|
36888
|
+
optionInput("kind", "Type", PIVOT_TRADITIONAL, [PIVOT_TRADITIONAL, PIVOT_FIBONACCI, PIVOT_CAMARILLA, PIVOT_WOODIE], "Formula used to calculate the levels. Traditional is the classic floor-trader method; Fibonacci projects 38.2/61.8/100% of the prior range around the pivot; Camarilla scales the prior range around the prior close; Woodie gives extra weight to the period open."),
|
|
36889
|
+
optionInput("anchor", "Pivots Timeframe", PIVOT_AUTO, [PIVOT_AUTO, "Daily", "Weekly", "Monthly", "Yearly"], "Period the pivots are anchored to. 'Auto' follows the chart's bar spacing: daily pivots up to 15-minute bars, weekly on other intraday bars, monthly on daily bars, yearly above."),
|
|
36890
|
+
colorInput(WARNING, "Pivot", "pivotColor", "Color of the central pivot (P) level."),
|
|
36891
|
+
colorInput(BEARISH, "Resistances", "resistanceColor", "Color of the resistance (R) levels."),
|
|
36892
|
+
colorInput(BULLISH, "Supports", "supportColor", "Color of the support (S) levels.")
|
|
36893
|
+
],
|
|
35442
36894
|
compute: (bars, inputs) => {
|
|
35443
|
-
const
|
|
36895
|
+
const interval = barInterval(bars);
|
|
36896
|
+
const requested = str3(inputs, "anchor", PIVOT_AUTO);
|
|
36897
|
+
const auto = interval <= 15 * 6e4 ? "Session" : interval < DAY_MS2 ? "Week" : interval <= DAY_MS2 ? "Month" : "Year";
|
|
36898
|
+
const anchor = requested === "Daily" ? "Session" : requested === "Weekly" ? "Week" : requested === "Monthly" ? "Month" : requested === "Yearly" ? "Year" : auto;
|
|
36899
|
+
const kind = str3(inputs, "kind", PIVOT_TRADITIONAL);
|
|
35444
36900
|
const aggs = /* @__PURE__ */ new Map();
|
|
35445
36901
|
for (const b of bars) {
|
|
35446
36902
|
const key = periodKey(anchor, b.time);
|
|
35447
36903
|
const a = aggs.get(key);
|
|
35448
|
-
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
36904
|
+
if (!a) aggs.set(key, { open: b.open, high: b.high, low: b.low, close: b.close });
|
|
35449
36905
|
else {
|
|
35450
36906
|
a.high = Math.max(a.high, b.high);
|
|
35451
36907
|
a.low = Math.min(a.low, b.low);
|
|
@@ -35454,31 +36910,61 @@ var pivotPoints = {
|
|
|
35454
36910
|
}
|
|
35455
36911
|
const n = bars.length;
|
|
35456
36912
|
const mk = () => new Array(n).fill(Number.NaN);
|
|
35457
|
-
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
36913
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk(), r4: mk(), s4: mk() };
|
|
36914
|
+
let lastKey = Number.NaN;
|
|
35458
36915
|
for (let i = 0; i < n; i++) {
|
|
35459
|
-
const
|
|
35460
|
-
|
|
35461
|
-
|
|
36916
|
+
const key = periodKey(anchor, bars[i].time);
|
|
36917
|
+
const fresh = key !== lastKey;
|
|
36918
|
+
lastKey = key;
|
|
36919
|
+
if (fresh) continue;
|
|
36920
|
+
const prev2 = aggs.get(key - 1);
|
|
36921
|
+
const curr = aggs.get(key);
|
|
36922
|
+
if (!prev2 || !curr) continue;
|
|
36923
|
+
const range = prev2.high - prev2.low;
|
|
36924
|
+
const traditional = (prev2.high + prev2.low + prev2.close) / 3;
|
|
36925
|
+
const p = kind === PIVOT_WOODIE ? (prev2.high + prev2.low + 2 * curr.open) / 4 : traditional;
|
|
35462
36926
|
levels.p[i] = p;
|
|
35463
|
-
|
|
35464
|
-
|
|
35465
|
-
|
|
35466
|
-
|
|
35467
|
-
|
|
35468
|
-
|
|
35469
|
-
|
|
35470
|
-
|
|
35471
|
-
|
|
35472
|
-
|
|
35473
|
-
|
|
35474
|
-
|
|
35475
|
-
|
|
35476
|
-
|
|
35477
|
-
|
|
35478
|
-
|
|
35479
|
-
|
|
35480
|
-
|
|
35481
|
-
|
|
36927
|
+
if (kind === PIVOT_FIBONACCI) {
|
|
36928
|
+
levels.r1[i] = traditional + 0.382 * range;
|
|
36929
|
+
levels.s1[i] = traditional - 0.382 * range;
|
|
36930
|
+
levels.r2[i] = traditional + 0.618 * range;
|
|
36931
|
+
levels.s2[i] = traditional - 0.618 * range;
|
|
36932
|
+
levels.r3[i] = traditional + range;
|
|
36933
|
+
levels.s3[i] = traditional - range;
|
|
36934
|
+
} else if (kind === PIVOT_CAMARILLA) {
|
|
36935
|
+
const cam = 1.1 * range;
|
|
36936
|
+
levels.r1[i] = prev2.close + cam / 12;
|
|
36937
|
+
levels.s1[i] = prev2.close - cam / 12;
|
|
36938
|
+
levels.r2[i] = prev2.close + cam / 6;
|
|
36939
|
+
levels.s2[i] = prev2.close - cam / 6;
|
|
36940
|
+
levels.r3[i] = prev2.close + cam / 4;
|
|
36941
|
+
levels.s3[i] = prev2.close - cam / 4;
|
|
36942
|
+
levels.r4[i] = prev2.close + cam / 2;
|
|
36943
|
+
levels.s4[i] = prev2.close - cam / 2;
|
|
36944
|
+
} else {
|
|
36945
|
+
levels.r1[i] = 2 * p - prev2.low;
|
|
36946
|
+
levels.s1[i] = 2 * p - prev2.high;
|
|
36947
|
+
levels.r2[i] = p + range;
|
|
36948
|
+
levels.s2[i] = p - range;
|
|
36949
|
+
if (kind !== PIVOT_WOODIE) {
|
|
36950
|
+
levels.r3[i] = prev2.high + 2 * (p - prev2.low);
|
|
36951
|
+
levels.s3[i] = prev2.low - 2 * (prev2.high - p);
|
|
36952
|
+
}
|
|
36953
|
+
}
|
|
36954
|
+
}
|
|
36955
|
+
const resistance = str3(inputs, "resistanceColor", BEARISH);
|
|
36956
|
+
const support = str3(inputs, "supportColor", BULLISH);
|
|
36957
|
+
const plot = (key, title, color) => ({ key, title, values: levels[key], color });
|
|
36958
|
+
const plots = [
|
|
36959
|
+
plot("p", "P", str3(inputs, "pivotColor", WARNING)),
|
|
36960
|
+
plot("r1", "R1", resistance),
|
|
36961
|
+
plot("s1", "S1", support),
|
|
36962
|
+
plot("r2", "R2", resistance),
|
|
36963
|
+
plot("s2", "S2", support)
|
|
36964
|
+
];
|
|
36965
|
+
if (kind !== PIVOT_WOODIE) plots.push(plot("r3", "R3", resistance), plot("s3", "S3", support));
|
|
36966
|
+
if (kind === PIVOT_CAMARILLA) plots.push(plot("r4", "R4", resistance), plot("s4", "S4", support));
|
|
36967
|
+
return { plots };
|
|
35482
36968
|
}
|
|
35483
36969
|
};
|
|
35484
36970
|
var fiftyTwoWeek = {
|
|
@@ -35486,15 +36972,25 @@ var fiftyTwoWeek = {
|
|
|
35486
36972
|
title: "52 Week High/Low",
|
|
35487
36973
|
shortTitle: "52W H/L",
|
|
35488
36974
|
overlay: true,
|
|
35489
|
-
inputs: [
|
|
36975
|
+
inputs: [
|
|
36976
|
+
intInput("weeks", "Weeks", 52, 1, 520, "Length of the rolling window, in weeks."),
|
|
36977
|
+
boolInput("showAllTime", "Show All-Time High/Low", false, SETTINGS, "Additionally display the all-time high and low, tracked over the symbol's full loaded history."),
|
|
36978
|
+
colorInput(BULLISH, "52 Week High", "highColor", "Color of the 52 week high level."),
|
|
36979
|
+
colorInput(BEARISH, "52 Week Low", "lowColor", "Color of the 52 week low level."),
|
|
36980
|
+
colorInput(NEUTRAL, "All-Time High/Low", "allTimeColor", "Color of the all-time high and all-time low levels.")
|
|
36981
|
+
],
|
|
35490
36982
|
compute: (bars, inputs) => {
|
|
35491
36983
|
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
35492
36984
|
const n = bars.length;
|
|
35493
36985
|
const hi = new Array(n).fill(Number.NaN);
|
|
35494
36986
|
const lo = new Array(n).fill(Number.NaN);
|
|
36987
|
+
const ath = new Array(n).fill(Number.NaN);
|
|
36988
|
+
const atl = new Array(n).fill(Number.NaN);
|
|
35495
36989
|
const maxIdx = [];
|
|
35496
36990
|
const minIdx = [];
|
|
35497
36991
|
let from = 0;
|
|
36992
|
+
let runHigh = -Infinity;
|
|
36993
|
+
let runLow = Infinity;
|
|
35498
36994
|
for (let i = 0; i < n; i++) {
|
|
35499
36995
|
const b = bars[i];
|
|
35500
36996
|
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
@@ -35506,13 +37002,20 @@ var fiftyTwoWeek = {
|
|
|
35506
37002
|
while (minIdx[0] < from) minIdx.shift();
|
|
35507
37003
|
hi[i] = bars[maxIdx[0]].high;
|
|
35508
37004
|
lo[i] = bars[minIdx[0]].low;
|
|
37005
|
+
runHigh = Math.max(runHigh, b.high);
|
|
37006
|
+
runLow = Math.min(runLow, b.low);
|
|
37007
|
+
ath[i] = runHigh;
|
|
37008
|
+
atl[i] = runLow;
|
|
37009
|
+
}
|
|
37010
|
+
const plots = [
|
|
37011
|
+
{ key: "high", title: "52 Week High", values: hi, kind: "step", color: str3(inputs, "highColor", BULLISH), width: 2 },
|
|
37012
|
+
{ key: "low", title: "52 Week Low", values: lo, kind: "step", color: str3(inputs, "lowColor", BEARISH), width: 2 }
|
|
37013
|
+
];
|
|
37014
|
+
if (bool(inputs, "showAllTime", false)) {
|
|
37015
|
+
const ink = str3(inputs, "allTimeColor", NEUTRAL);
|
|
37016
|
+
plots.push({ key: "ath", title: "All-Time High", values: ath, kind: "step", color: ink }, { key: "atl", title: "All-Time Low", values: atl, kind: "step", color: ink });
|
|
35509
37017
|
}
|
|
35510
|
-
return {
|
|
35511
|
-
plots: [
|
|
35512
|
-
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
35513
|
-
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
35514
|
-
]
|
|
35515
|
-
};
|
|
37018
|
+
return { plots };
|
|
35516
37019
|
}
|
|
35517
37020
|
};
|
|
35518
37021
|
var zigzag = {
|
|
@@ -35520,9 +37023,11 @@ var zigzag = {
|
|
|
35520
37023
|
title: "ZigZag",
|
|
35521
37024
|
overlay: true,
|
|
35522
37025
|
inputs: [
|
|
35523
|
-
|
|
35524
|
-
{
|
|
35525
|
-
colorInput()
|
|
37026
|
+
floatInput("deviation", "Deviation (%)", 5, 0.01, 100, 0.1, "Minimum reversal from the leg extreme, as a percentage of that extreme, required to confirm a new pivot."),
|
|
37027
|
+
{ ...intInput("depth", "Depth", 10, 2, 500, "Minimum number of bars required between two consecutive pivots.") },
|
|
37028
|
+
colorInput(BULLISH, "Up Leg", "bullColor", "Color of rising zigzag segments."),
|
|
37029
|
+
colorInput(BEARISH, "Down Leg", "bearColor", "Color of falling zigzag segments."),
|
|
37030
|
+
{ key: "lineWidth", title: "Width", type: "int", defval: 2, min: 1, max: 10, step: 1, group: STYLE, tooltip: "Width of the zigzag segments." }
|
|
35526
37031
|
],
|
|
35527
37032
|
compute: (bars, inputs) => {
|
|
35528
37033
|
const dev = num3(inputs, "deviation", 5) / 100;
|
|
@@ -35535,14 +37040,14 @@ var zigzag = {
|
|
|
35535
37040
|
const b = bars[i];
|
|
35536
37041
|
if (up) {
|
|
35537
37042
|
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
35538
|
-
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
37043
|
+
else if (b.low <= ext.price * (1 - dev) && ext.i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
35539
37044
|
pivots.push(ext);
|
|
35540
37045
|
up = false;
|
|
35541
37046
|
ext = { i, price: b.low };
|
|
35542
37047
|
}
|
|
35543
37048
|
} else if (b.low <= ext.price) {
|
|
35544
37049
|
ext = { i, price: b.low };
|
|
35545
|
-
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
37050
|
+
} else if (b.high >= ext.price * (1 + dev) && ext.i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
35546
37051
|
pivots.push(ext);
|
|
35547
37052
|
up = true;
|
|
35548
37053
|
ext = { i, price: b.high };
|
|
@@ -35550,22 +37055,29 @@ var zigzag = {
|
|
|
35550
37055
|
}
|
|
35551
37056
|
pivots.push(ext);
|
|
35552
37057
|
}
|
|
35553
|
-
|
|
35554
|
-
|
|
35555
|
-
|
|
35556
|
-
|
|
35557
|
-
|
|
35558
|
-
|
|
35559
|
-
|
|
35560
|
-
|
|
35561
|
-
|
|
35562
|
-
|
|
35563
|
-
|
|
35564
|
-
|
|
35565
|
-
|
|
35566
|
-
|
|
35567
|
-
|
|
35568
|
-
|
|
37058
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
37059
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
37060
|
+
const width = num3(inputs, "lineWidth", 2);
|
|
37061
|
+
const polylines = [];
|
|
37062
|
+
for (let k = 1; k < pivots.length; k++) {
|
|
37063
|
+
const a = pivots[k - 1];
|
|
37064
|
+
const b = pivots[k];
|
|
37065
|
+
polylines.push({
|
|
37066
|
+
key: `leg${k}`,
|
|
37067
|
+
points: [
|
|
37068
|
+
{ xloc: "bar_time", x: bars[a.i].time, price: a.price },
|
|
37069
|
+
{ xloc: "bar_time", x: bars[b.i].time, price: b.price }
|
|
37070
|
+
],
|
|
37071
|
+
curved: false,
|
|
37072
|
+
closed: false,
|
|
37073
|
+
lineColor: b.price >= a.price ? bull : bear,
|
|
37074
|
+
lineWidth: width,
|
|
37075
|
+
lineStyle: "solid",
|
|
37076
|
+
arrowLeft: false,
|
|
37077
|
+
arrowRight: false
|
|
37078
|
+
});
|
|
37079
|
+
}
|
|
37080
|
+
return { plots: [], polylines };
|
|
35569
37081
|
}
|
|
35570
37082
|
};
|
|
35571
37083
|
var williamsFractal = {
|
|
@@ -35573,10 +37085,16 @@ var williamsFractal = {
|
|
|
35573
37085
|
title: "Williams Fractal",
|
|
35574
37086
|
shortTitle: "Fractals",
|
|
35575
37087
|
overlay: true,
|
|
35576
|
-
inputs: [
|
|
37088
|
+
inputs: [
|
|
37089
|
+
intInput("periods", "Periods", 2, 2, 50, "Bars required on each side of the candidate bar. 2 is Bill Williams' classic 5-bar fractal; a fractal is confirmed only once this many later bars have closed."),
|
|
37090
|
+
colorInput(BEARISH, "Up Fractal", "upColor", "Color of the up fractal marker (swing high), drawn above the fractal bar."),
|
|
37091
|
+
colorInput(BULLISH, "Down Fractal", "downColor", "Color of the down fractal marker (swing low), drawn below the fractal bar.")
|
|
37092
|
+
],
|
|
35577
37093
|
compute: (bars, inputs) => {
|
|
35578
37094
|
const p = num3(inputs, "periods", 2);
|
|
35579
37095
|
const markers = [];
|
|
37096
|
+
const up = str3(inputs, "upColor", BEARISH);
|
|
37097
|
+
const down = str3(inputs, "downColor", BULLISH);
|
|
35580
37098
|
const isExtreme = (i, pick, better) => {
|
|
35581
37099
|
const v = pick(bars[i]);
|
|
35582
37100
|
for (let k = i - p; k <= i + p; k++) {
|
|
@@ -35586,12 +37104,8 @@ var williamsFractal = {
|
|
|
35586
37104
|
return true;
|
|
35587
37105
|
};
|
|
35588
37106
|
for (let i = p; i < bars.length - p; i++) {
|
|
35589
|
-
if (isExtreme(i, (b) => b.high, (a, b) => a
|
|
35590
|
-
|
|
35591
|
-
}
|
|
35592
|
-
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
35593
|
-
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
35594
|
-
}
|
|
37107
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a > b)) markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: up });
|
|
37108
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a < b)) markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: down });
|
|
35595
37109
|
}
|
|
35596
37110
|
return { plots: [], markers };
|
|
35597
37111
|
}
|