@luxalgo/vela 0.6.14 → 0.6.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +28 -28
- package/README.md +9 -2
- package/dist/{DataProvider-DhzstpQb.d.ts → DataProvider-XLdJB86x.d.ts} +1 -1
- package/dist/{DataProvider-DlMtrwqM.d.cts → DataProvider-xH2VBcFT.d.cts} +1 -1
- package/dist/{chunk-KG4YT3TI.js → chunk-6DPAYEGO.js} +8 -1
- package/dist/chunk-FE6V2YAQ.js +1 -0
- package/dist/{chunk-N2Q5EEFH.js → chunk-GIJYO4LO.js} +2596 -242
- package/dist/{chunk-F4M24ANM.js → chunk-I77F2SVE.js} +80 -4
- package/dist/{chunk-MHY7MVXH.js → chunk-MGU3USTP.js} +29 -16
- package/dist/{chunk-G77Y7LK2.js → chunk-Q3TFJNJW.js} +2 -2
- package/dist/{chunk-64TF7JAJ.js → chunk-SEOO6YG7.js} +408 -184
- package/dist/{contributions-lPojhTxI.d.cts → contributions-BEkxwA5F.d.cts} +40 -13
- package/dist/{contributions-37nni40G.d.ts → contributions-CoAX7_SF.d.ts} +40 -13
- package/dist/index.cjs +2755 -380
- package/dist/index.d.cts +20 -12
- package/dist/index.d.ts +20 -12
- package/dist/index.js +5 -5
- package/dist/{options-CYS5Wmlx.d.ts → options-bdSAtp7y.d.cts} +5 -0
- package/dist/{options-CYS5Wmlx.d.cts → options-bdSAtp7y.d.ts} +5 -0
- package/dist/{plugin-DHyaoMjW.d.ts → plugin-CNUgbRNq.d.ts} +11 -8
- package/dist/{plugin-Cwikpz1m.d.cts → plugin-Dxyq35Zf.d.cts} +11 -8
- package/dist/plugin.cjs +8 -0
- package/dist/plugin.d.cts +5 -5
- package/dist/plugin.d.ts +5 -5
- 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/{side-panel-CT9ZwIGz.d.cts → side-panel-HF0IAzwf.d.cts} +17 -0
- package/dist/{side-panel-CT9ZwIGz.d.ts → side-panel-HF0IAzwf.d.ts} +17 -0
- package/dist/{statusline-model-D-q_OOx9.d.ts → statusline-model-CbPza-Wc.d.cts} +6 -4
- package/dist/{statusline-model-CiJm-riV.d.cts → statusline-model-D7cWkw8A.d.ts} +6 -4
- package/dist/ui.cjs +34 -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 +2675 -300
- package/dist/vela.global.min.js +58 -52
- package/dist/widget.cjs +3296 -622
- package/dist/widget.d.cts +15 -11
- package/dist/widget.d.ts +15 -11
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +3296 -622
- package/dist/workspace.d.cts +49 -19
- package/dist/workspace.d.ts +49 -19
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
- package/dist/chunk-H26BEHF4.js +0 -7
package/dist/index.cjs
CHANGED
|
@@ -135,7 +135,7 @@ var LiveSession = class {
|
|
|
135
135
|
|
|
136
136
|
// src/core/engine/IndicatorHandleImpl.ts
|
|
137
137
|
var IndicatorHandleImpl = class {
|
|
138
|
-
constructor(id, title, controller, source) {
|
|
138
|
+
constructor(id, title, controller, source, nativeType) {
|
|
139
139
|
this.controller = controller;
|
|
140
140
|
this.schema = [];
|
|
141
141
|
this.propsSchema = [];
|
|
@@ -144,6 +144,7 @@ var IndicatorHandleImpl = class {
|
|
|
144
144
|
this.id = id;
|
|
145
145
|
this.title = title;
|
|
146
146
|
if (source !== void 0) this.source = source;
|
|
147
|
+
if (nativeType !== void 0) this.nativeType = nativeType;
|
|
147
148
|
}
|
|
148
149
|
get inputs() {
|
|
149
150
|
return this.schema;
|
|
@@ -234,24 +235,24 @@ function summarizeModel(model) {
|
|
|
234
235
|
}
|
|
235
236
|
function inspectModels(models) {
|
|
236
237
|
const indicators = models.map(summarizeModel);
|
|
237
|
-
const
|
|
238
|
+
const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
|
|
238
239
|
return {
|
|
239
240
|
indicators,
|
|
240
241
|
totals: {
|
|
241
242
|
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:
|
|
243
|
+
series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
|
|
244
|
+
fills: sum2((s) => s.fills),
|
|
245
|
+
backgrounds: sum2((s) => s.backgrounds),
|
|
246
|
+
priceLines: sum2((s) => s.priceLines),
|
|
247
|
+
lines: sum2((s) => s.lines),
|
|
248
|
+
boxes: sum2((s) => s.boxes),
|
|
249
|
+
labels: sum2((s) => s.labels),
|
|
250
|
+
polylines: sum2((s) => s.polylines),
|
|
251
|
+
linefills: sum2((s) => s.linefills),
|
|
252
|
+
tables: sum2((s) => s.tables),
|
|
253
|
+
barColors: sum2((s) => s.barColors),
|
|
254
|
+
trades: sum2((s) => s.trades),
|
|
255
|
+
forcedOverlay: sum2((s) => s.forcedOverlay)
|
|
255
256
|
}
|
|
256
257
|
};
|
|
257
258
|
}
|
|
@@ -300,6 +301,7 @@ var SERIES_LINE = "#3b82f6";
|
|
|
300
301
|
var CROSSHAIR = "#9aa0ad";
|
|
301
302
|
var SLATE_DEEP = "#1e293b";
|
|
302
303
|
var SLATE = "#475569";
|
|
304
|
+
var CHIP_PLATE = "#595959";
|
|
303
305
|
var TRADE_LONG = ACCENT;
|
|
304
306
|
var TRADE_SHORT = BEARISH;
|
|
305
307
|
var TRADE_EXIT = "#d500f9";
|
|
@@ -527,6 +529,10 @@ registerIcon(
|
|
|
527
529
|
"market-post",
|
|
528
530
|
svg24('<path d="M12 10V2"/><path d="m4.93 10.93 1.41 1.41"/><path d="M2 18h2"/><path d="M20 18h2"/><path d="m19.07 10.93-1.41 1.41"/><path d="M22 22H2"/><path d="m16 14-4 4-4-4"/><path d="M16 6a4 4 0 0 0-8 0"/>')
|
|
529
531
|
);
|
|
532
|
+
registerIcon(
|
|
533
|
+
"market-extended",
|
|
534
|
+
svg24('<path d="M22 22H2"/><path d="M12 3a5 5 0 0 0 7.5 7.5A7.5 7.5 0 1 1 12 3Z"/>')
|
|
535
|
+
);
|
|
530
536
|
registerIcon("market-closed", svg24('<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>'));
|
|
531
537
|
registerIcon(
|
|
532
538
|
"market-holiday",
|
|
@@ -540,6 +546,8 @@ registerIcon("chevrons-right", S('<path d="m4 3.5 4.5 4.5L4 12.5"/><path d="m8.5
|
|
|
540
546
|
registerIcon("chevrons-left", S('<path d="M12 3.5 7.5 8l4.5 4.5"/><path d="M7.5 3.5 3 8l4.5 4.5"/>'));
|
|
541
547
|
registerIcon("check", S('<path d="m2.8 8.4 3.4 3.4 7-7.6"/>'));
|
|
542
548
|
registerIcon("close", S('<path d="m3.8 3.8 8.4 8.4M12.2 3.8l-8.4 8.4"/>'));
|
|
549
|
+
registerIcon("pin", S('<path d="M5.6 2.5h4.8M6.4 2.5v3.6L4.4 8.4v.9h7.2v-.9L9.6 6.1V2.5M8 9.3v4.2"/>'));
|
|
550
|
+
registerIcon("pin-filled", S('<path d="M5.6 2.5h4.8M6.4 2.5v3.6L4.4 8.4v.9h7.2v-.9L9.6 6.1V2.5M8 9.3v4.2"/><path d="M6.4 2.5h3.2v3.6l2 2.3v.9H4.4v-.9l2-2.3z" fill="currentColor" stroke="none"/>'));
|
|
543
551
|
registerIcon("eye", S('<path d="M1.5 8s2.5-5.4 6.5-5.4S14.5 8 14.5 8 12 13.4 8 13.4 1.5 8 1.5 8z"/><circle cx="8" cy="8" r="1.8"/>'));
|
|
544
552
|
registerIcon("eye-off", S('<path d="M1.5 8s2.5-5.4 6.5-5.4S14.5 8 14.5 8 12 13.4 8 13.4 1.5 8 1.5 8z" opacity="0.45"/><path d="m3 13 10-10"/>'));
|
|
545
553
|
registerIcon("lock", S('<rect x="3.2" y="7" width="9.6" height="6.6" rx="1.2"/><path d="M5.6 7V5.2a2.4 2.4 0 0 1 4.8 0V7"/>'));
|
|
@@ -6724,8 +6732,8 @@ var DrawingController = class {
|
|
|
6724
6732
|
this.port?.setExternalGhost?.(doc);
|
|
6725
6733
|
}
|
|
6726
6734
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
6727
|
-
setToolShortcuts(
|
|
6728
|
-
this.port?.setToolShortcuts?.(
|
|
6735
|
+
setToolShortcuts(map2) {
|
|
6736
|
+
this.port?.setToolShortcuts?.(map2);
|
|
6729
6737
|
}
|
|
6730
6738
|
// ── programmatic CRUD (facade-facing) ── each is one undo step
|
|
6731
6739
|
add(type, init = {}) {
|
|
@@ -8449,16 +8457,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8449
8457
|
/**
|
|
8450
8458
|
* Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
|
|
8451
8459
|
* indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
|
|
8452
|
-
* INSTANCE per type
|
|
8453
|
-
*
|
|
8460
|
+
* INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
|
|
8461
|
+
* single-instance type returns the existing handle; a multi-instance type gets a fresh
|
|
8462
|
+
* instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
|
|
8463
|
+
* addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
|
|
8454
8464
|
*/
|
|
8455
8465
|
addNativeIndicator(type, options = {}) {
|
|
8456
|
-
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
8457
|
-
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
|
|
8458
8466
|
const descriptor = getNativeIndicator(type);
|
|
8467
|
+
if (!descriptor?.multiInstance) {
|
|
8468
|
+
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
8469
|
+
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
|
|
8470
|
+
}
|
|
8459
8471
|
const id = this.registry.nextId("native");
|
|
8460
8472
|
const title = descriptor?.title ?? type;
|
|
8461
|
-
const handle = new IndicatorHandleImpl(id, title, this);
|
|
8473
|
+
const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
|
|
8462
8474
|
this.handles.set(id, handle);
|
|
8463
8475
|
if (!descriptor) {
|
|
8464
8476
|
console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
|
|
@@ -8470,16 +8482,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8470
8482
|
void this.startNativeIndicator(id, handle);
|
|
8471
8483
|
return handle;
|
|
8472
8484
|
}
|
|
8473
|
-
/**
|
|
8474
|
-
*
|
|
8475
|
-
* entry's `supported` (applies to the current symbol) and `present` (already added — a second
|
|
8476
|
-
* add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
|
|
8477
|
-
* because a descriptor's `isSupported` may probe the provider for a required capability.
|
|
8478
|
-
*/
|
|
8479
|
-
/** The native types present on the chart — SYNC (registry state; no support probe). */
|
|
8485
|
+
/** The native types present on the chart, one entry per INSTANCE in registry order (a
|
|
8486
|
+
* multi-instance type repeats) — SYNC (registry state; no support probe). */
|
|
8480
8487
|
presentNativeIndicators() {
|
|
8481
8488
|
return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
|
|
8482
8489
|
}
|
|
8490
|
+
/**
|
|
8491
|
+
* The catalog of registered native-indicator types with their live state on THIS chart: each
|
|
8492
|
+
* entry's `supported` (applies to the current symbol) and `present` (at least one instance
|
|
8493
|
+
* added — a second add of a single-instance type is a no-op). Powers a host "add native
|
|
8494
|
+
* indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
|
|
8495
|
+
* probe the provider for a required capability.
|
|
8496
|
+
*/
|
|
8483
8497
|
async availableNativeIndicators() {
|
|
8484
8498
|
const symbol = this.qualifiedSymbol();
|
|
8485
8499
|
const present = new Set(this.presentNativeIndicators());
|
|
@@ -8489,7 +8503,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8489
8503
|
title: d.title,
|
|
8490
8504
|
supported: await this.isNativeSupported(d, symbol),
|
|
8491
8505
|
present: present.has(d.type),
|
|
8492
|
-
beta: d.beta
|
|
8506
|
+
beta: d.beta,
|
|
8507
|
+
...d.multiInstance ? { multiInstance: true } : {}
|
|
8493
8508
|
}))
|
|
8494
8509
|
);
|
|
8495
8510
|
}
|
|
@@ -9296,6 +9311,15 @@ var RendererControl = class {
|
|
|
9296
9311
|
console.warn(`[vela] renderer "${this.renderer.name}" does not support screenshot().`);
|
|
9297
9312
|
return null;
|
|
9298
9313
|
}
|
|
9314
|
+
/**
|
|
9315
|
+
* Raster of the current chart onto a canvas (same pixels as {@link screenshot}),
|
|
9316
|
+
* or null if the renderer has no canvas export. Silent — a host compositing
|
|
9317
|
+
* several charts skips a renderer that cannot contribute.
|
|
9318
|
+
*/
|
|
9319
|
+
screenshotCanvas() {
|
|
9320
|
+
if (typeof this.renderer.screenshotCanvas === "function") return this.renderer.screenshotCanvas();
|
|
9321
|
+
return null;
|
|
9322
|
+
}
|
|
9299
9323
|
/**
|
|
9300
9324
|
* The active renderer's full cosmetic config as a serializable, versioned JSON
|
|
9301
9325
|
* document — persist it (templates, saved settings) and feed it back to
|
|
@@ -9764,7 +9788,7 @@ function attachForming(closed, forming) {
|
|
|
9764
9788
|
|
|
9765
9789
|
// src/data/MultiProviderFeed.ts
|
|
9766
9790
|
function intervalMs(timeframe) {
|
|
9767
|
-
const
|
|
9791
|
+
const map2 = {
|
|
9768
9792
|
"1m": 6e4,
|
|
9769
9793
|
"3m": 18e4,
|
|
9770
9794
|
"5m": 3e5,
|
|
@@ -9786,7 +9810,7 @@ function intervalMs(timeframe) {
|
|
|
9786
9810
|
W: 6048e5,
|
|
9787
9811
|
M: 2592e6
|
|
9788
9812
|
};
|
|
9789
|
-
return
|
|
9813
|
+
return map2[timeframe] ?? 36e5;
|
|
9790
9814
|
}
|
|
9791
9815
|
var MultiProviderFeed = class {
|
|
9792
9816
|
constructor(store = sharedBarStore) {
|
|
@@ -10173,8 +10197,8 @@ var DrawingsControl = class {
|
|
|
10173
10197
|
/** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
|
|
10174
10198
|
* display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
|
|
10175
10199
|
* platform formatting, so hints always match the host's actual bindings. */
|
|
10176
|
-
setToolShortcuts(
|
|
10177
|
-
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(
|
|
10200
|
+
setToolShortcuts(map2) {
|
|
10201
|
+
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
|
|
10178
10202
|
return this;
|
|
10179
10203
|
}
|
|
10180
10204
|
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
@@ -10735,19 +10759,19 @@ var Surface = class _Surface {
|
|
|
10735
10759
|
}
|
|
10736
10760
|
setItems(items) {
|
|
10737
10761
|
this.items = items;
|
|
10738
|
-
for (const
|
|
10762
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
10739
10763
|
this.subs.clear();
|
|
10740
10764
|
this.build();
|
|
10741
10765
|
const api = this.api;
|
|
10742
|
-
for (const
|
|
10743
|
-
api.setChild(
|
|
10744
|
-
|
|
10766
|
+
for (const sub2 of this.subs.values()) {
|
|
10767
|
+
api.setChild(sub2.handle.service);
|
|
10768
|
+
sub2.api.setParent(this.handle.service);
|
|
10745
10769
|
}
|
|
10746
10770
|
this.handle.flush();
|
|
10747
10771
|
if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
|
|
10748
10772
|
}
|
|
10749
10773
|
destroy() {
|
|
10750
|
-
for (const
|
|
10774
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
10751
10775
|
this.subs.clear();
|
|
10752
10776
|
this.handle.stop();
|
|
10753
10777
|
this.positioner.remove();
|
|
@@ -10761,9 +10785,9 @@ var Surface = class _Surface {
|
|
|
10761
10785
|
for (const li of this.list.children) {
|
|
10762
10786
|
const id = li.dataset.veiId;
|
|
10763
10787
|
if (!id) continue;
|
|
10764
|
-
const
|
|
10765
|
-
if (
|
|
10766
|
-
vanilla.spreadProps(li, api.getTriggerItemProps(
|
|
10788
|
+
const sub2 = this.subs.get(id);
|
|
10789
|
+
if (sub2) {
|
|
10790
|
+
vanilla.spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
|
|
10767
10791
|
continue;
|
|
10768
10792
|
}
|
|
10769
10793
|
const item = this.byId(id);
|
|
@@ -10840,7 +10864,7 @@ var Surface = class _Surface {
|
|
|
10840
10864
|
const arrow = iconEl("chevron-right", doc);
|
|
10841
10865
|
arrow.classList.add("vela-menu-arrow");
|
|
10842
10866
|
li.appendChild(arrow);
|
|
10843
|
-
const
|
|
10867
|
+
const sub2 = new _Surface(doc, {
|
|
10844
10868
|
host: this.host,
|
|
10845
10869
|
placement: "right-start",
|
|
10846
10870
|
onSelect: this.onSelect,
|
|
@@ -10848,8 +10872,8 @@ var Surface = class _Surface {
|
|
|
10848
10872
|
checkmarks: this.checkmarks,
|
|
10849
10873
|
id: `${this.mid}--${item.id}`
|
|
10850
10874
|
});
|
|
10851
|
-
|
|
10852
|
-
this.subs.set(item.id,
|
|
10875
|
+
sub2.setItems(item.submenu ?? []);
|
|
10876
|
+
this.subs.set(item.id, sub2);
|
|
10853
10877
|
}
|
|
10854
10878
|
this.list.appendChild(li);
|
|
10855
10879
|
}
|
|
@@ -10914,6 +10938,12 @@ ${STATIC_DECLS}
|
|
|
10914
10938
|
.vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
|
|
10915
10939
|
/* Text ENTRY is the one exception: selection is part of editing. */
|
|
10916
10940
|
.vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
|
|
10941
|
+
/* iOS Safari zooms the page when a focused field is under 16px, and often
|
|
10942
|
+
keeps that zoom after the field blurs or a dialog closes. The shell's
|
|
10943
|
+
mobile size class is the honest gate \u2014 not a media query \u2014 so an embedded
|
|
10944
|
+
chart on a wide desktop still gets the rule when it is in the phone layout.
|
|
10945
|
+
Scoped to the kit root so a host page's own [data-layout] is never touched. */
|
|
10946
|
+
.vela-ui[data-layout='mobile'] :is(input, textarea, select) { font-size: 16px; }
|
|
10917
10947
|
.vela-icon { display: inline-flex; align-items: center; flex: none; }
|
|
10918
10948
|
.vela-icon svg { display: block; }
|
|
10919
10949
|
`;
|
|
@@ -12609,8 +12639,21 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
12609
12639
|
};
|
|
12610
12640
|
const recentRow = document.createElement("div");
|
|
12611
12641
|
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
12642
|
+
const recentSwatches = document.createElement("div");
|
|
12643
|
+
recentSwatches.style.cssText = "display:contents;";
|
|
12644
|
+
const add = document.createElement("label");
|
|
12645
|
+
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;`;
|
|
12646
|
+
add.textContent = "+";
|
|
12647
|
+
const customInput = document.createElement("input");
|
|
12648
|
+
customInput.type = "color";
|
|
12649
|
+
customInput.value = curHex;
|
|
12650
|
+
customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
12651
|
+
customInput.addEventListener("input", () => previewHex(customInput.value));
|
|
12652
|
+
customInput.addEventListener("change", () => pickHex(customInput.value, true));
|
|
12653
|
+
add.appendChild(customInput);
|
|
12654
|
+
recentRow.append(recentSwatches, add);
|
|
12612
12655
|
const renderRecents = () => {
|
|
12613
|
-
|
|
12656
|
+
recentSwatches.replaceChildren();
|
|
12614
12657
|
for (const c of recents) {
|
|
12615
12658
|
const sw = document.createElement("button");
|
|
12616
12659
|
sw.type = "button";
|
|
@@ -12619,18 +12662,8 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
12619
12662
|
e.stopPropagation();
|
|
12620
12663
|
pickHex(c);
|
|
12621
12664
|
});
|
|
12622
|
-
|
|
12665
|
+
recentSwatches.appendChild(sw);
|
|
12623
12666
|
}
|
|
12624
|
-
const add = document.createElement("label");
|
|
12625
|
-
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;`;
|
|
12626
|
-
add.textContent = "+";
|
|
12627
|
-
const input = document.createElement("input");
|
|
12628
|
-
input.type = "color";
|
|
12629
|
-
input.value = curHex;
|
|
12630
|
-
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
12631
|
-
input.addEventListener("input", () => pickHex(input.value, true));
|
|
12632
|
-
add.appendChild(input);
|
|
12633
|
-
recentRow.appendChild(add);
|
|
12634
12667
|
};
|
|
12635
12668
|
const opLabel = document.createElement("div");
|
|
12636
12669
|
opLabel.textContent = "Opacity";
|
|
@@ -12670,14 +12703,18 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
12670
12703
|
function emit() {
|
|
12671
12704
|
onChange(combineColor(curHex, curAlpha));
|
|
12672
12705
|
}
|
|
12673
|
-
function
|
|
12706
|
+
function previewHex(hex) {
|
|
12674
12707
|
curHex = splitColor(hex).hex6;
|
|
12675
|
-
addRecent(curHex);
|
|
12676
12708
|
paintSelection();
|
|
12677
12709
|
paintOpacity();
|
|
12678
|
-
if (custom) renderRecents();
|
|
12679
12710
|
emit();
|
|
12680
12711
|
}
|
|
12712
|
+
function pickHex(hex, custom = false) {
|
|
12713
|
+
previewHex(hex);
|
|
12714
|
+
addRecent(curHex);
|
|
12715
|
+
customInput.value = curHex;
|
|
12716
|
+
if (custom) renderRecents();
|
|
12717
|
+
}
|
|
12681
12718
|
renderRecents();
|
|
12682
12719
|
paintSelection();
|
|
12683
12720
|
paintOpacity();
|
|
@@ -14583,8 +14620,8 @@ var InputsUI = class {
|
|
|
14583
14620
|
lg.style.top = `${top}px`;
|
|
14584
14621
|
}
|
|
14585
14622
|
/** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
|
|
14586
|
-
setCollapsedPanes(
|
|
14587
|
-
this.paneCollapse =
|
|
14623
|
+
setCollapsedPanes(map2) {
|
|
14624
|
+
this.paneCollapse = map2;
|
|
14588
14625
|
}
|
|
14589
14626
|
/**
|
|
14590
14627
|
* Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
|
|
@@ -15345,6 +15382,66 @@ function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
|
15345
15382
|
center: (wickLeftDev + wickDev / 2) / dpr
|
|
15346
15383
|
};
|
|
15347
15384
|
}
|
|
15385
|
+
function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
|
|
15386
|
+
const out = [];
|
|
15387
|
+
let col = NaN;
|
|
15388
|
+
let intervals = [];
|
|
15389
|
+
const flush = () => {
|
|
15390
|
+
if (intervals.length === 0) return;
|
|
15391
|
+
const merged = [intervals[0]];
|
|
15392
|
+
for (let k = 1; k < intervals.length; k += 1) {
|
|
15393
|
+
const prev2 = merged[merged.length - 1];
|
|
15394
|
+
const next = intervals[k];
|
|
15395
|
+
if (Math.abs(yOf(prev2.hi) - yOf(next.lo)) < 1) {
|
|
15396
|
+
prev2.hi = next.hi;
|
|
15397
|
+
if (next.headIdx < prev2.headIdx) {
|
|
15398
|
+
prev2.headIdx = next.headIdx;
|
|
15399
|
+
prev2.headTime = next.headTime;
|
|
15400
|
+
prev2.open = next.open;
|
|
15401
|
+
}
|
|
15402
|
+
if (next.lastIdx > prev2.lastIdx) {
|
|
15403
|
+
prev2.lastIdx = next.lastIdx;
|
|
15404
|
+
prev2.close = next.close;
|
|
15405
|
+
}
|
|
15406
|
+
} else {
|
|
15407
|
+
merged.push(next);
|
|
15408
|
+
}
|
|
15409
|
+
}
|
|
15410
|
+
for (const iv of merged) out.push({ x: col, hi: iv.hi, lo: iv.lo, headTime: iv.headTime, open: iv.open, close: iv.close });
|
|
15411
|
+
intervals = [];
|
|
15412
|
+
};
|
|
15413
|
+
for (let i = i0; i <= i1; i += 1) {
|
|
15414
|
+
const b = bars[i];
|
|
15415
|
+
if (!b || b.high <= b.low) continue;
|
|
15416
|
+
const x = Math.round(xOf(i));
|
|
15417
|
+
if (x !== col) {
|
|
15418
|
+
flush();
|
|
15419
|
+
col = x;
|
|
15420
|
+
}
|
|
15421
|
+
let lo = b.low;
|
|
15422
|
+
let hi = b.high;
|
|
15423
|
+
let headIdx = i;
|
|
15424
|
+
let headTime = b.time;
|
|
15425
|
+
let open2 = b.open;
|
|
15426
|
+
for (let k = intervals.length - 1; k >= 0; k -= 1) {
|
|
15427
|
+
const iv = intervals[k];
|
|
15428
|
+
if (iv.lo > hi || iv.hi < lo) continue;
|
|
15429
|
+
if (iv.lo < lo) lo = iv.lo;
|
|
15430
|
+
if (iv.hi > hi) hi = iv.hi;
|
|
15431
|
+
if (iv.headIdx < headIdx) {
|
|
15432
|
+
headIdx = iv.headIdx;
|
|
15433
|
+
headTime = iv.headTime;
|
|
15434
|
+
open2 = iv.open;
|
|
15435
|
+
}
|
|
15436
|
+
intervals.splice(k, 1);
|
|
15437
|
+
}
|
|
15438
|
+
let insertAt = 0;
|
|
15439
|
+
while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
|
|
15440
|
+
intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open: open2, lastIdx: i, close: b.close });
|
|
15441
|
+
}
|
|
15442
|
+
flush();
|
|
15443
|
+
return out;
|
|
15444
|
+
}
|
|
15348
15445
|
|
|
15349
15446
|
// src/renderers/native/core/chartConfig.ts
|
|
15350
15447
|
var CHART_CONFIG_VERSION = 1;
|
|
@@ -15355,6 +15452,7 @@ var BASELINE_FILL_ALPHA_FAR = 0.05;
|
|
|
15355
15452
|
var BASELINE_LEVEL_DEFAULT = 50;
|
|
15356
15453
|
var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
|
|
15357
15454
|
var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
|
|
15455
|
+
var EXTENDED_SHADE = POSTMARKET_SHADE;
|
|
15358
15456
|
function defaultChartStyle() {
|
|
15359
15457
|
return {
|
|
15360
15458
|
chartTypes: {},
|
|
@@ -15363,7 +15461,7 @@ function defaultChartStyle() {
|
|
|
15363
15461
|
gridHorz: { visible: true, color: null },
|
|
15364
15462
|
borderColor: null,
|
|
15365
15463
|
separatorColor: null,
|
|
15366
|
-
crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground:
|
|
15464
|
+
crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: CHIP_PLATE },
|
|
15367
15465
|
candle: {
|
|
15368
15466
|
bodyVisible: true,
|
|
15369
15467
|
borderVisible: false,
|
|
@@ -15386,7 +15484,7 @@ function defaultChartStyle() {
|
|
|
15386
15484
|
width: 2,
|
|
15387
15485
|
baselineLevel: BASELINE_LEVEL_DEFAULT
|
|
15388
15486
|
},
|
|
15389
|
-
sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
|
|
15487
|
+
sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE, extendedColor: EXTENDED_SHADE }
|
|
15390
15488
|
};
|
|
15391
15489
|
}
|
|
15392
15490
|
var LINE_STYLES = ["solid", "dashed", "dotted"];
|
|
@@ -15486,9 +15584,9 @@ function factoryResetConfig(factory) {
|
|
|
15486
15584
|
if (inst.enableKey) defaults2[inst.enableKey] = false;
|
|
15487
15585
|
addRows(inst.rows);
|
|
15488
15586
|
}
|
|
15489
|
-
for (const
|
|
15490
|
-
if (
|
|
15491
|
-
addRows(
|
|
15587
|
+
for (const sub2 of section.subsections ?? []) {
|
|
15588
|
+
if (sub2.enableKey) defaults2[sub2.enableKey] = false;
|
|
15589
|
+
addRows(sub2.rows);
|
|
15492
15590
|
}
|
|
15493
15591
|
if (!section.instances) addRows(section.rows);
|
|
15494
15592
|
bag[t.id] = defaults2;
|
|
@@ -15615,7 +15713,8 @@ function mergeConfig(base, patch) {
|
|
|
15615
15713
|
},
|
|
15616
15714
|
sessions: {
|
|
15617
15715
|
premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
|
|
15618
|
-
postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
|
|
15716
|
+
postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor,
|
|
15717
|
+
extendedColor: isColor(sessions.extendedColor) ? sessions.extendedColor : base.sessions.extendedColor
|
|
15619
15718
|
},
|
|
15620
15719
|
stacking: {
|
|
15621
15720
|
candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
|
|
@@ -16600,44 +16699,19 @@ var WebGL2Backend = class {
|
|
|
16600
16699
|
}
|
|
16601
16700
|
}
|
|
16602
16701
|
/**
|
|
16603
|
-
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated):
|
|
16604
|
-
*
|
|
16605
|
-
*
|
|
16702
|
+
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
|
|
16703
|
+
* rounded pixel column collapse into high-low sticks — one per contiguous
|
|
16704
|
+
* coverage run (see {@link aggregateCandleColumns}), so a price gap inside the
|
|
16705
|
+
* column stays a void. Draw cost stays bounded by screen width, not bar count;
|
|
16706
|
+
* stick color follows its own first-open→last-close.
|
|
16606
16707
|
*/
|
|
16607
16708
|
emitCandlesAggregated(b, bars, i0, i1, coords, pane, up, down, barColors) {
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
let headTime = 0;
|
|
16614
|
-
let active = false;
|
|
16615
|
-
const flush = () => {
|
|
16616
|
-
if (!active) return;
|
|
16617
|
-
const c = parseColor(barColors.get(headTime) ?? (closeV >= openV ? up : down));
|
|
16618
|
-
const hY = coords.priceToY(hi, pane.scale, pane.bounds);
|
|
16619
|
-
const lY = coords.priceToY(lo, pane.scale, pane.bounds);
|
|
16620
|
-
b.rect(px - 0.5, hY, 1, lY - hY, c);
|
|
16621
|
-
};
|
|
16622
|
-
for (let i = i0; i <= i1; i += 1) {
|
|
16623
|
-
const bar = bars[i];
|
|
16624
|
-
if (!bar || bar.high <= bar.low) continue;
|
|
16625
|
-
const x = Math.round(coords.logicalToX(i));
|
|
16626
|
-
if (!active || x !== px) {
|
|
16627
|
-
flush();
|
|
16628
|
-
px = x;
|
|
16629
|
-
lo = bar.low;
|
|
16630
|
-
hi = bar.high;
|
|
16631
|
-
openV = bar.open;
|
|
16632
|
-
headTime = bar.time;
|
|
16633
|
-
active = true;
|
|
16634
|
-
} else {
|
|
16635
|
-
if (bar.low < lo) lo = bar.low;
|
|
16636
|
-
if (bar.high > hi) hi = bar.high;
|
|
16637
|
-
}
|
|
16638
|
-
closeV = bar.close;
|
|
16709
|
+
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
16710
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
16711
|
+
const c = parseColor(barColors.get(s.headTime) ?? (s.close >= s.open ? up : down));
|
|
16712
|
+
const hY = yOf(s.hi);
|
|
16713
|
+
b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
|
|
16639
16714
|
}
|
|
16640
|
-
flush();
|
|
16641
16715
|
}
|
|
16642
16716
|
emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
16643
16717
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
@@ -16841,11 +16915,11 @@ function mergeBarColors(indicators) {
|
|
|
16841
16915
|
}
|
|
16842
16916
|
}
|
|
16843
16917
|
if (!any) return EMPTY_BARCOLORS;
|
|
16844
|
-
const
|
|
16918
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
16845
16919
|
for (const m of indicators.values()) {
|
|
16846
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
16920
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
16847
16921
|
}
|
|
16848
|
-
return
|
|
16922
|
+
return map2;
|
|
16849
16923
|
}
|
|
16850
16924
|
|
|
16851
16925
|
// src/renderers/native/core/ViewportState.ts
|
|
@@ -17142,9 +17216,9 @@ function isHorizontalWheel(deltaX, deltaY) {
|
|
|
17142
17216
|
function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
|
|
17143
17217
|
return rightOffset + deltaX / barSpacing;
|
|
17144
17218
|
}
|
|
17145
|
-
function wheelPanDelta(deltaX, deltaY,
|
|
17219
|
+
function wheelPanDelta(deltaX, deltaY, shift2) {
|
|
17146
17220
|
if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
|
|
17147
|
-
if (
|
|
17221
|
+
if (shift2) return deltaY;
|
|
17148
17222
|
return null;
|
|
17149
17223
|
}
|
|
17150
17224
|
function effectiveSnapMode(momentaryOverride, sticky) {
|
|
@@ -17935,13 +18009,15 @@ var SceneGraph = class {
|
|
|
17935
18009
|
if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
|
|
17936
18010
|
return this.orderedCache;
|
|
17937
18011
|
}
|
|
17938
|
-
/** The session zones resolved into colored bands (pre/post-market
|
|
17939
|
-
* config's session colors) — consumed by the same painting path as
|
|
18012
|
+
/** The session zones resolved into colored bands (pre/post-market or extended-hours
|
|
18013
|
+
* washes from the config's session colors) — consumed by the same painting path as
|
|
18014
|
+
* {@link highlights}. */
|
|
17940
18015
|
sessionHighlightBands() {
|
|
17941
18016
|
if (!this.sessionZones) return [];
|
|
17942
18017
|
const out = [];
|
|
17943
18018
|
for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
|
|
17944
18019
|
for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
|
|
18020
|
+
for (const [from, to] of this.sessionZones.extended) out.push({ from, to, color: this.style.sessions.extendedColor });
|
|
17945
18021
|
return out;
|
|
17946
18022
|
}
|
|
17947
18023
|
indicatorsForPane(paneId) {
|
|
@@ -18480,50 +18556,23 @@ var Canvas2dBackend = class {
|
|
|
18480
18556
|
}
|
|
18481
18557
|
}
|
|
18482
18558
|
/**
|
|
18483
|
-
* Sub-pixel LOD:
|
|
18484
|
-
*
|
|
18485
|
-
*
|
|
18486
|
-
*
|
|
18559
|
+
* Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
|
|
18560
|
+
* sticks (one per contiguous coverage run — see {@link aggregateCandleColumns}),
|
|
18561
|
+
* so draw cost is bounded by screen width (not bar count) when zoomed far out
|
|
18562
|
+
* and a price gap inside the column stays a void. Each stick's color follows
|
|
18563
|
+
* its first-open→last-close direction (a barcolor() on the head bar still wins).
|
|
18487
18564
|
*/
|
|
18488
18565
|
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, up, down, barColors) {
|
|
18489
|
-
|
|
18490
|
-
|
|
18491
|
-
|
|
18492
|
-
|
|
18493
|
-
|
|
18494
|
-
let headTime = 0;
|
|
18495
|
-
let active = false;
|
|
18496
|
-
const flush = () => {
|
|
18497
|
-
if (!active) return;
|
|
18498
|
-
const color = barColors.get(headTime) ?? (closeV >= openV ? up : down);
|
|
18499
|
-
const hY = coords.priceToY(hi, pane.scale, pane.bounds);
|
|
18500
|
-
const lY = coords.priceToY(lo, pane.scale, pane.bounds);
|
|
18501
|
-
ctx.strokeStyle = color;
|
|
18502
|
-
ctx.lineWidth = 1;
|
|
18566
|
+
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
18567
|
+
ctx.lineWidth = 1;
|
|
18568
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
18569
|
+
const x = s.x + 0.5;
|
|
18570
|
+
ctx.strokeStyle = barColors.get(s.headTime) ?? (s.close >= s.open ? up : down);
|
|
18503
18571
|
ctx.beginPath();
|
|
18504
|
-
ctx.moveTo(
|
|
18505
|
-
ctx.lineTo(
|
|
18572
|
+
ctx.moveTo(x, yOf(s.hi));
|
|
18573
|
+
ctx.lineTo(x, yOf(s.lo));
|
|
18506
18574
|
ctx.stroke();
|
|
18507
|
-
};
|
|
18508
|
-
for (let i = i0; i <= i1; i += 1) {
|
|
18509
|
-
const b = bars[i];
|
|
18510
|
-
if (!b || b.high <= b.low) continue;
|
|
18511
|
-
const x = Math.round(coords.logicalToX(i)) + 0.5;
|
|
18512
|
-
if (!active || x !== px) {
|
|
18513
|
-
flush();
|
|
18514
|
-
px = x;
|
|
18515
|
-
lo = b.low;
|
|
18516
|
-
hi = b.high;
|
|
18517
|
-
openV = b.open;
|
|
18518
|
-
headTime = b.time;
|
|
18519
|
-
active = true;
|
|
18520
|
-
} else {
|
|
18521
|
-
if (b.low < lo) lo = b.low;
|
|
18522
|
-
if (b.high > hi) hi = b.high;
|
|
18523
|
-
}
|
|
18524
|
-
closeV = b.close;
|
|
18525
18575
|
}
|
|
18526
|
-
flush();
|
|
18527
18576
|
}
|
|
18528
18577
|
drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
18529
18578
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
@@ -18791,11 +18840,11 @@ function mergeBarColors2(indicators) {
|
|
|
18791
18840
|
}
|
|
18792
18841
|
}
|
|
18793
18842
|
if (!any) return EMPTY_BARCOLORS2;
|
|
18794
|
-
const
|
|
18843
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
18795
18844
|
for (const m of indicators.values()) {
|
|
18796
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
18845
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
18797
18846
|
}
|
|
18798
|
-
return
|
|
18847
|
+
return map2;
|
|
18799
18848
|
}
|
|
18800
18849
|
|
|
18801
18850
|
// src/renderers/shared/drawing-geometry.ts
|
|
@@ -19547,6 +19596,32 @@ var DrawingSceneRenderer = class {
|
|
|
19547
19596
|
}
|
|
19548
19597
|
};
|
|
19549
19598
|
|
|
19599
|
+
// src/renderers/native/chrome/tz.ts
|
|
19600
|
+
function tzOffsetMs(ms, timeZone) {
|
|
19601
|
+
if (!timeZone || timeZone === "UTC") return 0;
|
|
19602
|
+
try {
|
|
19603
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
19604
|
+
timeZone,
|
|
19605
|
+
hourCycle: "h23",
|
|
19606
|
+
year: "numeric",
|
|
19607
|
+
month: "2-digit",
|
|
19608
|
+
day: "2-digit",
|
|
19609
|
+
hour: "2-digit",
|
|
19610
|
+
minute: "2-digit",
|
|
19611
|
+
second: "2-digit"
|
|
19612
|
+
});
|
|
19613
|
+
const parts = dtf.formatToParts(new Date(ms));
|
|
19614
|
+
const get = (t) => Number(parts.find((p) => p.type === t)?.value);
|
|
19615
|
+
const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
|
|
19616
|
+
return asUTC - ms;
|
|
19617
|
+
} catch {
|
|
19618
|
+
return 0;
|
|
19619
|
+
}
|
|
19620
|
+
}
|
|
19621
|
+
function zonedDate(ms, timeZone) {
|
|
19622
|
+
return new Date(ms + tzOffsetMs(ms, timeZone));
|
|
19623
|
+
}
|
|
19624
|
+
|
|
19550
19625
|
// src/renderers/native/chrome/ticks.ts
|
|
19551
19626
|
function priceTicks(min, max, target = 6) {
|
|
19552
19627
|
if (!(max > min) || !Number.isFinite(min) || !Number.isFinite(max)) return [];
|
|
@@ -19694,7 +19769,14 @@ var STEP_LADDER = [
|
|
|
19694
19769
|
YEAR
|
|
19695
19770
|
];
|
|
19696
19771
|
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
19772
|
+
var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
19697
19773
|
var pad2 = (n) => n < 10 ? `0${n}` : String(n);
|
|
19774
|
+
function formatTimeStamp(ms, timeZone, barIntervalMs) {
|
|
19775
|
+
const d = zonedDate(ms, timeZone);
|
|
19776
|
+
const date = `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} '${pad2(d.getUTCFullYear() % 100)}`;
|
|
19777
|
+
if (barIntervalMs >= DAY) return date;
|
|
19778
|
+
return `${date} ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}`;
|
|
19779
|
+
}
|
|
19698
19780
|
function pickStep(targetMs) {
|
|
19699
19781
|
for (const step of STEP_LADDER) if (step >= targetMs) return step;
|
|
19700
19782
|
return STEP_LADDER[STEP_LADDER.length - 1];
|
|
@@ -19736,32 +19818,6 @@ function timeTicks(fromMs, toMs, target = 8, offsetMs = 0) {
|
|
|
19736
19818
|
return out;
|
|
19737
19819
|
}
|
|
19738
19820
|
|
|
19739
|
-
// src/renderers/native/chrome/tz.ts
|
|
19740
|
-
function tzOffsetMs(ms, timeZone) {
|
|
19741
|
-
if (!timeZone || timeZone === "UTC") return 0;
|
|
19742
|
-
try {
|
|
19743
|
-
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
19744
|
-
timeZone,
|
|
19745
|
-
hourCycle: "h23",
|
|
19746
|
-
year: "numeric",
|
|
19747
|
-
month: "2-digit",
|
|
19748
|
-
day: "2-digit",
|
|
19749
|
-
hour: "2-digit",
|
|
19750
|
-
minute: "2-digit",
|
|
19751
|
-
second: "2-digit"
|
|
19752
|
-
});
|
|
19753
|
-
const parts = dtf.formatToParts(new Date(ms));
|
|
19754
|
-
const get = (t) => Number(parts.find((p) => p.type === t)?.value);
|
|
19755
|
-
const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
|
|
19756
|
-
return asUTC - ms;
|
|
19757
|
-
} catch {
|
|
19758
|
-
return 0;
|
|
19759
|
-
}
|
|
19760
|
-
}
|
|
19761
|
-
function zonedDate(ms, timeZone) {
|
|
19762
|
-
return new Date(ms + tzOffsetMs(ms, timeZone));
|
|
19763
|
-
}
|
|
19764
|
-
|
|
19765
19821
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
19766
19822
|
var ChromeRenderer = class {
|
|
19767
19823
|
constructor() {
|
|
@@ -20208,7 +20264,7 @@ var CrosshairRenderer = class {
|
|
|
20208
20264
|
const price = coords.yToPrice(ch.y, pane.scale, pane.bounds);
|
|
20209
20265
|
this.chip(ctx, dataW + 1, ch.y, formatAxisValue(pane.scale, pane.bounds.height, price, percentScaleFor(scene, pane), scene.priceMintick, pane.axisFormat), chipBg, "left", false, theme.background);
|
|
20210
20266
|
}
|
|
20211
|
-
this.chip(ctx, x, dataH + 1,
|
|
20267
|
+
this.chip(ctx, x, dataH + 1, formatTimeStamp(coords.logicalToTime(logical), scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
20212
20268
|
}
|
|
20213
20269
|
destroy() {
|
|
20214
20270
|
this.canvas = null;
|
|
@@ -20256,7 +20312,7 @@ var CrosshairRenderer = class {
|
|
|
20256
20312
|
this.chip(ctx, dataW + 1, ext.y, formatAxisValue(pane.scale, pane.bounds.height, ext.price, percentScaleFor(scene, pane), scene.priceMintick, pane.axisFormat), chipBg, "left", false, theme.background);
|
|
20257
20313
|
}
|
|
20258
20314
|
}
|
|
20259
|
-
this.chip(ctx, x, dataH + 1,
|
|
20315
|
+
this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
20260
20316
|
ctx.globalAlpha = 1;
|
|
20261
20317
|
}
|
|
20262
20318
|
/** A soft band + a brighter crisp center line over the hovered separator, so it reads as
|
|
@@ -20289,11 +20345,6 @@ function setDash3(ctx, style) {
|
|
|
20289
20345
|
else if (style === "dotted") ctx.setLineDash([2, 3]);
|
|
20290
20346
|
else ctx.setLineDash([]);
|
|
20291
20347
|
}
|
|
20292
|
-
function formatStamp(ms, timeZone) {
|
|
20293
|
-
const d = zonedDate(ms, timeZone);
|
|
20294
|
-
const pad = (n) => n < 10 ? `0${n}` : String(n);
|
|
20295
|
-
return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
20296
|
-
}
|
|
20297
20348
|
|
|
20298
20349
|
// src/core/timezones.ts
|
|
20299
20350
|
var TIMEZONES = [
|
|
@@ -20509,9 +20560,9 @@ function settingsIdCatalog(hostSections) {
|
|
|
20509
20560
|
};
|
|
20510
20561
|
if (section.rows) addRows(section.rows);
|
|
20511
20562
|
for (const inst of section.instances ?? []) addRows(inst.rows);
|
|
20512
|
-
for (const
|
|
20513
|
-
ids.add(`${scope}.${settingsIdSlug(
|
|
20514
|
-
addRows(
|
|
20563
|
+
for (const sub2 of section.subsections ?? []) {
|
|
20564
|
+
ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
|
|
20565
|
+
addRows(sub2.rows);
|
|
20515
20566
|
}
|
|
20516
20567
|
}
|
|
20517
20568
|
for (const hs of hostSections) {
|
|
@@ -21070,14 +21121,14 @@ var SettingsDialog = class {
|
|
|
21070
21121
|
};
|
|
21071
21122
|
if (section.rows) seed(section.rows);
|
|
21072
21123
|
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
21073
|
-
for (const
|
|
21124
|
+
for (const sub2 of section.subsections ?? []) seed(sub2.rows);
|
|
21074
21125
|
for (const inst of section.instances ?? []) {
|
|
21075
21126
|
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
21076
21127
|
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
21077
21128
|
}
|
|
21078
|
-
for (const
|
|
21079
|
-
if (!
|
|
21080
|
-
bag[
|
|
21129
|
+
for (const sub2 of section.subsections ?? []) {
|
|
21130
|
+
if (!sub2.enableKey || sub2.enableKey in bag) continue;
|
|
21131
|
+
bag[sub2.enableKey] = values[sub2.enableKey] === true;
|
|
21081
21132
|
}
|
|
21082
21133
|
const refreshers = [];
|
|
21083
21134
|
const put = (key, v) => {
|
|
@@ -21094,16 +21145,16 @@ var SettingsDialog = class {
|
|
|
21094
21145
|
if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
|
|
21095
21146
|
else this.flatTypeRows(rows, bag, put, refreshers, body);
|
|
21096
21147
|
}
|
|
21097
|
-
for (const
|
|
21098
|
-
if (settingsIdHidden(`${scope}.${settingsIdSlug(
|
|
21099
|
-
const rows = filterHiddenRows(
|
|
21148
|
+
for (const sub2 of section.subsections ?? []) {
|
|
21149
|
+
if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
|
|
21150
|
+
const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
|
|
21100
21151
|
if (rows.length === 0) continue;
|
|
21101
|
-
const subMarker = this.section(
|
|
21152
|
+
const subMarker = this.section(sub2.title);
|
|
21102
21153
|
subMarker.dataset.sdStyle = typeId;
|
|
21103
21154
|
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
21104
21155
|
subMarker.dataset.sdSub = "1";
|
|
21105
21156
|
body.append(subMarker);
|
|
21106
|
-
body.append(this.groupedRows(`${typeId}/${
|
|
21157
|
+
body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
|
|
21107
21158
|
}
|
|
21108
21159
|
for (const r of refreshers) r();
|
|
21109
21160
|
}
|
|
@@ -23211,11 +23262,11 @@ var DrawingInteraction = class {
|
|
|
23211
23262
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
23212
23263
|
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
23213
23264
|
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
23214
|
-
down(x, y, mode = "off",
|
|
23265
|
+
down(x, y, mode = "off", shift2 = false) {
|
|
23215
23266
|
const proj = this.deps.projector();
|
|
23216
23267
|
if (this.state.kind === "placing") {
|
|
23217
23268
|
if (this.state.draft.placementMode() !== "click") return;
|
|
23218
|
-
const pivot =
|
|
23269
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
23219
23270
|
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
23220
23271
|
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
23221
23272
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
@@ -23234,7 +23285,7 @@ var DrawingInteraction = class {
|
|
|
23234
23285
|
}
|
|
23235
23286
|
const hit = this.hitAt(x, y);
|
|
23236
23287
|
if (hit) {
|
|
23237
|
-
if (
|
|
23288
|
+
if (shift2) {
|
|
23238
23289
|
this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
|
|
23239
23290
|
return;
|
|
23240
23291
|
}
|
|
@@ -23244,7 +23295,7 @@ var DrawingInteraction = class {
|
|
|
23244
23295
|
}
|
|
23245
23296
|
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
23246
23297
|
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
23247
|
-
move(x, y, mode = "off",
|
|
23298
|
+
move(x, y, mode = "off", shift2 = false) {
|
|
23248
23299
|
if (this.state.kind === "idle") {
|
|
23249
23300
|
const tool = this.deps.activeTool();
|
|
23250
23301
|
const had = this.snapAt != null;
|
|
@@ -23258,7 +23309,7 @@ var DrawingInteraction = class {
|
|
|
23258
23309
|
this.captureFreehand(x, y);
|
|
23259
23310
|
return;
|
|
23260
23311
|
}
|
|
23261
|
-
const pivot =
|
|
23312
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
23262
23313
|
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
23263
23314
|
this.deps.changed();
|
|
23264
23315
|
return;
|
|
@@ -23271,7 +23322,7 @@ var DrawingInteraction = class {
|
|
|
23271
23322
|
if (this.state.handle >= 0) {
|
|
23272
23323
|
const a = d.anchors[this.state.handle];
|
|
23273
23324
|
if (a) {
|
|
23274
|
-
const pivot =
|
|
23325
|
+
const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
23275
23326
|
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
23276
23327
|
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
23277
23328
|
}
|
|
@@ -23969,21 +24020,21 @@ var DrawingSettingsPopup = class {
|
|
|
23969
24020
|
bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
|
|
23970
24021
|
bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
|
|
23971
24022
|
}
|
|
23972
|
-
const
|
|
23973
|
-
if (paths.has("vwap.midColor") &&
|
|
24023
|
+
const vwap2 = drawing.vwap;
|
|
24024
|
+
if (paths.has("vwap.midColor") && vwap2) {
|
|
23974
24025
|
const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
|
|
23975
24026
|
const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
|
|
23976
|
-
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON,
|
|
23977
|
-
bar.appendChild(this.dropdown("VWAP style", styles,
|
|
24027
|
+
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
|
|
24028
|
+
bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
|
|
23978
24029
|
bar.appendChild(
|
|
23979
|
-
this.dropdown("Band multiplier", MULTS,
|
|
24030
|
+
this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
|
|
23980
24031
|
label: (v) => `${v}\u03C3`,
|
|
23981
24032
|
labelInTrigger: true
|
|
23982
24033
|
})
|
|
23983
24034
|
);
|
|
23984
|
-
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON,
|
|
23985
|
-
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON,
|
|
23986
|
-
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON,
|
|
24035
|
+
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
|
|
24036
|
+
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
|
|
24037
|
+
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
|
|
23987
24038
|
}
|
|
23988
24039
|
if (paths.has("maxCurvature")) {
|
|
23989
24040
|
const cur = drawing.maxCurvature ?? 24;
|
|
@@ -24729,8 +24780,8 @@ var DrawingToolbar = class {
|
|
|
24729
24780
|
}
|
|
24730
24781
|
/** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
|
|
24731
24782
|
* right edge of the flyout rows, beside the favorite star. */
|
|
24732
|
-
setShortcuts(
|
|
24733
|
-
this.shortcuts = new Map(Object.entries(
|
|
24783
|
+
setShortcuts(map2) {
|
|
24784
|
+
this.shortcuts = new Map(Object.entries(map2));
|
|
24734
24785
|
}
|
|
24735
24786
|
/** Paint one star for the current favorite state (filled + gold, or outline). */
|
|
24736
24787
|
paintStar(el, type) {
|
|
@@ -25632,8 +25683,8 @@ var UserDrawingController = class {
|
|
|
25632
25683
|
this.toolbar.setFavorites(types);
|
|
25633
25684
|
}
|
|
25634
25685
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
25635
|
-
setToolShortcuts(
|
|
25636
|
-
this.toolbar.setShortcuts(
|
|
25686
|
+
setToolShortcuts(map2) {
|
|
25687
|
+
this.toolbar.setShortcuts(map2);
|
|
25637
25688
|
}
|
|
25638
25689
|
/** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
|
|
25639
25690
|
* in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
|
|
@@ -25751,7 +25802,7 @@ var UserDrawingController = class {
|
|
|
25751
25802
|
this.render();
|
|
25752
25803
|
}
|
|
25753
25804
|
}
|
|
25754
|
-
pointerDown(x, y, snap = "off",
|
|
25805
|
+
pointerDown(x, y, snap = "off", shift2 = false) {
|
|
25755
25806
|
if (this.eraserMode) {
|
|
25756
25807
|
this.erasing = true;
|
|
25757
25808
|
this.deleteAt(x, y);
|
|
@@ -25771,9 +25822,9 @@ var UserDrawingController = class {
|
|
|
25771
25822
|
return;
|
|
25772
25823
|
}
|
|
25773
25824
|
}
|
|
25774
|
-
this.interaction.down(x, y, snap,
|
|
25825
|
+
this.interaction.down(x, y, snap, shift2);
|
|
25775
25826
|
}
|
|
25776
|
-
pointerMove(x, y, snap = "off",
|
|
25827
|
+
pointerMove(x, y, snap = "off", shift2 = false) {
|
|
25777
25828
|
if (this.eraserMode) {
|
|
25778
25829
|
if (this.erasing) this.deleteAt(x, y);
|
|
25779
25830
|
return;
|
|
@@ -25784,7 +25835,7 @@ var UserDrawingController = class {
|
|
|
25784
25835
|
this.render();
|
|
25785
25836
|
return;
|
|
25786
25837
|
}
|
|
25787
|
-
this.interaction.move(x, y, snap,
|
|
25838
|
+
this.interaction.move(x, y, snap, shift2);
|
|
25788
25839
|
this.updateHover(x, y);
|
|
25789
25840
|
}
|
|
25790
25841
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -26461,14 +26512,14 @@ function layoutTable(ctx, t, args) {
|
|
|
26461
26512
|
}
|
|
26462
26513
|
for (const { box, w: w2, h: h2 } of spanning) {
|
|
26463
26514
|
if (box.cs > 1) {
|
|
26464
|
-
let
|
|
26465
|
-
for (let c = box.c; c < box.c + box.cs; c += 1)
|
|
26466
|
-
if (w2 >
|
|
26515
|
+
let sum2 = 0;
|
|
26516
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
|
|
26517
|
+
if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
|
|
26467
26518
|
}
|
|
26468
26519
|
if (box.rs > 1) {
|
|
26469
|
-
let
|
|
26470
|
-
for (let r = box.r; r < box.r + box.rs; r += 1)
|
|
26471
|
-
if (h2 >
|
|
26520
|
+
let sum2 = 0;
|
|
26521
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
|
|
26522
|
+
if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
|
|
26472
26523
|
}
|
|
26473
26524
|
}
|
|
26474
26525
|
let w = 0;
|
|
@@ -26782,6 +26833,9 @@ function clipHighlightRect(x1, x2, left, right) {
|
|
|
26782
26833
|
const end = Math.min(right, x2);
|
|
26783
26834
|
return end > x ? { x, width: end - x } : null;
|
|
26784
26835
|
}
|
|
26836
|
+
function bandEdgeSlot(logical) {
|
|
26837
|
+
return Math.ceil(logical) - 0.5;
|
|
26838
|
+
}
|
|
26785
26839
|
var BackdropRenderer = class {
|
|
26786
26840
|
constructor() {
|
|
26787
26841
|
this.canvas = null;
|
|
@@ -26812,21 +26866,22 @@ var BackdropRenderer = class {
|
|
|
26812
26866
|
this.drawGrid(ctx, scene, coords, theme, coords.width, gridAlpha);
|
|
26813
26867
|
}
|
|
26814
26868
|
/** Renderer-owned session highlight bands: full-height (all panes), behind the grid.
|
|
26815
|
-
* Session-zone washes
|
|
26869
|
+
* Session-zone washes paint first (edges snapped to bar-slot boundaries so a band
|
|
26870
|
+
* never cuts a candle in two), host highlights on top (continuous time mapping —
|
|
26871
|
+
* hosts may paint sub-bar spans). */
|
|
26816
26872
|
drawHighlights(ctx, scene, coords) {
|
|
26817
26873
|
const sessions = scene.sessionHighlightBands();
|
|
26818
26874
|
if (sessions.length > 0) {
|
|
26819
26875
|
const left = Math.max(0, coords.logicalToX(-0.5));
|
|
26820
26876
|
const right = Math.min(coords.width, coords.logicalToX(coords.barCount - 0.5));
|
|
26821
|
-
this.drawHighlightSet(ctx, sessions, coords, left, right);
|
|
26877
|
+
this.drawHighlightSet(ctx, sessions, coords, left, right, true);
|
|
26822
26878
|
}
|
|
26823
|
-
this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width);
|
|
26879
|
+
this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width, false);
|
|
26824
26880
|
}
|
|
26825
|
-
drawHighlightSet(ctx, bands, coords, left, right) {
|
|
26881
|
+
drawHighlightSet(ctx, bands, coords, left, right, snapToSlots) {
|
|
26882
|
+
const edgeX = (ms) => snapToSlots ? coords.logicalToX(bandEdgeSlot(coords.timeToLogical(ms))) : coords.timeToX(ms);
|
|
26826
26883
|
for (const band of bands) {
|
|
26827
|
-
const
|
|
26828
|
-
const x2 = coords.timeToX(band.to);
|
|
26829
|
-
const rect = clipHighlightRect(x1, x2, left, right);
|
|
26884
|
+
const rect = clipHighlightRect(edgeX(band.from), edgeX(band.to), left, right);
|
|
26830
26885
|
if (!rect) continue;
|
|
26831
26886
|
ctx.fillStyle = band.color;
|
|
26832
26887
|
ctx.fillRect(rect.x, 0, rect.width, coords.height);
|
|
@@ -27085,6 +27140,7 @@ function createCustomMark(doc, html, background) {
|
|
|
27085
27140
|
});
|
|
27086
27141
|
el.style.color = attributionMarkColor(background);
|
|
27087
27142
|
el.innerHTML = html;
|
|
27143
|
+
el.dataset.velaScreenshot = "1";
|
|
27088
27144
|
return el;
|
|
27089
27145
|
}
|
|
27090
27146
|
function createAttributionMark(doc, background) {
|
|
@@ -27103,6 +27159,7 @@ function createAttributionMark(doc, background) {
|
|
|
27103
27159
|
cursor: "pointer"
|
|
27104
27160
|
});
|
|
27105
27161
|
applyAttributionMarkTheme(a, background);
|
|
27162
|
+
a.dataset.velaScreenshot = "1";
|
|
27106
27163
|
const symbol = doc.createElement("span");
|
|
27107
27164
|
symbol.className = "vela-attr-symbol";
|
|
27108
27165
|
symbol.setAttribute("aria-hidden", "true");
|
|
@@ -27185,24 +27242,24 @@ function rasterizeOverlay(ctx, root, frame) {
|
|
|
27185
27242
|
drawSvgShapes(el, alpha);
|
|
27186
27243
|
continue;
|
|
27187
27244
|
}
|
|
27188
|
-
const
|
|
27245
|
+
const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
|
|
27189
27246
|
let path = null;
|
|
27190
27247
|
if (tag === "path") {
|
|
27191
27248
|
const d = el.getAttribute("d");
|
|
27192
27249
|
if (d) path = new Path2D(d);
|
|
27193
27250
|
} else if (tag === "circle") {
|
|
27194
27251
|
path = new Path2D();
|
|
27195
|
-
path.arc(
|
|
27252
|
+
path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
|
|
27196
27253
|
} else if (tag === "ellipse") {
|
|
27197
27254
|
path = new Path2D();
|
|
27198
|
-
path.ellipse(
|
|
27255
|
+
path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
|
|
27199
27256
|
} else if (tag === "rect") {
|
|
27200
27257
|
path = new Path2D();
|
|
27201
|
-
path.rect(
|
|
27258
|
+
path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
|
|
27202
27259
|
} else if (tag === "line") {
|
|
27203
27260
|
path = new Path2D();
|
|
27204
|
-
path.moveTo(
|
|
27205
|
-
path.lineTo(
|
|
27261
|
+
path.moveTo(num4("x1"), num4("y1"));
|
|
27262
|
+
path.lineTo(num4("x2"), num4("y2"));
|
|
27206
27263
|
} else if (tag === "polyline" || tag === "polygon") {
|
|
27207
27264
|
const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
|
|
27208
27265
|
if (pts.length < 4 || pts.some(Number.isNaN)) continue;
|
|
@@ -28107,7 +28164,7 @@ var NativeRenderer = class {
|
|
|
28107
28164
|
};
|
|
28108
28165
|
})(),
|
|
28109
28166
|
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
|
|
28110
|
-
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
|
|
28167
|
+
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor, extendedColor: s.sessions.extendedColor }
|
|
28111
28168
|
};
|
|
28112
28169
|
}
|
|
28113
28170
|
/**
|
|
@@ -28189,7 +28246,7 @@ var NativeRenderer = class {
|
|
|
28189
28246
|
width: next.baseline.width,
|
|
28190
28247
|
baselineLevel: next.baseline.baselineLevel
|
|
28191
28248
|
};
|
|
28192
|
-
s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
|
|
28249
|
+
s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor, extendedColor: next.sessions.extendedColor };
|
|
28193
28250
|
this.setPriceStyle(next.series.style);
|
|
28194
28251
|
this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
|
|
28195
28252
|
this.scene.baselineValue = next.series.baseline;
|
|
@@ -28411,9 +28468,10 @@ var NativeRenderer = class {
|
|
|
28411
28468
|
* opts in with a `data-vela-screenshot` attribute on the mount container's
|
|
28412
28469
|
* subtree (the widget marks its status line, and its symbol watermark with
|
|
28413
28470
|
* `"under"` — drawn beneath the canvases, where it sits on screen). Only the
|
|
28414
|
-
* crosshair (L2) is intentionally excluded.
|
|
28471
|
+
* crosshair (L2) is intentionally excluded. {@link screenshot} is this canvas
|
|
28472
|
+
* as a PNG data URL.
|
|
28415
28473
|
*/
|
|
28416
|
-
|
|
28474
|
+
screenshotCanvas() {
|
|
28417
28475
|
if (!this.dataCanvas) return null;
|
|
28418
28476
|
this.computeScales();
|
|
28419
28477
|
this.paintData();
|
|
@@ -28441,7 +28499,10 @@ var NativeRenderer = class {
|
|
|
28441
28499
|
if (el.getAttribute("data-vela-screenshot") !== "under") rasterizeOverlay(ctx, el, frame);
|
|
28442
28500
|
}
|
|
28443
28501
|
}
|
|
28444
|
-
return out
|
|
28502
|
+
return out;
|
|
28503
|
+
}
|
|
28504
|
+
screenshot() {
|
|
28505
|
+
return this.screenshotCanvas()?.toDataURL("image/png") ?? null;
|
|
28445
28506
|
}
|
|
28446
28507
|
/**
|
|
28447
28508
|
* The candles-first-appear reveal — animates the **candles themselves** (no extra
|
|
@@ -28615,8 +28676,8 @@ var NativeRenderer = class {
|
|
|
28615
28676
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
28616
28677
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
28617
28678
|
drawingsSnapMode: () => this.snapMode,
|
|
28618
|
-
drawingsPointerDown: (x, y, snap,
|
|
28619
|
-
drawingsPointerMove: (x, y, snap,
|
|
28679
|
+
drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
|
|
28680
|
+
drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
|
|
28620
28681
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
28621
28682
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
28622
28683
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -28933,18 +28994,18 @@ var NativeRenderer = class {
|
|
|
28933
28994
|
for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
|
|
28934
28995
|
if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
|
|
28935
28996
|
const newHeadTime = this.bars[0].time;
|
|
28936
|
-
let
|
|
28997
|
+
let shift2 = 0;
|
|
28937
28998
|
if (newHeadTime !== prevHeadTime) {
|
|
28938
28999
|
const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
|
|
28939
|
-
if (prepended > 0)
|
|
29000
|
+
if (prepended > 0) shift2 = prepended;
|
|
28940
29001
|
else {
|
|
28941
29002
|
const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
|
|
28942
|
-
if (trimmed > 0)
|
|
29003
|
+
if (trimmed > 0) shift2 = -trimmed;
|
|
28943
29004
|
}
|
|
28944
29005
|
}
|
|
28945
|
-
if (
|
|
28946
|
-
this.zoomAnchorLogical +=
|
|
28947
|
-
if (this.hoverLogical != null) this.hoverLogical +=
|
|
29006
|
+
if (shift2 !== 0) {
|
|
29007
|
+
this.zoomAnchorLogical += shift2;
|
|
29008
|
+
if (this.hoverLogical != null) this.hoverLogical += shift2;
|
|
28948
29009
|
}
|
|
28949
29010
|
}
|
|
28950
29011
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
@@ -29836,7 +29897,7 @@ var NativeRenderer = class {
|
|
|
29836
29897
|
const pricePane = this.dataWindowPricePane();
|
|
29837
29898
|
const ohlc = this.dataWindowOHLC(bar, pricePane);
|
|
29838
29899
|
const parts = [];
|
|
29839
|
-
if (bar) parts.push(
|
|
29900
|
+
if (bar) parts.push(formatStamp(bar.time, this.scene.timezone));
|
|
29840
29901
|
if (ohlc) parts.push(`Open ${ohlc.o}, High ${ohlc.h}, Low ${ohlc.l}, Close ${ohlc.c}`);
|
|
29841
29902
|
for (const group of this.dataWindowGroups(idx, pricePane)) {
|
|
29842
29903
|
for (const row of group.rows) parts.push(`${row.label} ${row.value}`);
|
|
@@ -30550,15 +30611,15 @@ var NativeRenderer = class {
|
|
|
30550
30611
|
/** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
|
|
30551
30612
|
* strip — merged own-scale indicators are hidden while collapsed. */
|
|
30552
30613
|
collapsedMasterMap() {
|
|
30553
|
-
const
|
|
30614
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
30554
30615
|
for (const pane of this.scene.panes.values()) {
|
|
30555
30616
|
if (!pane.collapsed) continue;
|
|
30556
30617
|
const models = this.scene.orderedIndicatorsForPane(pane.id);
|
|
30557
30618
|
const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
|
|
30558
30619
|
const master = models.find((m) => !merged.has(m.id)) ?? models[0];
|
|
30559
|
-
|
|
30620
|
+
map2.set(pane.id, master?.id ?? null);
|
|
30560
30621
|
}
|
|
30561
|
-
return
|
|
30622
|
+
return map2;
|
|
30562
30623
|
}
|
|
30563
30624
|
/** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
|
|
30564
30625
|
setToolbarGutter(px) {
|
|
@@ -30723,7 +30784,7 @@ function formatStampParts(ms, timeZone) {
|
|
|
30723
30784
|
time: `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`
|
|
30724
30785
|
};
|
|
30725
30786
|
}
|
|
30726
|
-
function
|
|
30787
|
+
function formatStamp(ms, timeZone) {
|
|
30727
30788
|
const { date, time } = formatStampParts(ms, timeZone);
|
|
30728
30789
|
return `${date} ${time}`;
|
|
30729
30790
|
}
|
|
@@ -30754,7 +30815,7 @@ function sanitizeSessionZones(value) {
|
|
|
30754
30815
|
}
|
|
30755
30816
|
return out.sort((a, b) => a[0] - b[0]);
|
|
30756
30817
|
};
|
|
30757
|
-
return { pre: windows(v.pre), post: windows(v.post) };
|
|
30818
|
+
return { pre: windows(v.pre), post: windows(v.post), extended: windows(v.extended) };
|
|
30758
30819
|
}
|
|
30759
30820
|
function clamp012(v) {
|
|
30760
30821
|
return Math.max(0, Math.min(1, v));
|
|
@@ -30954,130 +31015,2449 @@ function registerVpvr() {
|
|
|
30954
31015
|
registerNativeIndicator(vpvrDescriptor);
|
|
30955
31016
|
}
|
|
30956
31017
|
|
|
30957
|
-
// src/
|
|
30958
|
-
|
|
30959
|
-
|
|
30960
|
-
|
|
30961
|
-
|
|
30962
|
-
|
|
30963
|
-
|
|
30964
|
-
|
|
30965
|
-
|
|
30966
|
-
|
|
30967
|
-
|
|
30968
|
-
|
|
30969
|
-
|
|
30970
|
-
|
|
30971
|
-
|
|
30972
|
-
|
|
30973
|
-
|
|
30974
|
-
|
|
30975
|
-
|
|
30976
|
-
|
|
30977
|
-
|
|
30978
|
-
|
|
30979
|
-
|
|
30980
|
-
glow: options.glow ?? 0,
|
|
30981
|
-
upColor: options.upColor ?? BULLISH,
|
|
30982
|
-
downColor: options.downColor ?? BEARISH,
|
|
30983
|
-
priceStyle: options.priceStyle ?? "candles"
|
|
30984
|
-
};
|
|
30985
|
-
const RendererClass = options.renderer ?? NativeRenderer;
|
|
30986
|
-
if (typeof RendererClass !== "function") {
|
|
30987
|
-
throw new Error(
|
|
30988
|
-
"[vela] options.renderer must be a renderer class, e.g. import { LwcRenderer } from 'vela/renderers/lwc'. The 'lwc'/'native' string options were removed."
|
|
30989
|
-
);
|
|
30990
|
-
}
|
|
30991
|
-
const renderer = deps.renderer ?? new RendererClass(display);
|
|
30992
|
-
const engines = deps.engines ?? [];
|
|
30993
|
-
const feed = deps.dataFeed ?? new MultiProviderFeed();
|
|
30994
|
-
const config = {
|
|
30995
|
-
market: {
|
|
30996
|
-
symbol: options.symbol,
|
|
30997
|
-
timeframe: options.timeframe,
|
|
30998
|
-
bars: options.bars,
|
|
30999
|
-
session: options.session,
|
|
31000
|
-
visibleRange: options.visibleRange,
|
|
31001
|
-
data: options.data
|
|
31002
|
-
},
|
|
31003
|
-
live: options.live ?? false,
|
|
31004
|
-
theme,
|
|
31005
|
-
defaultLanguage: options.defaultLanguage ?? engines[0]?.language ?? "pine",
|
|
31006
|
-
drawings: options.drawings,
|
|
31007
|
-
volume: options.volume ?? true
|
|
31008
|
-
// the volume indicator is on by default
|
|
31009
|
-
};
|
|
31010
|
-
this.rendererControl = new RendererControl(renderer);
|
|
31011
|
-
if (options.settings) this.rendererControl.setSettingsVisibility(options.settings);
|
|
31012
|
-
this.dataControl = new DataControl(feed);
|
|
31013
|
-
this.orchestrator = new EngineOrchestrator(element, renderer, feed, engines, config, this.dataControl);
|
|
31014
|
-
const defaults2 = rendererDefaults();
|
|
31015
|
-
if (Object.keys(defaults2).length > 0) this.rendererControl.set(defaults2);
|
|
31016
|
-
this.panesControl = new PanesControl(this.orchestrator);
|
|
31017
|
-
this.drawingsControl = new DrawingsControl(this.orchestrator.drawings);
|
|
31018
|
+
// src/core/model/identity.ts
|
|
31019
|
+
function stableSeriesId(parts) {
|
|
31020
|
+
const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
31021
|
+
return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
|
|
31022
|
+
}
|
|
31023
|
+
|
|
31024
|
+
// src/core/native-indicators/classics/define.ts
|
|
31025
|
+
function num3(inputs, key, fallback) {
|
|
31026
|
+
const v = inputs[key];
|
|
31027
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
31028
|
+
}
|
|
31029
|
+
function str3(inputs, key, fallback) {
|
|
31030
|
+
const v = inputs[key];
|
|
31031
|
+
return typeof v === "string" && v !== "" ? v : fallback;
|
|
31032
|
+
}
|
|
31033
|
+
function toPoints(bars, values, colors) {
|
|
31034
|
+
const pts = new Array(bars.length);
|
|
31035
|
+
for (let i = 0; i < bars.length; i++) {
|
|
31036
|
+
const v = values[i];
|
|
31037
|
+
const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
|
|
31038
|
+
const c = colors?.[i];
|
|
31039
|
+
if (c != null) p.color = c;
|
|
31040
|
+
pts[i] = p;
|
|
31018
31041
|
}
|
|
31019
|
-
|
|
31020
|
-
|
|
31021
|
-
|
|
31022
|
-
|
|
31023
|
-
|
|
31024
|
-
|
|
31025
|
-
* language replaces it.
|
|
31026
|
-
*/
|
|
31027
|
-
registerEngine(language, engine) {
|
|
31028
|
-
this.orchestrator.registerEngine(language, engine);
|
|
31029
|
-
return this;
|
|
31042
|
+
return pts;
|
|
31043
|
+
}
|
|
31044
|
+
var ClassicIndicator = class {
|
|
31045
|
+
constructor(spec) {
|
|
31046
|
+
this.spec = spec;
|
|
31047
|
+
this.inputs = {};
|
|
31030
31048
|
}
|
|
31031
|
-
|
|
31032
|
-
|
|
31033
|
-
|
|
31049
|
+
start(ctx, inputs) {
|
|
31050
|
+
this.ctx = ctx;
|
|
31051
|
+
this.inputs = inputs;
|
|
31052
|
+
this.recompute();
|
|
31034
31053
|
}
|
|
31035
|
-
|
|
31036
|
-
|
|
31037
|
-
* e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide, remove,
|
|
31038
|
-
* events) and is single-instance per type (a second call returns the existing handle). Returns
|
|
31039
|
-
* a fail-soft handle for an unregistered type. Native renderer only.
|
|
31040
|
-
*/
|
|
31041
|
-
addNativeIndicator(type, options) {
|
|
31042
|
-
return this.orchestrator.addNativeIndicator(type, options);
|
|
31054
|
+
onBars() {
|
|
31055
|
+
this.recompute();
|
|
31043
31056
|
}
|
|
31044
|
-
|
|
31045
|
-
* The catalog of built-in native indicators with their live state on this chart — each entry's
|
|
31046
|
-
* `type`, `title`, whether it `supported`s the current symbol, and whether it's already `present`
|
|
31047
|
-
* (native indicators are single-instance per type, so a second `addNativeIndicator` is a no-op).
|
|
31048
|
-
* Lets a host "add indicator" UI list them, gate unsupported ones, and avoid duplicates. Async
|
|
31049
|
-
* because support may probe the provider (a type may need data the symbol lacks).
|
|
31050
|
-
*/
|
|
31051
|
-
availableNativeIndicators() {
|
|
31052
|
-
return this.orchestrator.availableNativeIndicators();
|
|
31057
|
+
onViewport() {
|
|
31053
31058
|
}
|
|
31054
|
-
|
|
31055
|
-
|
|
31056
|
-
|
|
31057
|
-
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
31058
|
-
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|
|
31059
|
-
*/
|
|
31060
|
-
presentNativeIndicators() {
|
|
31061
|
-
return this.orchestrator.presentNativeIndicators();
|
|
31059
|
+
setInputs(inputs) {
|
|
31060
|
+
this.inputs = inputs;
|
|
31061
|
+
this.recompute();
|
|
31062
31062
|
}
|
|
31063
|
-
/**
|
|
31064
|
-
|
|
31065
|
-
indicators() {
|
|
31066
|
-
return this.orchestrator.listIndicators();
|
|
31063
|
+
/** Nothing to free — the compute reads the live bar accessor on demand. */
|
|
31064
|
+
suspend() {
|
|
31067
31065
|
}
|
|
31068
|
-
|
|
31069
|
-
|
|
31070
|
-
|
|
31071
|
-
|
|
31072
|
-
|
|
31073
|
-
|
|
31074
|
-
|
|
31075
|
-
|
|
31076
|
-
|
|
31077
|
-
|
|
31078
|
-
|
|
31079
|
-
|
|
31080
|
-
|
|
31066
|
+
resume() {
|
|
31067
|
+
this.recompute();
|
|
31068
|
+
}
|
|
31069
|
+
stop() {
|
|
31070
|
+
}
|
|
31071
|
+
recompute() {
|
|
31072
|
+
const bars = this.ctx.bars();
|
|
31073
|
+
const out = this.spec.compute(bars, this.inputs);
|
|
31074
|
+
const instanceId = this.spec.type;
|
|
31075
|
+
const series = [];
|
|
31076
|
+
const idsByKey = /* @__PURE__ */ new Map();
|
|
31077
|
+
out.plots.forEach((plot, ordinal) => {
|
|
31078
|
+
const kind = plot.kind ?? "line";
|
|
31079
|
+
const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
|
|
31080
|
+
idsByKey.set(plot.key, id);
|
|
31081
|
+
series.push({
|
|
31082
|
+
id,
|
|
31083
|
+
title: plot.title,
|
|
31084
|
+
paneId: "",
|
|
31085
|
+
// stamped by the orchestrator
|
|
31086
|
+
kind,
|
|
31087
|
+
points: toPoints(bars, plot.values, plot.colors),
|
|
31088
|
+
style: {
|
|
31089
|
+
color: plot.color,
|
|
31090
|
+
width: plot.width ?? 1,
|
|
31091
|
+
lineStyle: plot.lineStyle ?? "solid",
|
|
31092
|
+
...plot.base != null ? { base: plot.base } : {}
|
|
31093
|
+
},
|
|
31094
|
+
...plot.overlay ? { overlay: true } : {}
|
|
31095
|
+
});
|
|
31096
|
+
});
|
|
31097
|
+
if (out.markers && out.markers.length > 0) {
|
|
31098
|
+
series.push({
|
|
31099
|
+
id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
|
|
31100
|
+
title: this.spec.shortTitle ?? this.spec.title,
|
|
31101
|
+
paneId: "",
|
|
31102
|
+
kind: "markers",
|
|
31103
|
+
markers: out.markers
|
|
31104
|
+
});
|
|
31105
|
+
}
|
|
31106
|
+
const fills = (out.bands ?? []).flatMap((band, ordinal) => {
|
|
31107
|
+
const from = idsByKey.get(band.from);
|
|
31108
|
+
const to = idsByKey.get(band.to);
|
|
31109
|
+
if (!from || !to) return [];
|
|
31110
|
+
return [
|
|
31111
|
+
{
|
|
31112
|
+
id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
|
|
31113
|
+
paneId: "",
|
|
31114
|
+
fromSeriesId: from,
|
|
31115
|
+
toSeriesId: to,
|
|
31116
|
+
color: band.color
|
|
31117
|
+
}
|
|
31118
|
+
];
|
|
31119
|
+
});
|
|
31120
|
+
const priceLines = (out.levels ?? []).map((level, ordinal) => ({
|
|
31121
|
+
id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
|
|
31122
|
+
paneId: "",
|
|
31123
|
+
price: level.price,
|
|
31124
|
+
...level.color != null ? { color: level.color } : {},
|
|
31125
|
+
lineStyle: level.lineStyle ?? "dashed",
|
|
31126
|
+
width: 1,
|
|
31127
|
+
...level.title != null ? { title: level.title } : {}
|
|
31128
|
+
}));
|
|
31129
|
+
const polylines = (out.polylines ?? []).map((poly, ordinal) => {
|
|
31130
|
+
const { key, ...rest } = poly;
|
|
31131
|
+
return {
|
|
31132
|
+
id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
|
|
31133
|
+
paneId: "",
|
|
31134
|
+
...rest
|
|
31135
|
+
};
|
|
31136
|
+
});
|
|
31137
|
+
this.ctx.emit({ series, fills, priceLines, polylines });
|
|
31138
|
+
this.ctx.setStatus("idle");
|
|
31139
|
+
}
|
|
31140
|
+
};
|
|
31141
|
+
function classicDescriptor(spec) {
|
|
31142
|
+
return {
|
|
31143
|
+
type: spec.type,
|
|
31144
|
+
title: spec.title,
|
|
31145
|
+
...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
|
|
31146
|
+
paneHint: spec.overlay ? "price" : "new",
|
|
31147
|
+
overlay: spec.overlay,
|
|
31148
|
+
// Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
|
|
31149
|
+
multiInstance: true,
|
|
31150
|
+
inputsSchema: () => spec.inputs,
|
|
31151
|
+
defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
|
|
31152
|
+
create: () => new ClassicIndicator(spec)
|
|
31153
|
+
};
|
|
31154
|
+
}
|
|
31155
|
+
|
|
31156
|
+
// src/core/native-indicators/classics/math.ts
|
|
31157
|
+
var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
|
|
31158
|
+
function sourceValues(bars, source) {
|
|
31159
|
+
const out = new Array(bars.length);
|
|
31160
|
+
for (let i = 0; i < bars.length; i++) {
|
|
31161
|
+
const b = bars[i];
|
|
31162
|
+
switch (source) {
|
|
31163
|
+
case "Open":
|
|
31164
|
+
out[i] = b.open;
|
|
31165
|
+
break;
|
|
31166
|
+
case "High":
|
|
31167
|
+
out[i] = b.high;
|
|
31168
|
+
break;
|
|
31169
|
+
case "Low":
|
|
31170
|
+
out[i] = b.low;
|
|
31171
|
+
break;
|
|
31172
|
+
case "HL2":
|
|
31173
|
+
out[i] = (b.high + b.low) / 2;
|
|
31174
|
+
break;
|
|
31175
|
+
case "HLC3":
|
|
31176
|
+
out[i] = (b.high + b.low + b.close) / 3;
|
|
31177
|
+
break;
|
|
31178
|
+
case "OHLC4":
|
|
31179
|
+
out[i] = (b.open + b.high + b.low + b.close) / 4;
|
|
31180
|
+
break;
|
|
31181
|
+
case "HLCC4":
|
|
31182
|
+
out[i] = (b.high + b.low + b.close + b.close) / 4;
|
|
31183
|
+
break;
|
|
31184
|
+
default:
|
|
31185
|
+
out[i] = b.close;
|
|
31186
|
+
}
|
|
31187
|
+
}
|
|
31188
|
+
return out;
|
|
31189
|
+
}
|
|
31190
|
+
function volumes(bars) {
|
|
31191
|
+
return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
|
|
31192
|
+
}
|
|
31193
|
+
function highs(bars) {
|
|
31194
|
+
return bars.map((b) => b.high);
|
|
31195
|
+
}
|
|
31196
|
+
function lows(bars) {
|
|
31197
|
+
return bars.map((b) => b.low);
|
|
31198
|
+
}
|
|
31199
|
+
function closes(bars) {
|
|
31200
|
+
return bars.map((b) => b.close);
|
|
31201
|
+
}
|
|
31202
|
+
function opens(bars) {
|
|
31203
|
+
return bars.map((b) => b.open);
|
|
31204
|
+
}
|
|
31205
|
+
function zip(a, b, f) {
|
|
31206
|
+
const out = new Array(a.length);
|
|
31207
|
+
for (let i = 0; i < a.length; i++) {
|
|
31208
|
+
const x = a[i];
|
|
31209
|
+
const y = b[i];
|
|
31210
|
+
out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
|
|
31211
|
+
}
|
|
31212
|
+
return out;
|
|
31213
|
+
}
|
|
31214
|
+
function map(a, f) {
|
|
31215
|
+
const out = new Array(a.length);
|
|
31216
|
+
for (let i = 0; i < a.length; i++) {
|
|
31217
|
+
const x = a[i];
|
|
31218
|
+
out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
|
|
31219
|
+
}
|
|
31220
|
+
return out;
|
|
31221
|
+
}
|
|
31222
|
+
function sub(a, b) {
|
|
31223
|
+
return zip(a, b, (x, y) => x - y);
|
|
31224
|
+
}
|
|
31225
|
+
function windowed(v, len, fold) {
|
|
31226
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31227
|
+
if (len <= 0) return out;
|
|
31228
|
+
let run = 0;
|
|
31229
|
+
for (let i = 0; i < v.length; i++) {
|
|
31230
|
+
run = Number.isFinite(v[i]) ? run + 1 : 0;
|
|
31231
|
+
if (run >= len) out[i] = fold(v, i - len + 1, i);
|
|
31232
|
+
}
|
|
31233
|
+
return out;
|
|
31234
|
+
}
|
|
31235
|
+
function sum(v, len) {
|
|
31236
|
+
return windowed(v, len, (w, from, to) => {
|
|
31237
|
+
let s = 0;
|
|
31238
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
31239
|
+
return s;
|
|
31240
|
+
});
|
|
31241
|
+
}
|
|
31242
|
+
function sma(v, len) {
|
|
31243
|
+
return windowed(v, len, (w, from, to) => {
|
|
31244
|
+
let s = 0;
|
|
31245
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
31246
|
+
return s / len;
|
|
31247
|
+
});
|
|
31248
|
+
}
|
|
31249
|
+
function wma(v, len) {
|
|
31250
|
+
const norm = len * (len + 1) / 2;
|
|
31251
|
+
return windowed(v, len, (w, from, to) => {
|
|
31252
|
+
let s = 0;
|
|
31253
|
+
for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
|
|
31254
|
+
return s / norm;
|
|
31255
|
+
});
|
|
31256
|
+
}
|
|
31257
|
+
function vwma(src, vol, len) {
|
|
31258
|
+
const pv = zip(src, vol, (p, q) => p * q);
|
|
31259
|
+
return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
|
|
31260
|
+
}
|
|
31261
|
+
function stdev(v, len) {
|
|
31262
|
+
return windowed(v, len, (w, from, to) => {
|
|
31263
|
+
let s = 0;
|
|
31264
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
31265
|
+
const mean = s / len;
|
|
31266
|
+
let sq = 0;
|
|
31267
|
+
for (let k = from; k <= to; k++) {
|
|
31268
|
+
const d = w[k] - mean;
|
|
31269
|
+
sq += d * d;
|
|
31270
|
+
}
|
|
31271
|
+
return Math.sqrt(sq / len);
|
|
31272
|
+
});
|
|
31273
|
+
}
|
|
31274
|
+
function highest(v, len) {
|
|
31275
|
+
return windowed(v, len, (w, from, to) => {
|
|
31276
|
+
let m = -Infinity;
|
|
31277
|
+
for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
|
|
31278
|
+
return m;
|
|
31279
|
+
});
|
|
31280
|
+
}
|
|
31281
|
+
function lowest(v, len) {
|
|
31282
|
+
return windowed(v, len, (w, from, to) => {
|
|
31283
|
+
let m = Infinity;
|
|
31284
|
+
for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
|
|
31285
|
+
return m;
|
|
31286
|
+
});
|
|
31287
|
+
}
|
|
31288
|
+
function barsSinceHighest(v, len) {
|
|
31289
|
+
return windowed(v, len, (w, from, to) => {
|
|
31290
|
+
let m = -Infinity;
|
|
31291
|
+
let at = to;
|
|
31292
|
+
for (let k = from; k <= to; k++) {
|
|
31293
|
+
if (w[k] >= m) {
|
|
31294
|
+
m = w[k];
|
|
31295
|
+
at = k;
|
|
31296
|
+
}
|
|
31297
|
+
}
|
|
31298
|
+
return to - at;
|
|
31299
|
+
});
|
|
31300
|
+
}
|
|
31301
|
+
function barsSinceLowest(v, len) {
|
|
31302
|
+
return windowed(v, len, (w, from, to) => {
|
|
31303
|
+
let m = Infinity;
|
|
31304
|
+
let at = to;
|
|
31305
|
+
for (let k = from; k <= to; k++) {
|
|
31306
|
+
if (w[k] <= m) {
|
|
31307
|
+
m = w[k];
|
|
31308
|
+
at = k;
|
|
31309
|
+
}
|
|
31310
|
+
}
|
|
31311
|
+
return to - at;
|
|
31312
|
+
});
|
|
31313
|
+
}
|
|
31314
|
+
function recursive(v, len, alpha) {
|
|
31315
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31316
|
+
let run = 0;
|
|
31317
|
+
let prev2 = Number.NaN;
|
|
31318
|
+
for (let i = 0; i < v.length; i++) {
|
|
31319
|
+
const x = v[i];
|
|
31320
|
+
if (!Number.isFinite(x)) {
|
|
31321
|
+
run = 0;
|
|
31322
|
+
prev2 = Number.NaN;
|
|
31323
|
+
continue;
|
|
31324
|
+
}
|
|
31325
|
+
run += 1;
|
|
31326
|
+
if (Number.isFinite(prev2)) {
|
|
31327
|
+
prev2 = alpha * x + (1 - alpha) * prev2;
|
|
31328
|
+
out[i] = prev2;
|
|
31329
|
+
} else if (run >= len) {
|
|
31330
|
+
let s = 0;
|
|
31331
|
+
for (let k = i - len + 1; k <= i; k++) s += v[k];
|
|
31332
|
+
prev2 = s / len;
|
|
31333
|
+
out[i] = prev2;
|
|
31334
|
+
}
|
|
31335
|
+
}
|
|
31336
|
+
return out;
|
|
31337
|
+
}
|
|
31338
|
+
function ema(v, len) {
|
|
31339
|
+
return recursive(v, len, 2 / (len + 1));
|
|
31340
|
+
}
|
|
31341
|
+
function rma(v, len) {
|
|
31342
|
+
return recursive(v, len, 1 / len);
|
|
31343
|
+
}
|
|
31344
|
+
function change(v, n = 1) {
|
|
31345
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31346
|
+
for (let i = n; i < v.length; i++) {
|
|
31347
|
+
const a = v[i];
|
|
31348
|
+
const b = v[i - n];
|
|
31349
|
+
if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
|
|
31350
|
+
}
|
|
31351
|
+
return out;
|
|
31352
|
+
}
|
|
31353
|
+
function roc(v, len) {
|
|
31354
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31355
|
+
for (let i = len; i < v.length; i++) {
|
|
31356
|
+
const a = v[i];
|
|
31357
|
+
const b = v[i - len];
|
|
31358
|
+
if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
|
|
31359
|
+
}
|
|
31360
|
+
return out;
|
|
31361
|
+
}
|
|
31362
|
+
function shift(v, n) {
|
|
31363
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31364
|
+
for (let i = 0; i < v.length; i++) {
|
|
31365
|
+
const j = i - n;
|
|
31366
|
+
if (j >= 0 && j < v.length) out[i] = v[j];
|
|
31367
|
+
}
|
|
31368
|
+
return out;
|
|
31369
|
+
}
|
|
31370
|
+
function trueRange(bars) {
|
|
31371
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
31372
|
+
for (let i = 0; i < bars.length; i++) {
|
|
31373
|
+
const b = bars[i];
|
|
31374
|
+
if (i === 0) {
|
|
31375
|
+
out[i] = b.high - b.low;
|
|
31376
|
+
continue;
|
|
31377
|
+
}
|
|
31378
|
+
const pc = bars[i - 1].close;
|
|
31379
|
+
out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
|
|
31380
|
+
}
|
|
31381
|
+
return out;
|
|
31382
|
+
}
|
|
31383
|
+
function atr(bars, len) {
|
|
31384
|
+
return rma(trueRange(bars), len);
|
|
31385
|
+
}
|
|
31386
|
+
function rsi(v, len) {
|
|
31387
|
+
const d = change(v);
|
|
31388
|
+
const up = map(d, (x) => Math.max(x, 0));
|
|
31389
|
+
const dn = map(d, (x) => Math.max(-x, 0));
|
|
31390
|
+
return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
|
|
31391
|
+
}
|
|
31392
|
+
function stoch(v, hi, lo, len) {
|
|
31393
|
+
const hh = highest(hi, len);
|
|
31394
|
+
const ll = lowest(lo, len);
|
|
31395
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31396
|
+
for (let i = 0; i < v.length; i++) {
|
|
31397
|
+
const x = v[i];
|
|
31398
|
+
const h = hh[i];
|
|
31399
|
+
const l = ll[i];
|
|
31400
|
+
if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
31401
|
+
out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
|
|
31402
|
+
}
|
|
31403
|
+
return out;
|
|
31404
|
+
}
|
|
31405
|
+
function linreg(v, len) {
|
|
31406
|
+
const sx = (len - 1) * len / 2;
|
|
31407
|
+
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
31408
|
+
const denom = len * sxx - sx * sx;
|
|
31409
|
+
return windowed(v, len, (w, from, to) => {
|
|
31410
|
+
let sy = 0;
|
|
31411
|
+
let sxy = 0;
|
|
31412
|
+
for (let k = from; k <= to; k++) {
|
|
31413
|
+
const x = k - from;
|
|
31414
|
+
sy += w[k];
|
|
31415
|
+
sxy += x * w[k];
|
|
31416
|
+
}
|
|
31417
|
+
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
31418
|
+
const intercept = (sy - slope * sx) / len;
|
|
31419
|
+
return intercept + slope * (len - 1);
|
|
31420
|
+
});
|
|
31421
|
+
}
|
|
31422
|
+
function swma(v) {
|
|
31423
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31424
|
+
for (let i = 3; i < v.length; i++) {
|
|
31425
|
+
const a = v[i - 3];
|
|
31426
|
+
const b = v[i - 2];
|
|
31427
|
+
const c = v[i - 1];
|
|
31428
|
+
const d = v[i];
|
|
31429
|
+
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
|
|
31430
|
+
out[i] = (a + 2 * b + 2 * c + d) / 6;
|
|
31431
|
+
}
|
|
31432
|
+
}
|
|
31433
|
+
return out;
|
|
31434
|
+
}
|
|
31435
|
+
function percentRank(v, len) {
|
|
31436
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31437
|
+
for (let i = len; i < v.length; i++) {
|
|
31438
|
+
const x = v[i];
|
|
31439
|
+
if (!Number.isFinite(x)) continue;
|
|
31440
|
+
let below = 0;
|
|
31441
|
+
let count = 0;
|
|
31442
|
+
for (let k = i - len; k < i; k++) {
|
|
31443
|
+
const y = v[k];
|
|
31444
|
+
if (!Number.isFinite(y)) {
|
|
31445
|
+
count = -1;
|
|
31446
|
+
break;
|
|
31447
|
+
}
|
|
31448
|
+
count += 1;
|
|
31449
|
+
if (y <= x) below += 1;
|
|
31450
|
+
}
|
|
31451
|
+
if (count === len) out[i] = 100 * below / len;
|
|
31452
|
+
}
|
|
31453
|
+
return out;
|
|
31454
|
+
}
|
|
31455
|
+
function cumSum(v) {
|
|
31456
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31457
|
+
let acc = 0;
|
|
31458
|
+
let seen = false;
|
|
31459
|
+
for (let i = 0; i < v.length; i++) {
|
|
31460
|
+
const x = v[i];
|
|
31461
|
+
if (Number.isFinite(x)) {
|
|
31462
|
+
acc += x;
|
|
31463
|
+
seen = true;
|
|
31464
|
+
}
|
|
31465
|
+
if (seen) out[i] = acc;
|
|
31466
|
+
}
|
|
31467
|
+
return out;
|
|
31468
|
+
}
|
|
31469
|
+
|
|
31470
|
+
// src/core/native-indicators/classics/shared.ts
|
|
31471
|
+
function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
|
|
31472
|
+
return { key, title, type: "int", defval, min: 1, max, step: 1 };
|
|
31473
|
+
}
|
|
31474
|
+
function sourceInput(defval = "Close") {
|
|
31475
|
+
return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
|
|
31476
|
+
}
|
|
31477
|
+
function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
|
|
31478
|
+
return { key, title, type: "color", defval };
|
|
31479
|
+
}
|
|
31480
|
+
function withAlpha2(hex, alpha = 0.08) {
|
|
31481
|
+
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
31482
|
+
if (!m) return hex;
|
|
31483
|
+
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
31484
|
+
return `#${m[1]}${a}`;
|
|
31485
|
+
}
|
|
31486
|
+
|
|
31487
|
+
// src/core/native-indicators/classics/averages.ts
|
|
31488
|
+
var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
|
|
31489
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
|
|
31490
|
+
function fixedAverage(type, title, shortTitle, smooth) {
|
|
31491
|
+
return {
|
|
31492
|
+
type,
|
|
31493
|
+
title,
|
|
31494
|
+
shortTitle,
|
|
31495
|
+
overlay: true,
|
|
31496
|
+
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
31497
|
+
compute: (bars, inputs) => {
|
|
31498
|
+
const len = num3(inputs, "length", 20);
|
|
31499
|
+
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
31500
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
31501
|
+
if (offset !== 0) values = shift(values, offset);
|
|
31502
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31503
|
+
}
|
|
31504
|
+
};
|
|
31505
|
+
}
|
|
31506
|
+
var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
|
|
31507
|
+
var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
|
|
31508
|
+
var movingAverage = {
|
|
31509
|
+
type: "moving-average",
|
|
31510
|
+
title: "Moving Average",
|
|
31511
|
+
shortTitle: "MA",
|
|
31512
|
+
overlay: true,
|
|
31513
|
+
inputs: [
|
|
31514
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
|
|
31515
|
+
lengthInput(20),
|
|
31516
|
+
sourceInput(),
|
|
31517
|
+
offsetInput,
|
|
31518
|
+
colorInput()
|
|
31519
|
+
],
|
|
31520
|
+
compute: (bars, inputs) => {
|
|
31521
|
+
const len = num3(inputs, "length", 20);
|
|
31522
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31523
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
31524
|
+
let values;
|
|
31525
|
+
switch (kind) {
|
|
31526
|
+
case "EMA":
|
|
31527
|
+
values = ema(src, len);
|
|
31528
|
+
break;
|
|
31529
|
+
case "WMA":
|
|
31530
|
+
values = wma(src, len);
|
|
31531
|
+
break;
|
|
31532
|
+
case "RMA":
|
|
31533
|
+
values = rma(src, len);
|
|
31534
|
+
break;
|
|
31535
|
+
case "VWMA":
|
|
31536
|
+
values = vwma(src, volumes(bars), len);
|
|
31537
|
+
break;
|
|
31538
|
+
default:
|
|
31539
|
+
values = sma(src, len);
|
|
31540
|
+
}
|
|
31541
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
31542
|
+
if (offset !== 0) values = shift(values, offset);
|
|
31543
|
+
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31544
|
+
}
|
|
31545
|
+
};
|
|
31546
|
+
var smoothedMa = {
|
|
31547
|
+
type: "rma",
|
|
31548
|
+
title: "Smoothed Moving Average",
|
|
31549
|
+
shortTitle: "RMA",
|
|
31550
|
+
overlay: true,
|
|
31551
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
31552
|
+
compute: (bars, inputs) => ({
|
|
31553
|
+
plots: [
|
|
31554
|
+
{
|
|
31555
|
+
key: "rma",
|
|
31556
|
+
title: "RMA",
|
|
31557
|
+
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
31558
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
31559
|
+
width: 2
|
|
31560
|
+
}
|
|
31561
|
+
]
|
|
31562
|
+
})
|
|
31563
|
+
};
|
|
31564
|
+
var zlema = {
|
|
31565
|
+
type: "zlema",
|
|
31566
|
+
title: "Zero-Lag Exponential Moving Average",
|
|
31567
|
+
shortTitle: "ZLEMA",
|
|
31568
|
+
overlay: true,
|
|
31569
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
31570
|
+
compute: (bars, inputs) => {
|
|
31571
|
+
const len = num3(inputs, "length", 14);
|
|
31572
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31573
|
+
const lag = Math.floor((len - 1) / 2);
|
|
31574
|
+
const lagged = shift(src, lag);
|
|
31575
|
+
const delagged = zip(src, lagged, (a, b) => 2 * a - b);
|
|
31576
|
+
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31577
|
+
}
|
|
31578
|
+
};
|
|
31579
|
+
var vidya = {
|
|
31580
|
+
type: "vidya",
|
|
31581
|
+
title: "Variable Index Dynamic Average",
|
|
31582
|
+
shortTitle: "VIDYA",
|
|
31583
|
+
overlay: true,
|
|
31584
|
+
inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
|
|
31585
|
+
compute: (bars, inputs) => {
|
|
31586
|
+
const len = num3(inputs, "length", 14);
|
|
31587
|
+
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
31588
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31589
|
+
const d = change(src);
|
|
31590
|
+
const su = new Array(src.length).fill(Number.NaN);
|
|
31591
|
+
const sd = new Array(src.length).fill(Number.NaN);
|
|
31592
|
+
for (let i = cmoLen; i < src.length; i++) {
|
|
31593
|
+
let u = 0;
|
|
31594
|
+
let w = 0;
|
|
31595
|
+
let ok = true;
|
|
31596
|
+
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
31597
|
+
const x = d[k];
|
|
31598
|
+
if (!Number.isFinite(x)) {
|
|
31599
|
+
ok = false;
|
|
31600
|
+
break;
|
|
31601
|
+
}
|
|
31602
|
+
if (x > 0) u += x;
|
|
31603
|
+
else w -= x;
|
|
31604
|
+
}
|
|
31605
|
+
if (ok) {
|
|
31606
|
+
su[i] = u;
|
|
31607
|
+
sd[i] = w;
|
|
31608
|
+
}
|
|
31609
|
+
}
|
|
31610
|
+
const alpha = 2 / (len + 1);
|
|
31611
|
+
const out = new Array(src.length).fill(Number.NaN);
|
|
31612
|
+
let prev2 = Number.NaN;
|
|
31613
|
+
for (let i = 0; i < src.length; i++) {
|
|
31614
|
+
const x = src[i];
|
|
31615
|
+
const u = su[i];
|
|
31616
|
+
const w = sd[i];
|
|
31617
|
+
if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
|
|
31618
|
+
const total = u + w;
|
|
31619
|
+
const k = total === 0 ? 0 : Math.abs((u - w) / total);
|
|
31620
|
+
prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
|
|
31621
|
+
out[i] = prev2;
|
|
31622
|
+
}
|
|
31623
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31624
|
+
}
|
|
31625
|
+
};
|
|
31626
|
+
var maEnvelope = {
|
|
31627
|
+
type: "ma-envelope",
|
|
31628
|
+
title: "Moving Average Envelope",
|
|
31629
|
+
shortTitle: "MA Env",
|
|
31630
|
+
overlay: true,
|
|
31631
|
+
inputs: [
|
|
31632
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
|
|
31633
|
+
lengthInput(20),
|
|
31634
|
+
sourceInput(),
|
|
31635
|
+
{ key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
|
|
31636
|
+
colorInput()
|
|
31637
|
+
],
|
|
31638
|
+
compute: (bars, inputs) => {
|
|
31639
|
+
const len = num3(inputs, "length", 20);
|
|
31640
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31641
|
+
const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
|
|
31642
|
+
const k = num3(inputs, "percent", 2.5) / 100;
|
|
31643
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
31644
|
+
return {
|
|
31645
|
+
plots: [
|
|
31646
|
+
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
31647
|
+
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
31648
|
+
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
31649
|
+
],
|
|
31650
|
+
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
31651
|
+
};
|
|
31652
|
+
}
|
|
31653
|
+
};
|
|
31654
|
+
var linearRegression = {
|
|
31655
|
+
type: "linear-regression",
|
|
31656
|
+
title: "Linear Regression Curve",
|
|
31657
|
+
shortTitle: "LinReg",
|
|
31658
|
+
overlay: true,
|
|
31659
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
31660
|
+
compute: (bars, inputs) => ({
|
|
31661
|
+
plots: [
|
|
31662
|
+
{
|
|
31663
|
+
key: "linreg",
|
|
31664
|
+
title: "LinReg",
|
|
31665
|
+
values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
31666
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
31667
|
+
width: 2
|
|
31668
|
+
}
|
|
31669
|
+
]
|
|
31670
|
+
})
|
|
31671
|
+
};
|
|
31672
|
+
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
31673
|
+
|
|
31674
|
+
// src/core/native-indicators/classics/bands.ts
|
|
31675
|
+
function bollinger(bars, inputs) {
|
|
31676
|
+
const len = num3(inputs, "length", 20);
|
|
31677
|
+
const mult = num3(inputs, "mult", 2);
|
|
31678
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31679
|
+
const basis = sma(src, len);
|
|
31680
|
+
const dev = map(stdev(src, len), (x) => x * mult);
|
|
31681
|
+
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
31682
|
+
}
|
|
31683
|
+
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
31684
|
+
var bollingerBands = {
|
|
31685
|
+
type: "bollinger-bands",
|
|
31686
|
+
title: "Bollinger Bands",
|
|
31687
|
+
shortTitle: "BB",
|
|
31688
|
+
overlay: true,
|
|
31689
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
31690
|
+
compute: (bars, inputs) => {
|
|
31691
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
31692
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
31693
|
+
return {
|
|
31694
|
+
plots: [
|
|
31695
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
31696
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
31697
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
31698
|
+
],
|
|
31699
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
31700
|
+
};
|
|
31701
|
+
}
|
|
31702
|
+
};
|
|
31703
|
+
var percentB = {
|
|
31704
|
+
type: "percent-b",
|
|
31705
|
+
title: "Bollinger Bands %B",
|
|
31706
|
+
shortTitle: "%B",
|
|
31707
|
+
overlay: false,
|
|
31708
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
31709
|
+
compute: (bars, inputs) => {
|
|
31710
|
+
const { upper, lower } = bollinger(bars, inputs);
|
|
31711
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31712
|
+
const values = src.map((x, i) => {
|
|
31713
|
+
const u = upper[i];
|
|
31714
|
+
const l = lower[i];
|
|
31715
|
+
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
31716
|
+
});
|
|
31717
|
+
return {
|
|
31718
|
+
plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
31719
|
+
levels: [
|
|
31720
|
+
{ key: "upper", price: 1, color: NEUTRAL },
|
|
31721
|
+
{ key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
|
|
31722
|
+
{ key: "lower", price: 0, color: NEUTRAL }
|
|
31723
|
+
]
|
|
31724
|
+
};
|
|
31725
|
+
}
|
|
31726
|
+
};
|
|
31727
|
+
var bandwidth = {
|
|
31728
|
+
type: "bandwidth",
|
|
31729
|
+
title: "Bollinger Bands Width",
|
|
31730
|
+
shortTitle: "BandWidth",
|
|
31731
|
+
overlay: false,
|
|
31732
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
31733
|
+
compute: (bars, inputs) => {
|
|
31734
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
31735
|
+
const values = basis.map((b, i) => {
|
|
31736
|
+
const u = upper[i];
|
|
31737
|
+
const l = lower[i];
|
|
31738
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
|
|
31739
|
+
});
|
|
31740
|
+
return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31741
|
+
}
|
|
31742
|
+
};
|
|
31743
|
+
var keltner = {
|
|
31744
|
+
type: "keltner-channels",
|
|
31745
|
+
title: "Keltner Channels",
|
|
31746
|
+
shortTitle: "KC",
|
|
31747
|
+
overlay: true,
|
|
31748
|
+
inputs: [
|
|
31749
|
+
lengthInput(20),
|
|
31750
|
+
{ key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
31751
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
31752
|
+
sourceInput(),
|
|
31753
|
+
colorInput()
|
|
31754
|
+
],
|
|
31755
|
+
compute: (bars, inputs) => {
|
|
31756
|
+
const len = num3(inputs, "length", 20);
|
|
31757
|
+
const mult = num3(inputs, "mult", 2);
|
|
31758
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
31759
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
|
|
31760
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
31761
|
+
return {
|
|
31762
|
+
plots: [
|
|
31763
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
31764
|
+
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
31765
|
+
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
31766
|
+
],
|
|
31767
|
+
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
31768
|
+
};
|
|
31769
|
+
}
|
|
31770
|
+
};
|
|
31771
|
+
var donchian = {
|
|
31772
|
+
type: "donchian-channels",
|
|
31773
|
+
title: "Donchian Channels",
|
|
31774
|
+
shortTitle: "DC",
|
|
31775
|
+
overlay: true,
|
|
31776
|
+
inputs: [lengthInput(20), colorInput()],
|
|
31777
|
+
compute: (bars, inputs) => {
|
|
31778
|
+
const len = num3(inputs, "length", 20);
|
|
31779
|
+
const upper = highest(highs(bars), len);
|
|
31780
|
+
const lower = lowest(lows(bars), len);
|
|
31781
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
31782
|
+
return {
|
|
31783
|
+
plots: [
|
|
31784
|
+
{ key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
|
|
31785
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
31786
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
31787
|
+
],
|
|
31788
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
31789
|
+
};
|
|
31790
|
+
}
|
|
31791
|
+
};
|
|
31792
|
+
var chandelier = {
|
|
31793
|
+
type: "chandelier-exit",
|
|
31794
|
+
title: "Chandelier Exit",
|
|
31795
|
+
shortTitle: "CE",
|
|
31796
|
+
overlay: true,
|
|
31797
|
+
inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
|
|
31798
|
+
compute: (bars, inputs) => {
|
|
31799
|
+
const len = num3(inputs, "length", 22);
|
|
31800
|
+
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
31801
|
+
const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
31802
|
+
const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
31803
|
+
return {
|
|
31804
|
+
plots: [
|
|
31805
|
+
{ key: "long", title: "Long stop", values: long, color: BULLISH },
|
|
31806
|
+
{ key: "short", title: "Short stop", values: short, color: BEARISH }
|
|
31807
|
+
]
|
|
31808
|
+
};
|
|
31809
|
+
}
|
|
31810
|
+
};
|
|
31811
|
+
var chandeKroll = {
|
|
31812
|
+
type: "chande-kroll-stop",
|
|
31813
|
+
title: "Chande Kroll Stop",
|
|
31814
|
+
shortTitle: "CKS",
|
|
31815
|
+
overlay: true,
|
|
31816
|
+
inputs: [
|
|
31817
|
+
{ key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
31818
|
+
{ key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
|
|
31819
|
+
{ key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
|
|
31820
|
+
],
|
|
31821
|
+
compute: (bars, inputs) => {
|
|
31822
|
+
const p = num3(inputs, "p", 10);
|
|
31823
|
+
const x = num3(inputs, "x", 1);
|
|
31824
|
+
const q = num3(inputs, "q", 9);
|
|
31825
|
+
const range = map(atr(bars, p), (v) => v * x);
|
|
31826
|
+
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
31827
|
+
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
31828
|
+
return {
|
|
31829
|
+
plots: [
|
|
31830
|
+
{ key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
|
|
31831
|
+
{ key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
|
|
31832
|
+
]
|
|
31833
|
+
};
|
|
31834
|
+
}
|
|
31835
|
+
};
|
|
31836
|
+
var supertrend = {
|
|
31837
|
+
type: "supertrend",
|
|
31838
|
+
title: "SuperTrend",
|
|
31839
|
+
shortTitle: "SuperTrend",
|
|
31840
|
+
overlay: true,
|
|
31841
|
+
inputs: [
|
|
31842
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
31843
|
+
{ key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
|
|
31844
|
+
],
|
|
31845
|
+
compute: (bars, inputs) => {
|
|
31846
|
+
const len = num3(inputs, "atrLength", 10);
|
|
31847
|
+
const mult = num3(inputs, "mult", 3);
|
|
31848
|
+
const range = atr(bars, len);
|
|
31849
|
+
const n = bars.length;
|
|
31850
|
+
const st = new Array(n).fill(Number.NaN);
|
|
31851
|
+
const dir = new Array(n).fill(1);
|
|
31852
|
+
let up = Number.NaN;
|
|
31853
|
+
let dn = Number.NaN;
|
|
31854
|
+
let trend = 1;
|
|
31855
|
+
for (let i = 0; i < n; i++) {
|
|
31856
|
+
const r = range[i];
|
|
31857
|
+
if (!Number.isFinite(r)) continue;
|
|
31858
|
+
const b = bars[i];
|
|
31859
|
+
const mid = (b.high + b.low) / 2;
|
|
31860
|
+
const basicUp = mid - mult * r;
|
|
31861
|
+
const basicDn = mid + mult * r;
|
|
31862
|
+
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
31863
|
+
up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
|
|
31864
|
+
dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
|
|
31865
|
+
if (trend === 1 && b.close < up) trend = -1;
|
|
31866
|
+
else if (trend === -1 && b.close > dn) trend = 1;
|
|
31867
|
+
dir[i] = trend;
|
|
31868
|
+
st[i] = trend === 1 ? up : dn;
|
|
31869
|
+
}
|
|
31870
|
+
const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
|
|
31871
|
+
return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
|
|
31872
|
+
}
|
|
31873
|
+
};
|
|
31874
|
+
var alligator = {
|
|
31875
|
+
type: "williams-alligator",
|
|
31876
|
+
title: "Williams Alligator",
|
|
31877
|
+
shortTitle: "Alligator",
|
|
31878
|
+
overlay: true,
|
|
31879
|
+
inputs: [
|
|
31880
|
+
{ key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
|
|
31881
|
+
{ key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
|
|
31882
|
+
{ key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
|
|
31883
|
+
{ key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
|
|
31884
|
+
{ key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
|
|
31885
|
+
{ key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
|
|
31886
|
+
],
|
|
31887
|
+
compute: (bars, inputs) => {
|
|
31888
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
31889
|
+
const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
31890
|
+
const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
31891
|
+
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
31892
|
+
return {
|
|
31893
|
+
plots: [
|
|
31894
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
|
|
31895
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
|
|
31896
|
+
{ key: "lips", title: "Lips", values: lips, color: BULLISH }
|
|
31897
|
+
]
|
|
31898
|
+
};
|
|
31899
|
+
}
|
|
31900
|
+
};
|
|
31901
|
+
var gator = {
|
|
31902
|
+
type: "gator-oscillator",
|
|
31903
|
+
title: "Gator Oscillator",
|
|
31904
|
+
shortTitle: "Gator Oscillator",
|
|
31905
|
+
overlay: false,
|
|
31906
|
+
inputs: [],
|
|
31907
|
+
compute: (bars) => {
|
|
31908
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
31909
|
+
const jaw = shift(rma(hl2, 13), 8);
|
|
31910
|
+
const teeth = shift(rma(hl2, 8), 5);
|
|
31911
|
+
const lips = shift(rma(hl2, 5), 3);
|
|
31912
|
+
const upper = map(sub(jaw, teeth), Math.abs);
|
|
31913
|
+
const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
|
|
31914
|
+
const colorByExpansion = (v) => v.map((x, i) => {
|
|
31915
|
+
if (!Number.isFinite(x)) return null;
|
|
31916
|
+
const prev2 = i > 0 ? v[i - 1] : Number.NaN;
|
|
31917
|
+
return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
|
|
31918
|
+
});
|
|
31919
|
+
return {
|
|
31920
|
+
plots: [
|
|
31921
|
+
{ key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
|
|
31922
|
+
{ key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
|
|
31923
|
+
]
|
|
31924
|
+
};
|
|
31925
|
+
}
|
|
31926
|
+
};
|
|
31927
|
+
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
31928
|
+
|
|
31929
|
+
// src/core/native-indicators/classics/oscillators.ts
|
|
31930
|
+
function guideLevels(low, high, mid) {
|
|
31931
|
+
const levels = [
|
|
31932
|
+
{ key: "upper", price: high, color: NEUTRAL },
|
|
31933
|
+
{ key: "lower", price: low, color: NEUTRAL }
|
|
31934
|
+
];
|
|
31935
|
+
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
31936
|
+
return levels;
|
|
31937
|
+
}
|
|
31938
|
+
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
31939
|
+
function histColors(values) {
|
|
31940
|
+
return values.map((x, i) => {
|
|
31941
|
+
if (!Number.isFinite(x)) return null;
|
|
31942
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
31943
|
+
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
31944
|
+
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
31945
|
+
return rising ? `${BEARISH}80` : BEARISH;
|
|
31946
|
+
});
|
|
31947
|
+
}
|
|
31948
|
+
var rsiSpec = {
|
|
31949
|
+
type: "rsi",
|
|
31950
|
+
title: "Relative Strength Index",
|
|
31951
|
+
shortTitle: "RSI",
|
|
31952
|
+
overlay: false,
|
|
31953
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
31954
|
+
compute: (bars, inputs) => ({
|
|
31955
|
+
plots: [
|
|
31956
|
+
{
|
|
31957
|
+
key: "rsi",
|
|
31958
|
+
title: "RSI",
|
|
31959
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
31960
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
31961
|
+
width: 2
|
|
31962
|
+
}
|
|
31963
|
+
],
|
|
31964
|
+
levels: guideLevels(30, 70, 50)
|
|
31965
|
+
})
|
|
31966
|
+
};
|
|
31967
|
+
var stochastic = {
|
|
31968
|
+
type: "stochastic",
|
|
31969
|
+
title: "Stochastic",
|
|
31970
|
+
shortTitle: "Stoch",
|
|
31971
|
+
overlay: false,
|
|
31972
|
+
inputs: [
|
|
31973
|
+
lengthInput(14, "kLength", "%K length"),
|
|
31974
|
+
lengthInput(1, "kSmoothing", "%K smoothing", 500),
|
|
31975
|
+
lengthInput(3, "dLength", "%D smoothing", 500)
|
|
31976
|
+
],
|
|
31977
|
+
compute: (bars, inputs) => {
|
|
31978
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
|
|
31979
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
31980
|
+
return {
|
|
31981
|
+
plots: [
|
|
31982
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
31983
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
31984
|
+
],
|
|
31985
|
+
levels: guideLevels(20, 80)
|
|
31986
|
+
};
|
|
31987
|
+
}
|
|
31988
|
+
};
|
|
31989
|
+
var stochasticRsi = {
|
|
31990
|
+
type: "stochastic-rsi",
|
|
31991
|
+
title: "Stochastic RSI",
|
|
31992
|
+
shortTitle: "Stoch RSI",
|
|
31993
|
+
overlay: false,
|
|
31994
|
+
inputs: [
|
|
31995
|
+
lengthInput(14, "rsiLength", "RSI length"),
|
|
31996
|
+
lengthInput(14, "stochLength", "Stochastic length"),
|
|
31997
|
+
lengthInput(3, "kSmoothing", "%K smoothing", 500),
|
|
31998
|
+
lengthInput(3, "dLength", "%D smoothing", 500),
|
|
31999
|
+
sourceInput()
|
|
32000
|
+
],
|
|
32001
|
+
compute: (bars, inputs) => {
|
|
32002
|
+
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
32003
|
+
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
32004
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
32005
|
+
return {
|
|
32006
|
+
plots: [
|
|
32007
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
32008
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
32009
|
+
],
|
|
32010
|
+
levels: guideLevels(20, 80)
|
|
32011
|
+
};
|
|
32012
|
+
}
|
|
32013
|
+
};
|
|
32014
|
+
var macd = {
|
|
32015
|
+
type: "macd",
|
|
32016
|
+
title: "MACD",
|
|
32017
|
+
overlay: false,
|
|
32018
|
+
inputs: [
|
|
32019
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
32020
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
32021
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
32022
|
+
sourceInput()
|
|
32023
|
+
],
|
|
32024
|
+
compute: (bars, inputs) => {
|
|
32025
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32026
|
+
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
32027
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
32028
|
+
const hist = sub(line, signal);
|
|
32029
|
+
return {
|
|
32030
|
+
plots: [
|
|
32031
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
32032
|
+
{ key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
|
|
32033
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
32034
|
+
],
|
|
32035
|
+
levels: ZERO_LEVEL
|
|
32036
|
+
};
|
|
32037
|
+
}
|
|
32038
|
+
};
|
|
32039
|
+
var ppo = {
|
|
32040
|
+
type: "ppo",
|
|
32041
|
+
title: "Percentage Price Oscillator",
|
|
32042
|
+
shortTitle: "PPO",
|
|
32043
|
+
overlay: false,
|
|
32044
|
+
inputs: [
|
|
32045
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
32046
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
32047
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
32048
|
+
sourceInput()
|
|
32049
|
+
],
|
|
32050
|
+
compute: (bars, inputs) => {
|
|
32051
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32052
|
+
const fast = ema(src, num3(inputs, "fastLength", 12));
|
|
32053
|
+
const slow = ema(src, num3(inputs, "slowLength", 26));
|
|
32054
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
32055
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
32056
|
+
const hist = sub(line, signal);
|
|
32057
|
+
return {
|
|
32058
|
+
plots: [
|
|
32059
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
32060
|
+
{ key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
|
|
32061
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
32062
|
+
],
|
|
32063
|
+
levels: ZERO_LEVEL
|
|
32064
|
+
};
|
|
32065
|
+
}
|
|
32066
|
+
};
|
|
32067
|
+
var awesome = {
|
|
32068
|
+
type: "awesome-oscillator",
|
|
32069
|
+
title: "Awesome Oscillator",
|
|
32070
|
+
shortTitle: "AO",
|
|
32071
|
+
overlay: false,
|
|
32072
|
+
inputs: [],
|
|
32073
|
+
compute: (bars) => {
|
|
32074
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
32075
|
+
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
32076
|
+
const colors = ao.map((x, i) => {
|
|
32077
|
+
if (!Number.isFinite(x)) return null;
|
|
32078
|
+
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
32079
|
+
return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
|
|
32080
|
+
});
|
|
32081
|
+
return {
|
|
32082
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
|
|
32083
|
+
levels: ZERO_LEVEL
|
|
32084
|
+
};
|
|
32085
|
+
}
|
|
32086
|
+
};
|
|
32087
|
+
var cci = {
|
|
32088
|
+
type: "commodity-channel-index",
|
|
32089
|
+
title: "Commodity Channel Index",
|
|
32090
|
+
shortTitle: "CCI",
|
|
32091
|
+
overlay: false,
|
|
32092
|
+
inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
|
|
32093
|
+
compute: (bars, inputs) => {
|
|
32094
|
+
const len = num3(inputs, "length", 20);
|
|
32095
|
+
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
32096
|
+
const basis = sma(src, len);
|
|
32097
|
+
const dev = new Array(src.length).fill(Number.NaN);
|
|
32098
|
+
for (let i = len - 1; i < src.length; i++) {
|
|
32099
|
+
const mean = basis[i];
|
|
32100
|
+
if (!Number.isFinite(mean)) continue;
|
|
32101
|
+
let d = 0;
|
|
32102
|
+
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
32103
|
+
dev[i] = d / len;
|
|
32104
|
+
}
|
|
32105
|
+
const values = src.map((x, i) => {
|
|
32106
|
+
const b = basis[i];
|
|
32107
|
+
const d = dev[i];
|
|
32108
|
+
return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
|
|
32109
|
+
});
|
|
32110
|
+
return {
|
|
32111
|
+
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32112
|
+
levels: guideLevels(-100, 100, 0)
|
|
32113
|
+
};
|
|
32114
|
+
}
|
|
32115
|
+
};
|
|
32116
|
+
var williamsR = {
|
|
32117
|
+
type: "williams-percent-r",
|
|
32118
|
+
title: "Williams %R",
|
|
32119
|
+
shortTitle: "%R",
|
|
32120
|
+
overlay: false,
|
|
32121
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32122
|
+
compute: (bars, inputs) => {
|
|
32123
|
+
const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
|
|
32124
|
+
return {
|
|
32125
|
+
plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32126
|
+
levels: guideLevels(-80, -20)
|
|
32127
|
+
};
|
|
32128
|
+
}
|
|
32129
|
+
};
|
|
32130
|
+
var cmo = {
|
|
32131
|
+
type: "chande-momentum-oscillator",
|
|
32132
|
+
title: "Chande Momentum Oscillator",
|
|
32133
|
+
shortTitle: "CMO",
|
|
32134
|
+
overlay: false,
|
|
32135
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
32136
|
+
compute: (bars, inputs) => {
|
|
32137
|
+
const len = num3(inputs, "length", 9);
|
|
32138
|
+
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
32139
|
+
const su = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
32140
|
+
const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
32141
|
+
const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
32142
|
+
return {
|
|
32143
|
+
plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32144
|
+
levels: guideLevels(-50, 50, 0)
|
|
32145
|
+
};
|
|
32146
|
+
}
|
|
32147
|
+
};
|
|
32148
|
+
var connorsRsi = {
|
|
32149
|
+
type: "connors-rsi",
|
|
32150
|
+
title: "Connors RSI",
|
|
32151
|
+
shortTitle: "CRSI",
|
|
32152
|
+
overlay: false,
|
|
32153
|
+
inputs: [
|
|
32154
|
+
lengthInput(3, "rsiLength", "RSI length"),
|
|
32155
|
+
lengthInput(2, "streakLength", "Streak RSI length"),
|
|
32156
|
+
lengthInput(100, "rankLength", "Percent-rank length"),
|
|
32157
|
+
colorInput()
|
|
32158
|
+
],
|
|
32159
|
+
compute: (bars, inputs) => {
|
|
32160
|
+
const c = closes(bars);
|
|
32161
|
+
const streak = new Array(c.length).fill(0);
|
|
32162
|
+
for (let i = 1; i < c.length; i++) {
|
|
32163
|
+
const d = c[i] - c[i - 1];
|
|
32164
|
+
streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
|
|
32165
|
+
}
|
|
32166
|
+
const a = rsi(c, num3(inputs, "rsiLength", 3));
|
|
32167
|
+
const b = rsi(streak, num3(inputs, "streakLength", 2));
|
|
32168
|
+
const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
|
|
32169
|
+
const values = c.map((_, i) => {
|
|
32170
|
+
const x = a[i];
|
|
32171
|
+
const y = b[i];
|
|
32172
|
+
const z = r[i];
|
|
32173
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
32174
|
+
});
|
|
32175
|
+
return {
|
|
32176
|
+
plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32177
|
+
levels: guideLevels(20, 80, 50)
|
|
32178
|
+
};
|
|
32179
|
+
}
|
|
32180
|
+
};
|
|
32181
|
+
var fisher = {
|
|
32182
|
+
type: "fisher-transform",
|
|
32183
|
+
title: "Fisher Transform",
|
|
32184
|
+
shortTitle: "Fisher",
|
|
32185
|
+
overlay: false,
|
|
32186
|
+
inputs: [lengthInput(9)],
|
|
32187
|
+
compute: (bars, inputs) => {
|
|
32188
|
+
const len = num3(inputs, "length", 9);
|
|
32189
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
32190
|
+
const hi = highest(hl2, len);
|
|
32191
|
+
const lo = lowest(hl2, len);
|
|
32192
|
+
const n = bars.length;
|
|
32193
|
+
const out = new Array(n).fill(Number.NaN);
|
|
32194
|
+
let value = 0;
|
|
32195
|
+
let fish = 0;
|
|
32196
|
+
for (let i = 0; i < n; i++) {
|
|
32197
|
+
const h = hi[i];
|
|
32198
|
+
const l = lo[i];
|
|
32199
|
+
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
32200
|
+
const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
|
|
32201
|
+
value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
|
|
32202
|
+
fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
|
|
32203
|
+
out[i] = fish;
|
|
32204
|
+
}
|
|
32205
|
+
return {
|
|
32206
|
+
plots: [
|
|
32207
|
+
{ key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
|
|
32208
|
+
{ key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
|
|
32209
|
+
],
|
|
32210
|
+
levels: ZERO_LEVEL
|
|
32211
|
+
};
|
|
32212
|
+
}
|
|
32213
|
+
};
|
|
32214
|
+
var trix = {
|
|
32215
|
+
type: "trix",
|
|
32216
|
+
title: "TRIX",
|
|
32217
|
+
overlay: false,
|
|
32218
|
+
inputs: [lengthInput(18), colorInput()],
|
|
32219
|
+
compute: (bars, inputs) => {
|
|
32220
|
+
const len = num3(inputs, "length", 18);
|
|
32221
|
+
const logClose = map(closes(bars), Math.log);
|
|
32222
|
+
const smooth = ema(ema(ema(logClose, len), len), len);
|
|
32223
|
+
const values = map(change(smooth), (x) => x * 1e4);
|
|
32224
|
+
return {
|
|
32225
|
+
plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32226
|
+
levels: ZERO_LEVEL
|
|
32227
|
+
};
|
|
32228
|
+
}
|
|
32229
|
+
};
|
|
32230
|
+
function tsiLine(src, long, short) {
|
|
32231
|
+
const mom = change(src);
|
|
32232
|
+
const numer = ema(ema(mom, long), short);
|
|
32233
|
+
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
32234
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
|
|
32235
|
+
}
|
|
32236
|
+
var tsi = {
|
|
32237
|
+
type: "true-strength-index",
|
|
32238
|
+
title: "True Strength Index",
|
|
32239
|
+
shortTitle: "TSI",
|
|
32240
|
+
overlay: false,
|
|
32241
|
+
inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
|
|
32242
|
+
compute: (bars, inputs) => {
|
|
32243
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
|
|
32244
|
+
return {
|
|
32245
|
+
plots: [
|
|
32246
|
+
{ key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
|
|
32247
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
32248
|
+
],
|
|
32249
|
+
levels: ZERO_LEVEL
|
|
32250
|
+
};
|
|
32251
|
+
}
|
|
32252
|
+
};
|
|
32253
|
+
var smiErgodic = {
|
|
32254
|
+
type: "smi-ergodic",
|
|
32255
|
+
title: "SMI Ergodic Oscillator",
|
|
32256
|
+
shortTitle: "SMIE",
|
|
32257
|
+
overlay: false,
|
|
32258
|
+
inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
|
|
32259
|
+
compute: (bars, inputs) => {
|
|
32260
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
32261
|
+
const signal = ema(line, num3(inputs, "signalLength", 5));
|
|
32262
|
+
const osc = sub(line, signal);
|
|
32263
|
+
return {
|
|
32264
|
+
plots: [
|
|
32265
|
+
{ key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
|
|
32266
|
+
{ key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
|
|
32267
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
32268
|
+
],
|
|
32269
|
+
levels: ZERO_LEVEL
|
|
32270
|
+
};
|
|
32271
|
+
}
|
|
32272
|
+
};
|
|
32273
|
+
var stc = {
|
|
32274
|
+
type: "schaff-trend-cycle",
|
|
32275
|
+
title: "Schaff Trend Cycle",
|
|
32276
|
+
shortTitle: "Schaff Trend Cycle",
|
|
32277
|
+
overlay: false,
|
|
32278
|
+
inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
|
|
32279
|
+
compute: (bars, inputs) => {
|
|
32280
|
+
const len = num3(inputs, "cycleLength", 10);
|
|
32281
|
+
const src = closes(bars);
|
|
32282
|
+
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
32283
|
+
const stochOf = (v) => {
|
|
32284
|
+
const lo = lowest(v, len);
|
|
32285
|
+
const hi = highest(v, len);
|
|
32286
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
32287
|
+
let prevRaw = Number.NaN;
|
|
32288
|
+
let smoothed = Number.NaN;
|
|
32289
|
+
for (let i = 0; i < v.length; i++) {
|
|
32290
|
+
const x = v[i];
|
|
32291
|
+
const l = lo[i];
|
|
32292
|
+
const h = hi[i];
|
|
32293
|
+
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
32294
|
+
const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
|
|
32295
|
+
prevRaw = raw;
|
|
32296
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
|
|
32297
|
+
out[i] = smoothed;
|
|
32298
|
+
}
|
|
32299
|
+
return out;
|
|
32300
|
+
};
|
|
32301
|
+
const values = stochOf(stochOf(macdLine));
|
|
32302
|
+
return {
|
|
32303
|
+
plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
|
|
32304
|
+
levels: guideLevels(25, 75)
|
|
32305
|
+
};
|
|
32306
|
+
}
|
|
32307
|
+
};
|
|
32308
|
+
var kst = {
|
|
32309
|
+
type: "know-sure-thing",
|
|
32310
|
+
title: "Know Sure Thing",
|
|
32311
|
+
shortTitle: "KST",
|
|
32312
|
+
overlay: false,
|
|
32313
|
+
inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
|
|
32314
|
+
compute: (bars, inputs) => {
|
|
32315
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32316
|
+
const line = [
|
|
32317
|
+
sma(roc(src, 10), 10),
|
|
32318
|
+
sma(roc(src, 15), 10),
|
|
32319
|
+
sma(roc(src, 20), 10),
|
|
32320
|
+
sma(roc(src, 30), 15)
|
|
32321
|
+
].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
|
|
32322
|
+
return {
|
|
32323
|
+
plots: [
|
|
32324
|
+
{ key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
|
|
32325
|
+
{ key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
|
|
32326
|
+
],
|
|
32327
|
+
levels: ZERO_LEVEL
|
|
32328
|
+
};
|
|
32329
|
+
}
|
|
32330
|
+
};
|
|
32331
|
+
var coppock = {
|
|
32332
|
+
type: "coppock-curve",
|
|
32333
|
+
title: "Coppock Curve",
|
|
32334
|
+
shortTitle: "Coppock",
|
|
32335
|
+
overlay: false,
|
|
32336
|
+
inputs: [
|
|
32337
|
+
lengthInput(10, "wmaLength", "WMA length"),
|
|
32338
|
+
lengthInput(14, "longRoc", "Long RoC length"),
|
|
32339
|
+
lengthInput(11, "shortRoc", "Short RoC length"),
|
|
32340
|
+
sourceInput(),
|
|
32341
|
+
colorInput()
|
|
32342
|
+
],
|
|
32343
|
+
compute: (bars, inputs) => {
|
|
32344
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32345
|
+
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
32346
|
+
return {
|
|
32347
|
+
plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32348
|
+
levels: ZERO_LEVEL
|
|
32349
|
+
};
|
|
32350
|
+
}
|
|
32351
|
+
};
|
|
32352
|
+
var dpo = {
|
|
32353
|
+
type: "detrended-price-oscillator",
|
|
32354
|
+
title: "Detrended Price Oscillator",
|
|
32355
|
+
shortTitle: "DPO",
|
|
32356
|
+
overlay: false,
|
|
32357
|
+
inputs: [lengthInput(21), colorInput()],
|
|
32358
|
+
compute: (bars, inputs) => {
|
|
32359
|
+
const len = num3(inputs, "length", 21);
|
|
32360
|
+
const barsback = Math.floor(len / 2) + 1;
|
|
32361
|
+
const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
|
|
32362
|
+
return {
|
|
32363
|
+
plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32364
|
+
levels: ZERO_LEVEL
|
|
32365
|
+
};
|
|
32366
|
+
}
|
|
32367
|
+
};
|
|
32368
|
+
var rocSpec = {
|
|
32369
|
+
type: "rate-of-change",
|
|
32370
|
+
title: "Rate of Change",
|
|
32371
|
+
shortTitle: "ROC",
|
|
32372
|
+
overlay: false,
|
|
32373
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
32374
|
+
compute: (bars, inputs) => ({
|
|
32375
|
+
plots: [
|
|
32376
|
+
{
|
|
32377
|
+
key: "roc",
|
|
32378
|
+
title: "RoC",
|
|
32379
|
+
values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
|
|
32380
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
32381
|
+
width: 2
|
|
32382
|
+
}
|
|
32383
|
+
],
|
|
32384
|
+
levels: ZERO_LEVEL
|
|
32385
|
+
})
|
|
32386
|
+
};
|
|
32387
|
+
var ultimate = {
|
|
32388
|
+
type: "ultimate-oscillator",
|
|
32389
|
+
title: "Ultimate Oscillator",
|
|
32390
|
+
shortTitle: "UO",
|
|
32391
|
+
overlay: false,
|
|
32392
|
+
inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
|
|
32393
|
+
compute: (bars, inputs) => {
|
|
32394
|
+
const n = bars.length;
|
|
32395
|
+
const bp = new Array(n).fill(Number.NaN);
|
|
32396
|
+
const tr = new Array(n).fill(Number.NaN);
|
|
32397
|
+
for (let i = 0; i < n; i++) {
|
|
32398
|
+
const b = bars[i];
|
|
32399
|
+
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
32400
|
+
const lo = Math.min(b.low, pc);
|
|
32401
|
+
const hi = Math.max(b.high, pc);
|
|
32402
|
+
bp[i] = b.close - lo;
|
|
32403
|
+
tr[i] = hi - lo;
|
|
32404
|
+
}
|
|
32405
|
+
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
32406
|
+
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
32407
|
+
const a14 = avg(num3(inputs, "middleLength", 14));
|
|
32408
|
+
const a28 = avg(num3(inputs, "slowLength", 28));
|
|
32409
|
+
const values = a7.map((x, i) => {
|
|
32410
|
+
const y = a14[i];
|
|
32411
|
+
const z = a28[i];
|
|
32412
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
32413
|
+
});
|
|
32414
|
+
return {
|
|
32415
|
+
plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32416
|
+
levels: guideLevels(30, 70, 50)
|
|
32417
|
+
};
|
|
32418
|
+
}
|
|
32419
|
+
};
|
|
32420
|
+
var rvgi = {
|
|
32421
|
+
type: "relative-vigor-index",
|
|
32422
|
+
title: "Relative Vigor Index",
|
|
32423
|
+
shortTitle: "RVGI",
|
|
32424
|
+
overlay: false,
|
|
32425
|
+
inputs: [lengthInput(10)],
|
|
32426
|
+
compute: (bars, inputs) => {
|
|
32427
|
+
const len = num3(inputs, "length", 10);
|
|
32428
|
+
const co = swma(sub(closes(bars), opens(bars)));
|
|
32429
|
+
const hl = swma(sub(highs(bars), lows(bars)));
|
|
32430
|
+
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
32431
|
+
return {
|
|
32432
|
+
plots: [
|
|
32433
|
+
{ key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
|
|
32434
|
+
{ key: "signal", title: "Signal", values: swma(line), color: WARNING }
|
|
32435
|
+
],
|
|
32436
|
+
levels: ZERO_LEVEL
|
|
32437
|
+
};
|
|
32438
|
+
}
|
|
32439
|
+
};
|
|
32440
|
+
var relVolatility = {
|
|
32441
|
+
type: "relative-volatility-index",
|
|
32442
|
+
title: "Relative Volatility Index",
|
|
32443
|
+
shortTitle: "RVI",
|
|
32444
|
+
overlay: false,
|
|
32445
|
+
inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
|
|
32446
|
+
compute: (bars, inputs) => {
|
|
32447
|
+
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
32448
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32449
|
+
const dev = stdev(src, num3(inputs, "stdevLength", 10));
|
|
32450
|
+
const d = change(src);
|
|
32451
|
+
const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
|
|
32452
|
+
const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
|
|
32453
|
+
const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
|
|
32454
|
+
return {
|
|
32455
|
+
plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32456
|
+
levels: guideLevels(20, 80, 50)
|
|
32457
|
+
};
|
|
32458
|
+
}
|
|
32459
|
+
};
|
|
32460
|
+
var bop = {
|
|
32461
|
+
type: "balance-of-power",
|
|
32462
|
+
title: "Balance of Power",
|
|
32463
|
+
shortTitle: "BOP",
|
|
32464
|
+
overlay: false,
|
|
32465
|
+
inputs: [colorInput()],
|
|
32466
|
+
compute: (bars, inputs) => {
|
|
32467
|
+
const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
32468
|
+
return {
|
|
32469
|
+
plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32470
|
+
levels: ZERO_LEVEL
|
|
32471
|
+
};
|
|
32472
|
+
}
|
|
32473
|
+
};
|
|
32474
|
+
var elderRay = {
|
|
32475
|
+
type: "elder-ray",
|
|
32476
|
+
title: "Elder Ray",
|
|
32477
|
+
overlay: false,
|
|
32478
|
+
inputs: [lengthInput(13)],
|
|
32479
|
+
compute: (bars, inputs) => {
|
|
32480
|
+
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
32481
|
+
return {
|
|
32482
|
+
plots: [
|
|
32483
|
+
{ key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
|
|
32484
|
+
{ key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
|
|
32485
|
+
],
|
|
32486
|
+
levels: ZERO_LEVEL
|
|
32487
|
+
};
|
|
32488
|
+
}
|
|
32489
|
+
};
|
|
32490
|
+
var ttmSqueeze = {
|
|
32491
|
+
type: "ttm-squeeze",
|
|
32492
|
+
title: "TTM Squeeze",
|
|
32493
|
+
overlay: false,
|
|
32494
|
+
inputs: [
|
|
32495
|
+
lengthInput(20),
|
|
32496
|
+
{ key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
32497
|
+
{ key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
|
|
32498
|
+
],
|
|
32499
|
+
compute: (bars, inputs) => {
|
|
32500
|
+
const len = num3(inputs, "length", 20);
|
|
32501
|
+
const c = closes(bars);
|
|
32502
|
+
const basis = sma(c, len);
|
|
32503
|
+
const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
32504
|
+
const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
32505
|
+
const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
32506
|
+
const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
|
|
32507
|
+
const mom = linreg(sub(c, anchor), len);
|
|
32508
|
+
const squeezed = c.map((_, i) => {
|
|
32509
|
+
const d = dev[i];
|
|
32510
|
+
const r = kcRange[i];
|
|
32511
|
+
return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
|
|
32512
|
+
});
|
|
32513
|
+
const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
|
|
32514
|
+
return {
|
|
32515
|
+
plots: [
|
|
32516
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
|
|
32517
|
+
{ key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
|
|
32518
|
+
]
|
|
32519
|
+
};
|
|
32520
|
+
}
|
|
32521
|
+
};
|
|
32522
|
+
var oscillatorSpecs = [
|
|
32523
|
+
rsiSpec,
|
|
32524
|
+
stochastic,
|
|
32525
|
+
stochasticRsi,
|
|
32526
|
+
macd,
|
|
32527
|
+
ppo,
|
|
32528
|
+
awesome,
|
|
32529
|
+
cci,
|
|
32530
|
+
williamsR,
|
|
32531
|
+
cmo,
|
|
32532
|
+
connorsRsi,
|
|
32533
|
+
fisher,
|
|
32534
|
+
trix,
|
|
32535
|
+
tsi,
|
|
32536
|
+
smiErgodic,
|
|
32537
|
+
stc,
|
|
32538
|
+
kst,
|
|
32539
|
+
coppock,
|
|
32540
|
+
dpo,
|
|
32541
|
+
rocSpec,
|
|
32542
|
+
ultimate,
|
|
32543
|
+
rvgi,
|
|
32544
|
+
relVolatility,
|
|
32545
|
+
bop,
|
|
32546
|
+
elderRay,
|
|
32547
|
+
ttmSqueeze
|
|
32548
|
+
];
|
|
32549
|
+
|
|
32550
|
+
// src/core/native-indicators/classics/trend.ts
|
|
32551
|
+
var aroon = {
|
|
32552
|
+
type: "aroon",
|
|
32553
|
+
title: "Aroon",
|
|
32554
|
+
overlay: false,
|
|
32555
|
+
inputs: [lengthInput(14)],
|
|
32556
|
+
compute: (bars, inputs) => {
|
|
32557
|
+
const len = num3(inputs, "length", 14);
|
|
32558
|
+
const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
32559
|
+
const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
32560
|
+
return {
|
|
32561
|
+
plots: [
|
|
32562
|
+
{ key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
|
|
32563
|
+
{ key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
|
|
32564
|
+
],
|
|
32565
|
+
levels: [
|
|
32566
|
+
{ key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
|
|
32567
|
+
{ key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
|
|
32568
|
+
]
|
|
32569
|
+
};
|
|
32570
|
+
}
|
|
32571
|
+
};
|
|
32572
|
+
var adx = {
|
|
32573
|
+
type: "average-directional-index",
|
|
32574
|
+
title: "Average Directional Index",
|
|
32575
|
+
shortTitle: "ADX",
|
|
32576
|
+
overlay: false,
|
|
32577
|
+
inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
|
|
32578
|
+
compute: (bars, inputs) => {
|
|
32579
|
+
const diLen = num3(inputs, "diLength", 14);
|
|
32580
|
+
const adxLen = num3(inputs, "adxLength", 14);
|
|
32581
|
+
const n = bars.length;
|
|
32582
|
+
const plusDm = new Array(n).fill(Number.NaN);
|
|
32583
|
+
const minusDm = new Array(n).fill(Number.NaN);
|
|
32584
|
+
for (let i = 1; i < n; i++) {
|
|
32585
|
+
const up = bars[i].high - bars[i - 1].high;
|
|
32586
|
+
const dn = bars[i - 1].low - bars[i].low;
|
|
32587
|
+
plusDm[i] = up > dn && up > 0 ? up : 0;
|
|
32588
|
+
minusDm[i] = dn > up && dn > 0 ? dn : 0;
|
|
32589
|
+
}
|
|
32590
|
+
const atrLine = rma(trueRange(bars), diLen);
|
|
32591
|
+
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
32592
|
+
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
32593
|
+
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
32594
|
+
return {
|
|
32595
|
+
plots: [
|
|
32596
|
+
{ key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
|
|
32597
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
|
|
32598
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
|
|
32599
|
+
],
|
|
32600
|
+
levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
|
|
32601
|
+
};
|
|
32602
|
+
}
|
|
32603
|
+
};
|
|
32604
|
+
var vortex = {
|
|
32605
|
+
type: "vortex-indicator",
|
|
32606
|
+
title: "Vortex Indicator",
|
|
32607
|
+
shortTitle: "VI",
|
|
32608
|
+
overlay: false,
|
|
32609
|
+
inputs: [lengthInput(14)],
|
|
32610
|
+
compute: (bars, inputs) => {
|
|
32611
|
+
const len = num3(inputs, "length", 14);
|
|
32612
|
+
const n = bars.length;
|
|
32613
|
+
const vmPlus = new Array(n).fill(Number.NaN);
|
|
32614
|
+
const vmMinus = new Array(n).fill(Number.NaN);
|
|
32615
|
+
for (let i = 1; i < n; i++) {
|
|
32616
|
+
vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
|
|
32617
|
+
vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
|
|
32618
|
+
}
|
|
32619
|
+
const trSum = sum(trueRange(bars), len);
|
|
32620
|
+
return {
|
|
32621
|
+
plots: [
|
|
32622
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
|
|
32623
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
|
|
32624
|
+
],
|
|
32625
|
+
levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
|
|
32626
|
+
};
|
|
32627
|
+
}
|
|
32628
|
+
};
|
|
32629
|
+
var trendSpecs = [aroon, adx, vortex];
|
|
32630
|
+
|
|
32631
|
+
// src/core/native-indicators/classics/volatility.ts
|
|
32632
|
+
var atrSpec = {
|
|
32633
|
+
type: "average-true-range",
|
|
32634
|
+
title: "Average True Range",
|
|
32635
|
+
shortTitle: "ATR",
|
|
32636
|
+
overlay: false,
|
|
32637
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32638
|
+
compute: (bars, inputs) => ({
|
|
32639
|
+
plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
32640
|
+
})
|
|
32641
|
+
};
|
|
32642
|
+
var historicalVolatility = {
|
|
32643
|
+
type: "historical-volatility",
|
|
32644
|
+
title: "Historical Volatility",
|
|
32645
|
+
shortTitle: "HV",
|
|
32646
|
+
overlay: false,
|
|
32647
|
+
inputs: [
|
|
32648
|
+
lengthInput(10),
|
|
32649
|
+
{ 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)" },
|
|
32650
|
+
colorInput()
|
|
32651
|
+
],
|
|
32652
|
+
compute: (bars, inputs) => {
|
|
32653
|
+
const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
|
|
32654
|
+
const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
|
|
32655
|
+
return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
32656
|
+
}
|
|
32657
|
+
};
|
|
32658
|
+
var chaikinVolatility = {
|
|
32659
|
+
type: "chaikin-volatility",
|
|
32660
|
+
title: "Chaikin Volatility",
|
|
32661
|
+
shortTitle: "CHV",
|
|
32662
|
+
overlay: false,
|
|
32663
|
+
inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
|
|
32664
|
+
compute: (bars, inputs) => {
|
|
32665
|
+
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
32666
|
+
return {
|
|
32667
|
+
plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32668
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32669
|
+
};
|
|
32670
|
+
}
|
|
32671
|
+
};
|
|
32672
|
+
var massIndex = {
|
|
32673
|
+
type: "mass-index",
|
|
32674
|
+
title: "Mass Index",
|
|
32675
|
+
shortTitle: "MI",
|
|
32676
|
+
overlay: false,
|
|
32677
|
+
inputs: [lengthInput(25), colorInput()],
|
|
32678
|
+
compute: (bars, inputs) => {
|
|
32679
|
+
const range = sub(highs(bars), lows(bars));
|
|
32680
|
+
const single = ema(range, 9);
|
|
32681
|
+
const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
32682
|
+
return {
|
|
32683
|
+
plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32684
|
+
levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
|
|
32685
|
+
};
|
|
32686
|
+
}
|
|
32687
|
+
};
|
|
32688
|
+
var standardDeviation = {
|
|
32689
|
+
type: "standard-deviation",
|
|
32690
|
+
title: "Standard Deviation",
|
|
32691
|
+
shortTitle: "StdDev",
|
|
32692
|
+
overlay: false,
|
|
32693
|
+
inputs: [lengthInput(20), sourceInput(), colorInput()],
|
|
32694
|
+
compute: (bars, inputs) => ({
|
|
32695
|
+
plots: [
|
|
32696
|
+
{
|
|
32697
|
+
key: "stdev",
|
|
32698
|
+
title: "StdDev",
|
|
32699
|
+
values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
|
|
32700
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
32701
|
+
width: 2
|
|
32702
|
+
}
|
|
32703
|
+
]
|
|
32704
|
+
})
|
|
32705
|
+
};
|
|
32706
|
+
var ulcerIndex = {
|
|
32707
|
+
type: "ulcer-index",
|
|
32708
|
+
title: "Ulcer Index",
|
|
32709
|
+
overlay: false,
|
|
32710
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32711
|
+
compute: (bars, inputs) => {
|
|
32712
|
+
const len = num3(inputs, "length", 14);
|
|
32713
|
+
const c = closes(bars);
|
|
32714
|
+
const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
|
|
32715
|
+
const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
|
|
32716
|
+
return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
32717
|
+
}
|
|
32718
|
+
};
|
|
32719
|
+
var choppiness = {
|
|
32720
|
+
type: "choppiness-index",
|
|
32721
|
+
title: "Choppiness Index",
|
|
32722
|
+
shortTitle: "CHOP",
|
|
32723
|
+
overlay: false,
|
|
32724
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32725
|
+
compute: (bars, inputs) => {
|
|
32726
|
+
const len = num3(inputs, "length", 14);
|
|
32727
|
+
const trSum = sum(trueRange(bars), len);
|
|
32728
|
+
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
32729
|
+
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
32730
|
+
return {
|
|
32731
|
+
plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32732
|
+
levels: [
|
|
32733
|
+
{ key: "upper", price: 61.8, color: NEUTRAL },
|
|
32734
|
+
{ key: "lower", price: 38.2, color: NEUTRAL }
|
|
32735
|
+
]
|
|
32736
|
+
};
|
|
32737
|
+
}
|
|
32738
|
+
};
|
|
32739
|
+
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
32740
|
+
|
|
32741
|
+
// src/core/native-indicators/classics/volume.ts
|
|
32742
|
+
function moneyFlowVolume(bars) {
|
|
32743
|
+
return bars.map((b) => {
|
|
32744
|
+
if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
32745
|
+
if (b.high === b.low) return 0;
|
|
32746
|
+
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
32747
|
+
});
|
|
32748
|
+
}
|
|
32749
|
+
var obv = {
|
|
32750
|
+
type: "on-balance-volume",
|
|
32751
|
+
title: "On Balance Volume",
|
|
32752
|
+
shortTitle: "OBV",
|
|
32753
|
+
overlay: false,
|
|
32754
|
+
inputs: [colorInput()],
|
|
32755
|
+
compute: (bars, inputs) => {
|
|
32756
|
+
const vol = volumes(bars);
|
|
32757
|
+
const d = change(closes(bars));
|
|
32758
|
+
const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
|
|
32759
|
+
return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
32760
|
+
}
|
|
32761
|
+
};
|
|
32762
|
+
var accDist = {
|
|
32763
|
+
type: "accumulation-distribution",
|
|
32764
|
+
title: "Accumulation / Distribution",
|
|
32765
|
+
shortTitle: "A/D",
|
|
32766
|
+
overlay: false,
|
|
32767
|
+
inputs: [colorInput()],
|
|
32768
|
+
compute: (bars, inputs) => ({
|
|
32769
|
+
plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
32770
|
+
})
|
|
32771
|
+
};
|
|
32772
|
+
var cmf = {
|
|
32773
|
+
type: "chaikin-money-flow",
|
|
32774
|
+
title: "Chaikin Money Flow",
|
|
32775
|
+
shortTitle: "CMF",
|
|
32776
|
+
overlay: false,
|
|
32777
|
+
inputs: [lengthInput(20), colorInput()],
|
|
32778
|
+
compute: (bars, inputs) => {
|
|
32779
|
+
const len = num3(inputs, "length", 20);
|
|
32780
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
32781
|
+
return {
|
|
32782
|
+
plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32783
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32784
|
+
};
|
|
32785
|
+
}
|
|
32786
|
+
};
|
|
32787
|
+
var chaikinOscillator = {
|
|
32788
|
+
type: "chaikin-oscillator",
|
|
32789
|
+
title: "Chaikin Oscillator",
|
|
32790
|
+
shortTitle: "Chaikin Osc",
|
|
32791
|
+
overlay: false,
|
|
32792
|
+
inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
32793
|
+
compute: (bars, inputs) => {
|
|
32794
|
+
const ad = cumSum(moneyFlowVolume(bars));
|
|
32795
|
+
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
32796
|
+
return {
|
|
32797
|
+
plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32798
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32799
|
+
};
|
|
32800
|
+
}
|
|
32801
|
+
};
|
|
32802
|
+
var eom = {
|
|
32803
|
+
type: "ease-of-movement",
|
|
32804
|
+
title: "Ease of Movement",
|
|
32805
|
+
shortTitle: "EOM",
|
|
32806
|
+
overlay: false,
|
|
32807
|
+
inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
|
|
32808
|
+
compute: (bars, inputs) => {
|
|
32809
|
+
const div = num3(inputs, "divisor", 1e4);
|
|
32810
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
32811
|
+
const move = change(hl2);
|
|
32812
|
+
const raw = bars.map((b, i) => {
|
|
32813
|
+
const m = move[i];
|
|
32814
|
+
if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
|
|
32815
|
+
return div * m * (b.high - b.low) / b.volume;
|
|
32816
|
+
});
|
|
32817
|
+
return {
|
|
32818
|
+
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32819
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32820
|
+
};
|
|
32821
|
+
}
|
|
32822
|
+
};
|
|
32823
|
+
var forceIndex = {
|
|
32824
|
+
type: "force-index",
|
|
32825
|
+
title: "Force Index",
|
|
32826
|
+
shortTitle: "FI",
|
|
32827
|
+
overlay: false,
|
|
32828
|
+
inputs: [lengthInput(13), colorInput()],
|
|
32829
|
+
compute: (bars, inputs) => {
|
|
32830
|
+
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
32831
|
+
return {
|
|
32832
|
+
plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32833
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32834
|
+
};
|
|
32835
|
+
}
|
|
32836
|
+
};
|
|
32837
|
+
var klinger = {
|
|
32838
|
+
type: "klinger-oscillator",
|
|
32839
|
+
title: "Klinger Oscillator",
|
|
32840
|
+
shortTitle: "KVO",
|
|
32841
|
+
overlay: false,
|
|
32842
|
+
inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
|
|
32843
|
+
compute: (bars, inputs) => {
|
|
32844
|
+
const hlc3 = sourceValues(bars, "HLC3");
|
|
32845
|
+
const trend = change(hlc3);
|
|
32846
|
+
const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
|
|
32847
|
+
const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
|
|
32848
|
+
return {
|
|
32849
|
+
plots: [
|
|
32850
|
+
{ key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
|
|
32851
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
32852
|
+
],
|
|
32853
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32854
|
+
};
|
|
32855
|
+
}
|
|
32856
|
+
};
|
|
32857
|
+
var mfi = {
|
|
32858
|
+
type: "money-flow-index",
|
|
32859
|
+
title: "Money Flow Index",
|
|
32860
|
+
shortTitle: "MFI",
|
|
32861
|
+
overlay: false,
|
|
32862
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32863
|
+
compute: (bars, inputs) => {
|
|
32864
|
+
const len = num3(inputs, "length", 14);
|
|
32865
|
+
const tp = sourceValues(bars, "HLC3");
|
|
32866
|
+
const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
|
|
32867
|
+
const d = change(tp);
|
|
32868
|
+
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
32869
|
+
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
32870
|
+
const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
|
|
32871
|
+
return {
|
|
32872
|
+
plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32873
|
+
levels: [
|
|
32874
|
+
{ key: "upper", price: 80, color: NEUTRAL },
|
|
32875
|
+
{ key: "lower", price: 20, color: NEUTRAL }
|
|
32876
|
+
]
|
|
32877
|
+
};
|
|
32878
|
+
}
|
|
32879
|
+
};
|
|
32880
|
+
function volumeIndex(bars, when) {
|
|
32881
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
32882
|
+
let index = 1e3;
|
|
32883
|
+
let started = false;
|
|
32884
|
+
for (let i = 1; i < bars.length; i++) {
|
|
32885
|
+
const v = bars[i].volume;
|
|
32886
|
+
const pv = bars[i - 1].volume;
|
|
32887
|
+
const pc = bars[i - 1].close;
|
|
32888
|
+
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
32889
|
+
if (started) out[i] = index;
|
|
32890
|
+
continue;
|
|
32891
|
+
}
|
|
32892
|
+
started = true;
|
|
32893
|
+
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
32894
|
+
out[i] = index;
|
|
32895
|
+
}
|
|
32896
|
+
return out;
|
|
32897
|
+
}
|
|
32898
|
+
var nvi = {
|
|
32899
|
+
type: "negative-volume-index",
|
|
32900
|
+
title: "Negative Volume Index",
|
|
32901
|
+
shortTitle: "Negative Volume Index",
|
|
32902
|
+
overlay: false,
|
|
32903
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
32904
|
+
compute: (bars, inputs) => {
|
|
32905
|
+
const line = volumeIndex(bars, (v, pv) => v < pv);
|
|
32906
|
+
return {
|
|
32907
|
+
plots: [
|
|
32908
|
+
{ key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
32909
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
32910
|
+
]
|
|
32911
|
+
};
|
|
32912
|
+
}
|
|
32913
|
+
};
|
|
32914
|
+
var pvi = {
|
|
32915
|
+
type: "positive-volume-index",
|
|
32916
|
+
title: "Positive Volume Index",
|
|
32917
|
+
shortTitle: "Positive Volume Index",
|
|
32918
|
+
overlay: false,
|
|
32919
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
32920
|
+
compute: (bars, inputs) => {
|
|
32921
|
+
const line = volumeIndex(bars, (v, pv) => v > pv);
|
|
32922
|
+
return {
|
|
32923
|
+
plots: [
|
|
32924
|
+
{ key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
32925
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
32926
|
+
]
|
|
32927
|
+
};
|
|
32928
|
+
}
|
|
32929
|
+
};
|
|
32930
|
+
var pvt = {
|
|
32931
|
+
type: "price-volume-trend",
|
|
32932
|
+
title: "Price Volume Trend",
|
|
32933
|
+
shortTitle: "PVT",
|
|
32934
|
+
overlay: false,
|
|
32935
|
+
inputs: [colorInput()],
|
|
32936
|
+
compute: (bars, inputs) => {
|
|
32937
|
+
const c = closes(bars);
|
|
32938
|
+
const raw = bars.map((b, i) => {
|
|
32939
|
+
if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
32940
|
+
const pc = c[i - 1];
|
|
32941
|
+
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
32942
|
+
});
|
|
32943
|
+
return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
32944
|
+
}
|
|
32945
|
+
};
|
|
32946
|
+
var volumeOscillator = {
|
|
32947
|
+
type: "volume-oscillator",
|
|
32948
|
+
title: "Volume Oscillator",
|
|
32949
|
+
shortTitle: "Vol Osc",
|
|
32950
|
+
overlay: false,
|
|
32951
|
+
inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
32952
|
+
compute: (bars, inputs) => {
|
|
32953
|
+
const vol = volumes(bars);
|
|
32954
|
+
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
32955
|
+
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
32956
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
32957
|
+
return {
|
|
32958
|
+
plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32959
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32960
|
+
};
|
|
32961
|
+
}
|
|
32962
|
+
};
|
|
32963
|
+
var pvo = {
|
|
32964
|
+
type: "pvo",
|
|
32965
|
+
title: "Percentage Volume Oscillator",
|
|
32966
|
+
shortTitle: "PVO",
|
|
32967
|
+
overlay: false,
|
|
32968
|
+
inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
|
|
32969
|
+
compute: (bars, inputs) => {
|
|
32970
|
+
const vol = volumes(bars);
|
|
32971
|
+
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
32972
|
+
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
32973
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
32974
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
32975
|
+
const hist = sub(line, signal);
|
|
32976
|
+
return {
|
|
32977
|
+
plots: [
|
|
32978
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
|
|
32979
|
+
{ key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
|
|
32980
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
32981
|
+
],
|
|
32982
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32983
|
+
};
|
|
32984
|
+
}
|
|
32985
|
+
};
|
|
32986
|
+
var vfi = {
|
|
32987
|
+
type: "volume-flow-indicator",
|
|
32988
|
+
title: "Volume Flow Indicator",
|
|
32989
|
+
shortTitle: "Volume Flow Indicator",
|
|
32990
|
+
overlay: false,
|
|
32991
|
+
inputs: [
|
|
32992
|
+
lengthInput(130),
|
|
32993
|
+
{ key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
|
|
32994
|
+
{ key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
|
|
32995
|
+
lengthInput(3, "smoothLength", "Smoothing length")
|
|
32996
|
+
],
|
|
32997
|
+
compute: (bars, inputs) => {
|
|
32998
|
+
const len = num3(inputs, "length", 130);
|
|
32999
|
+
const coef = num3(inputs, "coef", 0.2);
|
|
33000
|
+
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
33001
|
+
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
33002
|
+
const inter = change(typical);
|
|
33003
|
+
const vInter = stdev(inter, 30);
|
|
33004
|
+
const vol = volumes(bars);
|
|
33005
|
+
const vAve = sma(vol, len);
|
|
33006
|
+
const mf = change(sourceValues(bars, "HLC3"));
|
|
33007
|
+
const raw = bars.map((b, i) => {
|
|
33008
|
+
const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
|
|
33009
|
+
const v = vol[i];
|
|
33010
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
33011
|
+
const m = mf[i];
|
|
33012
|
+
if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
|
|
33013
|
+
const capped = Math.min(v, ave * volCoef);
|
|
33014
|
+
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
33015
|
+
});
|
|
33016
|
+
const values = bars.map((_, i) => {
|
|
33017
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
33018
|
+
return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
|
|
33019
|
+
});
|
|
33020
|
+
const flowSum = sum(raw, len);
|
|
33021
|
+
const line = zip(flowSum, values, (f, a) => f / a);
|
|
33022
|
+
return {
|
|
33023
|
+
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
33024
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
33025
|
+
};
|
|
33026
|
+
}
|
|
33027
|
+
};
|
|
33028
|
+
var intradayIntensity = {
|
|
33029
|
+
type: "intraday-intensity",
|
|
33030
|
+
title: "Intraday Intensity",
|
|
33031
|
+
shortTitle: "Intraday Intensity",
|
|
33032
|
+
overlay: false,
|
|
33033
|
+
inputs: [lengthInput(21), colorInput()],
|
|
33034
|
+
compute: (bars, inputs) => {
|
|
33035
|
+
const len = num3(inputs, "length", 21);
|
|
33036
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
|
|
33037
|
+
return {
|
|
33038
|
+
plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
33039
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
33040
|
+
};
|
|
33041
|
+
}
|
|
33042
|
+
};
|
|
33043
|
+
var volumeSpecs = [
|
|
33044
|
+
obv,
|
|
33045
|
+
accDist,
|
|
33046
|
+
cmf,
|
|
33047
|
+
chaikinOscillator,
|
|
33048
|
+
eom,
|
|
33049
|
+
forceIndex,
|
|
33050
|
+
klinger,
|
|
33051
|
+
mfi,
|
|
33052
|
+
nvi,
|
|
33053
|
+
pvi,
|
|
33054
|
+
pvt,
|
|
33055
|
+
volumeOscillator,
|
|
33056
|
+
pvo,
|
|
33057
|
+
vfi,
|
|
33058
|
+
intradayIntensity
|
|
33059
|
+
];
|
|
33060
|
+
|
|
33061
|
+
// src/core/native-indicators/classics/overlays.ts
|
|
33062
|
+
var DAY_MS2 = 864e5;
|
|
33063
|
+
function periodKey(anchor, time) {
|
|
33064
|
+
if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
|
|
33065
|
+
if (anchor === "Month") {
|
|
33066
|
+
const d = new Date(time);
|
|
33067
|
+
return d.getUTCFullYear() * 12 + d.getUTCMonth();
|
|
33068
|
+
}
|
|
33069
|
+
return Math.floor(time / DAY_MS2);
|
|
33070
|
+
}
|
|
33071
|
+
var parabolicSar = {
|
|
33072
|
+
type: "parabolic-sar",
|
|
33073
|
+
title: "Parabolic SAR",
|
|
33074
|
+
shortTitle: "SAR",
|
|
33075
|
+
overlay: true,
|
|
33076
|
+
inputs: [
|
|
33077
|
+
{ key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
33078
|
+
{ key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
33079
|
+
{ key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
|
|
33080
|
+
],
|
|
33081
|
+
compute: (bars, inputs) => {
|
|
33082
|
+
const start = num3(inputs, "start", 0.02);
|
|
33083
|
+
const inc = num3(inputs, "increment", 0.02);
|
|
33084
|
+
const max = num3(inputs, "maximum", 0.2);
|
|
33085
|
+
const n = bars.length;
|
|
33086
|
+
const values = new Array(n).fill(Number.NaN);
|
|
33087
|
+
const colors = new Array(n).fill(null);
|
|
33088
|
+
if (n >= 2) {
|
|
33089
|
+
let long = bars[1].close >= bars[0].close;
|
|
33090
|
+
let sar = long ? bars[0].low : bars[0].high;
|
|
33091
|
+
let ep = long ? bars[0].high : bars[0].low;
|
|
33092
|
+
let af = start;
|
|
33093
|
+
for (let i = 1; i < n; i++) {
|
|
33094
|
+
const b = bars[i];
|
|
33095
|
+
sar += af * (ep - sar);
|
|
33096
|
+
if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
|
|
33097
|
+
else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
|
|
33098
|
+
const reversed = long ? b.low < sar : b.high > sar;
|
|
33099
|
+
if (reversed) {
|
|
33100
|
+
long = !long;
|
|
33101
|
+
sar = ep;
|
|
33102
|
+
ep = long ? b.high : b.low;
|
|
33103
|
+
af = start;
|
|
33104
|
+
} else if (long && b.high > ep) {
|
|
33105
|
+
ep = b.high;
|
|
33106
|
+
af = Math.min(max, af + inc);
|
|
33107
|
+
} else if (!long && b.low < ep) {
|
|
33108
|
+
ep = b.low;
|
|
33109
|
+
af = Math.min(max, af + inc);
|
|
33110
|
+
}
|
|
33111
|
+
values[i] = sar;
|
|
33112
|
+
colors[i] = long ? BULLISH : BEARISH;
|
|
33113
|
+
}
|
|
33114
|
+
}
|
|
33115
|
+
return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
|
|
33116
|
+
}
|
|
33117
|
+
};
|
|
33118
|
+
var vwap = {
|
|
33119
|
+
type: "vwap",
|
|
33120
|
+
title: "Volume Weighted Average Price",
|
|
33121
|
+
shortTitle: "VWAP",
|
|
33122
|
+
overlay: true,
|
|
33123
|
+
inputs: [
|
|
33124
|
+
{ key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
|
|
33125
|
+
{ key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
|
|
33126
|
+
colorInput(INFO)
|
|
33127
|
+
],
|
|
33128
|
+
compute: (bars, inputs) => {
|
|
33129
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
33130
|
+
const source = str3(inputs, "source", "HLC3");
|
|
33131
|
+
const values = new Array(bars.length).fill(Number.NaN);
|
|
33132
|
+
let period = Number.NaN;
|
|
33133
|
+
let cumPV = 0;
|
|
33134
|
+
let cumV = 0;
|
|
33135
|
+
for (let i = 0; i < bars.length; i++) {
|
|
33136
|
+
const b = bars[i];
|
|
33137
|
+
const key = periodKey(anchor, b.time);
|
|
33138
|
+
if (key !== period) {
|
|
33139
|
+
period = key;
|
|
33140
|
+
cumPV = 0;
|
|
33141
|
+
cumV = 0;
|
|
33142
|
+
}
|
|
33143
|
+
const v = b.volume;
|
|
33144
|
+
if (v != null && Number.isFinite(v) && v > 0) {
|
|
33145
|
+
const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
|
|
33146
|
+
cumPV += tp * v;
|
|
33147
|
+
cumV += v;
|
|
33148
|
+
}
|
|
33149
|
+
if (cumV > 0) values[i] = cumPV / cumV;
|
|
33150
|
+
}
|
|
33151
|
+
return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
33152
|
+
}
|
|
33153
|
+
};
|
|
33154
|
+
var pivotPoints = {
|
|
33155
|
+
type: "pivot-points",
|
|
33156
|
+
title: "Pivot Points",
|
|
33157
|
+
shortTitle: "Pivots",
|
|
33158
|
+
overlay: true,
|
|
33159
|
+
inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
|
|
33160
|
+
compute: (bars, inputs) => {
|
|
33161
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
33162
|
+
const aggs = /* @__PURE__ */ new Map();
|
|
33163
|
+
for (const b of bars) {
|
|
33164
|
+
const key = periodKey(anchor, b.time);
|
|
33165
|
+
const a = aggs.get(key);
|
|
33166
|
+
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
33167
|
+
else {
|
|
33168
|
+
a.high = Math.max(a.high, b.high);
|
|
33169
|
+
a.low = Math.min(a.low, b.low);
|
|
33170
|
+
a.close = b.close;
|
|
33171
|
+
}
|
|
33172
|
+
}
|
|
33173
|
+
const n = bars.length;
|
|
33174
|
+
const mk = () => new Array(n).fill(Number.NaN);
|
|
33175
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
33176
|
+
for (let i = 0; i < n; i++) {
|
|
33177
|
+
const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
|
|
33178
|
+
if (!prev2) continue;
|
|
33179
|
+
const p = (prev2.high + prev2.low + prev2.close) / 3;
|
|
33180
|
+
levels.p[i] = p;
|
|
33181
|
+
levels.r1[i] = 2 * p - prev2.low;
|
|
33182
|
+
levels.s1[i] = 2 * p - prev2.high;
|
|
33183
|
+
levels.r2[i] = p + (prev2.high - prev2.low);
|
|
33184
|
+
levels.s2[i] = p - (prev2.high - prev2.low);
|
|
33185
|
+
levels.r3[i] = prev2.high + 2 * (p - prev2.low);
|
|
33186
|
+
levels.s3[i] = prev2.low - 2 * (prev2.high - p);
|
|
33187
|
+
}
|
|
33188
|
+
const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
|
|
33189
|
+
return {
|
|
33190
|
+
plots: [
|
|
33191
|
+
plot("p", "P", WARNING),
|
|
33192
|
+
plot("r1", "R1", BEARISH),
|
|
33193
|
+
plot("s1", "S1", BULLISH),
|
|
33194
|
+
plot("r2", "R2", BEARISH),
|
|
33195
|
+
plot("s2", "S2", BULLISH),
|
|
33196
|
+
plot("r3", "R3", BEARISH),
|
|
33197
|
+
plot("s3", "S3", BULLISH)
|
|
33198
|
+
]
|
|
33199
|
+
};
|
|
33200
|
+
}
|
|
33201
|
+
};
|
|
33202
|
+
var fiftyTwoWeek = {
|
|
33203
|
+
type: "52-week-high-low",
|
|
33204
|
+
title: "52 Week High/Low",
|
|
33205
|
+
shortTitle: "52W H/L",
|
|
33206
|
+
overlay: true,
|
|
33207
|
+
inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
|
|
33208
|
+
compute: (bars, inputs) => {
|
|
33209
|
+
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
33210
|
+
const n = bars.length;
|
|
33211
|
+
const hi = new Array(n).fill(Number.NaN);
|
|
33212
|
+
const lo = new Array(n).fill(Number.NaN);
|
|
33213
|
+
const maxIdx = [];
|
|
33214
|
+
const minIdx = [];
|
|
33215
|
+
let from = 0;
|
|
33216
|
+
for (let i = 0; i < n; i++) {
|
|
33217
|
+
const b = bars[i];
|
|
33218
|
+
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
33219
|
+
maxIdx.push(i);
|
|
33220
|
+
while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
|
|
33221
|
+
minIdx.push(i);
|
|
33222
|
+
while (bars[from].time < b.time - span) from += 1;
|
|
33223
|
+
while (maxIdx[0] < from) maxIdx.shift();
|
|
33224
|
+
while (minIdx[0] < from) minIdx.shift();
|
|
33225
|
+
hi[i] = bars[maxIdx[0]].high;
|
|
33226
|
+
lo[i] = bars[minIdx[0]].low;
|
|
33227
|
+
}
|
|
33228
|
+
return {
|
|
33229
|
+
plots: [
|
|
33230
|
+
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
33231
|
+
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
33232
|
+
]
|
|
33233
|
+
};
|
|
33234
|
+
}
|
|
33235
|
+
};
|
|
33236
|
+
var zigzag = {
|
|
33237
|
+
type: "zigzag",
|
|
33238
|
+
title: "ZigZag",
|
|
33239
|
+
overlay: true,
|
|
33240
|
+
inputs: [
|
|
33241
|
+
{ key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
|
|
33242
|
+
{ key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
33243
|
+
colorInput()
|
|
33244
|
+
],
|
|
33245
|
+
compute: (bars, inputs) => {
|
|
33246
|
+
const dev = num3(inputs, "deviation", 5) / 100;
|
|
33247
|
+
const depth = num3(inputs, "depth", 10);
|
|
33248
|
+
const pivots = [];
|
|
33249
|
+
if (bars.length > 1) {
|
|
33250
|
+
let up = bars[1].close >= bars[0].close;
|
|
33251
|
+
let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
|
|
33252
|
+
for (let i = 1; i < bars.length; i++) {
|
|
33253
|
+
const b = bars[i];
|
|
33254
|
+
if (up) {
|
|
33255
|
+
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
33256
|
+
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
33257
|
+
pivots.push(ext);
|
|
33258
|
+
up = false;
|
|
33259
|
+
ext = { i, price: b.low };
|
|
33260
|
+
}
|
|
33261
|
+
} else if (b.low <= ext.price) {
|
|
33262
|
+
ext = { i, price: b.low };
|
|
33263
|
+
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
33264
|
+
pivots.push(ext);
|
|
33265
|
+
up = true;
|
|
33266
|
+
ext = { i, price: b.high };
|
|
33267
|
+
}
|
|
33268
|
+
}
|
|
33269
|
+
pivots.push(ext);
|
|
33270
|
+
}
|
|
33271
|
+
return {
|
|
33272
|
+
plots: [],
|
|
33273
|
+
polylines: [
|
|
33274
|
+
{
|
|
33275
|
+
key: "zigzag",
|
|
33276
|
+
points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
|
|
33277
|
+
curved: false,
|
|
33278
|
+
closed: false,
|
|
33279
|
+
lineColor: str3(inputs, "color", SERIES_LINE),
|
|
33280
|
+
lineWidth: 2,
|
|
33281
|
+
lineStyle: "solid",
|
|
33282
|
+
arrowLeft: false,
|
|
33283
|
+
arrowRight: false
|
|
33284
|
+
}
|
|
33285
|
+
]
|
|
33286
|
+
};
|
|
33287
|
+
}
|
|
33288
|
+
};
|
|
33289
|
+
var williamsFractal = {
|
|
33290
|
+
type: "williams-fractal",
|
|
33291
|
+
title: "Williams Fractal",
|
|
33292
|
+
shortTitle: "Fractals",
|
|
33293
|
+
overlay: true,
|
|
33294
|
+
inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
|
|
33295
|
+
compute: (bars, inputs) => {
|
|
33296
|
+
const p = num3(inputs, "periods", 2);
|
|
33297
|
+
const markers = [];
|
|
33298
|
+
const isExtreme = (i, pick, better) => {
|
|
33299
|
+
const v = pick(bars[i]);
|
|
33300
|
+
for (let k = i - p; k <= i + p; k++) {
|
|
33301
|
+
if (k === i) continue;
|
|
33302
|
+
if (!better(v, pick(bars[k]))) return false;
|
|
33303
|
+
}
|
|
33304
|
+
return true;
|
|
33305
|
+
};
|
|
33306
|
+
for (let i = p; i < bars.length - p; i++) {
|
|
33307
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
|
|
33308
|
+
markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
|
|
33309
|
+
}
|
|
33310
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
33311
|
+
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
33312
|
+
}
|
|
33313
|
+
}
|
|
33314
|
+
return { plots: [], markers };
|
|
33315
|
+
}
|
|
33316
|
+
};
|
|
33317
|
+
var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
|
|
33318
|
+
|
|
33319
|
+
// src/core/native-indicators/classics/index.ts
|
|
33320
|
+
var classicSpecs = [
|
|
33321
|
+
...averageSpecs,
|
|
33322
|
+
...bandSpecs,
|
|
33323
|
+
...oscillatorSpecs,
|
|
33324
|
+
...trendSpecs,
|
|
33325
|
+
...volatilitySpecs,
|
|
33326
|
+
...volumeSpecs,
|
|
33327
|
+
...overlaySpecs
|
|
33328
|
+
];
|
|
33329
|
+
function registerClassicIndicators() {
|
|
33330
|
+
for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
|
|
33331
|
+
}
|
|
33332
|
+
|
|
33333
|
+
// src/Vela.ts
|
|
33334
|
+
var Vela = class {
|
|
33335
|
+
constructor(container, options = {}, deps = {}) {
|
|
33336
|
+
registerBuiltinChartTypes();
|
|
33337
|
+
registerVolume();
|
|
33338
|
+
registerVpvr();
|
|
33339
|
+
registerClassicIndicators();
|
|
33340
|
+
const element = resolveElement(container);
|
|
33341
|
+
const theme = resolveTheme(options.theme);
|
|
33342
|
+
let animZoom = true;
|
|
33343
|
+
let animPan = true;
|
|
33344
|
+
if (typeof options.animations === "boolean") {
|
|
33345
|
+
animZoom = options.animations;
|
|
33346
|
+
animPan = options.animations;
|
|
33347
|
+
} else if (options.animations) {
|
|
33348
|
+
animZoom = options.animations.zoom ?? true;
|
|
33349
|
+
animPan = options.animations.pan ?? true;
|
|
33350
|
+
}
|
|
33351
|
+
const display = {
|
|
33352
|
+
currentPriceLine: options.currentPriceLine ?? true,
|
|
33353
|
+
logScale: options.logScale ?? false,
|
|
33354
|
+
nativeBackend: options.nativeBackend ?? "auto",
|
|
33355
|
+
animZoom,
|
|
33356
|
+
animPan,
|
|
33357
|
+
glow: options.glow ?? 0,
|
|
33358
|
+
upColor: options.upColor ?? BULLISH,
|
|
33359
|
+
downColor: options.downColor ?? BEARISH,
|
|
33360
|
+
priceStyle: options.priceStyle ?? "candles"
|
|
33361
|
+
};
|
|
33362
|
+
const RendererClass = options.renderer ?? NativeRenderer;
|
|
33363
|
+
if (typeof RendererClass !== "function") {
|
|
33364
|
+
throw new Error(
|
|
33365
|
+
"[vela] options.renderer must be a renderer class, e.g. import { LwcRenderer } from 'vela/renderers/lwc'. The 'lwc'/'native' string options were removed."
|
|
33366
|
+
);
|
|
33367
|
+
}
|
|
33368
|
+
const renderer = deps.renderer ?? new RendererClass(display);
|
|
33369
|
+
const engines = deps.engines ?? [];
|
|
33370
|
+
const feed = deps.dataFeed ?? new MultiProviderFeed();
|
|
33371
|
+
const config = {
|
|
33372
|
+
market: {
|
|
33373
|
+
symbol: options.symbol,
|
|
33374
|
+
timeframe: options.timeframe,
|
|
33375
|
+
bars: options.bars,
|
|
33376
|
+
session: options.session,
|
|
33377
|
+
visibleRange: options.visibleRange,
|
|
33378
|
+
data: options.data
|
|
33379
|
+
},
|
|
33380
|
+
live: options.live ?? false,
|
|
33381
|
+
theme,
|
|
33382
|
+
defaultLanguage: options.defaultLanguage ?? engines[0]?.language ?? "pine",
|
|
33383
|
+
drawings: options.drawings,
|
|
33384
|
+
volume: options.volume ?? true
|
|
33385
|
+
// the volume indicator is on by default
|
|
33386
|
+
};
|
|
33387
|
+
this.rendererControl = new RendererControl(renderer);
|
|
33388
|
+
if (options.settings) this.rendererControl.setSettingsVisibility(options.settings);
|
|
33389
|
+
this.dataControl = new DataControl(feed);
|
|
33390
|
+
this.orchestrator = new EngineOrchestrator(element, renderer, feed, engines, config, this.dataControl);
|
|
33391
|
+
const defaults2 = rendererDefaults();
|
|
33392
|
+
if (Object.keys(defaults2).length > 0) this.rendererControl.set(defaults2);
|
|
33393
|
+
this.panesControl = new PanesControl(this.orchestrator);
|
|
33394
|
+
this.drawingsControl = new DrawingsControl(this.orchestrator.drawings);
|
|
33395
|
+
}
|
|
33396
|
+
/**
|
|
33397
|
+
* Register a scripting engine so `addIndicator({ language })` can run that
|
|
33398
|
+
* language. Vela ships NO engine — install the one you need (Pine Script:
|
|
33399
|
+
* `@luxalgo/vela-pinets`) and register it, e.g.
|
|
33400
|
+
* `chart.registerEngine('pine', new PineEngine())`; without one the chart
|
|
33401
|
+
* displays candles, drawings and native indicators only. Re-registering a
|
|
33402
|
+
* language replaces it.
|
|
33403
|
+
*/
|
|
33404
|
+
registerEngine(language, engine) {
|
|
33405
|
+
this.orchestrator.registerEngine(language, engine);
|
|
33406
|
+
return this;
|
|
33407
|
+
}
|
|
33408
|
+
/** Run an indicator script on the chart's market data and render it. */
|
|
33409
|
+
addIndicator(source, options) {
|
|
33410
|
+
return this.orchestrator.addIndicator(source, options);
|
|
33411
|
+
}
|
|
33412
|
+
/**
|
|
33413
|
+
* Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
|
|
33414
|
+
* e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
33415
|
+
* remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
|
|
33416
|
+
* call returns the existing handle; the classic studies allow several instances, so each call
|
|
33417
|
+
* adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
|
|
33418
|
+
*/
|
|
33419
|
+
addNativeIndicator(type, options) {
|
|
33420
|
+
return this.orchestrator.addNativeIndicator(type, options);
|
|
33421
|
+
}
|
|
33422
|
+
/**
|
|
33423
|
+
* The catalog of built-in native indicators with their live state on this chart — each entry's
|
|
33424
|
+
* `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
|
|
33425
|
+
* and whether it allows several instances (`multiInstance`; otherwise a second
|
|
33426
|
+
* `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
|
|
33427
|
+
* ones, and de-duplicate the single-instance ones. Async because support may probe the
|
|
33428
|
+
* provider (a type may need data the symbol lacks).
|
|
33429
|
+
*/
|
|
33430
|
+
availableNativeIndicators() {
|
|
33431
|
+
return this.orchestrator.availableNativeIndicators();
|
|
33432
|
+
}
|
|
33433
|
+
/**
|
|
33434
|
+
* The native-indicator types PRESENT on the chart right now, one entry per instance (a
|
|
33435
|
+
* multi-instance type repeats) — the synchronous slice of
|
|
33436
|
+
* {@link availableNativeIndicators} (only support probing is async; presence never is).
|
|
33437
|
+
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
33438
|
+
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|
|
33439
|
+
*/
|
|
33440
|
+
presentNativeIndicators() {
|
|
33441
|
+
return this.orchestrator.presentNativeIndicators();
|
|
33442
|
+
}
|
|
33443
|
+
/** Live handles of every indicator currently on the chart (script + native) — drive
|
|
33444
|
+
* host panels (object trees, indicator lists) with per-id visibility/removal. */
|
|
33445
|
+
indicators() {
|
|
33446
|
+
return this.orchestrator.listIndicators();
|
|
33447
|
+
}
|
|
33448
|
+
/**
|
|
33449
|
+
* Execute an indicator script and INJECT it only if the run succeeds — the seam for
|
|
33450
|
+
* host editors and consoles. Resolves `{ ok: true, handle }` after the first
|
|
33451
|
+
* successful evaluation, or `{ ok: false, error }` after a compile/runtime failure —
|
|
33452
|
+
* in which case the failed indicator is removed again (no dead legend row).
|
|
33453
|
+
* Never rejects.
|
|
33454
|
+
*/
|
|
33455
|
+
/**
|
|
33456
|
+
* Execute a script and resolve its FIRST computed run — the data-out door for host
|
|
33457
|
+
* editors, consoles and dashboards. The script is injected only if it runs (a failure
|
|
33458
|
+
* removes it again, leaving no dead legend row), and the result carries the run itself
|
|
33459
|
+
* plus the controls for what it put on the chart: `onUpdate` to follow later runs,
|
|
33460
|
+
* `remove` to take it off. Never rejects.
|
|
31081
33461
|
*
|
|
31082
33462
|
* `runScript` is `runIndicator` with the run as its payload rather than a handle to go
|
|
31083
33463
|
* fetch from — the same relationship `script:run` has to `context:changed`.
|
|
@@ -31419,18 +33799,13 @@ function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_
|
|
|
31419
33799
|
return Math.round(Math.min(Math.max(px, lo), hi));
|
|
31420
33800
|
}
|
|
31421
33801
|
|
|
31422
|
-
// src/core/model/identity.ts
|
|
31423
|
-
function stableSeriesId(parts) {
|
|
31424
|
-
const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
31425
|
-
return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
|
|
31426
|
-
}
|
|
31427
|
-
|
|
31428
33802
|
exports.ACCENT = ACCENT;
|
|
31429
33803
|
exports.ACCENT_BRIGHT = ACCENT_BRIGHT;
|
|
31430
33804
|
exports.BEARISH = BEARISH;
|
|
31431
33805
|
exports.BULLISH = BULLISH;
|
|
31432
33806
|
exports.BarStore = BarStore;
|
|
31433
33807
|
exports.CATEGORICAL = CATEGORICAL;
|
|
33808
|
+
exports.CHIP_PLATE = CHIP_PLATE;
|
|
31434
33809
|
exports.CROSSHAIR = CROSSHAIR;
|
|
31435
33810
|
exports.CachingDataFeed = CachingDataFeed;
|
|
31436
33811
|
exports.DARK_THEME = DARK_THEME;
|