@luxalgo/vela 0.6.15 → 0.6.17
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/README.md +12 -1
- package/dist/{DataProvider-XLdJB86x.d.ts → DataProvider-B-Jz59Rf.d.ts} +1 -1
- package/dist/{DataProvider-xH2VBcFT.d.cts → DataProvider-OfR3fXg9.d.cts} +1 -1
- package/dist/{chunk-6DPAYEGO.js → chunk-CAFCLMPF.js} +1 -0
- package/dist/chunk-FE6V2YAQ.js +1 -0
- package/dist/{chunk-32A7KHFM.js → chunk-IFJJPXSV.js} +8 -2
- package/dist/{chunk-7C7NWEXH.js → chunk-OSZDSH7F.js} +83 -59
- package/dist/{chunk-MWDNPGU3.js → chunk-T5Z5YUCF.js} +23 -16
- package/dist/{chunk-MWS4CUJL.js → chunk-XCBJU674.js} +2 -2
- package/dist/{chunk-J5QIR2Z3.js → chunk-YCD72KGK.js} +2543 -159
- package/dist/{contributions-x7iamlnd.d.cts → contributions-C3b0Sx5P.d.cts} +29 -13
- package/dist/{contributions-h5BzEiV2.d.ts → contributions-Swzz10fG.d.ts} +29 -13
- package/dist/index.cjs +2746 -355
- package/dist/index.d.cts +15 -6
- package/dist/index.d.ts +15 -6
- package/dist/index.js +5 -5
- package/dist/{options-bdSAtp7y.d.ts → options-CX1lSYWA.d.cts} +12 -2
- package/dist/{options-bdSAtp7y.d.cts → options-CX1lSYWA.d.ts} +12 -2
- package/dist/{plugin-2sJcf5mk.d.cts → plugin-BjnEE1zA.d.cts} +3 -3
- package/dist/{plugin-BLaNPh55.d.ts → plugin-mvddudqd.d.ts} +3 -3
- package/dist/plugin.cjs +1 -0
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +3 -3
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/{statusline-model--tH0VWCy.d.cts → statusline-model-D91cU2fF.d.cts} +3 -3
- package/dist/{statusline-model-CLpaO3Dl.d.ts → statusline-model-IURCya0H.d.ts} +3 -3
- package/dist/ui.cjs +23 -15
- package/dist/ui.d.cts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +2600 -209
- package/dist/vela.global.min.js +52 -55
- package/dist/widget.cjs +2663 -241
- package/dist/widget.d.cts +6 -6
- package/dist/widget.d.ts +6 -6
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +2663 -241
- package/dist/workspace.d.cts +25 -14
- package/dist/workspace.d.ts +25 -14
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
- package/dist/chunk-H26BEHF4.js +0 -7
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,23 @@ function _interopNamespace(e) {
|
|
|
25
25
|
var menu__namespace = /*#__PURE__*/_interopNamespace(menu);
|
|
26
26
|
var dialog__namespace = /*#__PURE__*/_interopNamespace(dialog);
|
|
27
27
|
|
|
28
|
+
// src/core/options.ts
|
|
29
|
+
var LIVE_BAR_EASE_DEFAULT_MS = 90;
|
|
30
|
+
var LIVE_BAR_EASE_MAX_MS = 1e3;
|
|
31
|
+
function resolveLiveBarEaseMs(value) {
|
|
32
|
+
if (value === true) return LIVE_BAR_EASE_DEFAULT_MS;
|
|
33
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
|
|
34
|
+
return Math.min(value, LIVE_BAR_EASE_MAX_MS);
|
|
35
|
+
}
|
|
36
|
+
function resolveAnimations(animations) {
|
|
37
|
+
if (typeof animations === "boolean") return { animZoom: animations, animPan: animations, animLiveBar: 0 };
|
|
38
|
+
return {
|
|
39
|
+
animZoom: animations?.zoom ?? true,
|
|
40
|
+
animPan: animations?.pan ?? true,
|
|
41
|
+
animLiveBar: resolveLiveBarEaseMs(animations?.liveBar)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
28
45
|
// src/core/model/series.ts
|
|
29
46
|
function isLineLikeSeries(spec) {
|
|
30
47
|
return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
|
|
@@ -135,7 +152,7 @@ var LiveSession = class {
|
|
|
135
152
|
|
|
136
153
|
// src/core/engine/IndicatorHandleImpl.ts
|
|
137
154
|
var IndicatorHandleImpl = class {
|
|
138
|
-
constructor(id, title, controller, source) {
|
|
155
|
+
constructor(id, title, controller, source, nativeType) {
|
|
139
156
|
this.controller = controller;
|
|
140
157
|
this.schema = [];
|
|
141
158
|
this.propsSchema = [];
|
|
@@ -144,6 +161,7 @@ var IndicatorHandleImpl = class {
|
|
|
144
161
|
this.id = id;
|
|
145
162
|
this.title = title;
|
|
146
163
|
if (source !== void 0) this.source = source;
|
|
164
|
+
if (nativeType !== void 0) this.nativeType = nativeType;
|
|
147
165
|
}
|
|
148
166
|
get inputs() {
|
|
149
167
|
return this.schema;
|
|
@@ -234,24 +252,24 @@ function summarizeModel(model) {
|
|
|
234
252
|
}
|
|
235
253
|
function inspectModels(models) {
|
|
236
254
|
const indicators = models.map(summarizeModel);
|
|
237
|
-
const
|
|
255
|
+
const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
|
|
238
256
|
return {
|
|
239
257
|
indicators,
|
|
240
258
|
totals: {
|
|
241
259
|
panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
|
|
242
|
-
series:
|
|
243
|
-
fills:
|
|
244
|
-
backgrounds:
|
|
245
|
-
priceLines:
|
|
246
|
-
lines:
|
|
247
|
-
boxes:
|
|
248
|
-
labels:
|
|
249
|
-
polylines:
|
|
250
|
-
linefills:
|
|
251
|
-
tables:
|
|
252
|
-
barColors:
|
|
253
|
-
trades:
|
|
254
|
-
forcedOverlay:
|
|
260
|
+
series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
|
|
261
|
+
fills: sum2((s) => s.fills),
|
|
262
|
+
backgrounds: sum2((s) => s.backgrounds),
|
|
263
|
+
priceLines: sum2((s) => s.priceLines),
|
|
264
|
+
lines: sum2((s) => s.lines),
|
|
265
|
+
boxes: sum2((s) => s.boxes),
|
|
266
|
+
labels: sum2((s) => s.labels),
|
|
267
|
+
polylines: sum2((s) => s.polylines),
|
|
268
|
+
linefills: sum2((s) => s.linefills),
|
|
269
|
+
tables: sum2((s) => s.tables),
|
|
270
|
+
barColors: sum2((s) => s.barColors),
|
|
271
|
+
trades: sum2((s) => s.trades),
|
|
272
|
+
forcedOverlay: sum2((s) => s.forcedOverlay)
|
|
255
273
|
}
|
|
256
274
|
};
|
|
257
275
|
}
|
|
@@ -574,6 +592,7 @@ registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L
|
|
|
574
592
|
registerIcon("grip", S('<circle cx="6" cy="3.5" r="1"/><circle cx="10" cy="3.5" r="1"/><circle cx="6" cy="8" r="1"/><circle cx="10" cy="8" r="1"/><circle cx="6" cy="12.5" r="1"/><circle cx="10" cy="12.5" r="1"/>', 'fill="currentColor" stroke="none"'));
|
|
575
593
|
registerIcon("kebab", S('<circle cx="8" cy="3.2" r="1.2"/><circle cx="8" cy="8" r="1.2"/><circle cx="8" cy="12.8" r="1.2"/>', 'fill="currentColor" stroke="none"'));
|
|
576
594
|
registerIcon("burger", S('<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>'));
|
|
595
|
+
registerIcon("info", S('<path d="M8 7.4v4.4"/><circle cx="8" cy="4.6" r="0.9" fill="currentColor" stroke="none"/>'));
|
|
577
596
|
registerIcon("reset", S('<path d="M2 8a6 6 0 1 0 6-6 6.5 6.5 0 0 0-4.5 1.83L2 5.33"/><path d="M2 2v3.33h3.33"/>'));
|
|
578
597
|
registerIcon("pane-collapse", S('<path d="M2.6 9.4h4v4M13.4 6.6h-4v-4"/><path d="m9.4 6.6 4.2-4.2M2.4 13.6l4.2-4.2"/>'));
|
|
579
598
|
registerIcon("pane-expand", S('<path d="M9.8 2.4h3.8v3.8M6.2 13.6H2.4V9.8"/><path d="m13.6 2.4-4.4 4.4M2.4 13.6l4.4-4.4"/>'));
|
|
@@ -6731,8 +6750,8 @@ var DrawingController = class {
|
|
|
6731
6750
|
this.port?.setExternalGhost?.(doc);
|
|
6732
6751
|
}
|
|
6733
6752
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
6734
|
-
setToolShortcuts(
|
|
6735
|
-
this.port?.setToolShortcuts?.(
|
|
6753
|
+
setToolShortcuts(map2) {
|
|
6754
|
+
this.port?.setToolShortcuts?.(map2);
|
|
6736
6755
|
}
|
|
6737
6756
|
// ── programmatic CRUD (facade-facing) ── each is one undo step
|
|
6738
6757
|
add(type, init = {}) {
|
|
@@ -8456,16 +8475,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8456
8475
|
/**
|
|
8457
8476
|
* Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
|
|
8458
8477
|
* indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
|
|
8459
|
-
* INSTANCE per type
|
|
8460
|
-
*
|
|
8478
|
+
* INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
|
|
8479
|
+
* single-instance type returns the existing handle; a multi-instance type gets a fresh
|
|
8480
|
+
* instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
|
|
8481
|
+
* addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
|
|
8461
8482
|
*/
|
|
8462
8483
|
addNativeIndicator(type, options = {}) {
|
|
8463
|
-
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
8464
|
-
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
|
|
8465
8484
|
const descriptor = getNativeIndicator(type);
|
|
8485
|
+
if (!descriptor?.multiInstance) {
|
|
8486
|
+
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
8487
|
+
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
|
|
8488
|
+
}
|
|
8466
8489
|
const id = this.registry.nextId("native");
|
|
8467
8490
|
const title = descriptor?.title ?? type;
|
|
8468
|
-
const handle = new IndicatorHandleImpl(id, title, this);
|
|
8491
|
+
const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
|
|
8469
8492
|
this.handles.set(id, handle);
|
|
8470
8493
|
if (!descriptor) {
|
|
8471
8494
|
console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
|
|
@@ -8477,16 +8500,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8477
8500
|
void this.startNativeIndicator(id, handle);
|
|
8478
8501
|
return handle;
|
|
8479
8502
|
}
|
|
8480
|
-
/**
|
|
8481
|
-
*
|
|
8482
|
-
* entry's `supported` (applies to the current symbol) and `present` (already added — a second
|
|
8483
|
-
* add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
|
|
8484
|
-
* because a descriptor's `isSupported` may probe the provider for a required capability.
|
|
8485
|
-
*/
|
|
8486
|
-
/** The native types present on the chart — SYNC (registry state; no support probe). */
|
|
8503
|
+
/** The native types present on the chart, one entry per INSTANCE in registry order (a
|
|
8504
|
+
* multi-instance type repeats) — SYNC (registry state; no support probe). */
|
|
8487
8505
|
presentNativeIndicators() {
|
|
8488
8506
|
return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
|
|
8489
8507
|
}
|
|
8508
|
+
/**
|
|
8509
|
+
* The catalog of registered native-indicator types with their live state on THIS chart: each
|
|
8510
|
+
* entry's `supported` (applies to the current symbol) and `present` (at least one instance
|
|
8511
|
+
* added — a second add of a single-instance type is a no-op). Powers a host "add native
|
|
8512
|
+
* indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
|
|
8513
|
+
* probe the provider for a required capability.
|
|
8514
|
+
*/
|
|
8490
8515
|
async availableNativeIndicators() {
|
|
8491
8516
|
const symbol = this.qualifiedSymbol();
|
|
8492
8517
|
const present = new Set(this.presentNativeIndicators());
|
|
@@ -8496,7 +8521,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8496
8521
|
title: d.title,
|
|
8497
8522
|
supported: await this.isNativeSupported(d, symbol),
|
|
8498
8523
|
present: present.has(d.type),
|
|
8499
|
-
beta: d.beta
|
|
8524
|
+
beta: d.beta,
|
|
8525
|
+
...d.multiInstance ? { multiInstance: true } : {}
|
|
8500
8526
|
}))
|
|
8501
8527
|
);
|
|
8502
8528
|
}
|
|
@@ -9780,7 +9806,7 @@ function attachForming(closed, forming) {
|
|
|
9780
9806
|
|
|
9781
9807
|
// src/data/MultiProviderFeed.ts
|
|
9782
9808
|
function intervalMs(timeframe) {
|
|
9783
|
-
const
|
|
9809
|
+
const map2 = {
|
|
9784
9810
|
"1m": 6e4,
|
|
9785
9811
|
"3m": 18e4,
|
|
9786
9812
|
"5m": 3e5,
|
|
@@ -9802,7 +9828,7 @@ function intervalMs(timeframe) {
|
|
|
9802
9828
|
W: 6048e5,
|
|
9803
9829
|
M: 2592e6
|
|
9804
9830
|
};
|
|
9805
|
-
return
|
|
9831
|
+
return map2[timeframe] ?? 36e5;
|
|
9806
9832
|
}
|
|
9807
9833
|
var MultiProviderFeed = class {
|
|
9808
9834
|
constructor(store = sharedBarStore) {
|
|
@@ -10189,8 +10215,8 @@ var DrawingsControl = class {
|
|
|
10189
10215
|
/** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
|
|
10190
10216
|
* display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
|
|
10191
10217
|
* platform formatting, so hints always match the host's actual bindings. */
|
|
10192
|
-
setToolShortcuts(
|
|
10193
|
-
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(
|
|
10218
|
+
setToolShortcuts(map2) {
|
|
10219
|
+
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
|
|
10194
10220
|
return this;
|
|
10195
10221
|
}
|
|
10196
10222
|
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
@@ -10517,6 +10543,23 @@ function attachChromeTooltip(anchor, opts) {
|
|
|
10517
10543
|
clear();
|
|
10518
10544
|
};
|
|
10519
10545
|
}
|
|
10546
|
+
function chromeHint(text, opts) {
|
|
10547
|
+
const px = opts.size ?? 16;
|
|
10548
|
+
const el = document.createElement("span");
|
|
10549
|
+
el.className = "vela-hint";
|
|
10550
|
+
el.setAttribute("role", "img");
|
|
10551
|
+
el.setAttribute("aria-label", text);
|
|
10552
|
+
el.innerHTML = iconAt("info", px);
|
|
10553
|
+
el.style.cssText = `flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:${px}px;height:${px}px;border-radius:50%;cursor:help;user-select:none;background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;`;
|
|
10554
|
+
const hover = (on) => {
|
|
10555
|
+
el.style.background = on ? "var(--vela-active)" : "var(--vela-hover)";
|
|
10556
|
+
el.style.color = on ? "var(--vela-fg-bright)" : "var(--vela-fg-muted)";
|
|
10557
|
+
};
|
|
10558
|
+
el.addEventListener("pointerenter", () => hover(true));
|
|
10559
|
+
el.addEventListener("pointerleave", () => hover(false));
|
|
10560
|
+
const dispose = attachChromeTooltip(el, { host: opts.host, theme: opts.theme, text: () => text, wrap: true, delayMs: 250 });
|
|
10561
|
+
return { el, dispose };
|
|
10562
|
+
}
|
|
10520
10563
|
function runMachine(machine3, props, render) {
|
|
10521
10564
|
const m = new vanilla.VanillaMachine(machine3, props);
|
|
10522
10565
|
const unsub = m.subscribe(render);
|
|
@@ -10751,19 +10794,19 @@ var Surface = class _Surface {
|
|
|
10751
10794
|
}
|
|
10752
10795
|
setItems(items) {
|
|
10753
10796
|
this.items = items;
|
|
10754
|
-
for (const
|
|
10797
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
10755
10798
|
this.subs.clear();
|
|
10756
10799
|
this.build();
|
|
10757
10800
|
const api = this.api;
|
|
10758
|
-
for (const
|
|
10759
|
-
api.setChild(
|
|
10760
|
-
|
|
10801
|
+
for (const sub2 of this.subs.values()) {
|
|
10802
|
+
api.setChild(sub2.handle.service);
|
|
10803
|
+
sub2.api.setParent(this.handle.service);
|
|
10761
10804
|
}
|
|
10762
10805
|
this.handle.flush();
|
|
10763
10806
|
if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
|
|
10764
10807
|
}
|
|
10765
10808
|
destroy() {
|
|
10766
|
-
for (const
|
|
10809
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
10767
10810
|
this.subs.clear();
|
|
10768
10811
|
this.handle.stop();
|
|
10769
10812
|
this.positioner.remove();
|
|
@@ -10777,9 +10820,9 @@ var Surface = class _Surface {
|
|
|
10777
10820
|
for (const li of this.list.children) {
|
|
10778
10821
|
const id = li.dataset.veiId;
|
|
10779
10822
|
if (!id) continue;
|
|
10780
|
-
const
|
|
10781
|
-
if (
|
|
10782
|
-
vanilla.spreadProps(li, api.getTriggerItemProps(
|
|
10823
|
+
const sub2 = this.subs.get(id);
|
|
10824
|
+
if (sub2) {
|
|
10825
|
+
vanilla.spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
|
|
10783
10826
|
continue;
|
|
10784
10827
|
}
|
|
10785
10828
|
const item = this.byId(id);
|
|
@@ -10856,7 +10899,7 @@ var Surface = class _Surface {
|
|
|
10856
10899
|
const arrow = iconEl("chevron-right", doc);
|
|
10857
10900
|
arrow.classList.add("vela-menu-arrow");
|
|
10858
10901
|
li.appendChild(arrow);
|
|
10859
|
-
const
|
|
10902
|
+
const sub2 = new _Surface(doc, {
|
|
10860
10903
|
host: this.host,
|
|
10861
10904
|
placement: "right-start",
|
|
10862
10905
|
onSelect: this.onSelect,
|
|
@@ -10864,8 +10907,8 @@ var Surface = class _Surface {
|
|
|
10864
10907
|
checkmarks: this.checkmarks,
|
|
10865
10908
|
id: `${this.mid}--${item.id}`
|
|
10866
10909
|
});
|
|
10867
|
-
|
|
10868
|
-
this.subs.set(item.id,
|
|
10910
|
+
sub2.setItems(item.submenu ?? []);
|
|
10911
|
+
this.subs.set(item.id, sub2);
|
|
10869
10912
|
}
|
|
10870
10913
|
this.list.appendChild(li);
|
|
10871
10914
|
}
|
|
@@ -12631,8 +12674,21 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
12631
12674
|
};
|
|
12632
12675
|
const recentRow = document.createElement("div");
|
|
12633
12676
|
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
12677
|
+
const recentSwatches = document.createElement("div");
|
|
12678
|
+
recentSwatches.style.cssText = "display:contents;";
|
|
12679
|
+
const add = document.createElement("label");
|
|
12680
|
+
add.style.cssText = `width:17px;height:17px;border-radius:var(--vela-radius-sm);border:1px dashed var(--vela-border-strong);cursor:pointer;display:flex;align-items:center;justify-content:center;color:${theme.textColor};font:14px ${theme.fontFamily};position:relative;`;
|
|
12681
|
+
add.textContent = "+";
|
|
12682
|
+
const customInput = document.createElement("input");
|
|
12683
|
+
customInput.type = "color";
|
|
12684
|
+
customInput.value = curHex;
|
|
12685
|
+
customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
12686
|
+
customInput.addEventListener("input", () => previewHex(customInput.value));
|
|
12687
|
+
customInput.addEventListener("change", () => pickHex(customInput.value, true));
|
|
12688
|
+
add.appendChild(customInput);
|
|
12689
|
+
recentRow.append(recentSwatches, add);
|
|
12634
12690
|
const renderRecents = () => {
|
|
12635
|
-
|
|
12691
|
+
recentSwatches.replaceChildren();
|
|
12636
12692
|
for (const c of recents) {
|
|
12637
12693
|
const sw = document.createElement("button");
|
|
12638
12694
|
sw.type = "button";
|
|
@@ -12641,18 +12697,8 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
12641
12697
|
e.stopPropagation();
|
|
12642
12698
|
pickHex(c);
|
|
12643
12699
|
});
|
|
12644
|
-
|
|
12700
|
+
recentSwatches.appendChild(sw);
|
|
12645
12701
|
}
|
|
12646
|
-
const add = document.createElement("label");
|
|
12647
|
-
add.style.cssText = `width:17px;height:17px;border-radius:var(--vela-radius-sm);border:1px dashed var(--vela-border-strong);cursor:pointer;display:flex;align-items:center;justify-content:center;color:${theme.textColor};font:14px ${theme.fontFamily};position:relative;`;
|
|
12648
|
-
add.textContent = "+";
|
|
12649
|
-
const input = document.createElement("input");
|
|
12650
|
-
input.type = "color";
|
|
12651
|
-
input.value = curHex;
|
|
12652
|
-
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
12653
|
-
input.addEventListener("input", () => pickHex(input.value, true));
|
|
12654
|
-
add.appendChild(input);
|
|
12655
|
-
recentRow.appendChild(add);
|
|
12656
12702
|
};
|
|
12657
12703
|
const opLabel = document.createElement("div");
|
|
12658
12704
|
opLabel.textContent = "Opacity";
|
|
@@ -12692,14 +12738,18 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
12692
12738
|
function emit() {
|
|
12693
12739
|
onChange(combineColor(curHex, curAlpha));
|
|
12694
12740
|
}
|
|
12695
|
-
function
|
|
12741
|
+
function previewHex(hex) {
|
|
12696
12742
|
curHex = splitColor(hex).hex6;
|
|
12697
|
-
addRecent(curHex);
|
|
12698
12743
|
paintSelection();
|
|
12699
12744
|
paintOpacity();
|
|
12700
|
-
if (custom) renderRecents();
|
|
12701
12745
|
emit();
|
|
12702
12746
|
}
|
|
12747
|
+
function pickHex(hex, custom = false) {
|
|
12748
|
+
previewHex(hex);
|
|
12749
|
+
addRecent(curHex);
|
|
12750
|
+
customInput.value = curHex;
|
|
12751
|
+
if (custom) renderRecents();
|
|
12752
|
+
}
|
|
12703
12753
|
renderRecents();
|
|
12704
12754
|
paintSelection();
|
|
12705
12755
|
paintOpacity();
|
|
@@ -13651,14 +13701,11 @@ var IndicatorInputsDialog = class {
|
|
|
13651
13701
|
buildTextField(id, current, emit) {
|
|
13652
13702
|
return buildFieldControl({ kind: "text", id, value: current, onChange: emit }).el;
|
|
13653
13703
|
}
|
|
13654
|
-
/**
|
|
13704
|
+
/** The shared ⓘ hint carrying an input's `tooltip` (see {@link chromeHint}). */
|
|
13655
13705
|
infoButton(tooltip) {
|
|
13656
|
-
const
|
|
13657
|
-
|
|
13658
|
-
|
|
13659
|
-
b.className = "vela-ind-hint";
|
|
13660
|
-
b.style.cssText = "flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;font-weight:600;font-size:11px;font-family:inherit;line-height:1;cursor:help;user-select:none;";
|
|
13661
|
-
return b;
|
|
13706
|
+
const { el, dispose } = chromeHint(tooltip, { host: this.host.container, theme: () => this.host.theme(), size: 18 });
|
|
13707
|
+
this.dialogTips.push(dispose);
|
|
13708
|
+
return el;
|
|
13662
13709
|
}
|
|
13663
13710
|
/** A dropdown over plain string options (value === label) — a thin case of {@link selectPairs}. */
|
|
13664
13711
|
select(id, options, current, emit) {
|
|
@@ -14095,8 +14142,6 @@ function ensureDialogStyles() {
|
|
|
14095
14142
|
${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
14096
14143
|
.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;}
|
|
14097
14144
|
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
14098
|
-
.vela-ind-hint{background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
14099
|
-
.vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
14100
14145
|
.vela-ind-ctl,.vela-ind-close{background-color:transparent;color:inherit;transition:color var(--vela-dur-fast) ease,background-color var(--vela-dur-fast) ease;}
|
|
14101
14146
|
.vela-ind-ctl svg,.vela-ind-close svg{width:${LEGEND_ICON_PX}px;height:${LEGEND_ICON_PX}px;display:block;flex:none;stroke-width:1;}
|
|
14102
14147
|
.vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
|
|
@@ -14605,8 +14650,8 @@ var InputsUI = class {
|
|
|
14605
14650
|
lg.style.top = `${top}px`;
|
|
14606
14651
|
}
|
|
14607
14652
|
/** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
|
|
14608
|
-
setCollapsedPanes(
|
|
14609
|
-
this.paneCollapse =
|
|
14653
|
+
setCollapsedPanes(map2) {
|
|
14654
|
+
this.paneCollapse = map2;
|
|
14610
14655
|
}
|
|
14611
14656
|
/**
|
|
14612
14657
|
* Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
|
|
@@ -15569,9 +15614,9 @@ function factoryResetConfig(factory) {
|
|
|
15569
15614
|
if (inst.enableKey) defaults2[inst.enableKey] = false;
|
|
15570
15615
|
addRows(inst.rows);
|
|
15571
15616
|
}
|
|
15572
|
-
for (const
|
|
15573
|
-
if (
|
|
15574
|
-
addRows(
|
|
15617
|
+
for (const sub2 of section.subsections ?? []) {
|
|
15618
|
+
if (sub2.enableKey) defaults2[sub2.enableKey] = false;
|
|
15619
|
+
addRows(sub2.rows);
|
|
15575
15620
|
}
|
|
15576
15621
|
if (!section.instances) addRows(section.rows);
|
|
15577
15622
|
bag[t.id] = defaults2;
|
|
@@ -15640,7 +15685,8 @@ function mergeConfig(base, patch) {
|
|
|
15640
15685
|
labelsVisible: isBool(ps.labelsVisible) ? ps.labelsVisible : base.priceScale.labelsVisible,
|
|
15641
15686
|
currentPriceLine: isBool(ps.currentPriceLine) ? ps.currentPriceLine : base.priceScale.currentPriceLine,
|
|
15642
15687
|
priceLabel: isBool(ps.priceLabel) ? ps.priceLabel : base.priceScale.priceLabel,
|
|
15643
|
-
countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown
|
|
15688
|
+
countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown,
|
|
15689
|
+
animateLastPrice: isBool(ps.animateLastPrice) ? ps.animateLastPrice : base.priceScale.animateLastPrice
|
|
15644
15690
|
},
|
|
15645
15691
|
panes: {
|
|
15646
15692
|
separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
|
|
@@ -16900,11 +16946,11 @@ function mergeBarColors(indicators) {
|
|
|
16900
16946
|
}
|
|
16901
16947
|
}
|
|
16902
16948
|
if (!any) return EMPTY_BARCOLORS;
|
|
16903
|
-
const
|
|
16949
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
16904
16950
|
for (const m of indicators.values()) {
|
|
16905
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
16951
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
16906
16952
|
}
|
|
16907
|
-
return
|
|
16953
|
+
return map2;
|
|
16908
16954
|
}
|
|
16909
16955
|
|
|
16910
16956
|
// src/renderers/native/core/ViewportState.ts
|
|
@@ -17201,9 +17247,9 @@ function isHorizontalWheel(deltaX, deltaY) {
|
|
|
17201
17247
|
function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
|
|
17202
17248
|
return rightOffset + deltaX / barSpacing;
|
|
17203
17249
|
}
|
|
17204
|
-
function wheelPanDelta(deltaX, deltaY,
|
|
17250
|
+
function wheelPanDelta(deltaX, deltaY, shift2) {
|
|
17205
17251
|
if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
|
|
17206
|
-
if (
|
|
17252
|
+
if (shift2) return deltaY;
|
|
17207
17253
|
return null;
|
|
17208
17254
|
}
|
|
17209
17255
|
function effectiveSnapMode(momentaryOverride, sticky) {
|
|
@@ -18825,11 +18871,11 @@ function mergeBarColors2(indicators) {
|
|
|
18825
18871
|
}
|
|
18826
18872
|
}
|
|
18827
18873
|
if (!any) return EMPTY_BARCOLORS2;
|
|
18828
|
-
const
|
|
18874
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
18829
18875
|
for (const m of indicators.values()) {
|
|
18830
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
18876
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
18831
18877
|
}
|
|
18832
|
-
return
|
|
18878
|
+
return map2;
|
|
18833
18879
|
}
|
|
18834
18880
|
|
|
18835
18881
|
// src/renderers/shared/drawing-geometry.ts
|
|
@@ -20504,6 +20550,8 @@ var BUILTIN_SETTINGS_IDS = [
|
|
|
20504
20550
|
"symbol.style.baseline.fill-bottom",
|
|
20505
20551
|
"symbol.style.baseline.base-level",
|
|
20506
20552
|
"symbol.style.baseline.width",
|
|
20553
|
+
"symbol.animation",
|
|
20554
|
+
"symbol.animation.price-changes",
|
|
20507
20555
|
"symbol.timezone",
|
|
20508
20556
|
"scales",
|
|
20509
20557
|
"scales.price-scale",
|
|
@@ -20545,9 +20593,9 @@ function settingsIdCatalog(hostSections) {
|
|
|
20545
20593
|
};
|
|
20546
20594
|
if (section.rows) addRows(section.rows);
|
|
20547
20595
|
for (const inst of section.instances ?? []) addRows(inst.rows);
|
|
20548
|
-
for (const
|
|
20549
|
-
ids.add(`${scope}.${settingsIdSlug(
|
|
20550
|
-
addRows(
|
|
20596
|
+
for (const sub2 of section.subsections ?? []) {
|
|
20597
|
+
ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
|
|
20598
|
+
addRows(sub2.rows);
|
|
20551
20599
|
}
|
|
20552
20600
|
}
|
|
20553
20601
|
for (const hs of hostSections) {
|
|
@@ -20646,8 +20694,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
20646
20694
|
.vela-sd-mobile .vela-select-trigger,.vela-sd-mobile .vela-num input,.vela-sd-mobile .vela-width-field{height:34px;}
|
|
20647
20695
|
.vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
|
|
20648
20696
|
.vela-sd-mobile .vela-sd-btn{height:38px;}
|
|
20649
|
-
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}
|
|
20650
|
-
`;
|
|
20697
|
+
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}`;
|
|
20651
20698
|
if (!existing) document.head.appendChild(st);
|
|
20652
20699
|
}
|
|
20653
20700
|
var SettingsDialog = class {
|
|
@@ -20668,6 +20715,8 @@ var SettingsDialog = class {
|
|
|
20668
20715
|
this.themeControl = null;
|
|
20669
20716
|
/** The built tabs, by title — how `showSection` reaches a pane while the dialog is open. */
|
|
20670
20717
|
this.tabs = [];
|
|
20718
|
+
this.hintTips = [];
|
|
20719
|
+
// ⓘ tooltip disposers of the open build (see hint)
|
|
20671
20720
|
/** Active instance-strip tab per chart type — remembered across dialog rebuilds. */
|
|
20672
20721
|
this.typeActiveInstance = /* @__PURE__ */ new Map();
|
|
20673
20722
|
/** Active TOC group per structured pane (`<typeId>/<pane>` → group label). */
|
|
@@ -20856,6 +20905,13 @@ var SettingsDialog = class {
|
|
|
20856
20905
|
body.append(g);
|
|
20857
20906
|
}
|
|
20858
20907
|
showActive(config.series.style);
|
|
20908
|
+
body.append(sid(this.sectionTitle("Animation"), "symbol.animation"));
|
|
20909
|
+
body.append(sid(this.boolRow(
|
|
20910
|
+
"Animate price changes",
|
|
20911
|
+
config.priceScale.animateLastPrice,
|
|
20912
|
+
(v) => this.emit({ priceScale: { animateLastPrice: v } }),
|
|
20913
|
+
this.hint("Glide the live bar to each new price instead of snapping.")
|
|
20914
|
+
), "symbol.animation.price-changes"));
|
|
20859
20915
|
body.append(sid(this.sectionTitle("Time zone"), "symbol.timezone"));
|
|
20860
20916
|
body.append(sid(this.selectRowLabeled("Time zone", normalizeTimezone(config.timeScale.timezone), timezoneOptions(config.timeScale.timezone), (v) => this.emit({ timeScale: { timezone: v } })), "symbol.timezone"));
|
|
20861
20917
|
const renderHostSections = (placement) => {
|
|
@@ -21063,6 +21119,8 @@ var SettingsDialog = class {
|
|
|
21063
21119
|
this.ui = null;
|
|
21064
21120
|
this.root = null;
|
|
21065
21121
|
this.tabs = [];
|
|
21122
|
+
for (const dispose of this.hintTips) dispose();
|
|
21123
|
+
this.hintTips = [];
|
|
21066
21124
|
ui?.destroy();
|
|
21067
21125
|
}
|
|
21068
21126
|
destroy() {
|
|
@@ -21106,14 +21164,14 @@ var SettingsDialog = class {
|
|
|
21106
21164
|
};
|
|
21107
21165
|
if (section.rows) seed(section.rows);
|
|
21108
21166
|
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
21109
|
-
for (const
|
|
21167
|
+
for (const sub2 of section.subsections ?? []) seed(sub2.rows);
|
|
21110
21168
|
for (const inst of section.instances ?? []) {
|
|
21111
21169
|
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
21112
21170
|
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
21113
21171
|
}
|
|
21114
|
-
for (const
|
|
21115
|
-
if (!
|
|
21116
|
-
bag[
|
|
21172
|
+
for (const sub2 of section.subsections ?? []) {
|
|
21173
|
+
if (!sub2.enableKey || sub2.enableKey in bag) continue;
|
|
21174
|
+
bag[sub2.enableKey] = values[sub2.enableKey] === true;
|
|
21117
21175
|
}
|
|
21118
21176
|
const refreshers = [];
|
|
21119
21177
|
const put = (key, v) => {
|
|
@@ -21130,16 +21188,16 @@ var SettingsDialog = class {
|
|
|
21130
21188
|
if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
|
|
21131
21189
|
else this.flatTypeRows(rows, bag, put, refreshers, body);
|
|
21132
21190
|
}
|
|
21133
|
-
for (const
|
|
21134
|
-
if (settingsIdHidden(`${scope}.${settingsIdSlug(
|
|
21135
|
-
const rows = filterHiddenRows(
|
|
21191
|
+
for (const sub2 of section.subsections ?? []) {
|
|
21192
|
+
if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
|
|
21193
|
+
const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
|
|
21136
21194
|
if (rows.length === 0) continue;
|
|
21137
|
-
const subMarker = this.section(
|
|
21195
|
+
const subMarker = this.section(sub2.title);
|
|
21138
21196
|
subMarker.dataset.sdStyle = typeId;
|
|
21139
21197
|
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
21140
21198
|
subMarker.dataset.sdSub = "1";
|
|
21141
21199
|
body.append(subMarker);
|
|
21142
|
-
body.append(this.groupedRows(`${typeId}/${
|
|
21200
|
+
body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
|
|
21143
21201
|
}
|
|
21144
21202
|
for (const r of refreshers) r();
|
|
21145
21203
|
}
|
|
@@ -21476,23 +21534,31 @@ var SettingsDialog = class {
|
|
|
21476
21534
|
}).el
|
|
21477
21535
|
});
|
|
21478
21536
|
}
|
|
21479
|
-
/** A toggle row: the checkbox sits to the LEFT of its label (never in the control area).
|
|
21480
|
-
|
|
21481
|
-
|
|
21537
|
+
/** A toggle row: the checkbox sits to the LEFT of its label (never in the control area).
|
|
21538
|
+
* `info` (see {@link hint}) rides after the label. */
|
|
21539
|
+
boolRow(label, value, onChange, info) {
|
|
21540
|
+
return this.toggleRow(label, value, onChange, [], void 0, info);
|
|
21482
21541
|
}
|
|
21483
21542
|
/** An enable row: checkbox + label in the label column, dependent controls in the
|
|
21484
21543
|
* shared control column; the control group dims and ignores input while the toggle
|
|
21485
21544
|
* is off. With `get`, the row re-reads its state on a 'vela-sync' event. */
|
|
21486
|
-
toggleRow(label, value, onToggle, controls, get) {
|
|
21545
|
+
toggleRow(label, value, onToggle, controls, get, info) {
|
|
21487
21546
|
return fieldRow({
|
|
21488
21547
|
label,
|
|
21489
21548
|
labelSize: "sm",
|
|
21490
21549
|
bool: controls.length === 0,
|
|
21491
21550
|
toggle: { checked: value, onChange: onToggle, get },
|
|
21492
21551
|
control: controls,
|
|
21552
|
+
info,
|
|
21493
21553
|
className: controls.length === 0 ? "vela-sd-bool" : "vela-sd-row"
|
|
21494
21554
|
});
|
|
21495
21555
|
}
|
|
21556
|
+
/** The shared ⓘ hint after a row label (see {@link chromeHint}). Disposed with the dialog. */
|
|
21557
|
+
hint(text) {
|
|
21558
|
+
const { el, dispose } = chromeHint(text, { host: this.container, theme: () => this.theme });
|
|
21559
|
+
this.hintTips.push(dispose);
|
|
21560
|
+
return el;
|
|
21561
|
+
}
|
|
21496
21562
|
numberRow(label, value, min, max, step, onChange) {
|
|
21497
21563
|
return fieldRow({
|
|
21498
21564
|
label,
|
|
@@ -23247,11 +23313,11 @@ var DrawingInteraction = class {
|
|
|
23247
23313
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
23248
23314
|
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
23249
23315
|
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
23250
|
-
down(x, y, mode = "off",
|
|
23316
|
+
down(x, y, mode = "off", shift2 = false) {
|
|
23251
23317
|
const proj = this.deps.projector();
|
|
23252
23318
|
if (this.state.kind === "placing") {
|
|
23253
23319
|
if (this.state.draft.placementMode() !== "click") return;
|
|
23254
|
-
const pivot =
|
|
23320
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
23255
23321
|
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
23256
23322
|
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
23257
23323
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
@@ -23270,7 +23336,7 @@ var DrawingInteraction = class {
|
|
|
23270
23336
|
}
|
|
23271
23337
|
const hit = this.hitAt(x, y);
|
|
23272
23338
|
if (hit) {
|
|
23273
|
-
if (
|
|
23339
|
+
if (shift2) {
|
|
23274
23340
|
this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
|
|
23275
23341
|
return;
|
|
23276
23342
|
}
|
|
@@ -23280,7 +23346,7 @@ var DrawingInteraction = class {
|
|
|
23280
23346
|
}
|
|
23281
23347
|
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
23282
23348
|
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
23283
|
-
move(x, y, mode = "off",
|
|
23349
|
+
move(x, y, mode = "off", shift2 = false) {
|
|
23284
23350
|
if (this.state.kind === "idle") {
|
|
23285
23351
|
const tool = this.deps.activeTool();
|
|
23286
23352
|
const had = this.snapAt != null;
|
|
@@ -23294,7 +23360,7 @@ var DrawingInteraction = class {
|
|
|
23294
23360
|
this.captureFreehand(x, y);
|
|
23295
23361
|
return;
|
|
23296
23362
|
}
|
|
23297
|
-
const pivot =
|
|
23363
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
23298
23364
|
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
23299
23365
|
this.deps.changed();
|
|
23300
23366
|
return;
|
|
@@ -23307,7 +23373,7 @@ var DrawingInteraction = class {
|
|
|
23307
23373
|
if (this.state.handle >= 0) {
|
|
23308
23374
|
const a = d.anchors[this.state.handle];
|
|
23309
23375
|
if (a) {
|
|
23310
|
-
const pivot =
|
|
23376
|
+
const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
23311
23377
|
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
23312
23378
|
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
23313
23379
|
}
|
|
@@ -24005,21 +24071,21 @@ var DrawingSettingsPopup = class {
|
|
|
24005
24071
|
bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
|
|
24006
24072
|
bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
|
|
24007
24073
|
}
|
|
24008
|
-
const
|
|
24009
|
-
if (paths.has("vwap.midColor") &&
|
|
24074
|
+
const vwap2 = drawing.vwap;
|
|
24075
|
+
if (paths.has("vwap.midColor") && vwap2) {
|
|
24010
24076
|
const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
|
|
24011
24077
|
const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
|
|
24012
|
-
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON,
|
|
24013
|
-
bar.appendChild(this.dropdown("VWAP style", styles,
|
|
24078
|
+
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
|
|
24079
|
+
bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
|
|
24014
24080
|
bar.appendChild(
|
|
24015
|
-
this.dropdown("Band multiplier", MULTS,
|
|
24081
|
+
this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
|
|
24016
24082
|
label: (v) => `${v}\u03C3`,
|
|
24017
24083
|
labelInTrigger: true
|
|
24018
24084
|
})
|
|
24019
24085
|
);
|
|
24020
|
-
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON,
|
|
24021
|
-
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON,
|
|
24022
|
-
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON,
|
|
24086
|
+
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
|
|
24087
|
+
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
|
|
24088
|
+
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
|
|
24023
24089
|
}
|
|
24024
24090
|
if (paths.has("maxCurvature")) {
|
|
24025
24091
|
const cur = drawing.maxCurvature ?? 24;
|
|
@@ -24765,8 +24831,8 @@ var DrawingToolbar = class {
|
|
|
24765
24831
|
}
|
|
24766
24832
|
/** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
|
|
24767
24833
|
* right edge of the flyout rows, beside the favorite star. */
|
|
24768
|
-
setShortcuts(
|
|
24769
|
-
this.shortcuts = new Map(Object.entries(
|
|
24834
|
+
setShortcuts(map2) {
|
|
24835
|
+
this.shortcuts = new Map(Object.entries(map2));
|
|
24770
24836
|
}
|
|
24771
24837
|
/** Paint one star for the current favorite state (filled + gold, or outline). */
|
|
24772
24838
|
paintStar(el, type) {
|
|
@@ -25668,8 +25734,8 @@ var UserDrawingController = class {
|
|
|
25668
25734
|
this.toolbar.setFavorites(types);
|
|
25669
25735
|
}
|
|
25670
25736
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
25671
|
-
setToolShortcuts(
|
|
25672
|
-
this.toolbar.setShortcuts(
|
|
25737
|
+
setToolShortcuts(map2) {
|
|
25738
|
+
this.toolbar.setShortcuts(map2);
|
|
25673
25739
|
}
|
|
25674
25740
|
/** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
|
|
25675
25741
|
* in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
|
|
@@ -25787,7 +25853,7 @@ var UserDrawingController = class {
|
|
|
25787
25853
|
this.render();
|
|
25788
25854
|
}
|
|
25789
25855
|
}
|
|
25790
|
-
pointerDown(x, y, snap = "off",
|
|
25856
|
+
pointerDown(x, y, snap = "off", shift2 = false) {
|
|
25791
25857
|
if (this.eraserMode) {
|
|
25792
25858
|
this.erasing = true;
|
|
25793
25859
|
this.deleteAt(x, y);
|
|
@@ -25807,9 +25873,9 @@ var UserDrawingController = class {
|
|
|
25807
25873
|
return;
|
|
25808
25874
|
}
|
|
25809
25875
|
}
|
|
25810
|
-
this.interaction.down(x, y, snap,
|
|
25876
|
+
this.interaction.down(x, y, snap, shift2);
|
|
25811
25877
|
}
|
|
25812
|
-
pointerMove(x, y, snap = "off",
|
|
25878
|
+
pointerMove(x, y, snap = "off", shift2 = false) {
|
|
25813
25879
|
if (this.eraserMode) {
|
|
25814
25880
|
if (this.erasing) this.deleteAt(x, y);
|
|
25815
25881
|
return;
|
|
@@ -25820,7 +25886,7 @@ var UserDrawingController = class {
|
|
|
25820
25886
|
this.render();
|
|
25821
25887
|
return;
|
|
25822
25888
|
}
|
|
25823
|
-
this.interaction.move(x, y, snap,
|
|
25889
|
+
this.interaction.move(x, y, snap, shift2);
|
|
25824
25890
|
this.updateHover(x, y);
|
|
25825
25891
|
}
|
|
25826
25892
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -26497,14 +26563,14 @@ function layoutTable(ctx, t, args) {
|
|
|
26497
26563
|
}
|
|
26498
26564
|
for (const { box, w: w2, h: h2 } of spanning) {
|
|
26499
26565
|
if (box.cs > 1) {
|
|
26500
|
-
let
|
|
26501
|
-
for (let c = box.c; c < box.c + box.cs; c += 1)
|
|
26502
|
-
if (w2 >
|
|
26566
|
+
let sum2 = 0;
|
|
26567
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
|
|
26568
|
+
if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
|
|
26503
26569
|
}
|
|
26504
26570
|
if (box.rs > 1) {
|
|
26505
|
-
let
|
|
26506
|
-
for (let r = box.r; r < box.r + box.rs; r += 1)
|
|
26507
|
-
if (h2 >
|
|
26571
|
+
let sum2 = 0;
|
|
26572
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
|
|
26573
|
+
if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
|
|
26508
26574
|
}
|
|
26509
26575
|
}
|
|
26510
26576
|
let w = 0;
|
|
@@ -27227,24 +27293,24 @@ function rasterizeOverlay(ctx, root, frame) {
|
|
|
27227
27293
|
drawSvgShapes(el, alpha);
|
|
27228
27294
|
continue;
|
|
27229
27295
|
}
|
|
27230
|
-
const
|
|
27296
|
+
const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
|
|
27231
27297
|
let path = null;
|
|
27232
27298
|
if (tag === "path") {
|
|
27233
27299
|
const d = el.getAttribute("d");
|
|
27234
27300
|
if (d) path = new Path2D(d);
|
|
27235
27301
|
} else if (tag === "circle") {
|
|
27236
27302
|
path = new Path2D();
|
|
27237
|
-
path.arc(
|
|
27303
|
+
path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
|
|
27238
27304
|
} else if (tag === "ellipse") {
|
|
27239
27305
|
path = new Path2D();
|
|
27240
|
-
path.ellipse(
|
|
27306
|
+
path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
|
|
27241
27307
|
} else if (tag === "rect") {
|
|
27242
27308
|
path = new Path2D();
|
|
27243
|
-
path.rect(
|
|
27309
|
+
path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
|
|
27244
27310
|
} else if (tag === "line") {
|
|
27245
27311
|
path = new Path2D();
|
|
27246
|
-
path.moveTo(
|
|
27247
|
-
path.lineTo(
|
|
27312
|
+
path.moveTo(num4("x1"), num4("y1"));
|
|
27313
|
+
path.lineTo(num4("x2"), num4("y2"));
|
|
27248
27314
|
} else if (tag === "polyline" || tag === "polygon") {
|
|
27249
27315
|
const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
|
|
27250
27316
|
if (pts.length < 4 || pts.some(Number.isNaN)) continue;
|
|
@@ -27505,7 +27571,6 @@ var ZOOM_OUT_MARGIN_BARS = 6;
|
|
|
27505
27571
|
var ZOOM_TAU_MS = 70;
|
|
27506
27572
|
var SCALE_TAU_MS = 80;
|
|
27507
27573
|
var FLING_TAU_MS = 110;
|
|
27508
|
-
var LIVE_BAR_TAU_MS = 90;
|
|
27509
27574
|
var SCROLL_TO_TAU_MS = 130;
|
|
27510
27575
|
var FLING_STOP_PX = 0.02;
|
|
27511
27576
|
var PRICE_SCALE_K = 4e-3;
|
|
@@ -27576,9 +27641,13 @@ var NativeRenderer = class {
|
|
|
27576
27641
|
/** Drawings layer self-serves Ctrl+Z/Y (see the `historyChords` feature). */
|
|
27577
27642
|
this.historyChordsEnabled = true;
|
|
27578
27643
|
this.liveRegion = null;
|
|
27579
|
-
// ── animation state (eased zoom + inertial pan) ──
|
|
27644
|
+
// ── animation state (eased zoom + inertial pan + live-bar glide) ──
|
|
27580
27645
|
this.animZoom = true;
|
|
27581
27646
|
this.animPan = true;
|
|
27647
|
+
this.animLiveBarMs = 0;
|
|
27648
|
+
// forming-bar OHLC glide time-constant; 0 = each tick snaps
|
|
27649
|
+
this.animLiveBarOnMs = LIVE_BAR_EASE_DEFAULT_MS;
|
|
27650
|
+
// the duration the settings-dialog on/off toggle restores (last non-zero value configured)
|
|
27582
27651
|
// Brand default candles.
|
|
27583
27652
|
this.candleUp = BULLISH;
|
|
27584
27653
|
this.candleDown = BEARISH;
|
|
@@ -27703,7 +27772,7 @@ var NativeRenderer = class {
|
|
|
27703
27772
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
27704
27773
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
27705
27774
|
this.name = "native";
|
|
27706
|
-
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
27775
|
+
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
27707
27776
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
27708
27777
|
* so moving onto the button doesn't count as leaving). */
|
|
27709
27778
|
this.onScrollProximityMove = (e) => {
|
|
@@ -27737,6 +27806,7 @@ var NativeRenderer = class {
|
|
|
27737
27806
|
this.backendMode = opts.nativeBackend;
|
|
27738
27807
|
this.animZoom = opts.animZoom;
|
|
27739
27808
|
this.animPan = opts.animPan;
|
|
27809
|
+
this.setLiveBarEase(resolveLiveBarEaseMs(opts.animLiveBar));
|
|
27740
27810
|
this.glowAmount = opts.glow;
|
|
27741
27811
|
this.candleUp = opts.upColor;
|
|
27742
27812
|
this.candleDown = opts.downColor;
|
|
@@ -27786,6 +27856,10 @@ var NativeRenderer = class {
|
|
|
27786
27856
|
case "animPan":
|
|
27787
27857
|
this.animPan = Boolean(value);
|
|
27788
27858
|
return;
|
|
27859
|
+
case "animLiveBar":
|
|
27860
|
+
this.setLiveBarEase(resolveLiveBarEaseMs(value));
|
|
27861
|
+
return;
|
|
27862
|
+
// affects the next tick only; a glide in flight finishes at the new rate (or snaps at 0)
|
|
27789
27863
|
case "intro": {
|
|
27790
27864
|
const s = value === false || value === "none" || value === "off" || value == null ? "" : String(value);
|
|
27791
27865
|
this.introStyle = s;
|
|
@@ -27922,6 +27996,8 @@ var NativeRenderer = class {
|
|
|
27922
27996
|
return this.animZoom;
|
|
27923
27997
|
case "animPan":
|
|
27924
27998
|
return this.animPan;
|
|
27999
|
+
case "animLiveBar":
|
|
28000
|
+
return this.animLiveBarMs;
|
|
27925
28001
|
case "intro":
|
|
27926
28002
|
return this.introStyle;
|
|
27927
28003
|
case "zoomAnchor":
|
|
@@ -28090,7 +28166,8 @@ var NativeRenderer = class {
|
|
|
28090
28166
|
labelsVisible: this.scene.showAxisLabels,
|
|
28091
28167
|
currentPriceLine: this.scene.showPriceLine,
|
|
28092
28168
|
priceLabel: this.scene.showPriceLabel,
|
|
28093
|
-
countdown: this.scene.showCountdown
|
|
28169
|
+
countdown: this.scene.showCountdown,
|
|
28170
|
+
animateLastPrice: this.animLiveBarMs > 0
|
|
28094
28171
|
},
|
|
28095
28172
|
panes: { separatorColor: s.separatorColor ?? t.borderColor },
|
|
28096
28173
|
trades: {
|
|
@@ -28197,6 +28274,7 @@ var NativeRenderer = class {
|
|
|
28197
28274
|
this.scene.showPriceLabel = next.priceScale.priceLabel;
|
|
28198
28275
|
this.scene.showCountdown = next.priceScale.countdown;
|
|
28199
28276
|
this.syncCountdownTimer();
|
|
28277
|
+
this.animLiveBarMs = next.priceScale.animateLastPrice ? this.animLiveBarOnMs : 0;
|
|
28200
28278
|
s.separatorColor = keepInherit(s.separatorColor, next.panes.separatorColor, prevTheme.borderColor);
|
|
28201
28279
|
this.scene.tradeMarkers = {
|
|
28202
28280
|
visible: next.trades.visible,
|
|
@@ -28661,8 +28739,8 @@ var NativeRenderer = class {
|
|
|
28661
28739
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
28662
28740
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
28663
28741
|
drawingsSnapMode: () => this.snapMode,
|
|
28664
|
-
drawingsPointerDown: (x, y, snap,
|
|
28665
|
-
drawingsPointerMove: (x, y, snap,
|
|
28742
|
+
drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
|
|
28743
|
+
drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
|
|
28666
28744
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
28667
28745
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
28668
28746
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -28979,18 +29057,18 @@ var NativeRenderer = class {
|
|
|
28979
29057
|
for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
|
|
28980
29058
|
if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
|
|
28981
29059
|
const newHeadTime = this.bars[0].time;
|
|
28982
|
-
let
|
|
29060
|
+
let shift2 = 0;
|
|
28983
29061
|
if (newHeadTime !== prevHeadTime) {
|
|
28984
29062
|
const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
|
|
28985
|
-
if (prepended > 0)
|
|
29063
|
+
if (prepended > 0) shift2 = prepended;
|
|
28986
29064
|
else {
|
|
28987
29065
|
const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
|
|
28988
|
-
if (trimmed > 0)
|
|
29066
|
+
if (trimmed > 0) shift2 = -trimmed;
|
|
28989
29067
|
}
|
|
28990
29068
|
}
|
|
28991
|
-
if (
|
|
28992
|
-
this.zoomAnchorLogical +=
|
|
28993
|
-
if (this.hoverLogical != null) this.hoverLogical +=
|
|
29069
|
+
if (shift2 !== 0) {
|
|
29070
|
+
this.zoomAnchorLogical += shift2;
|
|
29071
|
+
if (this.hoverLogical != null) this.hoverLogical += shift2;
|
|
28994
29072
|
}
|
|
28995
29073
|
}
|
|
28996
29074
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
@@ -29013,8 +29091,11 @@ var NativeRenderer = class {
|
|
|
29013
29091
|
const last = this.bars[n - 1];
|
|
29014
29092
|
if (last && bar.time === last.time) {
|
|
29015
29093
|
this.bars[n - 1] = bar;
|
|
29016
|
-
if (this.liveEaseTime !== bar.time)
|
|
29017
|
-
|
|
29094
|
+
if (this.animLiveBarMs <= 0 || this.liveEaseTime !== bar.time) {
|
|
29095
|
+
this.syncLiveEase(bar);
|
|
29096
|
+
} else {
|
|
29097
|
+
this.animator.start();
|
|
29098
|
+
}
|
|
29018
29099
|
} else if (!last || bar.time > last.time) {
|
|
29019
29100
|
this.bars.push(bar);
|
|
29020
29101
|
this.syncLiveEase(bar);
|
|
@@ -29026,6 +29107,12 @@ var NativeRenderer = class {
|
|
|
29026
29107
|
this.scene.bars = this.bars;
|
|
29027
29108
|
this.scheduler.invalidate(4 /* Full */);
|
|
29028
29109
|
}
|
|
29110
|
+
/** Set the live-bar glide duration (0 = off). A non-zero value is also remembered as
|
|
29111
|
+
* what the config's on/off toggle (`priceScale.animateLastPrice`) switches back on to. */
|
|
29112
|
+
setLiveBarEase(ms) {
|
|
29113
|
+
this.animLiveBarMs = ms;
|
|
29114
|
+
if (ms > 0) this.animLiveBarOnMs = ms;
|
|
29115
|
+
}
|
|
29029
29116
|
/** Snap the eased forming-bar state to `bar` — no glide (a fresh bar or the first tick of one). */
|
|
29030
29117
|
syncLiveEase(bar) {
|
|
29031
29118
|
this.liveEaseTime = bar.time;
|
|
@@ -29038,9 +29125,10 @@ var NativeRenderer = class {
|
|
|
29038
29125
|
const target = this.bars[this.bars.length - 1];
|
|
29039
29126
|
if (!target || this.liveEaseTime !== target.time) return false;
|
|
29040
29127
|
const eps = Math.max(1e-9, Math.abs(target.close) * 1e-6);
|
|
29041
|
-
const
|
|
29042
|
-
const
|
|
29043
|
-
const
|
|
29128
|
+
const tau = this.animLiveBarMs;
|
|
29129
|
+
const nh = easeToward(this.liveEaseHigh, target.high, dtMs, tau);
|
|
29130
|
+
const nl = easeToward(this.liveEaseLow, target.low, dtMs, tau);
|
|
29131
|
+
const nc = easeToward(this.liveEaseClose, target.close, dtMs, tau);
|
|
29044
29132
|
const active = Math.abs(nh - target.high) > eps || Math.abs(nl - target.low) > eps || Math.abs(nc - target.close) > eps;
|
|
29045
29133
|
this.liveEaseHigh = active ? nh : target.high;
|
|
29046
29134
|
this.liveEaseLow = active ? nl : target.low;
|
|
@@ -30596,15 +30684,15 @@ var NativeRenderer = class {
|
|
|
30596
30684
|
/** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
|
|
30597
30685
|
* strip — merged own-scale indicators are hidden while collapsed. */
|
|
30598
30686
|
collapsedMasterMap() {
|
|
30599
|
-
const
|
|
30687
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
30600
30688
|
for (const pane of this.scene.panes.values()) {
|
|
30601
30689
|
if (!pane.collapsed) continue;
|
|
30602
30690
|
const models = this.scene.orderedIndicatorsForPane(pane.id);
|
|
30603
30691
|
const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
|
|
30604
30692
|
const master = models.find((m) => !merged.has(m.id)) ?? models[0];
|
|
30605
|
-
|
|
30693
|
+
map2.set(pane.id, master?.id ?? null);
|
|
30606
30694
|
}
|
|
30607
|
-
return
|
|
30695
|
+
return map2;
|
|
30608
30696
|
}
|
|
30609
30697
|
/** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
|
|
30610
30698
|
setToolbarGutter(px) {
|
|
@@ -31000,213 +31088,2522 @@ function registerVpvr() {
|
|
|
31000
31088
|
registerNativeIndicator(vpvrDescriptor);
|
|
31001
31089
|
}
|
|
31002
31090
|
|
|
31003
|
-
// src/
|
|
31004
|
-
|
|
31005
|
-
|
|
31006
|
-
|
|
31007
|
-
|
|
31008
|
-
|
|
31009
|
-
|
|
31010
|
-
|
|
31011
|
-
|
|
31012
|
-
|
|
31013
|
-
|
|
31014
|
-
|
|
31015
|
-
|
|
31016
|
-
|
|
31017
|
-
|
|
31018
|
-
|
|
31019
|
-
|
|
31020
|
-
|
|
31021
|
-
|
|
31022
|
-
|
|
31023
|
-
|
|
31024
|
-
|
|
31025
|
-
|
|
31026
|
-
glow: options.glow ?? 0,
|
|
31027
|
-
upColor: options.upColor ?? BULLISH,
|
|
31028
|
-
downColor: options.downColor ?? BEARISH,
|
|
31029
|
-
priceStyle: options.priceStyle ?? "candles"
|
|
31030
|
-
};
|
|
31031
|
-
const RendererClass = options.renderer ?? NativeRenderer;
|
|
31032
|
-
if (typeof RendererClass !== "function") {
|
|
31033
|
-
throw new Error(
|
|
31034
|
-
"[vela] options.renderer must be a renderer class, e.g. import { LwcRenderer } from 'vela/renderers/lwc'. The 'lwc'/'native' string options were removed."
|
|
31035
|
-
);
|
|
31036
|
-
}
|
|
31037
|
-
const renderer = deps.renderer ?? new RendererClass(display);
|
|
31038
|
-
const engines = deps.engines ?? [];
|
|
31039
|
-
const feed = deps.dataFeed ?? new MultiProviderFeed();
|
|
31040
|
-
const config = {
|
|
31041
|
-
market: {
|
|
31042
|
-
symbol: options.symbol,
|
|
31043
|
-
timeframe: options.timeframe,
|
|
31044
|
-
bars: options.bars,
|
|
31045
|
-
session: options.session,
|
|
31046
|
-
visibleRange: options.visibleRange,
|
|
31047
|
-
data: options.data
|
|
31048
|
-
},
|
|
31049
|
-
live: options.live ?? false,
|
|
31050
|
-
theme,
|
|
31051
|
-
defaultLanguage: options.defaultLanguage ?? engines[0]?.language ?? "pine",
|
|
31052
|
-
drawings: options.drawings,
|
|
31053
|
-
volume: options.volume ?? true
|
|
31054
|
-
// the volume indicator is on by default
|
|
31055
|
-
};
|
|
31056
|
-
this.rendererControl = new RendererControl(renderer);
|
|
31057
|
-
if (options.settings) this.rendererControl.setSettingsVisibility(options.settings);
|
|
31058
|
-
this.dataControl = new DataControl(feed);
|
|
31059
|
-
this.orchestrator = new EngineOrchestrator(element, renderer, feed, engines, config, this.dataControl);
|
|
31060
|
-
const defaults2 = rendererDefaults();
|
|
31061
|
-
if (Object.keys(defaults2).length > 0) this.rendererControl.set(defaults2);
|
|
31062
|
-
this.panesControl = new PanesControl(this.orchestrator);
|
|
31063
|
-
this.drawingsControl = new DrawingsControl(this.orchestrator.drawings);
|
|
31091
|
+
// src/core/model/identity.ts
|
|
31092
|
+
function stableSeriesId(parts) {
|
|
31093
|
+
const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
31094
|
+
return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
|
|
31095
|
+
}
|
|
31096
|
+
|
|
31097
|
+
// src/core/native-indicators/classics/define.ts
|
|
31098
|
+
function num3(inputs, key, fallback) {
|
|
31099
|
+
const v = inputs[key];
|
|
31100
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
31101
|
+
}
|
|
31102
|
+
function str3(inputs, key, fallback) {
|
|
31103
|
+
const v = inputs[key];
|
|
31104
|
+
return typeof v === "string" && v !== "" ? v : fallback;
|
|
31105
|
+
}
|
|
31106
|
+
function toPoints(bars, values, colors) {
|
|
31107
|
+
const pts = new Array(bars.length);
|
|
31108
|
+
for (let i = 0; i < bars.length; i++) {
|
|
31109
|
+
const v = values[i];
|
|
31110
|
+
const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
|
|
31111
|
+
const c = colors?.[i];
|
|
31112
|
+
if (c != null) p.color = c;
|
|
31113
|
+
pts[i] = p;
|
|
31064
31114
|
}
|
|
31065
|
-
|
|
31066
|
-
|
|
31067
|
-
|
|
31068
|
-
|
|
31069
|
-
|
|
31070
|
-
|
|
31071
|
-
* language replaces it.
|
|
31072
|
-
*/
|
|
31073
|
-
registerEngine(language, engine) {
|
|
31074
|
-
this.orchestrator.registerEngine(language, engine);
|
|
31075
|
-
return this;
|
|
31115
|
+
return pts;
|
|
31116
|
+
}
|
|
31117
|
+
var ClassicIndicator = class {
|
|
31118
|
+
constructor(spec) {
|
|
31119
|
+
this.spec = spec;
|
|
31120
|
+
this.inputs = {};
|
|
31076
31121
|
}
|
|
31077
|
-
|
|
31078
|
-
|
|
31079
|
-
|
|
31122
|
+
start(ctx, inputs) {
|
|
31123
|
+
this.ctx = ctx;
|
|
31124
|
+
this.inputs = inputs;
|
|
31125
|
+
this.recompute();
|
|
31080
31126
|
}
|
|
31081
|
-
|
|
31082
|
-
|
|
31083
|
-
* e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide, remove,
|
|
31084
|
-
* events) and is single-instance per type (a second call returns the existing handle). Returns
|
|
31085
|
-
* a fail-soft handle for an unregistered type. Native renderer only.
|
|
31086
|
-
*/
|
|
31087
|
-
addNativeIndicator(type, options) {
|
|
31088
|
-
return this.orchestrator.addNativeIndicator(type, options);
|
|
31127
|
+
onBars() {
|
|
31128
|
+
this.recompute();
|
|
31089
31129
|
}
|
|
31090
|
-
|
|
31091
|
-
* The catalog of built-in native indicators with their live state on this chart — each entry's
|
|
31092
|
-
* `type`, `title`, whether it `supported`s the current symbol, and whether it's already `present`
|
|
31093
|
-
* (native indicators are single-instance per type, so a second `addNativeIndicator` is a no-op).
|
|
31094
|
-
* Lets a host "add indicator" UI list them, gate unsupported ones, and avoid duplicates. Async
|
|
31095
|
-
* because support may probe the provider (a type may need data the symbol lacks).
|
|
31096
|
-
*/
|
|
31097
|
-
availableNativeIndicators() {
|
|
31098
|
-
return this.orchestrator.availableNativeIndicators();
|
|
31130
|
+
onViewport() {
|
|
31099
31131
|
}
|
|
31100
|
-
|
|
31101
|
-
|
|
31102
|
-
|
|
31103
|
-
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
31104
|
-
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|
|
31105
|
-
*/
|
|
31106
|
-
presentNativeIndicators() {
|
|
31107
|
-
return this.orchestrator.presentNativeIndicators();
|
|
31132
|
+
setInputs(inputs) {
|
|
31133
|
+
this.inputs = inputs;
|
|
31134
|
+
this.recompute();
|
|
31108
31135
|
}
|
|
31109
|
-
/**
|
|
31110
|
-
|
|
31111
|
-
indicators() {
|
|
31112
|
-
return this.orchestrator.listIndicators();
|
|
31136
|
+
/** Nothing to free — the compute reads the live bar accessor on demand. */
|
|
31137
|
+
suspend() {
|
|
31113
31138
|
}
|
|
31114
|
-
|
|
31115
|
-
|
|
31116
|
-
* host editors and consoles. Resolves `{ ok: true, handle }` after the first
|
|
31117
|
-
* successful evaluation, or `{ ok: false, error }` after a compile/runtime failure —
|
|
31118
|
-
* in which case the failed indicator is removed again (no dead legend row).
|
|
31119
|
-
* Never rejects.
|
|
31120
|
-
*/
|
|
31121
|
-
/**
|
|
31122
|
-
* Execute a script and resolve its FIRST computed run — the data-out door for host
|
|
31123
|
-
* editors, consoles and dashboards. The script is injected only if it runs (a failure
|
|
31124
|
-
* removes it again, leaving no dead legend row), and the result carries the run itself
|
|
31125
|
-
* plus the controls for what it put on the chart: `onUpdate` to follow later runs,
|
|
31126
|
-
* `remove` to take it off. Never rejects.
|
|
31127
|
-
*
|
|
31128
|
-
* `runScript` is `runIndicator` with the run as its payload rather than a handle to go
|
|
31129
|
-
* fetch from — the same relationship `script:run` has to `context:changed`.
|
|
31130
|
-
*/
|
|
31131
|
-
runScript(source, options) {
|
|
31132
|
-
const handle = this.addIndicator(source, options);
|
|
31133
|
-
const updates = /* @__PURE__ */ new Set();
|
|
31134
|
-
const drop = () => {
|
|
31135
|
-
try {
|
|
31136
|
-
handle.remove();
|
|
31137
|
-
} catch {
|
|
31138
|
-
}
|
|
31139
|
-
};
|
|
31140
|
-
return new Promise((resolve) => {
|
|
31141
|
-
let settled = false;
|
|
31142
|
-
const offRun = this.on("script:run", (run) => {
|
|
31143
|
-
if (run.id !== handle.id) return;
|
|
31144
|
-
if (settled) {
|
|
31145
|
-
for (const handler of updates) handler(run);
|
|
31146
|
-
return;
|
|
31147
|
-
}
|
|
31148
|
-
settled = true;
|
|
31149
|
-
offError();
|
|
31150
|
-
resolve({
|
|
31151
|
-
ok: true,
|
|
31152
|
-
run,
|
|
31153
|
-
error: null,
|
|
31154
|
-
onUpdate: (handler) => {
|
|
31155
|
-
updates.add(handler);
|
|
31156
|
-
return () => updates.delete(handler);
|
|
31157
|
-
},
|
|
31158
|
-
remove: () => {
|
|
31159
|
-
offRun();
|
|
31160
|
-
updates.clear();
|
|
31161
|
-
drop();
|
|
31162
|
-
}
|
|
31163
|
-
});
|
|
31164
|
-
});
|
|
31165
|
-
const offError = handle.on("error", ({ error }) => {
|
|
31166
|
-
if (settled) return;
|
|
31167
|
-
settled = true;
|
|
31168
|
-
offRun();
|
|
31169
|
-
offError();
|
|
31170
|
-
drop();
|
|
31171
|
-
resolve({ ok: false, run: null, error, onUpdate: () => () => void 0, remove: () => void 0 });
|
|
31172
|
-
});
|
|
31173
|
-
});
|
|
31139
|
+
resume() {
|
|
31140
|
+
this.recompute();
|
|
31174
31141
|
}
|
|
31175
|
-
|
|
31176
|
-
|
|
31177
|
-
|
|
31178
|
-
|
|
31179
|
-
|
|
31180
|
-
|
|
31181
|
-
|
|
31142
|
+
stop() {
|
|
31143
|
+
}
|
|
31144
|
+
recompute() {
|
|
31145
|
+
const bars = this.ctx.bars();
|
|
31146
|
+
const out = this.spec.compute(bars, this.inputs);
|
|
31147
|
+
const instanceId = this.spec.type;
|
|
31148
|
+
const series = [];
|
|
31149
|
+
const idsByKey = /* @__PURE__ */ new Map();
|
|
31150
|
+
out.plots.forEach((plot, ordinal) => {
|
|
31151
|
+
const kind = plot.kind ?? "line";
|
|
31152
|
+
const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
|
|
31153
|
+
idsByKey.set(plot.key, id);
|
|
31154
|
+
series.push({
|
|
31155
|
+
id,
|
|
31156
|
+
title: plot.title,
|
|
31157
|
+
paneId: "",
|
|
31158
|
+
// stamped by the orchestrator
|
|
31159
|
+
kind,
|
|
31160
|
+
points: toPoints(bars, plot.values, plot.colors),
|
|
31161
|
+
style: {
|
|
31162
|
+
color: plot.color,
|
|
31163
|
+
width: plot.width ?? 1,
|
|
31164
|
+
lineStyle: plot.lineStyle ?? "solid",
|
|
31165
|
+
...plot.base != null ? { base: plot.base } : {}
|
|
31166
|
+
},
|
|
31167
|
+
...plot.overlay ? { overlay: true } : {}
|
|
31182
31168
|
});
|
|
31183
|
-
|
|
31184
|
-
|
|
31185
|
-
|
|
31186
|
-
|
|
31187
|
-
|
|
31188
|
-
|
|
31189
|
-
|
|
31190
|
-
|
|
31191
|
-
resolve({ ok: false, handle: null, error, context });
|
|
31192
|
-
});
|
|
31169
|
+
});
|
|
31170
|
+
if (out.markers && out.markers.length > 0) {
|
|
31171
|
+
series.push({
|
|
31172
|
+
id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
|
|
31173
|
+
title: this.spec.shortTitle ?? this.spec.title,
|
|
31174
|
+
paneId: "",
|
|
31175
|
+
kind: "markers",
|
|
31176
|
+
markers: out.markers
|
|
31193
31177
|
});
|
|
31178
|
+
}
|
|
31179
|
+
const fills = (out.bands ?? []).flatMap((band, ordinal) => {
|
|
31180
|
+
const from = idsByKey.get(band.from);
|
|
31181
|
+
const to = idsByKey.get(band.to);
|
|
31182
|
+
if (!from || !to) return [];
|
|
31183
|
+
return [
|
|
31184
|
+
{
|
|
31185
|
+
id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
|
|
31186
|
+
paneId: "",
|
|
31187
|
+
fromSeriesId: from,
|
|
31188
|
+
toSeriesId: to,
|
|
31189
|
+
color: band.color
|
|
31190
|
+
}
|
|
31191
|
+
];
|
|
31194
31192
|
});
|
|
31193
|
+
const priceLines = (out.levels ?? []).map((level, ordinal) => ({
|
|
31194
|
+
id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
|
|
31195
|
+
paneId: "",
|
|
31196
|
+
price: level.price,
|
|
31197
|
+
...level.color != null ? { color: level.color } : {},
|
|
31198
|
+
lineStyle: level.lineStyle ?? "dashed",
|
|
31199
|
+
width: 1,
|
|
31200
|
+
...level.title != null ? { title: level.title } : {}
|
|
31201
|
+
}));
|
|
31202
|
+
const polylines = (out.polylines ?? []).map((poly, ordinal) => {
|
|
31203
|
+
const { key, ...rest } = poly;
|
|
31204
|
+
return {
|
|
31205
|
+
id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
|
|
31206
|
+
paneId: "",
|
|
31207
|
+
...rest
|
|
31208
|
+
};
|
|
31209
|
+
});
|
|
31210
|
+
this.ctx.emit({ series, fills, priceLines, polylines });
|
|
31211
|
+
this.ctx.setStatus("idle");
|
|
31195
31212
|
}
|
|
31196
|
-
|
|
31197
|
-
|
|
31198
|
-
|
|
31199
|
-
|
|
31200
|
-
|
|
31201
|
-
|
|
31202
|
-
|
|
31203
|
-
|
|
31204
|
-
|
|
31205
|
-
|
|
31206
|
-
|
|
31213
|
+
};
|
|
31214
|
+
function classicDescriptor(spec) {
|
|
31215
|
+
return {
|
|
31216
|
+
type: spec.type,
|
|
31217
|
+
title: spec.title,
|
|
31218
|
+
...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
|
|
31219
|
+
paneHint: spec.overlay ? "price" : "new",
|
|
31220
|
+
overlay: spec.overlay,
|
|
31221
|
+
// Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
|
|
31222
|
+
multiInstance: true,
|
|
31223
|
+
inputsSchema: () => spec.inputs,
|
|
31224
|
+
defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
|
|
31225
|
+
create: () => new ClassicIndicator(spec)
|
|
31226
|
+
};
|
|
31227
|
+
}
|
|
31228
|
+
|
|
31229
|
+
// src/core/native-indicators/classics/math.ts
|
|
31230
|
+
var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
|
|
31231
|
+
function sourceValues(bars, source) {
|
|
31232
|
+
const out = new Array(bars.length);
|
|
31233
|
+
for (let i = 0; i < bars.length; i++) {
|
|
31234
|
+
const b = bars[i];
|
|
31235
|
+
switch (source) {
|
|
31236
|
+
case "Open":
|
|
31237
|
+
out[i] = b.open;
|
|
31238
|
+
break;
|
|
31239
|
+
case "High":
|
|
31240
|
+
out[i] = b.high;
|
|
31241
|
+
break;
|
|
31242
|
+
case "Low":
|
|
31243
|
+
out[i] = b.low;
|
|
31244
|
+
break;
|
|
31245
|
+
case "HL2":
|
|
31246
|
+
out[i] = (b.high + b.low) / 2;
|
|
31247
|
+
break;
|
|
31248
|
+
case "HLC3":
|
|
31249
|
+
out[i] = (b.high + b.low + b.close) / 3;
|
|
31250
|
+
break;
|
|
31251
|
+
case "OHLC4":
|
|
31252
|
+
out[i] = (b.open + b.high + b.low + b.close) / 4;
|
|
31253
|
+
break;
|
|
31254
|
+
case "HLCC4":
|
|
31255
|
+
out[i] = (b.high + b.low + b.close + b.close) / 4;
|
|
31256
|
+
break;
|
|
31257
|
+
default:
|
|
31258
|
+
out[i] = b.close;
|
|
31259
|
+
}
|
|
31207
31260
|
}
|
|
31208
|
-
|
|
31209
|
-
|
|
31261
|
+
return out;
|
|
31262
|
+
}
|
|
31263
|
+
function volumes(bars) {
|
|
31264
|
+
return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
|
|
31265
|
+
}
|
|
31266
|
+
function highs(bars) {
|
|
31267
|
+
return bars.map((b) => b.high);
|
|
31268
|
+
}
|
|
31269
|
+
function lows(bars) {
|
|
31270
|
+
return bars.map((b) => b.low);
|
|
31271
|
+
}
|
|
31272
|
+
function closes(bars) {
|
|
31273
|
+
return bars.map((b) => b.close);
|
|
31274
|
+
}
|
|
31275
|
+
function opens(bars) {
|
|
31276
|
+
return bars.map((b) => b.open);
|
|
31277
|
+
}
|
|
31278
|
+
function zip(a, b, f) {
|
|
31279
|
+
const out = new Array(a.length);
|
|
31280
|
+
for (let i = 0; i < a.length; i++) {
|
|
31281
|
+
const x = a[i];
|
|
31282
|
+
const y = b[i];
|
|
31283
|
+
out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
|
|
31284
|
+
}
|
|
31285
|
+
return out;
|
|
31286
|
+
}
|
|
31287
|
+
function map(a, f) {
|
|
31288
|
+
const out = new Array(a.length);
|
|
31289
|
+
for (let i = 0; i < a.length; i++) {
|
|
31290
|
+
const x = a[i];
|
|
31291
|
+
out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
|
|
31292
|
+
}
|
|
31293
|
+
return out;
|
|
31294
|
+
}
|
|
31295
|
+
function sub(a, b) {
|
|
31296
|
+
return zip(a, b, (x, y) => x - y);
|
|
31297
|
+
}
|
|
31298
|
+
function windowed(v, len, fold) {
|
|
31299
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31300
|
+
if (len <= 0) return out;
|
|
31301
|
+
let run = 0;
|
|
31302
|
+
for (let i = 0; i < v.length; i++) {
|
|
31303
|
+
run = Number.isFinite(v[i]) ? run + 1 : 0;
|
|
31304
|
+
if (run >= len) out[i] = fold(v, i - len + 1, i);
|
|
31305
|
+
}
|
|
31306
|
+
return out;
|
|
31307
|
+
}
|
|
31308
|
+
function sum(v, len) {
|
|
31309
|
+
return windowed(v, len, (w, from, to) => {
|
|
31310
|
+
let s = 0;
|
|
31311
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
31312
|
+
return s;
|
|
31313
|
+
});
|
|
31314
|
+
}
|
|
31315
|
+
function sma(v, len) {
|
|
31316
|
+
return windowed(v, len, (w, from, to) => {
|
|
31317
|
+
let s = 0;
|
|
31318
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
31319
|
+
return s / len;
|
|
31320
|
+
});
|
|
31321
|
+
}
|
|
31322
|
+
function wma(v, len) {
|
|
31323
|
+
const norm = len * (len + 1) / 2;
|
|
31324
|
+
return windowed(v, len, (w, from, to) => {
|
|
31325
|
+
let s = 0;
|
|
31326
|
+
for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
|
|
31327
|
+
return s / norm;
|
|
31328
|
+
});
|
|
31329
|
+
}
|
|
31330
|
+
function vwma(src, vol, len) {
|
|
31331
|
+
const pv = zip(src, vol, (p, q) => p * q);
|
|
31332
|
+
return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
|
|
31333
|
+
}
|
|
31334
|
+
function stdev(v, len) {
|
|
31335
|
+
return windowed(v, len, (w, from, to) => {
|
|
31336
|
+
let s = 0;
|
|
31337
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
31338
|
+
const mean = s / len;
|
|
31339
|
+
let sq = 0;
|
|
31340
|
+
for (let k = from; k <= to; k++) {
|
|
31341
|
+
const d = w[k] - mean;
|
|
31342
|
+
sq += d * d;
|
|
31343
|
+
}
|
|
31344
|
+
return Math.sqrt(sq / len);
|
|
31345
|
+
});
|
|
31346
|
+
}
|
|
31347
|
+
function highest(v, len) {
|
|
31348
|
+
return windowed(v, len, (w, from, to) => {
|
|
31349
|
+
let m = -Infinity;
|
|
31350
|
+
for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
|
|
31351
|
+
return m;
|
|
31352
|
+
});
|
|
31353
|
+
}
|
|
31354
|
+
function lowest(v, len) {
|
|
31355
|
+
return windowed(v, len, (w, from, to) => {
|
|
31356
|
+
let m = Infinity;
|
|
31357
|
+
for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
|
|
31358
|
+
return m;
|
|
31359
|
+
});
|
|
31360
|
+
}
|
|
31361
|
+
function barsSinceHighest(v, len) {
|
|
31362
|
+
return windowed(v, len, (w, from, to) => {
|
|
31363
|
+
let m = -Infinity;
|
|
31364
|
+
let at = to;
|
|
31365
|
+
for (let k = from; k <= to; k++) {
|
|
31366
|
+
if (w[k] >= m) {
|
|
31367
|
+
m = w[k];
|
|
31368
|
+
at = k;
|
|
31369
|
+
}
|
|
31370
|
+
}
|
|
31371
|
+
return to - at;
|
|
31372
|
+
});
|
|
31373
|
+
}
|
|
31374
|
+
function barsSinceLowest(v, len) {
|
|
31375
|
+
return windowed(v, len, (w, from, to) => {
|
|
31376
|
+
let m = Infinity;
|
|
31377
|
+
let at = to;
|
|
31378
|
+
for (let k = from; k <= to; k++) {
|
|
31379
|
+
if (w[k] <= m) {
|
|
31380
|
+
m = w[k];
|
|
31381
|
+
at = k;
|
|
31382
|
+
}
|
|
31383
|
+
}
|
|
31384
|
+
return to - at;
|
|
31385
|
+
});
|
|
31386
|
+
}
|
|
31387
|
+
function recursive(v, len, alpha) {
|
|
31388
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31389
|
+
let run = 0;
|
|
31390
|
+
let prev2 = Number.NaN;
|
|
31391
|
+
for (let i = 0; i < v.length; i++) {
|
|
31392
|
+
const x = v[i];
|
|
31393
|
+
if (!Number.isFinite(x)) {
|
|
31394
|
+
run = 0;
|
|
31395
|
+
prev2 = Number.NaN;
|
|
31396
|
+
continue;
|
|
31397
|
+
}
|
|
31398
|
+
run += 1;
|
|
31399
|
+
if (Number.isFinite(prev2)) {
|
|
31400
|
+
prev2 = alpha * x + (1 - alpha) * prev2;
|
|
31401
|
+
out[i] = prev2;
|
|
31402
|
+
} else if (run >= len) {
|
|
31403
|
+
let s = 0;
|
|
31404
|
+
for (let k = i - len + 1; k <= i; k++) s += v[k];
|
|
31405
|
+
prev2 = s / len;
|
|
31406
|
+
out[i] = prev2;
|
|
31407
|
+
}
|
|
31408
|
+
}
|
|
31409
|
+
return out;
|
|
31410
|
+
}
|
|
31411
|
+
function ema(v, len) {
|
|
31412
|
+
return recursive(v, len, 2 / (len + 1));
|
|
31413
|
+
}
|
|
31414
|
+
function rma(v, len) {
|
|
31415
|
+
return recursive(v, len, 1 / len);
|
|
31416
|
+
}
|
|
31417
|
+
function change(v, n = 1) {
|
|
31418
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31419
|
+
for (let i = n; i < v.length; i++) {
|
|
31420
|
+
const a = v[i];
|
|
31421
|
+
const b = v[i - n];
|
|
31422
|
+
if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
|
|
31423
|
+
}
|
|
31424
|
+
return out;
|
|
31425
|
+
}
|
|
31426
|
+
function roc(v, len) {
|
|
31427
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31428
|
+
for (let i = len; i < v.length; i++) {
|
|
31429
|
+
const a = v[i];
|
|
31430
|
+
const b = v[i - len];
|
|
31431
|
+
if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
|
|
31432
|
+
}
|
|
31433
|
+
return out;
|
|
31434
|
+
}
|
|
31435
|
+
function shift(v, n) {
|
|
31436
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31437
|
+
for (let i = 0; i < v.length; i++) {
|
|
31438
|
+
const j = i - n;
|
|
31439
|
+
if (j >= 0 && j < v.length) out[i] = v[j];
|
|
31440
|
+
}
|
|
31441
|
+
return out;
|
|
31442
|
+
}
|
|
31443
|
+
function trueRange(bars) {
|
|
31444
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
31445
|
+
for (let i = 0; i < bars.length; i++) {
|
|
31446
|
+
const b = bars[i];
|
|
31447
|
+
if (i === 0) {
|
|
31448
|
+
out[i] = b.high - b.low;
|
|
31449
|
+
continue;
|
|
31450
|
+
}
|
|
31451
|
+
const pc = bars[i - 1].close;
|
|
31452
|
+
out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
|
|
31453
|
+
}
|
|
31454
|
+
return out;
|
|
31455
|
+
}
|
|
31456
|
+
function atr(bars, len) {
|
|
31457
|
+
return rma(trueRange(bars), len);
|
|
31458
|
+
}
|
|
31459
|
+
function rsi(v, len) {
|
|
31460
|
+
const d = change(v);
|
|
31461
|
+
const up = map(d, (x) => Math.max(x, 0));
|
|
31462
|
+
const dn = map(d, (x) => Math.max(-x, 0));
|
|
31463
|
+
return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
|
|
31464
|
+
}
|
|
31465
|
+
function stoch(v, hi, lo, len) {
|
|
31466
|
+
const hh = highest(hi, len);
|
|
31467
|
+
const ll = lowest(lo, len);
|
|
31468
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31469
|
+
for (let i = 0; i < v.length; i++) {
|
|
31470
|
+
const x = v[i];
|
|
31471
|
+
const h = hh[i];
|
|
31472
|
+
const l = ll[i];
|
|
31473
|
+
if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
31474
|
+
out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
|
|
31475
|
+
}
|
|
31476
|
+
return out;
|
|
31477
|
+
}
|
|
31478
|
+
function linreg(v, len) {
|
|
31479
|
+
const sx = (len - 1) * len / 2;
|
|
31480
|
+
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
31481
|
+
const denom = len * sxx - sx * sx;
|
|
31482
|
+
return windowed(v, len, (w, from, to) => {
|
|
31483
|
+
let sy = 0;
|
|
31484
|
+
let sxy = 0;
|
|
31485
|
+
for (let k = from; k <= to; k++) {
|
|
31486
|
+
const x = k - from;
|
|
31487
|
+
sy += w[k];
|
|
31488
|
+
sxy += x * w[k];
|
|
31489
|
+
}
|
|
31490
|
+
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
31491
|
+
const intercept = (sy - slope * sx) / len;
|
|
31492
|
+
return intercept + slope * (len - 1);
|
|
31493
|
+
});
|
|
31494
|
+
}
|
|
31495
|
+
function swma(v) {
|
|
31496
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31497
|
+
for (let i = 3; i < v.length; i++) {
|
|
31498
|
+
const a = v[i - 3];
|
|
31499
|
+
const b = v[i - 2];
|
|
31500
|
+
const c = v[i - 1];
|
|
31501
|
+
const d = v[i];
|
|
31502
|
+
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
|
|
31503
|
+
out[i] = (a + 2 * b + 2 * c + d) / 6;
|
|
31504
|
+
}
|
|
31505
|
+
}
|
|
31506
|
+
return out;
|
|
31507
|
+
}
|
|
31508
|
+
function percentRank(v, len) {
|
|
31509
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31510
|
+
for (let i = len; i < v.length; i++) {
|
|
31511
|
+
const x = v[i];
|
|
31512
|
+
if (!Number.isFinite(x)) continue;
|
|
31513
|
+
let below = 0;
|
|
31514
|
+
let count = 0;
|
|
31515
|
+
for (let k = i - len; k < i; k++) {
|
|
31516
|
+
const y = v[k];
|
|
31517
|
+
if (!Number.isFinite(y)) {
|
|
31518
|
+
count = -1;
|
|
31519
|
+
break;
|
|
31520
|
+
}
|
|
31521
|
+
count += 1;
|
|
31522
|
+
if (y <= x) below += 1;
|
|
31523
|
+
}
|
|
31524
|
+
if (count === len) out[i] = 100 * below / len;
|
|
31525
|
+
}
|
|
31526
|
+
return out;
|
|
31527
|
+
}
|
|
31528
|
+
function cumSum(v) {
|
|
31529
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31530
|
+
let acc = 0;
|
|
31531
|
+
let seen = false;
|
|
31532
|
+
for (let i = 0; i < v.length; i++) {
|
|
31533
|
+
const x = v[i];
|
|
31534
|
+
if (Number.isFinite(x)) {
|
|
31535
|
+
acc += x;
|
|
31536
|
+
seen = true;
|
|
31537
|
+
}
|
|
31538
|
+
if (seen) out[i] = acc;
|
|
31539
|
+
}
|
|
31540
|
+
return out;
|
|
31541
|
+
}
|
|
31542
|
+
|
|
31543
|
+
// src/core/native-indicators/classics/shared.ts
|
|
31544
|
+
function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
|
|
31545
|
+
return { key, title, type: "int", defval, min: 1, max, step: 1 };
|
|
31546
|
+
}
|
|
31547
|
+
function sourceInput(defval = "Close") {
|
|
31548
|
+
return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
|
|
31549
|
+
}
|
|
31550
|
+
function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
|
|
31551
|
+
return { key, title, type: "color", defval };
|
|
31552
|
+
}
|
|
31553
|
+
function withAlpha2(hex, alpha = 0.08) {
|
|
31554
|
+
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
31555
|
+
if (!m) return hex;
|
|
31556
|
+
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
31557
|
+
return `#${m[1]}${a}`;
|
|
31558
|
+
}
|
|
31559
|
+
|
|
31560
|
+
// src/core/native-indicators/classics/averages.ts
|
|
31561
|
+
var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
|
|
31562
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
|
|
31563
|
+
function fixedAverage(type, title, shortTitle, smooth) {
|
|
31564
|
+
return {
|
|
31565
|
+
type,
|
|
31566
|
+
title,
|
|
31567
|
+
shortTitle,
|
|
31568
|
+
overlay: true,
|
|
31569
|
+
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
31570
|
+
compute: (bars, inputs) => {
|
|
31571
|
+
const len = num3(inputs, "length", 20);
|
|
31572
|
+
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
31573
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
31574
|
+
if (offset !== 0) values = shift(values, offset);
|
|
31575
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31576
|
+
}
|
|
31577
|
+
};
|
|
31578
|
+
}
|
|
31579
|
+
var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
|
|
31580
|
+
var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
|
|
31581
|
+
var movingAverage = {
|
|
31582
|
+
type: "moving-average",
|
|
31583
|
+
title: "Moving Average",
|
|
31584
|
+
shortTitle: "MA",
|
|
31585
|
+
overlay: true,
|
|
31586
|
+
inputs: [
|
|
31587
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
|
|
31588
|
+
lengthInput(20),
|
|
31589
|
+
sourceInput(),
|
|
31590
|
+
offsetInput,
|
|
31591
|
+
colorInput()
|
|
31592
|
+
],
|
|
31593
|
+
compute: (bars, inputs) => {
|
|
31594
|
+
const len = num3(inputs, "length", 20);
|
|
31595
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31596
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
31597
|
+
let values;
|
|
31598
|
+
switch (kind) {
|
|
31599
|
+
case "EMA":
|
|
31600
|
+
values = ema(src, len);
|
|
31601
|
+
break;
|
|
31602
|
+
case "WMA":
|
|
31603
|
+
values = wma(src, len);
|
|
31604
|
+
break;
|
|
31605
|
+
case "RMA":
|
|
31606
|
+
values = rma(src, len);
|
|
31607
|
+
break;
|
|
31608
|
+
case "VWMA":
|
|
31609
|
+
values = vwma(src, volumes(bars), len);
|
|
31610
|
+
break;
|
|
31611
|
+
default:
|
|
31612
|
+
values = sma(src, len);
|
|
31613
|
+
}
|
|
31614
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
31615
|
+
if (offset !== 0) values = shift(values, offset);
|
|
31616
|
+
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31617
|
+
}
|
|
31618
|
+
};
|
|
31619
|
+
var smoothedMa = {
|
|
31620
|
+
type: "rma",
|
|
31621
|
+
title: "Smoothed Moving Average",
|
|
31622
|
+
shortTitle: "RMA",
|
|
31623
|
+
overlay: true,
|
|
31624
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
31625
|
+
compute: (bars, inputs) => ({
|
|
31626
|
+
plots: [
|
|
31627
|
+
{
|
|
31628
|
+
key: "rma",
|
|
31629
|
+
title: "RMA",
|
|
31630
|
+
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
31631
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
31632
|
+
width: 2
|
|
31633
|
+
}
|
|
31634
|
+
]
|
|
31635
|
+
})
|
|
31636
|
+
};
|
|
31637
|
+
var zlema = {
|
|
31638
|
+
type: "zlema",
|
|
31639
|
+
title: "Zero-Lag Exponential Moving Average",
|
|
31640
|
+
shortTitle: "ZLEMA",
|
|
31641
|
+
overlay: true,
|
|
31642
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
31643
|
+
compute: (bars, inputs) => {
|
|
31644
|
+
const len = num3(inputs, "length", 14);
|
|
31645
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31646
|
+
const lag = Math.floor((len - 1) / 2);
|
|
31647
|
+
const lagged = shift(src, lag);
|
|
31648
|
+
const delagged = zip(src, lagged, (a, b) => 2 * a - b);
|
|
31649
|
+
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31650
|
+
}
|
|
31651
|
+
};
|
|
31652
|
+
var vidya = {
|
|
31653
|
+
type: "vidya",
|
|
31654
|
+
title: "Variable Index Dynamic Average",
|
|
31655
|
+
shortTitle: "VIDYA",
|
|
31656
|
+
overlay: true,
|
|
31657
|
+
inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
|
|
31658
|
+
compute: (bars, inputs) => {
|
|
31659
|
+
const len = num3(inputs, "length", 14);
|
|
31660
|
+
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
31661
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31662
|
+
const d = change(src);
|
|
31663
|
+
const su = new Array(src.length).fill(Number.NaN);
|
|
31664
|
+
const sd = new Array(src.length).fill(Number.NaN);
|
|
31665
|
+
for (let i = cmoLen; i < src.length; i++) {
|
|
31666
|
+
let u = 0;
|
|
31667
|
+
let w = 0;
|
|
31668
|
+
let ok = true;
|
|
31669
|
+
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
31670
|
+
const x = d[k];
|
|
31671
|
+
if (!Number.isFinite(x)) {
|
|
31672
|
+
ok = false;
|
|
31673
|
+
break;
|
|
31674
|
+
}
|
|
31675
|
+
if (x > 0) u += x;
|
|
31676
|
+
else w -= x;
|
|
31677
|
+
}
|
|
31678
|
+
if (ok) {
|
|
31679
|
+
su[i] = u;
|
|
31680
|
+
sd[i] = w;
|
|
31681
|
+
}
|
|
31682
|
+
}
|
|
31683
|
+
const alpha = 2 / (len + 1);
|
|
31684
|
+
const out = new Array(src.length).fill(Number.NaN);
|
|
31685
|
+
let prev2 = Number.NaN;
|
|
31686
|
+
for (let i = 0; i < src.length; i++) {
|
|
31687
|
+
const x = src[i];
|
|
31688
|
+
const u = su[i];
|
|
31689
|
+
const w = sd[i];
|
|
31690
|
+
if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
|
|
31691
|
+
const total = u + w;
|
|
31692
|
+
const k = total === 0 ? 0 : Math.abs((u - w) / total);
|
|
31693
|
+
prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
|
|
31694
|
+
out[i] = prev2;
|
|
31695
|
+
}
|
|
31696
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31697
|
+
}
|
|
31698
|
+
};
|
|
31699
|
+
var maEnvelope = {
|
|
31700
|
+
type: "ma-envelope",
|
|
31701
|
+
title: "Moving Average Envelope",
|
|
31702
|
+
shortTitle: "MA Env",
|
|
31703
|
+
overlay: true,
|
|
31704
|
+
inputs: [
|
|
31705
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
|
|
31706
|
+
lengthInput(20),
|
|
31707
|
+
sourceInput(),
|
|
31708
|
+
{ key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
|
|
31709
|
+
colorInput()
|
|
31710
|
+
],
|
|
31711
|
+
compute: (bars, inputs) => {
|
|
31712
|
+
const len = num3(inputs, "length", 20);
|
|
31713
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31714
|
+
const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
|
|
31715
|
+
const k = num3(inputs, "percent", 2.5) / 100;
|
|
31716
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
31717
|
+
return {
|
|
31718
|
+
plots: [
|
|
31719
|
+
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
31720
|
+
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
31721
|
+
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
31722
|
+
],
|
|
31723
|
+
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
31724
|
+
};
|
|
31725
|
+
}
|
|
31726
|
+
};
|
|
31727
|
+
var linearRegression = {
|
|
31728
|
+
type: "linear-regression",
|
|
31729
|
+
title: "Linear Regression Curve",
|
|
31730
|
+
shortTitle: "LinReg",
|
|
31731
|
+
overlay: true,
|
|
31732
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
31733
|
+
compute: (bars, inputs) => ({
|
|
31734
|
+
plots: [
|
|
31735
|
+
{
|
|
31736
|
+
key: "linreg",
|
|
31737
|
+
title: "LinReg",
|
|
31738
|
+
values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
31739
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
31740
|
+
width: 2
|
|
31741
|
+
}
|
|
31742
|
+
]
|
|
31743
|
+
})
|
|
31744
|
+
};
|
|
31745
|
+
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
31746
|
+
|
|
31747
|
+
// src/core/native-indicators/classics/bands.ts
|
|
31748
|
+
function bollinger(bars, inputs) {
|
|
31749
|
+
const len = num3(inputs, "length", 20);
|
|
31750
|
+
const mult = num3(inputs, "mult", 2);
|
|
31751
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31752
|
+
const basis = sma(src, len);
|
|
31753
|
+
const dev = map(stdev(src, len), (x) => x * mult);
|
|
31754
|
+
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
31755
|
+
}
|
|
31756
|
+
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
31757
|
+
var bollingerBands = {
|
|
31758
|
+
type: "bollinger-bands",
|
|
31759
|
+
title: "Bollinger Bands",
|
|
31760
|
+
shortTitle: "BB",
|
|
31761
|
+
overlay: true,
|
|
31762
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
31763
|
+
compute: (bars, inputs) => {
|
|
31764
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
31765
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
31766
|
+
return {
|
|
31767
|
+
plots: [
|
|
31768
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
31769
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
31770
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
31771
|
+
],
|
|
31772
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
31773
|
+
};
|
|
31774
|
+
}
|
|
31775
|
+
};
|
|
31776
|
+
var percentB = {
|
|
31777
|
+
type: "percent-b",
|
|
31778
|
+
title: "Bollinger Bands %B",
|
|
31779
|
+
shortTitle: "%B",
|
|
31780
|
+
overlay: false,
|
|
31781
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
31782
|
+
compute: (bars, inputs) => {
|
|
31783
|
+
const { upper, lower } = bollinger(bars, inputs);
|
|
31784
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31785
|
+
const values = src.map((x, i) => {
|
|
31786
|
+
const u = upper[i];
|
|
31787
|
+
const l = lower[i];
|
|
31788
|
+
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
31789
|
+
});
|
|
31790
|
+
return {
|
|
31791
|
+
plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
31792
|
+
levels: [
|
|
31793
|
+
{ key: "upper", price: 1, color: NEUTRAL },
|
|
31794
|
+
{ key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
|
|
31795
|
+
{ key: "lower", price: 0, color: NEUTRAL }
|
|
31796
|
+
]
|
|
31797
|
+
};
|
|
31798
|
+
}
|
|
31799
|
+
};
|
|
31800
|
+
var bandwidth = {
|
|
31801
|
+
type: "bandwidth",
|
|
31802
|
+
title: "Bollinger Bands Width",
|
|
31803
|
+
shortTitle: "BandWidth",
|
|
31804
|
+
overlay: false,
|
|
31805
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
31806
|
+
compute: (bars, inputs) => {
|
|
31807
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
31808
|
+
const values = basis.map((b, i) => {
|
|
31809
|
+
const u = upper[i];
|
|
31810
|
+
const l = lower[i];
|
|
31811
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
|
|
31812
|
+
});
|
|
31813
|
+
return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31814
|
+
}
|
|
31815
|
+
};
|
|
31816
|
+
var keltner = {
|
|
31817
|
+
type: "keltner-channels",
|
|
31818
|
+
title: "Keltner Channels",
|
|
31819
|
+
shortTitle: "KC",
|
|
31820
|
+
overlay: true,
|
|
31821
|
+
inputs: [
|
|
31822
|
+
lengthInput(20),
|
|
31823
|
+
{ key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
31824
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
31825
|
+
sourceInput(),
|
|
31826
|
+
colorInput()
|
|
31827
|
+
],
|
|
31828
|
+
compute: (bars, inputs) => {
|
|
31829
|
+
const len = num3(inputs, "length", 20);
|
|
31830
|
+
const mult = num3(inputs, "mult", 2);
|
|
31831
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
31832
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
|
|
31833
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
31834
|
+
return {
|
|
31835
|
+
plots: [
|
|
31836
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
31837
|
+
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
31838
|
+
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
31839
|
+
],
|
|
31840
|
+
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
31841
|
+
};
|
|
31842
|
+
}
|
|
31843
|
+
};
|
|
31844
|
+
var donchian = {
|
|
31845
|
+
type: "donchian-channels",
|
|
31846
|
+
title: "Donchian Channels",
|
|
31847
|
+
shortTitle: "DC",
|
|
31848
|
+
overlay: true,
|
|
31849
|
+
inputs: [lengthInput(20), colorInput()],
|
|
31850
|
+
compute: (bars, inputs) => {
|
|
31851
|
+
const len = num3(inputs, "length", 20);
|
|
31852
|
+
const upper = highest(highs(bars), len);
|
|
31853
|
+
const lower = lowest(lows(bars), len);
|
|
31854
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
31855
|
+
return {
|
|
31856
|
+
plots: [
|
|
31857
|
+
{ key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
|
|
31858
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
31859
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
31860
|
+
],
|
|
31861
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
31862
|
+
};
|
|
31863
|
+
}
|
|
31864
|
+
};
|
|
31865
|
+
var chandelier = {
|
|
31866
|
+
type: "chandelier-exit",
|
|
31867
|
+
title: "Chandelier Exit",
|
|
31868
|
+
shortTitle: "CE",
|
|
31869
|
+
overlay: true,
|
|
31870
|
+
inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
|
|
31871
|
+
compute: (bars, inputs) => {
|
|
31872
|
+
const len = num3(inputs, "length", 22);
|
|
31873
|
+
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
31874
|
+
const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
31875
|
+
const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
31876
|
+
return {
|
|
31877
|
+
plots: [
|
|
31878
|
+
{ key: "long", title: "Long stop", values: long, color: BULLISH },
|
|
31879
|
+
{ key: "short", title: "Short stop", values: short, color: BEARISH }
|
|
31880
|
+
]
|
|
31881
|
+
};
|
|
31882
|
+
}
|
|
31883
|
+
};
|
|
31884
|
+
var chandeKroll = {
|
|
31885
|
+
type: "chande-kroll-stop",
|
|
31886
|
+
title: "Chande Kroll Stop",
|
|
31887
|
+
shortTitle: "CKS",
|
|
31888
|
+
overlay: true,
|
|
31889
|
+
inputs: [
|
|
31890
|
+
{ key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
31891
|
+
{ key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
|
|
31892
|
+
{ key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
|
|
31893
|
+
],
|
|
31894
|
+
compute: (bars, inputs) => {
|
|
31895
|
+
const p = num3(inputs, "p", 10);
|
|
31896
|
+
const x = num3(inputs, "x", 1);
|
|
31897
|
+
const q = num3(inputs, "q", 9);
|
|
31898
|
+
const range = map(atr(bars, p), (v) => v * x);
|
|
31899
|
+
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
31900
|
+
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
31901
|
+
return {
|
|
31902
|
+
plots: [
|
|
31903
|
+
{ key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
|
|
31904
|
+
{ key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
|
|
31905
|
+
]
|
|
31906
|
+
};
|
|
31907
|
+
}
|
|
31908
|
+
};
|
|
31909
|
+
var supertrend = {
|
|
31910
|
+
type: "supertrend",
|
|
31911
|
+
title: "SuperTrend",
|
|
31912
|
+
shortTitle: "SuperTrend",
|
|
31913
|
+
overlay: true,
|
|
31914
|
+
inputs: [
|
|
31915
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
31916
|
+
{ key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
|
|
31917
|
+
],
|
|
31918
|
+
compute: (bars, inputs) => {
|
|
31919
|
+
const len = num3(inputs, "atrLength", 10);
|
|
31920
|
+
const mult = num3(inputs, "mult", 3);
|
|
31921
|
+
const range = atr(bars, len);
|
|
31922
|
+
const n = bars.length;
|
|
31923
|
+
const st = new Array(n).fill(Number.NaN);
|
|
31924
|
+
const dir = new Array(n).fill(1);
|
|
31925
|
+
let up = Number.NaN;
|
|
31926
|
+
let dn = Number.NaN;
|
|
31927
|
+
let trend = 1;
|
|
31928
|
+
for (let i = 0; i < n; i++) {
|
|
31929
|
+
const r = range[i];
|
|
31930
|
+
if (!Number.isFinite(r)) continue;
|
|
31931
|
+
const b = bars[i];
|
|
31932
|
+
const mid = (b.high + b.low) / 2;
|
|
31933
|
+
const basicUp = mid - mult * r;
|
|
31934
|
+
const basicDn = mid + mult * r;
|
|
31935
|
+
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
31936
|
+
up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
|
|
31937
|
+
dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
|
|
31938
|
+
if (trend === 1 && b.close < up) trend = -1;
|
|
31939
|
+
else if (trend === -1 && b.close > dn) trend = 1;
|
|
31940
|
+
dir[i] = trend;
|
|
31941
|
+
st[i] = trend === 1 ? up : dn;
|
|
31942
|
+
}
|
|
31943
|
+
const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
|
|
31944
|
+
return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
|
|
31945
|
+
}
|
|
31946
|
+
};
|
|
31947
|
+
var alligator = {
|
|
31948
|
+
type: "williams-alligator",
|
|
31949
|
+
title: "Williams Alligator",
|
|
31950
|
+
shortTitle: "Alligator",
|
|
31951
|
+
overlay: true,
|
|
31952
|
+
inputs: [
|
|
31953
|
+
{ key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
|
|
31954
|
+
{ key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
|
|
31955
|
+
{ key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
|
|
31956
|
+
{ key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
|
|
31957
|
+
{ key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
|
|
31958
|
+
{ key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
|
|
31959
|
+
],
|
|
31960
|
+
compute: (bars, inputs) => {
|
|
31961
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
31962
|
+
const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
31963
|
+
const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
31964
|
+
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
31965
|
+
return {
|
|
31966
|
+
plots: [
|
|
31967
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
|
|
31968
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
|
|
31969
|
+
{ key: "lips", title: "Lips", values: lips, color: BULLISH }
|
|
31970
|
+
]
|
|
31971
|
+
};
|
|
31972
|
+
}
|
|
31973
|
+
};
|
|
31974
|
+
var gator = {
|
|
31975
|
+
type: "gator-oscillator",
|
|
31976
|
+
title: "Gator Oscillator",
|
|
31977
|
+
shortTitle: "Gator Oscillator",
|
|
31978
|
+
overlay: false,
|
|
31979
|
+
inputs: [],
|
|
31980
|
+
compute: (bars) => {
|
|
31981
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
31982
|
+
const jaw = shift(rma(hl2, 13), 8);
|
|
31983
|
+
const teeth = shift(rma(hl2, 8), 5);
|
|
31984
|
+
const lips = shift(rma(hl2, 5), 3);
|
|
31985
|
+
const upper = map(sub(jaw, teeth), Math.abs);
|
|
31986
|
+
const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
|
|
31987
|
+
const colorByExpansion = (v) => v.map((x, i) => {
|
|
31988
|
+
if (!Number.isFinite(x)) return null;
|
|
31989
|
+
const prev2 = i > 0 ? v[i - 1] : Number.NaN;
|
|
31990
|
+
return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
|
|
31991
|
+
});
|
|
31992
|
+
return {
|
|
31993
|
+
plots: [
|
|
31994
|
+
{ key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
|
|
31995
|
+
{ key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
|
|
31996
|
+
]
|
|
31997
|
+
};
|
|
31998
|
+
}
|
|
31999
|
+
};
|
|
32000
|
+
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
32001
|
+
|
|
32002
|
+
// src/core/native-indicators/classics/oscillators.ts
|
|
32003
|
+
function guideLevels(low, high, mid) {
|
|
32004
|
+
const levels = [
|
|
32005
|
+
{ key: "upper", price: high, color: NEUTRAL },
|
|
32006
|
+
{ key: "lower", price: low, color: NEUTRAL }
|
|
32007
|
+
];
|
|
32008
|
+
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
32009
|
+
return levels;
|
|
32010
|
+
}
|
|
32011
|
+
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
32012
|
+
function histColors(values) {
|
|
32013
|
+
return values.map((x, i) => {
|
|
32014
|
+
if (!Number.isFinite(x)) return null;
|
|
32015
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
32016
|
+
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
32017
|
+
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
32018
|
+
return rising ? `${BEARISH}80` : BEARISH;
|
|
32019
|
+
});
|
|
32020
|
+
}
|
|
32021
|
+
var rsiSpec = {
|
|
32022
|
+
type: "rsi",
|
|
32023
|
+
title: "Relative Strength Index",
|
|
32024
|
+
shortTitle: "RSI",
|
|
32025
|
+
overlay: false,
|
|
32026
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
32027
|
+
compute: (bars, inputs) => ({
|
|
32028
|
+
plots: [
|
|
32029
|
+
{
|
|
32030
|
+
key: "rsi",
|
|
32031
|
+
title: "RSI",
|
|
32032
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
32033
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
32034
|
+
width: 2
|
|
32035
|
+
}
|
|
32036
|
+
],
|
|
32037
|
+
levels: guideLevels(30, 70, 50)
|
|
32038
|
+
})
|
|
32039
|
+
};
|
|
32040
|
+
var stochastic = {
|
|
32041
|
+
type: "stochastic",
|
|
32042
|
+
title: "Stochastic",
|
|
32043
|
+
shortTitle: "Stoch",
|
|
32044
|
+
overlay: false,
|
|
32045
|
+
inputs: [
|
|
32046
|
+
lengthInput(14, "kLength", "%K length"),
|
|
32047
|
+
lengthInput(1, "kSmoothing", "%K smoothing", 500),
|
|
32048
|
+
lengthInput(3, "dLength", "%D smoothing", 500)
|
|
32049
|
+
],
|
|
32050
|
+
compute: (bars, inputs) => {
|
|
32051
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
|
|
32052
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
32053
|
+
return {
|
|
32054
|
+
plots: [
|
|
32055
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
32056
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
32057
|
+
],
|
|
32058
|
+
levels: guideLevels(20, 80)
|
|
32059
|
+
};
|
|
32060
|
+
}
|
|
32061
|
+
};
|
|
32062
|
+
var stochasticRsi = {
|
|
32063
|
+
type: "stochastic-rsi",
|
|
32064
|
+
title: "Stochastic RSI",
|
|
32065
|
+
shortTitle: "Stoch RSI",
|
|
32066
|
+
overlay: false,
|
|
32067
|
+
inputs: [
|
|
32068
|
+
lengthInput(14, "rsiLength", "RSI length"),
|
|
32069
|
+
lengthInput(14, "stochLength", "Stochastic length"),
|
|
32070
|
+
lengthInput(3, "kSmoothing", "%K smoothing", 500),
|
|
32071
|
+
lengthInput(3, "dLength", "%D smoothing", 500),
|
|
32072
|
+
sourceInput()
|
|
32073
|
+
],
|
|
32074
|
+
compute: (bars, inputs) => {
|
|
32075
|
+
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
32076
|
+
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
32077
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
32078
|
+
return {
|
|
32079
|
+
plots: [
|
|
32080
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
32081
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
32082
|
+
],
|
|
32083
|
+
levels: guideLevels(20, 80)
|
|
32084
|
+
};
|
|
32085
|
+
}
|
|
32086
|
+
};
|
|
32087
|
+
var macd = {
|
|
32088
|
+
type: "macd",
|
|
32089
|
+
title: "MACD",
|
|
32090
|
+
overlay: false,
|
|
32091
|
+
inputs: [
|
|
32092
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
32093
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
32094
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
32095
|
+
sourceInput()
|
|
32096
|
+
],
|
|
32097
|
+
compute: (bars, inputs) => {
|
|
32098
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32099
|
+
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
32100
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
32101
|
+
const hist = sub(line, signal);
|
|
32102
|
+
return {
|
|
32103
|
+
plots: [
|
|
32104
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
32105
|
+
{ key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
|
|
32106
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
32107
|
+
],
|
|
32108
|
+
levels: ZERO_LEVEL
|
|
32109
|
+
};
|
|
32110
|
+
}
|
|
32111
|
+
};
|
|
32112
|
+
var ppo = {
|
|
32113
|
+
type: "ppo",
|
|
32114
|
+
title: "Percentage Price Oscillator",
|
|
32115
|
+
shortTitle: "PPO",
|
|
32116
|
+
overlay: false,
|
|
32117
|
+
inputs: [
|
|
32118
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
32119
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
32120
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
32121
|
+
sourceInput()
|
|
32122
|
+
],
|
|
32123
|
+
compute: (bars, inputs) => {
|
|
32124
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32125
|
+
const fast = ema(src, num3(inputs, "fastLength", 12));
|
|
32126
|
+
const slow = ema(src, num3(inputs, "slowLength", 26));
|
|
32127
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
32128
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
32129
|
+
const hist = sub(line, signal);
|
|
32130
|
+
return {
|
|
32131
|
+
plots: [
|
|
32132
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
32133
|
+
{ key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
|
|
32134
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
32135
|
+
],
|
|
32136
|
+
levels: ZERO_LEVEL
|
|
32137
|
+
};
|
|
32138
|
+
}
|
|
32139
|
+
};
|
|
32140
|
+
var awesome = {
|
|
32141
|
+
type: "awesome-oscillator",
|
|
32142
|
+
title: "Awesome Oscillator",
|
|
32143
|
+
shortTitle: "AO",
|
|
32144
|
+
overlay: false,
|
|
32145
|
+
inputs: [],
|
|
32146
|
+
compute: (bars) => {
|
|
32147
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
32148
|
+
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
32149
|
+
const colors = ao.map((x, i) => {
|
|
32150
|
+
if (!Number.isFinite(x)) return null;
|
|
32151
|
+
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
32152
|
+
return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
|
|
32153
|
+
});
|
|
32154
|
+
return {
|
|
32155
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
|
|
32156
|
+
levels: ZERO_LEVEL
|
|
32157
|
+
};
|
|
32158
|
+
}
|
|
32159
|
+
};
|
|
32160
|
+
var cci = {
|
|
32161
|
+
type: "commodity-channel-index",
|
|
32162
|
+
title: "Commodity Channel Index",
|
|
32163
|
+
shortTitle: "CCI",
|
|
32164
|
+
overlay: false,
|
|
32165
|
+
inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
|
|
32166
|
+
compute: (bars, inputs) => {
|
|
32167
|
+
const len = num3(inputs, "length", 20);
|
|
32168
|
+
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
32169
|
+
const basis = sma(src, len);
|
|
32170
|
+
const dev = new Array(src.length).fill(Number.NaN);
|
|
32171
|
+
for (let i = len - 1; i < src.length; i++) {
|
|
32172
|
+
const mean = basis[i];
|
|
32173
|
+
if (!Number.isFinite(mean)) continue;
|
|
32174
|
+
let d = 0;
|
|
32175
|
+
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
32176
|
+
dev[i] = d / len;
|
|
32177
|
+
}
|
|
32178
|
+
const values = src.map((x, i) => {
|
|
32179
|
+
const b = basis[i];
|
|
32180
|
+
const d = dev[i];
|
|
32181
|
+
return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
|
|
32182
|
+
});
|
|
32183
|
+
return {
|
|
32184
|
+
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32185
|
+
levels: guideLevels(-100, 100, 0)
|
|
32186
|
+
};
|
|
32187
|
+
}
|
|
32188
|
+
};
|
|
32189
|
+
var williamsR = {
|
|
32190
|
+
type: "williams-percent-r",
|
|
32191
|
+
title: "Williams %R",
|
|
32192
|
+
shortTitle: "%R",
|
|
32193
|
+
overlay: false,
|
|
32194
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32195
|
+
compute: (bars, inputs) => {
|
|
32196
|
+
const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
|
|
32197
|
+
return {
|
|
32198
|
+
plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32199
|
+
levels: guideLevels(-80, -20)
|
|
32200
|
+
};
|
|
32201
|
+
}
|
|
32202
|
+
};
|
|
32203
|
+
var cmo = {
|
|
32204
|
+
type: "chande-momentum-oscillator",
|
|
32205
|
+
title: "Chande Momentum Oscillator",
|
|
32206
|
+
shortTitle: "CMO",
|
|
32207
|
+
overlay: false,
|
|
32208
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
32209
|
+
compute: (bars, inputs) => {
|
|
32210
|
+
const len = num3(inputs, "length", 9);
|
|
32211
|
+
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
32212
|
+
const su = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
32213
|
+
const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
32214
|
+
const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
32215
|
+
return {
|
|
32216
|
+
plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32217
|
+
levels: guideLevels(-50, 50, 0)
|
|
32218
|
+
};
|
|
32219
|
+
}
|
|
32220
|
+
};
|
|
32221
|
+
var connorsRsi = {
|
|
32222
|
+
type: "connors-rsi",
|
|
32223
|
+
title: "Connors RSI",
|
|
32224
|
+
shortTitle: "CRSI",
|
|
32225
|
+
overlay: false,
|
|
32226
|
+
inputs: [
|
|
32227
|
+
lengthInput(3, "rsiLength", "RSI length"),
|
|
32228
|
+
lengthInput(2, "streakLength", "Streak RSI length"),
|
|
32229
|
+
lengthInput(100, "rankLength", "Percent-rank length"),
|
|
32230
|
+
colorInput()
|
|
32231
|
+
],
|
|
32232
|
+
compute: (bars, inputs) => {
|
|
32233
|
+
const c = closes(bars);
|
|
32234
|
+
const streak = new Array(c.length).fill(0);
|
|
32235
|
+
for (let i = 1; i < c.length; i++) {
|
|
32236
|
+
const d = c[i] - c[i - 1];
|
|
32237
|
+
streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
|
|
32238
|
+
}
|
|
32239
|
+
const a = rsi(c, num3(inputs, "rsiLength", 3));
|
|
32240
|
+
const b = rsi(streak, num3(inputs, "streakLength", 2));
|
|
32241
|
+
const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
|
|
32242
|
+
const values = c.map((_, i) => {
|
|
32243
|
+
const x = a[i];
|
|
32244
|
+
const y = b[i];
|
|
32245
|
+
const z = r[i];
|
|
32246
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
32247
|
+
});
|
|
32248
|
+
return {
|
|
32249
|
+
plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32250
|
+
levels: guideLevels(20, 80, 50)
|
|
32251
|
+
};
|
|
32252
|
+
}
|
|
32253
|
+
};
|
|
32254
|
+
var fisher = {
|
|
32255
|
+
type: "fisher-transform",
|
|
32256
|
+
title: "Fisher Transform",
|
|
32257
|
+
shortTitle: "Fisher",
|
|
32258
|
+
overlay: false,
|
|
32259
|
+
inputs: [lengthInput(9)],
|
|
32260
|
+
compute: (bars, inputs) => {
|
|
32261
|
+
const len = num3(inputs, "length", 9);
|
|
32262
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
32263
|
+
const hi = highest(hl2, len);
|
|
32264
|
+
const lo = lowest(hl2, len);
|
|
32265
|
+
const n = bars.length;
|
|
32266
|
+
const out = new Array(n).fill(Number.NaN);
|
|
32267
|
+
let value = 0;
|
|
32268
|
+
let fish = 0;
|
|
32269
|
+
for (let i = 0; i < n; i++) {
|
|
32270
|
+
const h = hi[i];
|
|
32271
|
+
const l = lo[i];
|
|
32272
|
+
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
32273
|
+
const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
|
|
32274
|
+
value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
|
|
32275
|
+
fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
|
|
32276
|
+
out[i] = fish;
|
|
32277
|
+
}
|
|
32278
|
+
return {
|
|
32279
|
+
plots: [
|
|
32280
|
+
{ key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
|
|
32281
|
+
{ key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
|
|
32282
|
+
],
|
|
32283
|
+
levels: ZERO_LEVEL
|
|
32284
|
+
};
|
|
32285
|
+
}
|
|
32286
|
+
};
|
|
32287
|
+
var trix = {
|
|
32288
|
+
type: "trix",
|
|
32289
|
+
title: "TRIX",
|
|
32290
|
+
overlay: false,
|
|
32291
|
+
inputs: [lengthInput(18), colorInput()],
|
|
32292
|
+
compute: (bars, inputs) => {
|
|
32293
|
+
const len = num3(inputs, "length", 18);
|
|
32294
|
+
const logClose = map(closes(bars), Math.log);
|
|
32295
|
+
const smooth = ema(ema(ema(logClose, len), len), len);
|
|
32296
|
+
const values = map(change(smooth), (x) => x * 1e4);
|
|
32297
|
+
return {
|
|
32298
|
+
plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32299
|
+
levels: ZERO_LEVEL
|
|
32300
|
+
};
|
|
32301
|
+
}
|
|
32302
|
+
};
|
|
32303
|
+
function tsiLine(src, long, short) {
|
|
32304
|
+
const mom = change(src);
|
|
32305
|
+
const numer = ema(ema(mom, long), short);
|
|
32306
|
+
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
32307
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
|
|
32308
|
+
}
|
|
32309
|
+
var tsi = {
|
|
32310
|
+
type: "true-strength-index",
|
|
32311
|
+
title: "True Strength Index",
|
|
32312
|
+
shortTitle: "TSI",
|
|
32313
|
+
overlay: false,
|
|
32314
|
+
inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
|
|
32315
|
+
compute: (bars, inputs) => {
|
|
32316
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
|
|
32317
|
+
return {
|
|
32318
|
+
plots: [
|
|
32319
|
+
{ key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
|
|
32320
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
32321
|
+
],
|
|
32322
|
+
levels: ZERO_LEVEL
|
|
32323
|
+
};
|
|
32324
|
+
}
|
|
32325
|
+
};
|
|
32326
|
+
var smiErgodic = {
|
|
32327
|
+
type: "smi-ergodic",
|
|
32328
|
+
title: "SMI Ergodic Oscillator",
|
|
32329
|
+
shortTitle: "SMIE",
|
|
32330
|
+
overlay: false,
|
|
32331
|
+
inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
|
|
32332
|
+
compute: (bars, inputs) => {
|
|
32333
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
32334
|
+
const signal = ema(line, num3(inputs, "signalLength", 5));
|
|
32335
|
+
const osc = sub(line, signal);
|
|
32336
|
+
return {
|
|
32337
|
+
plots: [
|
|
32338
|
+
{ key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
|
|
32339
|
+
{ key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
|
|
32340
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
32341
|
+
],
|
|
32342
|
+
levels: ZERO_LEVEL
|
|
32343
|
+
};
|
|
32344
|
+
}
|
|
32345
|
+
};
|
|
32346
|
+
var stc = {
|
|
32347
|
+
type: "schaff-trend-cycle",
|
|
32348
|
+
title: "Schaff Trend Cycle",
|
|
32349
|
+
shortTitle: "Schaff Trend Cycle",
|
|
32350
|
+
overlay: false,
|
|
32351
|
+
inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
|
|
32352
|
+
compute: (bars, inputs) => {
|
|
32353
|
+
const len = num3(inputs, "cycleLength", 10);
|
|
32354
|
+
const src = closes(bars);
|
|
32355
|
+
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
32356
|
+
const stochOf = (v) => {
|
|
32357
|
+
const lo = lowest(v, len);
|
|
32358
|
+
const hi = highest(v, len);
|
|
32359
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
32360
|
+
let prevRaw = Number.NaN;
|
|
32361
|
+
let smoothed = Number.NaN;
|
|
32362
|
+
for (let i = 0; i < v.length; i++) {
|
|
32363
|
+
const x = v[i];
|
|
32364
|
+
const l = lo[i];
|
|
32365
|
+
const h = hi[i];
|
|
32366
|
+
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
32367
|
+
const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
|
|
32368
|
+
prevRaw = raw;
|
|
32369
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
|
|
32370
|
+
out[i] = smoothed;
|
|
32371
|
+
}
|
|
32372
|
+
return out;
|
|
32373
|
+
};
|
|
32374
|
+
const values = stochOf(stochOf(macdLine));
|
|
32375
|
+
return {
|
|
32376
|
+
plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
|
|
32377
|
+
levels: guideLevels(25, 75)
|
|
32378
|
+
};
|
|
32379
|
+
}
|
|
32380
|
+
};
|
|
32381
|
+
var kst = {
|
|
32382
|
+
type: "know-sure-thing",
|
|
32383
|
+
title: "Know Sure Thing",
|
|
32384
|
+
shortTitle: "KST",
|
|
32385
|
+
overlay: false,
|
|
32386
|
+
inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
|
|
32387
|
+
compute: (bars, inputs) => {
|
|
32388
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32389
|
+
const line = [
|
|
32390
|
+
sma(roc(src, 10), 10),
|
|
32391
|
+
sma(roc(src, 15), 10),
|
|
32392
|
+
sma(roc(src, 20), 10),
|
|
32393
|
+
sma(roc(src, 30), 15)
|
|
32394
|
+
].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
|
|
32395
|
+
return {
|
|
32396
|
+
plots: [
|
|
32397
|
+
{ key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
|
|
32398
|
+
{ key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
|
|
32399
|
+
],
|
|
32400
|
+
levels: ZERO_LEVEL
|
|
32401
|
+
};
|
|
32402
|
+
}
|
|
32403
|
+
};
|
|
32404
|
+
var coppock = {
|
|
32405
|
+
type: "coppock-curve",
|
|
32406
|
+
title: "Coppock Curve",
|
|
32407
|
+
shortTitle: "Coppock",
|
|
32408
|
+
overlay: false,
|
|
32409
|
+
inputs: [
|
|
32410
|
+
lengthInput(10, "wmaLength", "WMA length"),
|
|
32411
|
+
lengthInput(14, "longRoc", "Long RoC length"),
|
|
32412
|
+
lengthInput(11, "shortRoc", "Short RoC length"),
|
|
32413
|
+
sourceInput(),
|
|
32414
|
+
colorInput()
|
|
32415
|
+
],
|
|
32416
|
+
compute: (bars, inputs) => {
|
|
32417
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32418
|
+
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
32419
|
+
return {
|
|
32420
|
+
plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32421
|
+
levels: ZERO_LEVEL
|
|
32422
|
+
};
|
|
32423
|
+
}
|
|
32424
|
+
};
|
|
32425
|
+
var dpo = {
|
|
32426
|
+
type: "detrended-price-oscillator",
|
|
32427
|
+
title: "Detrended Price Oscillator",
|
|
32428
|
+
shortTitle: "DPO",
|
|
32429
|
+
overlay: false,
|
|
32430
|
+
inputs: [lengthInput(21), colorInput()],
|
|
32431
|
+
compute: (bars, inputs) => {
|
|
32432
|
+
const len = num3(inputs, "length", 21);
|
|
32433
|
+
const barsback = Math.floor(len / 2) + 1;
|
|
32434
|
+
const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
|
|
32435
|
+
return {
|
|
32436
|
+
plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32437
|
+
levels: ZERO_LEVEL
|
|
32438
|
+
};
|
|
32439
|
+
}
|
|
32440
|
+
};
|
|
32441
|
+
var rocSpec = {
|
|
32442
|
+
type: "rate-of-change",
|
|
32443
|
+
title: "Rate of Change",
|
|
32444
|
+
shortTitle: "ROC",
|
|
32445
|
+
overlay: false,
|
|
32446
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
32447
|
+
compute: (bars, inputs) => ({
|
|
32448
|
+
plots: [
|
|
32449
|
+
{
|
|
32450
|
+
key: "roc",
|
|
32451
|
+
title: "RoC",
|
|
32452
|
+
values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
|
|
32453
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
32454
|
+
width: 2
|
|
32455
|
+
}
|
|
32456
|
+
],
|
|
32457
|
+
levels: ZERO_LEVEL
|
|
32458
|
+
})
|
|
32459
|
+
};
|
|
32460
|
+
var ultimate = {
|
|
32461
|
+
type: "ultimate-oscillator",
|
|
32462
|
+
title: "Ultimate Oscillator",
|
|
32463
|
+
shortTitle: "UO",
|
|
32464
|
+
overlay: false,
|
|
32465
|
+
inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
|
|
32466
|
+
compute: (bars, inputs) => {
|
|
32467
|
+
const n = bars.length;
|
|
32468
|
+
const bp = new Array(n).fill(Number.NaN);
|
|
32469
|
+
const tr = new Array(n).fill(Number.NaN);
|
|
32470
|
+
for (let i = 0; i < n; i++) {
|
|
32471
|
+
const b = bars[i];
|
|
32472
|
+
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
32473
|
+
const lo = Math.min(b.low, pc);
|
|
32474
|
+
const hi = Math.max(b.high, pc);
|
|
32475
|
+
bp[i] = b.close - lo;
|
|
32476
|
+
tr[i] = hi - lo;
|
|
32477
|
+
}
|
|
32478
|
+
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
32479
|
+
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
32480
|
+
const a14 = avg(num3(inputs, "middleLength", 14));
|
|
32481
|
+
const a28 = avg(num3(inputs, "slowLength", 28));
|
|
32482
|
+
const values = a7.map((x, i) => {
|
|
32483
|
+
const y = a14[i];
|
|
32484
|
+
const z = a28[i];
|
|
32485
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
32486
|
+
});
|
|
32487
|
+
return {
|
|
32488
|
+
plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32489
|
+
levels: guideLevels(30, 70, 50)
|
|
32490
|
+
};
|
|
32491
|
+
}
|
|
32492
|
+
};
|
|
32493
|
+
var rvgi = {
|
|
32494
|
+
type: "relative-vigor-index",
|
|
32495
|
+
title: "Relative Vigor Index",
|
|
32496
|
+
shortTitle: "RVGI",
|
|
32497
|
+
overlay: false,
|
|
32498
|
+
inputs: [lengthInput(10)],
|
|
32499
|
+
compute: (bars, inputs) => {
|
|
32500
|
+
const len = num3(inputs, "length", 10);
|
|
32501
|
+
const co = swma(sub(closes(bars), opens(bars)));
|
|
32502
|
+
const hl = swma(sub(highs(bars), lows(bars)));
|
|
32503
|
+
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
32504
|
+
return {
|
|
32505
|
+
plots: [
|
|
32506
|
+
{ key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
|
|
32507
|
+
{ key: "signal", title: "Signal", values: swma(line), color: WARNING }
|
|
32508
|
+
],
|
|
32509
|
+
levels: ZERO_LEVEL
|
|
32510
|
+
};
|
|
32511
|
+
}
|
|
32512
|
+
};
|
|
32513
|
+
var relVolatility = {
|
|
32514
|
+
type: "relative-volatility-index",
|
|
32515
|
+
title: "Relative Volatility Index",
|
|
32516
|
+
shortTitle: "RVI",
|
|
32517
|
+
overlay: false,
|
|
32518
|
+
inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
|
|
32519
|
+
compute: (bars, inputs) => {
|
|
32520
|
+
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
32521
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32522
|
+
const dev = stdev(src, num3(inputs, "stdevLength", 10));
|
|
32523
|
+
const d = change(src);
|
|
32524
|
+
const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
|
|
32525
|
+
const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
|
|
32526
|
+
const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
|
|
32527
|
+
return {
|
|
32528
|
+
plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32529
|
+
levels: guideLevels(20, 80, 50)
|
|
32530
|
+
};
|
|
32531
|
+
}
|
|
32532
|
+
};
|
|
32533
|
+
var bop = {
|
|
32534
|
+
type: "balance-of-power",
|
|
32535
|
+
title: "Balance of Power",
|
|
32536
|
+
shortTitle: "BOP",
|
|
32537
|
+
overlay: false,
|
|
32538
|
+
inputs: [colorInput()],
|
|
32539
|
+
compute: (bars, inputs) => {
|
|
32540
|
+
const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
32541
|
+
return {
|
|
32542
|
+
plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32543
|
+
levels: ZERO_LEVEL
|
|
32544
|
+
};
|
|
32545
|
+
}
|
|
32546
|
+
};
|
|
32547
|
+
var elderRay = {
|
|
32548
|
+
type: "elder-ray",
|
|
32549
|
+
title: "Elder Ray",
|
|
32550
|
+
overlay: false,
|
|
32551
|
+
inputs: [lengthInput(13)],
|
|
32552
|
+
compute: (bars, inputs) => {
|
|
32553
|
+
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
32554
|
+
return {
|
|
32555
|
+
plots: [
|
|
32556
|
+
{ key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
|
|
32557
|
+
{ key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
|
|
32558
|
+
],
|
|
32559
|
+
levels: ZERO_LEVEL
|
|
32560
|
+
};
|
|
32561
|
+
}
|
|
32562
|
+
};
|
|
32563
|
+
var ttmSqueeze = {
|
|
32564
|
+
type: "ttm-squeeze",
|
|
32565
|
+
title: "TTM Squeeze",
|
|
32566
|
+
overlay: false,
|
|
32567
|
+
inputs: [
|
|
32568
|
+
lengthInput(20),
|
|
32569
|
+
{ key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
32570
|
+
{ key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
|
|
32571
|
+
],
|
|
32572
|
+
compute: (bars, inputs) => {
|
|
32573
|
+
const len = num3(inputs, "length", 20);
|
|
32574
|
+
const c = closes(bars);
|
|
32575
|
+
const basis = sma(c, len);
|
|
32576
|
+
const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
32577
|
+
const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
32578
|
+
const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
32579
|
+
const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
|
|
32580
|
+
const mom = linreg(sub(c, anchor), len);
|
|
32581
|
+
const squeezed = c.map((_, i) => {
|
|
32582
|
+
const d = dev[i];
|
|
32583
|
+
const r = kcRange[i];
|
|
32584
|
+
return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
|
|
32585
|
+
});
|
|
32586
|
+
const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
|
|
32587
|
+
return {
|
|
32588
|
+
plots: [
|
|
32589
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
|
|
32590
|
+
{ key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
|
|
32591
|
+
]
|
|
32592
|
+
};
|
|
32593
|
+
}
|
|
32594
|
+
};
|
|
32595
|
+
var oscillatorSpecs = [
|
|
32596
|
+
rsiSpec,
|
|
32597
|
+
stochastic,
|
|
32598
|
+
stochasticRsi,
|
|
32599
|
+
macd,
|
|
32600
|
+
ppo,
|
|
32601
|
+
awesome,
|
|
32602
|
+
cci,
|
|
32603
|
+
williamsR,
|
|
32604
|
+
cmo,
|
|
32605
|
+
connorsRsi,
|
|
32606
|
+
fisher,
|
|
32607
|
+
trix,
|
|
32608
|
+
tsi,
|
|
32609
|
+
smiErgodic,
|
|
32610
|
+
stc,
|
|
32611
|
+
kst,
|
|
32612
|
+
coppock,
|
|
32613
|
+
dpo,
|
|
32614
|
+
rocSpec,
|
|
32615
|
+
ultimate,
|
|
32616
|
+
rvgi,
|
|
32617
|
+
relVolatility,
|
|
32618
|
+
bop,
|
|
32619
|
+
elderRay,
|
|
32620
|
+
ttmSqueeze
|
|
32621
|
+
];
|
|
32622
|
+
|
|
32623
|
+
// src/core/native-indicators/classics/trend.ts
|
|
32624
|
+
var aroon = {
|
|
32625
|
+
type: "aroon",
|
|
32626
|
+
title: "Aroon",
|
|
32627
|
+
overlay: false,
|
|
32628
|
+
inputs: [lengthInput(14)],
|
|
32629
|
+
compute: (bars, inputs) => {
|
|
32630
|
+
const len = num3(inputs, "length", 14);
|
|
32631
|
+
const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
32632
|
+
const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
32633
|
+
return {
|
|
32634
|
+
plots: [
|
|
32635
|
+
{ key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
|
|
32636
|
+
{ key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
|
|
32637
|
+
],
|
|
32638
|
+
levels: [
|
|
32639
|
+
{ key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
|
|
32640
|
+
{ key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
|
|
32641
|
+
]
|
|
32642
|
+
};
|
|
32643
|
+
}
|
|
32644
|
+
};
|
|
32645
|
+
var adx = {
|
|
32646
|
+
type: "average-directional-index",
|
|
32647
|
+
title: "Average Directional Index",
|
|
32648
|
+
shortTitle: "ADX",
|
|
32649
|
+
overlay: false,
|
|
32650
|
+
inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
|
|
32651
|
+
compute: (bars, inputs) => {
|
|
32652
|
+
const diLen = num3(inputs, "diLength", 14);
|
|
32653
|
+
const adxLen = num3(inputs, "adxLength", 14);
|
|
32654
|
+
const n = bars.length;
|
|
32655
|
+
const plusDm = new Array(n).fill(Number.NaN);
|
|
32656
|
+
const minusDm = new Array(n).fill(Number.NaN);
|
|
32657
|
+
for (let i = 1; i < n; i++) {
|
|
32658
|
+
const up = bars[i].high - bars[i - 1].high;
|
|
32659
|
+
const dn = bars[i - 1].low - bars[i].low;
|
|
32660
|
+
plusDm[i] = up > dn && up > 0 ? up : 0;
|
|
32661
|
+
minusDm[i] = dn > up && dn > 0 ? dn : 0;
|
|
32662
|
+
}
|
|
32663
|
+
const atrLine = rma(trueRange(bars), diLen);
|
|
32664
|
+
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
32665
|
+
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
32666
|
+
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
32667
|
+
return {
|
|
32668
|
+
plots: [
|
|
32669
|
+
{ key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
|
|
32670
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
|
|
32671
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
|
|
32672
|
+
],
|
|
32673
|
+
levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
|
|
32674
|
+
};
|
|
32675
|
+
}
|
|
32676
|
+
};
|
|
32677
|
+
var vortex = {
|
|
32678
|
+
type: "vortex-indicator",
|
|
32679
|
+
title: "Vortex Indicator",
|
|
32680
|
+
shortTitle: "VI",
|
|
32681
|
+
overlay: false,
|
|
32682
|
+
inputs: [lengthInput(14)],
|
|
32683
|
+
compute: (bars, inputs) => {
|
|
32684
|
+
const len = num3(inputs, "length", 14);
|
|
32685
|
+
const n = bars.length;
|
|
32686
|
+
const vmPlus = new Array(n).fill(Number.NaN);
|
|
32687
|
+
const vmMinus = new Array(n).fill(Number.NaN);
|
|
32688
|
+
for (let i = 1; i < n; i++) {
|
|
32689
|
+
vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
|
|
32690
|
+
vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
|
|
32691
|
+
}
|
|
32692
|
+
const trSum = sum(trueRange(bars), len);
|
|
32693
|
+
return {
|
|
32694
|
+
plots: [
|
|
32695
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
|
|
32696
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
|
|
32697
|
+
],
|
|
32698
|
+
levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
|
|
32699
|
+
};
|
|
32700
|
+
}
|
|
32701
|
+
};
|
|
32702
|
+
var trendSpecs = [aroon, adx, vortex];
|
|
32703
|
+
|
|
32704
|
+
// src/core/native-indicators/classics/volatility.ts
|
|
32705
|
+
var atrSpec = {
|
|
32706
|
+
type: "average-true-range",
|
|
32707
|
+
title: "Average True Range",
|
|
32708
|
+
shortTitle: "ATR",
|
|
32709
|
+
overlay: false,
|
|
32710
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32711
|
+
compute: (bars, inputs) => ({
|
|
32712
|
+
plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
32713
|
+
})
|
|
32714
|
+
};
|
|
32715
|
+
var historicalVolatility = {
|
|
32716
|
+
type: "historical-volatility",
|
|
32717
|
+
title: "Historical Volatility",
|
|
32718
|
+
shortTitle: "HV",
|
|
32719
|
+
overlay: false,
|
|
32720
|
+
inputs: [
|
|
32721
|
+
lengthInput(10),
|
|
32722
|
+
{ key: "annual", title: "Periods per year", type: "int", defval: 365, min: 1, max: 1e5, step: 1, tooltip: "Annualization factor (365 for daily crypto bars, 252 for stock sessions)" },
|
|
32723
|
+
colorInput()
|
|
32724
|
+
],
|
|
32725
|
+
compute: (bars, inputs) => {
|
|
32726
|
+
const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
|
|
32727
|
+
const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
|
|
32728
|
+
return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
32729
|
+
}
|
|
32730
|
+
};
|
|
32731
|
+
var chaikinVolatility = {
|
|
32732
|
+
type: "chaikin-volatility",
|
|
32733
|
+
title: "Chaikin Volatility",
|
|
32734
|
+
shortTitle: "CHV",
|
|
32735
|
+
overlay: false,
|
|
32736
|
+
inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
|
|
32737
|
+
compute: (bars, inputs) => {
|
|
32738
|
+
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
32739
|
+
return {
|
|
32740
|
+
plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32741
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32742
|
+
};
|
|
32743
|
+
}
|
|
32744
|
+
};
|
|
32745
|
+
var massIndex = {
|
|
32746
|
+
type: "mass-index",
|
|
32747
|
+
title: "Mass Index",
|
|
32748
|
+
shortTitle: "MI",
|
|
32749
|
+
overlay: false,
|
|
32750
|
+
inputs: [lengthInput(25), colorInput()],
|
|
32751
|
+
compute: (bars, inputs) => {
|
|
32752
|
+
const range = sub(highs(bars), lows(bars));
|
|
32753
|
+
const single = ema(range, 9);
|
|
32754
|
+
const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
32755
|
+
return {
|
|
32756
|
+
plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32757
|
+
levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
|
|
32758
|
+
};
|
|
32759
|
+
}
|
|
32760
|
+
};
|
|
32761
|
+
var standardDeviation = {
|
|
32762
|
+
type: "standard-deviation",
|
|
32763
|
+
title: "Standard Deviation",
|
|
32764
|
+
shortTitle: "StdDev",
|
|
32765
|
+
overlay: false,
|
|
32766
|
+
inputs: [lengthInput(20), sourceInput(), colorInput()],
|
|
32767
|
+
compute: (bars, inputs) => ({
|
|
32768
|
+
plots: [
|
|
32769
|
+
{
|
|
32770
|
+
key: "stdev",
|
|
32771
|
+
title: "StdDev",
|
|
32772
|
+
values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
|
|
32773
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
32774
|
+
width: 2
|
|
32775
|
+
}
|
|
32776
|
+
]
|
|
32777
|
+
})
|
|
32778
|
+
};
|
|
32779
|
+
var ulcerIndex = {
|
|
32780
|
+
type: "ulcer-index",
|
|
32781
|
+
title: "Ulcer Index",
|
|
32782
|
+
overlay: false,
|
|
32783
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32784
|
+
compute: (bars, inputs) => {
|
|
32785
|
+
const len = num3(inputs, "length", 14);
|
|
32786
|
+
const c = closes(bars);
|
|
32787
|
+
const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
|
|
32788
|
+
const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
|
|
32789
|
+
return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
32790
|
+
}
|
|
32791
|
+
};
|
|
32792
|
+
var choppiness = {
|
|
32793
|
+
type: "choppiness-index",
|
|
32794
|
+
title: "Choppiness Index",
|
|
32795
|
+
shortTitle: "CHOP",
|
|
32796
|
+
overlay: false,
|
|
32797
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32798
|
+
compute: (bars, inputs) => {
|
|
32799
|
+
const len = num3(inputs, "length", 14);
|
|
32800
|
+
const trSum = sum(trueRange(bars), len);
|
|
32801
|
+
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
32802
|
+
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
32803
|
+
return {
|
|
32804
|
+
plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32805
|
+
levels: [
|
|
32806
|
+
{ key: "upper", price: 61.8, color: NEUTRAL },
|
|
32807
|
+
{ key: "lower", price: 38.2, color: NEUTRAL }
|
|
32808
|
+
]
|
|
32809
|
+
};
|
|
32810
|
+
}
|
|
32811
|
+
};
|
|
32812
|
+
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
32813
|
+
|
|
32814
|
+
// src/core/native-indicators/classics/volume.ts
|
|
32815
|
+
function moneyFlowVolume(bars) {
|
|
32816
|
+
return bars.map((b) => {
|
|
32817
|
+
if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
32818
|
+
if (b.high === b.low) return 0;
|
|
32819
|
+
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
32820
|
+
});
|
|
32821
|
+
}
|
|
32822
|
+
var obv = {
|
|
32823
|
+
type: "on-balance-volume",
|
|
32824
|
+
title: "On Balance Volume",
|
|
32825
|
+
shortTitle: "OBV",
|
|
32826
|
+
overlay: false,
|
|
32827
|
+
inputs: [colorInput()],
|
|
32828
|
+
compute: (bars, inputs) => {
|
|
32829
|
+
const vol = volumes(bars);
|
|
32830
|
+
const d = change(closes(bars));
|
|
32831
|
+
const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
|
|
32832
|
+
return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
32833
|
+
}
|
|
32834
|
+
};
|
|
32835
|
+
var accDist = {
|
|
32836
|
+
type: "accumulation-distribution",
|
|
32837
|
+
title: "Accumulation / Distribution",
|
|
32838
|
+
shortTitle: "A/D",
|
|
32839
|
+
overlay: false,
|
|
32840
|
+
inputs: [colorInput()],
|
|
32841
|
+
compute: (bars, inputs) => ({
|
|
32842
|
+
plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
32843
|
+
})
|
|
32844
|
+
};
|
|
32845
|
+
var cmf = {
|
|
32846
|
+
type: "chaikin-money-flow",
|
|
32847
|
+
title: "Chaikin Money Flow",
|
|
32848
|
+
shortTitle: "CMF",
|
|
32849
|
+
overlay: false,
|
|
32850
|
+
inputs: [lengthInput(20), colorInput()],
|
|
32851
|
+
compute: (bars, inputs) => {
|
|
32852
|
+
const len = num3(inputs, "length", 20);
|
|
32853
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
32854
|
+
return {
|
|
32855
|
+
plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32856
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32857
|
+
};
|
|
32858
|
+
}
|
|
32859
|
+
};
|
|
32860
|
+
var chaikinOscillator = {
|
|
32861
|
+
type: "chaikin-oscillator",
|
|
32862
|
+
title: "Chaikin Oscillator",
|
|
32863
|
+
shortTitle: "Chaikin Osc",
|
|
32864
|
+
overlay: false,
|
|
32865
|
+
inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
32866
|
+
compute: (bars, inputs) => {
|
|
32867
|
+
const ad = cumSum(moneyFlowVolume(bars));
|
|
32868
|
+
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
32869
|
+
return {
|
|
32870
|
+
plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32871
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32872
|
+
};
|
|
32873
|
+
}
|
|
32874
|
+
};
|
|
32875
|
+
var eom = {
|
|
32876
|
+
type: "ease-of-movement",
|
|
32877
|
+
title: "Ease of Movement",
|
|
32878
|
+
shortTitle: "EOM",
|
|
32879
|
+
overlay: false,
|
|
32880
|
+
inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
|
|
32881
|
+
compute: (bars, inputs) => {
|
|
32882
|
+
const div = num3(inputs, "divisor", 1e4);
|
|
32883
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
32884
|
+
const move = change(hl2);
|
|
32885
|
+
const raw = bars.map((b, i) => {
|
|
32886
|
+
const m = move[i];
|
|
32887
|
+
if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
|
|
32888
|
+
return div * m * (b.high - b.low) / b.volume;
|
|
32889
|
+
});
|
|
32890
|
+
return {
|
|
32891
|
+
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32892
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32893
|
+
};
|
|
32894
|
+
}
|
|
32895
|
+
};
|
|
32896
|
+
var forceIndex = {
|
|
32897
|
+
type: "force-index",
|
|
32898
|
+
title: "Force Index",
|
|
32899
|
+
shortTitle: "FI",
|
|
32900
|
+
overlay: false,
|
|
32901
|
+
inputs: [lengthInput(13), colorInput()],
|
|
32902
|
+
compute: (bars, inputs) => {
|
|
32903
|
+
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
32904
|
+
return {
|
|
32905
|
+
plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32906
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32907
|
+
};
|
|
32908
|
+
}
|
|
32909
|
+
};
|
|
32910
|
+
var klinger = {
|
|
32911
|
+
type: "klinger-oscillator",
|
|
32912
|
+
title: "Klinger Oscillator",
|
|
32913
|
+
shortTitle: "KVO",
|
|
32914
|
+
overlay: false,
|
|
32915
|
+
inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
|
|
32916
|
+
compute: (bars, inputs) => {
|
|
32917
|
+
const hlc3 = sourceValues(bars, "HLC3");
|
|
32918
|
+
const trend = change(hlc3);
|
|
32919
|
+
const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
|
|
32920
|
+
const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
|
|
32921
|
+
return {
|
|
32922
|
+
plots: [
|
|
32923
|
+
{ key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
|
|
32924
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
32925
|
+
],
|
|
32926
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32927
|
+
};
|
|
32928
|
+
}
|
|
32929
|
+
};
|
|
32930
|
+
var mfi = {
|
|
32931
|
+
type: "money-flow-index",
|
|
32932
|
+
title: "Money Flow Index",
|
|
32933
|
+
shortTitle: "MFI",
|
|
32934
|
+
overlay: false,
|
|
32935
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32936
|
+
compute: (bars, inputs) => {
|
|
32937
|
+
const len = num3(inputs, "length", 14);
|
|
32938
|
+
const tp = sourceValues(bars, "HLC3");
|
|
32939
|
+
const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
|
|
32940
|
+
const d = change(tp);
|
|
32941
|
+
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
32942
|
+
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
32943
|
+
const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
|
|
32944
|
+
return {
|
|
32945
|
+
plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32946
|
+
levels: [
|
|
32947
|
+
{ key: "upper", price: 80, color: NEUTRAL },
|
|
32948
|
+
{ key: "lower", price: 20, color: NEUTRAL }
|
|
32949
|
+
]
|
|
32950
|
+
};
|
|
32951
|
+
}
|
|
32952
|
+
};
|
|
32953
|
+
function volumeIndex(bars, when) {
|
|
32954
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
32955
|
+
let index = 1e3;
|
|
32956
|
+
let started = false;
|
|
32957
|
+
for (let i = 1; i < bars.length; i++) {
|
|
32958
|
+
const v = bars[i].volume;
|
|
32959
|
+
const pv = bars[i - 1].volume;
|
|
32960
|
+
const pc = bars[i - 1].close;
|
|
32961
|
+
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
32962
|
+
if (started) out[i] = index;
|
|
32963
|
+
continue;
|
|
32964
|
+
}
|
|
32965
|
+
started = true;
|
|
32966
|
+
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
32967
|
+
out[i] = index;
|
|
32968
|
+
}
|
|
32969
|
+
return out;
|
|
32970
|
+
}
|
|
32971
|
+
var nvi = {
|
|
32972
|
+
type: "negative-volume-index",
|
|
32973
|
+
title: "Negative Volume Index",
|
|
32974
|
+
shortTitle: "Negative Volume Index",
|
|
32975
|
+
overlay: false,
|
|
32976
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
32977
|
+
compute: (bars, inputs) => {
|
|
32978
|
+
const line = volumeIndex(bars, (v, pv) => v < pv);
|
|
32979
|
+
return {
|
|
32980
|
+
plots: [
|
|
32981
|
+
{ key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
32982
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
32983
|
+
]
|
|
32984
|
+
};
|
|
32985
|
+
}
|
|
32986
|
+
};
|
|
32987
|
+
var pvi = {
|
|
32988
|
+
type: "positive-volume-index",
|
|
32989
|
+
title: "Positive Volume Index",
|
|
32990
|
+
shortTitle: "Positive Volume Index",
|
|
32991
|
+
overlay: false,
|
|
32992
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
32993
|
+
compute: (bars, inputs) => {
|
|
32994
|
+
const line = volumeIndex(bars, (v, pv) => v > pv);
|
|
32995
|
+
return {
|
|
32996
|
+
plots: [
|
|
32997
|
+
{ key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
32998
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
32999
|
+
]
|
|
33000
|
+
};
|
|
33001
|
+
}
|
|
33002
|
+
};
|
|
33003
|
+
var pvt = {
|
|
33004
|
+
type: "price-volume-trend",
|
|
33005
|
+
title: "Price Volume Trend",
|
|
33006
|
+
shortTitle: "PVT",
|
|
33007
|
+
overlay: false,
|
|
33008
|
+
inputs: [colorInput()],
|
|
33009
|
+
compute: (bars, inputs) => {
|
|
33010
|
+
const c = closes(bars);
|
|
33011
|
+
const raw = bars.map((b, i) => {
|
|
33012
|
+
if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
33013
|
+
const pc = c[i - 1];
|
|
33014
|
+
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
33015
|
+
});
|
|
33016
|
+
return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
33017
|
+
}
|
|
33018
|
+
};
|
|
33019
|
+
var volumeOscillator = {
|
|
33020
|
+
type: "volume-oscillator",
|
|
33021
|
+
title: "Volume Oscillator",
|
|
33022
|
+
shortTitle: "Vol Osc",
|
|
33023
|
+
overlay: false,
|
|
33024
|
+
inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
33025
|
+
compute: (bars, inputs) => {
|
|
33026
|
+
const vol = volumes(bars);
|
|
33027
|
+
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
33028
|
+
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
33029
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
33030
|
+
return {
|
|
33031
|
+
plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
33032
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
33033
|
+
};
|
|
33034
|
+
}
|
|
33035
|
+
};
|
|
33036
|
+
var pvo = {
|
|
33037
|
+
type: "pvo",
|
|
33038
|
+
title: "Percentage Volume Oscillator",
|
|
33039
|
+
shortTitle: "PVO",
|
|
33040
|
+
overlay: false,
|
|
33041
|
+
inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
|
|
33042
|
+
compute: (bars, inputs) => {
|
|
33043
|
+
const vol = volumes(bars);
|
|
33044
|
+
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
33045
|
+
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
33046
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
33047
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
33048
|
+
const hist = sub(line, signal);
|
|
33049
|
+
return {
|
|
33050
|
+
plots: [
|
|
33051
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
|
|
33052
|
+
{ key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
|
|
33053
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
33054
|
+
],
|
|
33055
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
33056
|
+
};
|
|
33057
|
+
}
|
|
33058
|
+
};
|
|
33059
|
+
var vfi = {
|
|
33060
|
+
type: "volume-flow-indicator",
|
|
33061
|
+
title: "Volume Flow Indicator",
|
|
33062
|
+
shortTitle: "Volume Flow Indicator",
|
|
33063
|
+
overlay: false,
|
|
33064
|
+
inputs: [
|
|
33065
|
+
lengthInput(130),
|
|
33066
|
+
{ key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
|
|
33067
|
+
{ key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
|
|
33068
|
+
lengthInput(3, "smoothLength", "Smoothing length")
|
|
33069
|
+
],
|
|
33070
|
+
compute: (bars, inputs) => {
|
|
33071
|
+
const len = num3(inputs, "length", 130);
|
|
33072
|
+
const coef = num3(inputs, "coef", 0.2);
|
|
33073
|
+
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
33074
|
+
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
33075
|
+
const inter = change(typical);
|
|
33076
|
+
const vInter = stdev(inter, 30);
|
|
33077
|
+
const vol = volumes(bars);
|
|
33078
|
+
const vAve = sma(vol, len);
|
|
33079
|
+
const mf = change(sourceValues(bars, "HLC3"));
|
|
33080
|
+
const raw = bars.map((b, i) => {
|
|
33081
|
+
const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
|
|
33082
|
+
const v = vol[i];
|
|
33083
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
33084
|
+
const m = mf[i];
|
|
33085
|
+
if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
|
|
33086
|
+
const capped = Math.min(v, ave * volCoef);
|
|
33087
|
+
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
33088
|
+
});
|
|
33089
|
+
const values = bars.map((_, i) => {
|
|
33090
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
33091
|
+
return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
|
|
33092
|
+
});
|
|
33093
|
+
const flowSum = sum(raw, len);
|
|
33094
|
+
const line = zip(flowSum, values, (f, a) => f / a);
|
|
33095
|
+
return {
|
|
33096
|
+
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
33097
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
33098
|
+
};
|
|
33099
|
+
}
|
|
33100
|
+
};
|
|
33101
|
+
var intradayIntensity = {
|
|
33102
|
+
type: "intraday-intensity",
|
|
33103
|
+
title: "Intraday Intensity",
|
|
33104
|
+
shortTitle: "Intraday Intensity",
|
|
33105
|
+
overlay: false,
|
|
33106
|
+
inputs: [lengthInput(21), colorInput()],
|
|
33107
|
+
compute: (bars, inputs) => {
|
|
33108
|
+
const len = num3(inputs, "length", 21);
|
|
33109
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
|
|
33110
|
+
return {
|
|
33111
|
+
plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
33112
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
33113
|
+
};
|
|
33114
|
+
}
|
|
33115
|
+
};
|
|
33116
|
+
var volumeSpecs = [
|
|
33117
|
+
obv,
|
|
33118
|
+
accDist,
|
|
33119
|
+
cmf,
|
|
33120
|
+
chaikinOscillator,
|
|
33121
|
+
eom,
|
|
33122
|
+
forceIndex,
|
|
33123
|
+
klinger,
|
|
33124
|
+
mfi,
|
|
33125
|
+
nvi,
|
|
33126
|
+
pvi,
|
|
33127
|
+
pvt,
|
|
33128
|
+
volumeOscillator,
|
|
33129
|
+
pvo,
|
|
33130
|
+
vfi,
|
|
33131
|
+
intradayIntensity
|
|
33132
|
+
];
|
|
33133
|
+
|
|
33134
|
+
// src/core/native-indicators/classics/overlays.ts
|
|
33135
|
+
var DAY_MS2 = 864e5;
|
|
33136
|
+
function periodKey(anchor, time) {
|
|
33137
|
+
if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
|
|
33138
|
+
if (anchor === "Month") {
|
|
33139
|
+
const d = new Date(time);
|
|
33140
|
+
return d.getUTCFullYear() * 12 + d.getUTCMonth();
|
|
33141
|
+
}
|
|
33142
|
+
return Math.floor(time / DAY_MS2);
|
|
33143
|
+
}
|
|
33144
|
+
var parabolicSar = {
|
|
33145
|
+
type: "parabolic-sar",
|
|
33146
|
+
title: "Parabolic SAR",
|
|
33147
|
+
shortTitle: "SAR",
|
|
33148
|
+
overlay: true,
|
|
33149
|
+
inputs: [
|
|
33150
|
+
{ key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
33151
|
+
{ key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
33152
|
+
{ key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
|
|
33153
|
+
],
|
|
33154
|
+
compute: (bars, inputs) => {
|
|
33155
|
+
const start = num3(inputs, "start", 0.02);
|
|
33156
|
+
const inc = num3(inputs, "increment", 0.02);
|
|
33157
|
+
const max = num3(inputs, "maximum", 0.2);
|
|
33158
|
+
const n = bars.length;
|
|
33159
|
+
const values = new Array(n).fill(Number.NaN);
|
|
33160
|
+
const colors = new Array(n).fill(null);
|
|
33161
|
+
if (n >= 2) {
|
|
33162
|
+
let long = bars[1].close >= bars[0].close;
|
|
33163
|
+
let sar = long ? bars[0].low : bars[0].high;
|
|
33164
|
+
let ep = long ? bars[0].high : bars[0].low;
|
|
33165
|
+
let af = start;
|
|
33166
|
+
for (let i = 1; i < n; i++) {
|
|
33167
|
+
const b = bars[i];
|
|
33168
|
+
sar += af * (ep - sar);
|
|
33169
|
+
if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
|
|
33170
|
+
else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
|
|
33171
|
+
const reversed = long ? b.low < sar : b.high > sar;
|
|
33172
|
+
if (reversed) {
|
|
33173
|
+
long = !long;
|
|
33174
|
+
sar = ep;
|
|
33175
|
+
ep = long ? b.high : b.low;
|
|
33176
|
+
af = start;
|
|
33177
|
+
} else if (long && b.high > ep) {
|
|
33178
|
+
ep = b.high;
|
|
33179
|
+
af = Math.min(max, af + inc);
|
|
33180
|
+
} else if (!long && b.low < ep) {
|
|
33181
|
+
ep = b.low;
|
|
33182
|
+
af = Math.min(max, af + inc);
|
|
33183
|
+
}
|
|
33184
|
+
values[i] = sar;
|
|
33185
|
+
colors[i] = long ? BULLISH : BEARISH;
|
|
33186
|
+
}
|
|
33187
|
+
}
|
|
33188
|
+
return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
|
|
33189
|
+
}
|
|
33190
|
+
};
|
|
33191
|
+
var vwap = {
|
|
33192
|
+
type: "vwap",
|
|
33193
|
+
title: "Volume Weighted Average Price",
|
|
33194
|
+
shortTitle: "VWAP",
|
|
33195
|
+
overlay: true,
|
|
33196
|
+
inputs: [
|
|
33197
|
+
{ key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
|
|
33198
|
+
{ key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
|
|
33199
|
+
colorInput(INFO)
|
|
33200
|
+
],
|
|
33201
|
+
compute: (bars, inputs) => {
|
|
33202
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
33203
|
+
const source = str3(inputs, "source", "HLC3");
|
|
33204
|
+
const values = new Array(bars.length).fill(Number.NaN);
|
|
33205
|
+
let period = Number.NaN;
|
|
33206
|
+
let cumPV = 0;
|
|
33207
|
+
let cumV = 0;
|
|
33208
|
+
for (let i = 0; i < bars.length; i++) {
|
|
33209
|
+
const b = bars[i];
|
|
33210
|
+
const key = periodKey(anchor, b.time);
|
|
33211
|
+
if (key !== period) {
|
|
33212
|
+
period = key;
|
|
33213
|
+
cumPV = 0;
|
|
33214
|
+
cumV = 0;
|
|
33215
|
+
}
|
|
33216
|
+
const v = b.volume;
|
|
33217
|
+
if (v != null && Number.isFinite(v) && v > 0) {
|
|
33218
|
+
const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
|
|
33219
|
+
cumPV += tp * v;
|
|
33220
|
+
cumV += v;
|
|
33221
|
+
}
|
|
33222
|
+
if (cumV > 0) values[i] = cumPV / cumV;
|
|
33223
|
+
}
|
|
33224
|
+
return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
33225
|
+
}
|
|
33226
|
+
};
|
|
33227
|
+
var pivotPoints = {
|
|
33228
|
+
type: "pivot-points",
|
|
33229
|
+
title: "Pivot Points",
|
|
33230
|
+
shortTitle: "Pivots",
|
|
33231
|
+
overlay: true,
|
|
33232
|
+
inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
|
|
33233
|
+
compute: (bars, inputs) => {
|
|
33234
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
33235
|
+
const aggs = /* @__PURE__ */ new Map();
|
|
33236
|
+
for (const b of bars) {
|
|
33237
|
+
const key = periodKey(anchor, b.time);
|
|
33238
|
+
const a = aggs.get(key);
|
|
33239
|
+
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
33240
|
+
else {
|
|
33241
|
+
a.high = Math.max(a.high, b.high);
|
|
33242
|
+
a.low = Math.min(a.low, b.low);
|
|
33243
|
+
a.close = b.close;
|
|
33244
|
+
}
|
|
33245
|
+
}
|
|
33246
|
+
const n = bars.length;
|
|
33247
|
+
const mk = () => new Array(n).fill(Number.NaN);
|
|
33248
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
33249
|
+
for (let i = 0; i < n; i++) {
|
|
33250
|
+
const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
|
|
33251
|
+
if (!prev2) continue;
|
|
33252
|
+
const p = (prev2.high + prev2.low + prev2.close) / 3;
|
|
33253
|
+
levels.p[i] = p;
|
|
33254
|
+
levels.r1[i] = 2 * p - prev2.low;
|
|
33255
|
+
levels.s1[i] = 2 * p - prev2.high;
|
|
33256
|
+
levels.r2[i] = p + (prev2.high - prev2.low);
|
|
33257
|
+
levels.s2[i] = p - (prev2.high - prev2.low);
|
|
33258
|
+
levels.r3[i] = prev2.high + 2 * (p - prev2.low);
|
|
33259
|
+
levels.s3[i] = prev2.low - 2 * (prev2.high - p);
|
|
33260
|
+
}
|
|
33261
|
+
const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
|
|
33262
|
+
return {
|
|
33263
|
+
plots: [
|
|
33264
|
+
plot("p", "P", WARNING),
|
|
33265
|
+
plot("r1", "R1", BEARISH),
|
|
33266
|
+
plot("s1", "S1", BULLISH),
|
|
33267
|
+
plot("r2", "R2", BEARISH),
|
|
33268
|
+
plot("s2", "S2", BULLISH),
|
|
33269
|
+
plot("r3", "R3", BEARISH),
|
|
33270
|
+
plot("s3", "S3", BULLISH)
|
|
33271
|
+
]
|
|
33272
|
+
};
|
|
33273
|
+
}
|
|
33274
|
+
};
|
|
33275
|
+
var fiftyTwoWeek = {
|
|
33276
|
+
type: "52-week-high-low",
|
|
33277
|
+
title: "52 Week High/Low",
|
|
33278
|
+
shortTitle: "52W H/L",
|
|
33279
|
+
overlay: true,
|
|
33280
|
+
inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
|
|
33281
|
+
compute: (bars, inputs) => {
|
|
33282
|
+
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
33283
|
+
const n = bars.length;
|
|
33284
|
+
const hi = new Array(n).fill(Number.NaN);
|
|
33285
|
+
const lo = new Array(n).fill(Number.NaN);
|
|
33286
|
+
const maxIdx = [];
|
|
33287
|
+
const minIdx = [];
|
|
33288
|
+
let from = 0;
|
|
33289
|
+
for (let i = 0; i < n; i++) {
|
|
33290
|
+
const b = bars[i];
|
|
33291
|
+
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
33292
|
+
maxIdx.push(i);
|
|
33293
|
+
while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
|
|
33294
|
+
minIdx.push(i);
|
|
33295
|
+
while (bars[from].time < b.time - span) from += 1;
|
|
33296
|
+
while (maxIdx[0] < from) maxIdx.shift();
|
|
33297
|
+
while (minIdx[0] < from) minIdx.shift();
|
|
33298
|
+
hi[i] = bars[maxIdx[0]].high;
|
|
33299
|
+
lo[i] = bars[minIdx[0]].low;
|
|
33300
|
+
}
|
|
33301
|
+
return {
|
|
33302
|
+
plots: [
|
|
33303
|
+
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
33304
|
+
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
33305
|
+
]
|
|
33306
|
+
};
|
|
33307
|
+
}
|
|
33308
|
+
};
|
|
33309
|
+
var zigzag = {
|
|
33310
|
+
type: "zigzag",
|
|
33311
|
+
title: "ZigZag",
|
|
33312
|
+
overlay: true,
|
|
33313
|
+
inputs: [
|
|
33314
|
+
{ key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
|
|
33315
|
+
{ key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
33316
|
+
colorInput()
|
|
33317
|
+
],
|
|
33318
|
+
compute: (bars, inputs) => {
|
|
33319
|
+
const dev = num3(inputs, "deviation", 5) / 100;
|
|
33320
|
+
const depth = num3(inputs, "depth", 10);
|
|
33321
|
+
const pivots = [];
|
|
33322
|
+
if (bars.length > 1) {
|
|
33323
|
+
let up = bars[1].close >= bars[0].close;
|
|
33324
|
+
let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
|
|
33325
|
+
for (let i = 1; i < bars.length; i++) {
|
|
33326
|
+
const b = bars[i];
|
|
33327
|
+
if (up) {
|
|
33328
|
+
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
33329
|
+
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
33330
|
+
pivots.push(ext);
|
|
33331
|
+
up = false;
|
|
33332
|
+
ext = { i, price: b.low };
|
|
33333
|
+
}
|
|
33334
|
+
} else if (b.low <= ext.price) {
|
|
33335
|
+
ext = { i, price: b.low };
|
|
33336
|
+
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
33337
|
+
pivots.push(ext);
|
|
33338
|
+
up = true;
|
|
33339
|
+
ext = { i, price: b.high };
|
|
33340
|
+
}
|
|
33341
|
+
}
|
|
33342
|
+
pivots.push(ext);
|
|
33343
|
+
}
|
|
33344
|
+
return {
|
|
33345
|
+
plots: [],
|
|
33346
|
+
polylines: [
|
|
33347
|
+
{
|
|
33348
|
+
key: "zigzag",
|
|
33349
|
+
points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
|
|
33350
|
+
curved: false,
|
|
33351
|
+
closed: false,
|
|
33352
|
+
lineColor: str3(inputs, "color", SERIES_LINE),
|
|
33353
|
+
lineWidth: 2,
|
|
33354
|
+
lineStyle: "solid",
|
|
33355
|
+
arrowLeft: false,
|
|
33356
|
+
arrowRight: false
|
|
33357
|
+
}
|
|
33358
|
+
]
|
|
33359
|
+
};
|
|
33360
|
+
}
|
|
33361
|
+
};
|
|
33362
|
+
var williamsFractal = {
|
|
33363
|
+
type: "williams-fractal",
|
|
33364
|
+
title: "Williams Fractal",
|
|
33365
|
+
shortTitle: "Fractals",
|
|
33366
|
+
overlay: true,
|
|
33367
|
+
inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
|
|
33368
|
+
compute: (bars, inputs) => {
|
|
33369
|
+
const p = num3(inputs, "periods", 2);
|
|
33370
|
+
const markers = [];
|
|
33371
|
+
const isExtreme = (i, pick, better) => {
|
|
33372
|
+
const v = pick(bars[i]);
|
|
33373
|
+
for (let k = i - p; k <= i + p; k++) {
|
|
33374
|
+
if (k === i) continue;
|
|
33375
|
+
if (!better(v, pick(bars[k]))) return false;
|
|
33376
|
+
}
|
|
33377
|
+
return true;
|
|
33378
|
+
};
|
|
33379
|
+
for (let i = p; i < bars.length - p; i++) {
|
|
33380
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
|
|
33381
|
+
markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
|
|
33382
|
+
}
|
|
33383
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
33384
|
+
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
33385
|
+
}
|
|
33386
|
+
}
|
|
33387
|
+
return { plots: [], markers };
|
|
33388
|
+
}
|
|
33389
|
+
};
|
|
33390
|
+
var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
|
|
33391
|
+
|
|
33392
|
+
// src/core/native-indicators/classics/index.ts
|
|
33393
|
+
var classicSpecs = [
|
|
33394
|
+
...averageSpecs,
|
|
33395
|
+
...bandSpecs,
|
|
33396
|
+
...oscillatorSpecs,
|
|
33397
|
+
...trendSpecs,
|
|
33398
|
+
...volatilitySpecs,
|
|
33399
|
+
...volumeSpecs,
|
|
33400
|
+
...overlaySpecs
|
|
33401
|
+
];
|
|
33402
|
+
function registerClassicIndicators() {
|
|
33403
|
+
for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
|
|
33404
|
+
}
|
|
33405
|
+
|
|
33406
|
+
// src/Vela.ts
|
|
33407
|
+
var Vela = class {
|
|
33408
|
+
constructor(container, options = {}, deps = {}) {
|
|
33409
|
+
registerBuiltinChartTypes();
|
|
33410
|
+
registerVolume();
|
|
33411
|
+
registerVpvr();
|
|
33412
|
+
registerClassicIndicators();
|
|
33413
|
+
const element = resolveElement(container);
|
|
33414
|
+
const theme = resolveTheme(options.theme);
|
|
33415
|
+
const display = {
|
|
33416
|
+
currentPriceLine: options.currentPriceLine ?? true,
|
|
33417
|
+
logScale: options.logScale ?? false,
|
|
33418
|
+
nativeBackend: options.nativeBackend ?? "auto",
|
|
33419
|
+
...resolveAnimations(options.animations),
|
|
33420
|
+
glow: options.glow ?? 0,
|
|
33421
|
+
upColor: options.upColor ?? BULLISH,
|
|
33422
|
+
downColor: options.downColor ?? BEARISH,
|
|
33423
|
+
priceStyle: options.priceStyle ?? "candles"
|
|
33424
|
+
};
|
|
33425
|
+
const RendererClass = options.renderer ?? NativeRenderer;
|
|
33426
|
+
if (typeof RendererClass !== "function") {
|
|
33427
|
+
throw new Error(
|
|
33428
|
+
"[vela] options.renderer must be a renderer class, e.g. import { LwcRenderer } from 'vela/renderers/lwc'. The 'lwc'/'native' string options were removed."
|
|
33429
|
+
);
|
|
33430
|
+
}
|
|
33431
|
+
const renderer = deps.renderer ?? new RendererClass(display);
|
|
33432
|
+
const engines = deps.engines ?? [];
|
|
33433
|
+
const feed = deps.dataFeed ?? new MultiProviderFeed();
|
|
33434
|
+
const config = {
|
|
33435
|
+
market: {
|
|
33436
|
+
symbol: options.symbol,
|
|
33437
|
+
timeframe: options.timeframe,
|
|
33438
|
+
bars: options.bars,
|
|
33439
|
+
session: options.session,
|
|
33440
|
+
visibleRange: options.visibleRange,
|
|
33441
|
+
data: options.data
|
|
33442
|
+
},
|
|
33443
|
+
live: options.live ?? false,
|
|
33444
|
+
theme,
|
|
33445
|
+
defaultLanguage: options.defaultLanguage ?? engines[0]?.language ?? "pine",
|
|
33446
|
+
drawings: options.drawings,
|
|
33447
|
+
volume: options.volume ?? true
|
|
33448
|
+
// the volume indicator is on by default
|
|
33449
|
+
};
|
|
33450
|
+
this.rendererControl = new RendererControl(renderer);
|
|
33451
|
+
if (options.settings) this.rendererControl.setSettingsVisibility(options.settings);
|
|
33452
|
+
this.dataControl = new DataControl(feed);
|
|
33453
|
+
this.orchestrator = new EngineOrchestrator(element, renderer, feed, engines, config, this.dataControl);
|
|
33454
|
+
const defaults2 = rendererDefaults();
|
|
33455
|
+
if (Object.keys(defaults2).length > 0) this.rendererControl.set(defaults2);
|
|
33456
|
+
this.panesControl = new PanesControl(this.orchestrator);
|
|
33457
|
+
this.drawingsControl = new DrawingsControl(this.orchestrator.drawings);
|
|
33458
|
+
}
|
|
33459
|
+
/**
|
|
33460
|
+
* Register a scripting engine so `addIndicator({ language })` can run that
|
|
33461
|
+
* language. Vela ships NO engine — install the one you need (Pine Script:
|
|
33462
|
+
* `@luxalgo/vela-pinets`) and register it, e.g.
|
|
33463
|
+
* `chart.registerEngine('pine', new PineEngine())`; without one the chart
|
|
33464
|
+
* displays candles, drawings and native indicators only. Re-registering a
|
|
33465
|
+
* language replaces it.
|
|
33466
|
+
*/
|
|
33467
|
+
registerEngine(language, engine) {
|
|
33468
|
+
this.orchestrator.registerEngine(language, engine);
|
|
33469
|
+
return this;
|
|
33470
|
+
}
|
|
33471
|
+
/** Run an indicator script on the chart's market data and render it. */
|
|
33472
|
+
addIndicator(source, options) {
|
|
33473
|
+
return this.orchestrator.addIndicator(source, options);
|
|
33474
|
+
}
|
|
33475
|
+
/**
|
|
33476
|
+
* Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
|
|
33477
|
+
* e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
33478
|
+
* remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
|
|
33479
|
+
* call returns the existing handle; the classic studies allow several instances, so each call
|
|
33480
|
+
* adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
|
|
33481
|
+
*/
|
|
33482
|
+
addNativeIndicator(type, options) {
|
|
33483
|
+
return this.orchestrator.addNativeIndicator(type, options);
|
|
33484
|
+
}
|
|
33485
|
+
/**
|
|
33486
|
+
* The catalog of built-in native indicators with their live state on this chart — each entry's
|
|
33487
|
+
* `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
|
|
33488
|
+
* and whether it allows several instances (`multiInstance`; otherwise a second
|
|
33489
|
+
* `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
|
|
33490
|
+
* ones, and de-duplicate the single-instance ones. Async because support may probe the
|
|
33491
|
+
* provider (a type may need data the symbol lacks).
|
|
33492
|
+
*/
|
|
33493
|
+
availableNativeIndicators() {
|
|
33494
|
+
return this.orchestrator.availableNativeIndicators();
|
|
33495
|
+
}
|
|
33496
|
+
/**
|
|
33497
|
+
* The native-indicator types PRESENT on the chart right now, one entry per instance (a
|
|
33498
|
+
* multi-instance type repeats) — the synchronous slice of
|
|
33499
|
+
* {@link availableNativeIndicators} (only support probing is async; presence never is).
|
|
33500
|
+
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
33501
|
+
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|
|
33502
|
+
*/
|
|
33503
|
+
presentNativeIndicators() {
|
|
33504
|
+
return this.orchestrator.presentNativeIndicators();
|
|
33505
|
+
}
|
|
33506
|
+
/** Live handles of every indicator currently on the chart (script + native) — drive
|
|
33507
|
+
* host panels (object trees, indicator lists) with per-id visibility/removal. */
|
|
33508
|
+
indicators() {
|
|
33509
|
+
return this.orchestrator.listIndicators();
|
|
33510
|
+
}
|
|
33511
|
+
/**
|
|
33512
|
+
* Execute an indicator script and INJECT it only if the run succeeds — the seam for
|
|
33513
|
+
* host editors and consoles. Resolves `{ ok: true, handle }` after the first
|
|
33514
|
+
* successful evaluation, or `{ ok: false, error }` after a compile/runtime failure —
|
|
33515
|
+
* in which case the failed indicator is removed again (no dead legend row).
|
|
33516
|
+
* Never rejects.
|
|
33517
|
+
*/
|
|
33518
|
+
/**
|
|
33519
|
+
* Execute a script and resolve its FIRST computed run — the data-out door for host
|
|
33520
|
+
* editors, consoles and dashboards. The script is injected only if it runs (a failure
|
|
33521
|
+
* removes it again, leaving no dead legend row), and the result carries the run itself
|
|
33522
|
+
* plus the controls for what it put on the chart: `onUpdate` to follow later runs,
|
|
33523
|
+
* `remove` to take it off. Never rejects.
|
|
33524
|
+
*
|
|
33525
|
+
* `runScript` is `runIndicator` with the run as its payload rather than a handle to go
|
|
33526
|
+
* fetch from — the same relationship `script:run` has to `context:changed`.
|
|
33527
|
+
*/
|
|
33528
|
+
runScript(source, options) {
|
|
33529
|
+
const handle = this.addIndicator(source, options);
|
|
33530
|
+
const updates = /* @__PURE__ */ new Set();
|
|
33531
|
+
const drop = () => {
|
|
33532
|
+
try {
|
|
33533
|
+
handle.remove();
|
|
33534
|
+
} catch {
|
|
33535
|
+
}
|
|
33536
|
+
};
|
|
33537
|
+
return new Promise((resolve) => {
|
|
33538
|
+
let settled = false;
|
|
33539
|
+
const offRun = this.on("script:run", (run) => {
|
|
33540
|
+
if (run.id !== handle.id) return;
|
|
33541
|
+
if (settled) {
|
|
33542
|
+
for (const handler of updates) handler(run);
|
|
33543
|
+
return;
|
|
33544
|
+
}
|
|
33545
|
+
settled = true;
|
|
33546
|
+
offError();
|
|
33547
|
+
resolve({
|
|
33548
|
+
ok: true,
|
|
33549
|
+
run,
|
|
33550
|
+
error: null,
|
|
33551
|
+
onUpdate: (handler) => {
|
|
33552
|
+
updates.add(handler);
|
|
33553
|
+
return () => updates.delete(handler);
|
|
33554
|
+
},
|
|
33555
|
+
remove: () => {
|
|
33556
|
+
offRun();
|
|
33557
|
+
updates.clear();
|
|
33558
|
+
drop();
|
|
33559
|
+
}
|
|
33560
|
+
});
|
|
33561
|
+
});
|
|
33562
|
+
const offError = handle.on("error", ({ error }) => {
|
|
33563
|
+
if (settled) return;
|
|
33564
|
+
settled = true;
|
|
33565
|
+
offRun();
|
|
33566
|
+
offError();
|
|
33567
|
+
drop();
|
|
33568
|
+
resolve({ ok: false, run: null, error, onUpdate: () => () => void 0, remove: () => void 0 });
|
|
33569
|
+
});
|
|
33570
|
+
});
|
|
33571
|
+
}
|
|
33572
|
+
runIndicator(source, options) {
|
|
33573
|
+
const handle = this.addIndicator(source, options);
|
|
33574
|
+
return new Promise((resolve) => {
|
|
33575
|
+
const offReady = handle.on("ready", () => {
|
|
33576
|
+
offReady();
|
|
33577
|
+
offError();
|
|
33578
|
+
resolve({ ok: true, handle, error: null, context: null });
|
|
33579
|
+
});
|
|
33580
|
+
const offError = handle.on("error", ({ error }) => {
|
|
33581
|
+
offReady();
|
|
33582
|
+
offError();
|
|
33583
|
+
void handle.context().catch(() => null).then((context) => {
|
|
33584
|
+
try {
|
|
33585
|
+
handle.remove();
|
|
33586
|
+
} catch {
|
|
33587
|
+
}
|
|
33588
|
+
resolve({ ok: false, handle: null, error, context });
|
|
33589
|
+
});
|
|
33590
|
+
});
|
|
33591
|
+
});
|
|
33592
|
+
}
|
|
33593
|
+
/**
|
|
33594
|
+
* Switch the chart's market IN PLACE — symbol, provider, timeframe, depth, or offline
|
|
33595
|
+
* data — WITHOUT destroying the chart: indicators re-execute over the new bars, native
|
|
33596
|
+
* indicators restart, and panes, user drawings, renderer config and event
|
|
33597
|
+
* subscriptions all survive. Resolves once the new market's history is painted (a
|
|
33598
|
+
* deep backfill continues behind it — await {@link historyComplete}); a call
|
|
33599
|
+
* superseded by a newer `setMarket` resolves silently. Emits `market:changed`
|
|
33600
|
+
* (with the previous identity) when the market identity changed.
|
|
33601
|
+
*/
|
|
33602
|
+
setMarket(next) {
|
|
33603
|
+
return this.orchestrator.setMarket(next);
|
|
33604
|
+
}
|
|
33605
|
+
/** The current market identity — the read counterpart of {@link setMarket}. A snapshot
|
|
33606
|
+
* of the REQUESTED market: it reflects an in-flight switch immediately (before the
|
|
31210
33607
|
* new bars land). Listen to `market:changed` for committed identity changes. */
|
|
31211
33608
|
get market() {
|
|
31212
33609
|
return this.orchestrator.marketSnapshot();
|
|
@@ -31465,12 +33862,6 @@ function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_
|
|
|
31465
33862
|
return Math.round(Math.min(Math.max(px, lo), hi));
|
|
31466
33863
|
}
|
|
31467
33864
|
|
|
31468
|
-
// src/core/model/identity.ts
|
|
31469
|
-
function stableSeriesId(parts) {
|
|
31470
|
-
const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
31471
|
-
return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
|
|
31472
|
-
}
|
|
31473
|
-
|
|
31474
33865
|
exports.ACCENT = ACCENT;
|
|
31475
33866
|
exports.ACCENT_BRIGHT = ACCENT_BRIGHT;
|
|
31476
33867
|
exports.BEARISH = BEARISH;
|