@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/vela.global.js
CHANGED
|
@@ -111,7 +111,7 @@ var Vela = (function (exports) {
|
|
|
111
111
|
|
|
112
112
|
// src/core/engine/IndicatorHandleImpl.ts
|
|
113
113
|
var IndicatorHandleImpl = class {
|
|
114
|
-
constructor(id, title, controller, source) {
|
|
114
|
+
constructor(id, title, controller, source, nativeType) {
|
|
115
115
|
this.controller = controller;
|
|
116
116
|
this.schema = [];
|
|
117
117
|
this.propsSchema = [];
|
|
@@ -120,6 +120,7 @@ var Vela = (function (exports) {
|
|
|
120
120
|
this.id = id;
|
|
121
121
|
this.title = title;
|
|
122
122
|
if (source !== void 0) this.source = source;
|
|
123
|
+
if (nativeType !== void 0) this.nativeType = nativeType;
|
|
123
124
|
}
|
|
124
125
|
get inputs() {
|
|
125
126
|
return this.schema;
|
|
@@ -210,24 +211,24 @@ var Vela = (function (exports) {
|
|
|
210
211
|
}
|
|
211
212
|
function inspectModels(models) {
|
|
212
213
|
const indicators = models.map(summarizeModel);
|
|
213
|
-
const
|
|
214
|
+
const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
|
|
214
215
|
return {
|
|
215
216
|
indicators,
|
|
216
217
|
totals: {
|
|
217
218
|
panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
|
|
218
|
-
series:
|
|
219
|
-
fills:
|
|
220
|
-
backgrounds:
|
|
221
|
-
priceLines:
|
|
222
|
-
lines:
|
|
223
|
-
boxes:
|
|
224
|
-
labels:
|
|
225
|
-
polylines:
|
|
226
|
-
linefills:
|
|
227
|
-
tables:
|
|
228
|
-
barColors:
|
|
229
|
-
trades:
|
|
230
|
-
forcedOverlay:
|
|
219
|
+
series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
|
|
220
|
+
fills: sum2((s) => s.fills),
|
|
221
|
+
backgrounds: sum2((s) => s.backgrounds),
|
|
222
|
+
priceLines: sum2((s) => s.priceLines),
|
|
223
|
+
lines: sum2((s) => s.lines),
|
|
224
|
+
boxes: sum2((s) => s.boxes),
|
|
225
|
+
labels: sum2((s) => s.labels),
|
|
226
|
+
polylines: sum2((s) => s.polylines),
|
|
227
|
+
linefills: sum2((s) => s.linefills),
|
|
228
|
+
tables: sum2((s) => s.tables),
|
|
229
|
+
barColors: sum2((s) => s.barColors),
|
|
230
|
+
trades: sum2((s) => s.trades),
|
|
231
|
+
forcedOverlay: sum2((s) => s.forcedOverlay)
|
|
231
232
|
}
|
|
232
233
|
};
|
|
233
234
|
}
|
|
@@ -276,6 +277,7 @@ var Vela = (function (exports) {
|
|
|
276
277
|
var CROSSHAIR = "#9aa0ad";
|
|
277
278
|
var SLATE_DEEP = "#1e293b";
|
|
278
279
|
var SLATE = "#475569";
|
|
280
|
+
var CHIP_PLATE = "#595959";
|
|
279
281
|
var TRADE_LONG = ACCENT;
|
|
280
282
|
var TRADE_SHORT = BEARISH;
|
|
281
283
|
var TRADE_EXIT = "#d500f9";
|
|
@@ -503,6 +505,10 @@ var Vela = (function (exports) {
|
|
|
503
505
|
"market-post",
|
|
504
506
|
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"/>')
|
|
505
507
|
);
|
|
508
|
+
registerIcon(
|
|
509
|
+
"market-extended",
|
|
510
|
+
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"/>')
|
|
511
|
+
);
|
|
506
512
|
registerIcon("market-closed", svg24('<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>'));
|
|
507
513
|
registerIcon(
|
|
508
514
|
"market-holiday",
|
|
@@ -516,6 +522,8 @@ var Vela = (function (exports) {
|
|
|
516
522
|
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"/>'));
|
|
517
523
|
registerIcon("check", S('<path d="m2.8 8.4 3.4 3.4 7-7.6"/>'));
|
|
518
524
|
registerIcon("close", S('<path d="m3.8 3.8 8.4 8.4M12.2 3.8l-8.4 8.4"/>'));
|
|
525
|
+
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"/>'));
|
|
526
|
+
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"/>'));
|
|
519
527
|
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"/>'));
|
|
520
528
|
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"/>'));
|
|
521
529
|
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"/>'));
|
|
@@ -6700,8 +6708,8 @@ var Vela = (function (exports) {
|
|
|
6700
6708
|
this.port?.setExternalGhost?.(doc);
|
|
6701
6709
|
}
|
|
6702
6710
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
6703
|
-
setToolShortcuts(
|
|
6704
|
-
this.port?.setToolShortcuts?.(
|
|
6711
|
+
setToolShortcuts(map2) {
|
|
6712
|
+
this.port?.setToolShortcuts?.(map2);
|
|
6705
6713
|
}
|
|
6706
6714
|
// ── programmatic CRUD (facade-facing) ── each is one undo step
|
|
6707
6715
|
add(type, init = {}) {
|
|
@@ -8425,16 +8433,20 @@ var Vela = (function (exports) {
|
|
|
8425
8433
|
/**
|
|
8426
8434
|
* Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
|
|
8427
8435
|
* indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
|
|
8428
|
-
* INSTANCE per type
|
|
8429
|
-
*
|
|
8436
|
+
* INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
|
|
8437
|
+
* single-instance type returns the existing handle; a multi-instance type gets a fresh
|
|
8438
|
+
* instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
|
|
8439
|
+
* addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
|
|
8430
8440
|
*/
|
|
8431
8441
|
addNativeIndicator(type, options = {}) {
|
|
8432
|
-
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
8433
|
-
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
|
|
8434
8442
|
const descriptor = getNativeIndicator(type);
|
|
8443
|
+
if (!descriptor?.multiInstance) {
|
|
8444
|
+
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
8445
|
+
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
|
|
8446
|
+
}
|
|
8435
8447
|
const id = this.registry.nextId("native");
|
|
8436
8448
|
const title = descriptor?.title ?? type;
|
|
8437
|
-
const handle = new IndicatorHandleImpl(id, title, this);
|
|
8449
|
+
const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
|
|
8438
8450
|
this.handles.set(id, handle);
|
|
8439
8451
|
if (!descriptor) {
|
|
8440
8452
|
console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
|
|
@@ -8446,16 +8458,18 @@ var Vela = (function (exports) {
|
|
|
8446
8458
|
void this.startNativeIndicator(id, handle);
|
|
8447
8459
|
return handle;
|
|
8448
8460
|
}
|
|
8449
|
-
/**
|
|
8450
|
-
*
|
|
8451
|
-
* entry's `supported` (applies to the current symbol) and `present` (already added — a second
|
|
8452
|
-
* add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
|
|
8453
|
-
* because a descriptor's `isSupported` may probe the provider for a required capability.
|
|
8454
|
-
*/
|
|
8455
|
-
/** The native types present on the chart — SYNC (registry state; no support probe). */
|
|
8461
|
+
/** The native types present on the chart, one entry per INSTANCE in registry order (a
|
|
8462
|
+
* multi-instance type repeats) — SYNC (registry state; no support probe). */
|
|
8456
8463
|
presentNativeIndicators() {
|
|
8457
8464
|
return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
|
|
8458
8465
|
}
|
|
8466
|
+
/**
|
|
8467
|
+
* The catalog of registered native-indicator types with their live state on THIS chart: each
|
|
8468
|
+
* entry's `supported` (applies to the current symbol) and `present` (at least one instance
|
|
8469
|
+
* added — a second add of a single-instance type is a no-op). Powers a host "add native
|
|
8470
|
+
* indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
|
|
8471
|
+
* probe the provider for a required capability.
|
|
8472
|
+
*/
|
|
8459
8473
|
async availableNativeIndicators() {
|
|
8460
8474
|
const symbol = this.qualifiedSymbol();
|
|
8461
8475
|
const present = new Set(this.presentNativeIndicators());
|
|
@@ -8465,7 +8479,8 @@ var Vela = (function (exports) {
|
|
|
8465
8479
|
title: d.title,
|
|
8466
8480
|
supported: await this.isNativeSupported(d, symbol),
|
|
8467
8481
|
present: present.has(d.type),
|
|
8468
|
-
beta: d.beta
|
|
8482
|
+
beta: d.beta,
|
|
8483
|
+
...d.multiInstance ? { multiInstance: true } : {}
|
|
8469
8484
|
}))
|
|
8470
8485
|
);
|
|
8471
8486
|
}
|
|
@@ -9272,6 +9287,15 @@ var Vela = (function (exports) {
|
|
|
9272
9287
|
console.warn(`[vela] renderer "${this.renderer.name}" does not support screenshot().`);
|
|
9273
9288
|
return null;
|
|
9274
9289
|
}
|
|
9290
|
+
/**
|
|
9291
|
+
* Raster of the current chart onto a canvas (same pixels as {@link screenshot}),
|
|
9292
|
+
* or null if the renderer has no canvas export. Silent — a host compositing
|
|
9293
|
+
* several charts skips a renderer that cannot contribute.
|
|
9294
|
+
*/
|
|
9295
|
+
screenshotCanvas() {
|
|
9296
|
+
if (typeof this.renderer.screenshotCanvas === "function") return this.renderer.screenshotCanvas();
|
|
9297
|
+
return null;
|
|
9298
|
+
}
|
|
9275
9299
|
/**
|
|
9276
9300
|
* The active renderer's full cosmetic config as a serializable, versioned JSON
|
|
9277
9301
|
* document — persist it (templates, saved settings) and feed it back to
|
|
@@ -9740,7 +9764,7 @@ var Vela = (function (exports) {
|
|
|
9740
9764
|
|
|
9741
9765
|
// src/data/MultiProviderFeed.ts
|
|
9742
9766
|
function intervalMs(timeframe) {
|
|
9743
|
-
const
|
|
9767
|
+
const map2 = {
|
|
9744
9768
|
"1m": 6e4,
|
|
9745
9769
|
"3m": 18e4,
|
|
9746
9770
|
"5m": 3e5,
|
|
@@ -9762,7 +9786,7 @@ var Vela = (function (exports) {
|
|
|
9762
9786
|
W: 6048e5,
|
|
9763
9787
|
M: 2592e6
|
|
9764
9788
|
};
|
|
9765
|
-
return
|
|
9789
|
+
return map2[timeframe] ?? 36e5;
|
|
9766
9790
|
}
|
|
9767
9791
|
var MultiProviderFeed = class {
|
|
9768
9792
|
constructor(store = sharedBarStore) {
|
|
@@ -10149,8 +10173,8 @@ var Vela = (function (exports) {
|
|
|
10149
10173
|
/** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
|
|
10150
10174
|
* display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
|
|
10151
10175
|
* platform formatting, so hints always match the host's actual bindings. */
|
|
10152
|
-
setToolShortcuts(
|
|
10153
|
-
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(
|
|
10176
|
+
setToolShortcuts(map2) {
|
|
10177
|
+
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
|
|
10154
10178
|
return this;
|
|
10155
10179
|
}
|
|
10156
10180
|
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
@@ -10615,7 +10639,7 @@ var Vela = (function (exports) {
|
|
|
10615
10639
|
}
|
|
10616
10640
|
|
|
10617
10641
|
// node_modules/@zag-js/core/dist/merge-props.mjs
|
|
10618
|
-
var clsx = (...args) => args.map((
|
|
10642
|
+
var clsx = (...args) => args.map((str4) => str4?.trim?.()).filter(Boolean).join(" ");
|
|
10619
10643
|
var CSS_REGEX = /((?:--)?(?:\w+-?)+)\s*:\s*([^;]*)/g;
|
|
10620
10644
|
var serialize = (style) => {
|
|
10621
10645
|
const res = {};
|
|
@@ -10839,12 +10863,12 @@ var Vela = (function (exports) {
|
|
|
10839
10863
|
return {
|
|
10840
10864
|
and: (...guards) => {
|
|
10841
10865
|
return function andGuard(params) {
|
|
10842
|
-
return guards.every((
|
|
10866
|
+
return guards.every((str4) => params.guard(str4));
|
|
10843
10867
|
};
|
|
10844
10868
|
},
|
|
10845
10869
|
or: (...guards) => {
|
|
10846
10870
|
return function orGuard(params) {
|
|
10847
|
-
return guards.some((
|
|
10871
|
+
return guards.some((str4) => params.guard(str4));
|
|
10848
10872
|
};
|
|
10849
10873
|
},
|
|
10850
10874
|
not: (guard) => {
|
|
@@ -11405,9 +11429,9 @@ var Vela = (function (exports) {
|
|
|
11405
11429
|
const { attributes, callback: fn } = options;
|
|
11406
11430
|
const win = node.ownerDocument.defaultView || window;
|
|
11407
11431
|
const obs = new win.MutationObserver((changes) => {
|
|
11408
|
-
for (const
|
|
11409
|
-
if (
|
|
11410
|
-
fn(
|
|
11432
|
+
for (const change2 of changes) {
|
|
11433
|
+
if (change2.type === "attributes" && change2.attributeName && attributes.includes(change2.attributeName)) {
|
|
11434
|
+
fn(change2);
|
|
11411
11435
|
}
|
|
11412
11436
|
}
|
|
11413
11437
|
});
|
|
@@ -11481,7 +11505,7 @@ var Vela = (function (exports) {
|
|
|
11481
11505
|
}
|
|
11482
11506
|
|
|
11483
11507
|
// node_modules/@zag-js/dom-query/dist/searchable.mjs
|
|
11484
|
-
var sanitize = (
|
|
11508
|
+
var sanitize = (str4) => str4.split("").map((char) => {
|
|
11485
11509
|
const code = char.charCodeAt(0);
|
|
11486
11510
|
if (code > 0 && code < 128) return char;
|
|
11487
11511
|
if (code >= 128 && code <= 255) return `/x${code.toString(16)}`.replace("/", "\\");
|
|
@@ -12181,10 +12205,10 @@ var Vela = (function (exports) {
|
|
|
12181
12205
|
fn?.(this.getParams());
|
|
12182
12206
|
}
|
|
12183
12207
|
});
|
|
12184
|
-
__publicField(this, "guard", (
|
|
12185
|
-
if (isFunction(
|
|
12186
|
-
const fn = this.machine.implementations?.guards?.[
|
|
12187
|
-
if (!fn) warn(`[zag-js] No implementation found for guard "${JSON.stringify(
|
|
12208
|
+
__publicField(this, "guard", (str4) => {
|
|
12209
|
+
if (isFunction(str4)) return str4(this.getParams());
|
|
12210
|
+
const fn = this.machine.implementations?.guards?.[str4];
|
|
12211
|
+
if (!fn) warn(`[zag-js] No implementation found for guard "${JSON.stringify(str4)}"`);
|
|
12188
12212
|
return fn?.(this.getParams());
|
|
12189
12213
|
});
|
|
12190
12214
|
__publicField(this, "effect", (keys) => {
|
|
@@ -16962,19 +16986,19 @@ var Vela = (function (exports) {
|
|
|
16962
16986
|
}
|
|
16963
16987
|
setItems(items) {
|
|
16964
16988
|
this.items = items;
|
|
16965
|
-
for (const
|
|
16989
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
16966
16990
|
this.subs.clear();
|
|
16967
16991
|
this.build();
|
|
16968
16992
|
const api = this.api;
|
|
16969
|
-
for (const
|
|
16970
|
-
api.setChild(
|
|
16971
|
-
|
|
16993
|
+
for (const sub2 of this.subs.values()) {
|
|
16994
|
+
api.setChild(sub2.handle.service);
|
|
16995
|
+
sub2.api.setParent(this.handle.service);
|
|
16972
16996
|
}
|
|
16973
16997
|
this.handle.flush();
|
|
16974
16998
|
if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
|
|
16975
16999
|
}
|
|
16976
17000
|
destroy() {
|
|
16977
|
-
for (const
|
|
17001
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
16978
17002
|
this.subs.clear();
|
|
16979
17003
|
this.handle.stop();
|
|
16980
17004
|
this.positioner.remove();
|
|
@@ -16988,9 +17012,9 @@ var Vela = (function (exports) {
|
|
|
16988
17012
|
for (const li of this.list.children) {
|
|
16989
17013
|
const id = li.dataset.veiId;
|
|
16990
17014
|
if (!id) continue;
|
|
16991
|
-
const
|
|
16992
|
-
if (
|
|
16993
|
-
spreadProps(li, api.getTriggerItemProps(
|
|
17015
|
+
const sub2 = this.subs.get(id);
|
|
17016
|
+
if (sub2) {
|
|
17017
|
+
spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
|
|
16994
17018
|
continue;
|
|
16995
17019
|
}
|
|
16996
17020
|
const item = this.byId(id);
|
|
@@ -17067,7 +17091,7 @@ var Vela = (function (exports) {
|
|
|
17067
17091
|
const arrow3 = iconEl("chevron-right", doc);
|
|
17068
17092
|
arrow3.classList.add("vela-menu-arrow");
|
|
17069
17093
|
li.appendChild(arrow3);
|
|
17070
|
-
const
|
|
17094
|
+
const sub2 = new _Surface(doc, {
|
|
17071
17095
|
host: this.host,
|
|
17072
17096
|
placement: "right-start",
|
|
17073
17097
|
onSelect: this.onSelect,
|
|
@@ -17075,8 +17099,8 @@ var Vela = (function (exports) {
|
|
|
17075
17099
|
checkmarks: this.checkmarks,
|
|
17076
17100
|
id: `${this.mid}--${item.id}`
|
|
17077
17101
|
});
|
|
17078
|
-
|
|
17079
|
-
this.subs.set(item.id,
|
|
17102
|
+
sub2.setItems(item.submenu ?? []);
|
|
17103
|
+
this.subs.set(item.id, sub2);
|
|
17080
17104
|
}
|
|
17081
17105
|
this.list.appendChild(li);
|
|
17082
17106
|
}
|
|
@@ -17141,6 +17165,12 @@ ${STATIC_DECLS}
|
|
|
17141
17165
|
.vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
|
|
17142
17166
|
/* Text ENTRY is the one exception: selection is part of editing. */
|
|
17143
17167
|
.vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
|
|
17168
|
+
/* iOS Safari zooms the page when a focused field is under 16px, and often
|
|
17169
|
+
keeps that zoom after the field blurs or a dialog closes. The shell's
|
|
17170
|
+
mobile size class is the honest gate \u2014 not a media query \u2014 so an embedded
|
|
17171
|
+
chart on a wide desktop still gets the rule when it is in the phone layout.
|
|
17172
|
+
Scoped to the kit root so a host page's own [data-layout] is never touched. */
|
|
17173
|
+
.vela-ui[data-layout='mobile'] :is(input, textarea, select) { font-size: 16px; }
|
|
17144
17174
|
.vela-icon { display: inline-flex; align-items: center; flex: none; }
|
|
17145
17175
|
.vela-icon svg { display: block; }
|
|
17146
17176
|
`;
|
|
@@ -20025,8 +20055,21 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20025
20055
|
};
|
|
20026
20056
|
const recentRow = document.createElement("div");
|
|
20027
20057
|
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
20058
|
+
const recentSwatches = document.createElement("div");
|
|
20059
|
+
recentSwatches.style.cssText = "display:contents;";
|
|
20060
|
+
const add = document.createElement("label");
|
|
20061
|
+
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;`;
|
|
20062
|
+
add.textContent = "+";
|
|
20063
|
+
const customInput = document.createElement("input");
|
|
20064
|
+
customInput.type = "color";
|
|
20065
|
+
customInput.value = curHex;
|
|
20066
|
+
customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
20067
|
+
customInput.addEventListener("input", () => previewHex(customInput.value));
|
|
20068
|
+
customInput.addEventListener("change", () => pickHex(customInput.value, true));
|
|
20069
|
+
add.appendChild(customInput);
|
|
20070
|
+
recentRow.append(recentSwatches, add);
|
|
20028
20071
|
const renderRecents = () => {
|
|
20029
|
-
|
|
20072
|
+
recentSwatches.replaceChildren();
|
|
20030
20073
|
for (const c of recents) {
|
|
20031
20074
|
const sw = document.createElement("button");
|
|
20032
20075
|
sw.type = "button";
|
|
@@ -20035,18 +20078,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20035
20078
|
e.stopPropagation();
|
|
20036
20079
|
pickHex(c);
|
|
20037
20080
|
});
|
|
20038
|
-
|
|
20081
|
+
recentSwatches.appendChild(sw);
|
|
20039
20082
|
}
|
|
20040
|
-
const add = document.createElement("label");
|
|
20041
|
-
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;`;
|
|
20042
|
-
add.textContent = "+";
|
|
20043
|
-
const input = document.createElement("input");
|
|
20044
|
-
input.type = "color";
|
|
20045
|
-
input.value = curHex;
|
|
20046
|
-
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
20047
|
-
input.addEventListener("input", () => pickHex(input.value, true));
|
|
20048
|
-
add.appendChild(input);
|
|
20049
|
-
recentRow.appendChild(add);
|
|
20050
20083
|
};
|
|
20051
20084
|
const opLabel = document.createElement("div");
|
|
20052
20085
|
opLabel.textContent = "Opacity";
|
|
@@ -20086,14 +20119,18 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20086
20119
|
function emit() {
|
|
20087
20120
|
onChange(combineColor(curHex, curAlpha));
|
|
20088
20121
|
}
|
|
20089
|
-
function
|
|
20122
|
+
function previewHex(hex) {
|
|
20090
20123
|
curHex = splitColor(hex).hex6;
|
|
20091
|
-
addRecent(curHex);
|
|
20092
20124
|
paintSelection();
|
|
20093
20125
|
paintOpacity();
|
|
20094
|
-
if (custom) renderRecents();
|
|
20095
20126
|
emit();
|
|
20096
20127
|
}
|
|
20128
|
+
function pickHex(hex, custom = false) {
|
|
20129
|
+
previewHex(hex);
|
|
20130
|
+
addRecent(curHex);
|
|
20131
|
+
customInput.value = curHex;
|
|
20132
|
+
if (custom) renderRecents();
|
|
20133
|
+
}
|
|
20097
20134
|
renderRecents();
|
|
20098
20135
|
paintSelection();
|
|
20099
20136
|
paintOpacity();
|
|
@@ -21999,8 +22036,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21999
22036
|
lg.style.top = `${top}px`;
|
|
22000
22037
|
}
|
|
22001
22038
|
/** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
|
|
22002
|
-
setCollapsedPanes(
|
|
22003
|
-
this.paneCollapse =
|
|
22039
|
+
setCollapsedPanes(map2) {
|
|
22040
|
+
this.paneCollapse = map2;
|
|
22004
22041
|
}
|
|
22005
22042
|
/**
|
|
22006
22043
|
* Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
|
|
@@ -22761,6 +22798,66 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22761
22798
|
center: (wickLeftDev + wickDev / 2) / dpr
|
|
22762
22799
|
};
|
|
22763
22800
|
}
|
|
22801
|
+
function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
|
|
22802
|
+
const out = [];
|
|
22803
|
+
let col = NaN;
|
|
22804
|
+
let intervals = [];
|
|
22805
|
+
const flush = () => {
|
|
22806
|
+
if (intervals.length === 0) return;
|
|
22807
|
+
const merged = [intervals[0]];
|
|
22808
|
+
for (let k = 1; k < intervals.length; k += 1) {
|
|
22809
|
+
const prev3 = merged[merged.length - 1];
|
|
22810
|
+
const next2 = intervals[k];
|
|
22811
|
+
if (Math.abs(yOf(prev3.hi) - yOf(next2.lo)) < 1) {
|
|
22812
|
+
prev3.hi = next2.hi;
|
|
22813
|
+
if (next2.headIdx < prev3.headIdx) {
|
|
22814
|
+
prev3.headIdx = next2.headIdx;
|
|
22815
|
+
prev3.headTime = next2.headTime;
|
|
22816
|
+
prev3.open = next2.open;
|
|
22817
|
+
}
|
|
22818
|
+
if (next2.lastIdx > prev3.lastIdx) {
|
|
22819
|
+
prev3.lastIdx = next2.lastIdx;
|
|
22820
|
+
prev3.close = next2.close;
|
|
22821
|
+
}
|
|
22822
|
+
} else {
|
|
22823
|
+
merged.push(next2);
|
|
22824
|
+
}
|
|
22825
|
+
}
|
|
22826
|
+
for (const iv of merged) out.push({ x: col, hi: iv.hi, lo: iv.lo, headTime: iv.headTime, open: iv.open, close: iv.close });
|
|
22827
|
+
intervals = [];
|
|
22828
|
+
};
|
|
22829
|
+
for (let i = i0; i <= i1; i += 1) {
|
|
22830
|
+
const b = bars[i];
|
|
22831
|
+
if (!b || b.high <= b.low) continue;
|
|
22832
|
+
const x = Math.round(xOf(i));
|
|
22833
|
+
if (x !== col) {
|
|
22834
|
+
flush();
|
|
22835
|
+
col = x;
|
|
22836
|
+
}
|
|
22837
|
+
let lo = b.low;
|
|
22838
|
+
let hi = b.high;
|
|
22839
|
+
let headIdx = i;
|
|
22840
|
+
let headTime = b.time;
|
|
22841
|
+
let open2 = b.open;
|
|
22842
|
+
for (let k = intervals.length - 1; k >= 0; k -= 1) {
|
|
22843
|
+
const iv = intervals[k];
|
|
22844
|
+
if (iv.lo > hi || iv.hi < lo) continue;
|
|
22845
|
+
if (iv.lo < lo) lo = iv.lo;
|
|
22846
|
+
if (iv.hi > hi) hi = iv.hi;
|
|
22847
|
+
if (iv.headIdx < headIdx) {
|
|
22848
|
+
headIdx = iv.headIdx;
|
|
22849
|
+
headTime = iv.headTime;
|
|
22850
|
+
open2 = iv.open;
|
|
22851
|
+
}
|
|
22852
|
+
intervals.splice(k, 1);
|
|
22853
|
+
}
|
|
22854
|
+
let insertAt = 0;
|
|
22855
|
+
while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
|
|
22856
|
+
intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open: open2, lastIdx: i, close: b.close });
|
|
22857
|
+
}
|
|
22858
|
+
flush();
|
|
22859
|
+
return out;
|
|
22860
|
+
}
|
|
22764
22861
|
|
|
22765
22862
|
// src/renderers/native/core/chartConfig.ts
|
|
22766
22863
|
var CHART_CONFIG_VERSION = 1;
|
|
@@ -22771,6 +22868,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22771
22868
|
var BASELINE_LEVEL_DEFAULT = 50;
|
|
22772
22869
|
var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
|
|
22773
22870
|
var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
|
|
22871
|
+
var EXTENDED_SHADE = POSTMARKET_SHADE;
|
|
22774
22872
|
function defaultChartStyle() {
|
|
22775
22873
|
return {
|
|
22776
22874
|
chartTypes: {},
|
|
@@ -22779,7 +22877,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22779
22877
|
gridHorz: { visible: true, color: null },
|
|
22780
22878
|
borderColor: null,
|
|
22781
22879
|
separatorColor: null,
|
|
22782
|
-
crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground:
|
|
22880
|
+
crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: CHIP_PLATE },
|
|
22783
22881
|
candle: {
|
|
22784
22882
|
bodyVisible: true,
|
|
22785
22883
|
borderVisible: false,
|
|
@@ -22802,7 +22900,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22802
22900
|
width: 2,
|
|
22803
22901
|
baselineLevel: BASELINE_LEVEL_DEFAULT
|
|
22804
22902
|
},
|
|
22805
|
-
sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
|
|
22903
|
+
sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE, extendedColor: EXTENDED_SHADE }
|
|
22806
22904
|
};
|
|
22807
22905
|
}
|
|
22808
22906
|
var LINE_STYLES = ["solid", "dashed", "dotted"];
|
|
@@ -22902,9 +23000,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22902
23000
|
if (inst.enableKey) defaults2[inst.enableKey] = false;
|
|
22903
23001
|
addRows(inst.rows);
|
|
22904
23002
|
}
|
|
22905
|
-
for (const
|
|
22906
|
-
if (
|
|
22907
|
-
addRows(
|
|
23003
|
+
for (const sub2 of section.subsections ?? []) {
|
|
23004
|
+
if (sub2.enableKey) defaults2[sub2.enableKey] = false;
|
|
23005
|
+
addRows(sub2.rows);
|
|
22908
23006
|
}
|
|
22909
23007
|
if (!section.instances) addRows(section.rows);
|
|
22910
23008
|
bag[t.id] = defaults2;
|
|
@@ -23031,7 +23129,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
23031
23129
|
},
|
|
23032
23130
|
sessions: {
|
|
23033
23131
|
premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
|
|
23034
|
-
postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
|
|
23132
|
+
postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor,
|
|
23133
|
+
extendedColor: isColor(sessions.extendedColor) ? sessions.extendedColor : base.sessions.extendedColor
|
|
23035
23134
|
},
|
|
23036
23135
|
stacking: {
|
|
23037
23136
|
candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
|
|
@@ -24016,44 +24115,19 @@ void main() {
|
|
|
24016
24115
|
}
|
|
24017
24116
|
}
|
|
24018
24117
|
/**
|
|
24019
|
-
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated):
|
|
24020
|
-
*
|
|
24021
|
-
*
|
|
24118
|
+
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
|
|
24119
|
+
* rounded pixel column collapse into high-low sticks — one per contiguous
|
|
24120
|
+
* coverage run (see {@link aggregateCandleColumns}), so a price gap inside the
|
|
24121
|
+
* column stays a void. Draw cost stays bounded by screen width, not bar count;
|
|
24122
|
+
* stick color follows its own first-open→last-close.
|
|
24022
24123
|
*/
|
|
24023
24124
|
emitCandlesAggregated(b, bars, i0, i1, coords, pane, up, down, barColors) {
|
|
24024
|
-
|
|
24025
|
-
|
|
24026
|
-
|
|
24027
|
-
|
|
24028
|
-
|
|
24029
|
-
let headTime = 0;
|
|
24030
|
-
let active = false;
|
|
24031
|
-
const flush = () => {
|
|
24032
|
-
if (!active) return;
|
|
24033
|
-
const c = parseColor(barColors.get(headTime) ?? (closeV >= openV ? up : down));
|
|
24034
|
-
const hY = coords.priceToY(hi, pane.scale, pane.bounds);
|
|
24035
|
-
const lY = coords.priceToY(lo, pane.scale, pane.bounds);
|
|
24036
|
-
b.rect(px - 0.5, hY, 1, lY - hY, c);
|
|
24037
|
-
};
|
|
24038
|
-
for (let i = i0; i <= i1; i += 1) {
|
|
24039
|
-
const bar = bars[i];
|
|
24040
|
-
if (!bar || bar.high <= bar.low) continue;
|
|
24041
|
-
const x = Math.round(coords.logicalToX(i));
|
|
24042
|
-
if (!active || x !== px) {
|
|
24043
|
-
flush();
|
|
24044
|
-
px = x;
|
|
24045
|
-
lo = bar.low;
|
|
24046
|
-
hi = bar.high;
|
|
24047
|
-
openV = bar.open;
|
|
24048
|
-
headTime = bar.time;
|
|
24049
|
-
active = true;
|
|
24050
|
-
} else {
|
|
24051
|
-
if (bar.low < lo) lo = bar.low;
|
|
24052
|
-
if (bar.high > hi) hi = bar.high;
|
|
24053
|
-
}
|
|
24054
|
-
closeV = bar.close;
|
|
24125
|
+
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
24126
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
24127
|
+
const c = parseColor(barColors.get(s.headTime) ?? (s.close >= s.open ? up : down));
|
|
24128
|
+
const hY = yOf(s.hi);
|
|
24129
|
+
b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
|
|
24055
24130
|
}
|
|
24056
|
-
flush();
|
|
24057
24131
|
}
|
|
24058
24132
|
emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
24059
24133
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
@@ -24257,11 +24331,11 @@ void main() {
|
|
|
24257
24331
|
}
|
|
24258
24332
|
}
|
|
24259
24333
|
if (!any) return EMPTY_BARCOLORS;
|
|
24260
|
-
const
|
|
24334
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
24261
24335
|
for (const m of indicators.values()) {
|
|
24262
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
24336
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
24263
24337
|
}
|
|
24264
|
-
return
|
|
24338
|
+
return map2;
|
|
24265
24339
|
}
|
|
24266
24340
|
|
|
24267
24341
|
// src/renderers/native/core/ViewportState.ts
|
|
@@ -24558,9 +24632,9 @@ void main() {
|
|
|
24558
24632
|
function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
|
|
24559
24633
|
return rightOffset + deltaX / barSpacing;
|
|
24560
24634
|
}
|
|
24561
|
-
function wheelPanDelta(deltaX, deltaY,
|
|
24635
|
+
function wheelPanDelta(deltaX, deltaY, shift4) {
|
|
24562
24636
|
if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
|
|
24563
|
-
if (
|
|
24637
|
+
if (shift4) return deltaY;
|
|
24564
24638
|
return null;
|
|
24565
24639
|
}
|
|
24566
24640
|
function effectiveSnapMode(momentaryOverride, sticky) {
|
|
@@ -25351,13 +25425,15 @@ void main() {
|
|
|
25351
25425
|
if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
|
|
25352
25426
|
return this.orderedCache;
|
|
25353
25427
|
}
|
|
25354
|
-
/** The session zones resolved into colored bands (pre/post-market
|
|
25355
|
-
* config's session colors) — consumed by the same painting path as
|
|
25428
|
+
/** The session zones resolved into colored bands (pre/post-market or extended-hours
|
|
25429
|
+
* washes from the config's session colors) — consumed by the same painting path as
|
|
25430
|
+
* {@link highlights}. */
|
|
25356
25431
|
sessionHighlightBands() {
|
|
25357
25432
|
if (!this.sessionZones) return [];
|
|
25358
25433
|
const out = [];
|
|
25359
25434
|
for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
|
|
25360
25435
|
for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
|
|
25436
|
+
for (const [from, to] of this.sessionZones.extended) out.push({ from, to, color: this.style.sessions.extendedColor });
|
|
25361
25437
|
return out;
|
|
25362
25438
|
}
|
|
25363
25439
|
indicatorsForPane(paneId) {
|
|
@@ -25896,50 +25972,23 @@ void main() {
|
|
|
25896
25972
|
}
|
|
25897
25973
|
}
|
|
25898
25974
|
/**
|
|
25899
|
-
* Sub-pixel LOD:
|
|
25900
|
-
*
|
|
25901
|
-
*
|
|
25902
|
-
*
|
|
25975
|
+
* Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
|
|
25976
|
+
* sticks (one per contiguous coverage run — see {@link aggregateCandleColumns}),
|
|
25977
|
+
* so draw cost is bounded by screen width (not bar count) when zoomed far out
|
|
25978
|
+
* and a price gap inside the column stays a void. Each stick's color follows
|
|
25979
|
+
* its first-open→last-close direction (a barcolor() on the head bar still wins).
|
|
25903
25980
|
*/
|
|
25904
25981
|
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, up, down, barColors) {
|
|
25905
|
-
|
|
25906
|
-
|
|
25907
|
-
|
|
25908
|
-
|
|
25909
|
-
|
|
25910
|
-
let headTime = 0;
|
|
25911
|
-
let active = false;
|
|
25912
|
-
const flush = () => {
|
|
25913
|
-
if (!active) return;
|
|
25914
|
-
const color = barColors.get(headTime) ?? (closeV >= openV ? up : down);
|
|
25915
|
-
const hY = coords.priceToY(hi, pane.scale, pane.bounds);
|
|
25916
|
-
const lY = coords.priceToY(lo, pane.scale, pane.bounds);
|
|
25917
|
-
ctx.strokeStyle = color;
|
|
25918
|
-
ctx.lineWidth = 1;
|
|
25982
|
+
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
25983
|
+
ctx.lineWidth = 1;
|
|
25984
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
25985
|
+
const x = s.x + 0.5;
|
|
25986
|
+
ctx.strokeStyle = barColors.get(s.headTime) ?? (s.close >= s.open ? up : down);
|
|
25919
25987
|
ctx.beginPath();
|
|
25920
|
-
ctx.moveTo(
|
|
25921
|
-
ctx.lineTo(
|
|
25988
|
+
ctx.moveTo(x, yOf(s.hi));
|
|
25989
|
+
ctx.lineTo(x, yOf(s.lo));
|
|
25922
25990
|
ctx.stroke();
|
|
25923
|
-
};
|
|
25924
|
-
for (let i = i0; i <= i1; i += 1) {
|
|
25925
|
-
const b = bars[i];
|
|
25926
|
-
if (!b || b.high <= b.low) continue;
|
|
25927
|
-
const x = Math.round(coords.logicalToX(i)) + 0.5;
|
|
25928
|
-
if (!active || x !== px) {
|
|
25929
|
-
flush();
|
|
25930
|
-
px = x;
|
|
25931
|
-
lo = b.low;
|
|
25932
|
-
hi = b.high;
|
|
25933
|
-
openV = b.open;
|
|
25934
|
-
headTime = b.time;
|
|
25935
|
-
active = true;
|
|
25936
|
-
} else {
|
|
25937
|
-
if (b.low < lo) lo = b.low;
|
|
25938
|
-
if (b.high > hi) hi = b.high;
|
|
25939
|
-
}
|
|
25940
|
-
closeV = b.close;
|
|
25941
25991
|
}
|
|
25942
|
-
flush();
|
|
25943
25992
|
}
|
|
25944
25993
|
drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
25945
25994
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
@@ -26207,11 +26256,11 @@ void main() {
|
|
|
26207
26256
|
}
|
|
26208
26257
|
}
|
|
26209
26258
|
if (!any) return EMPTY_BARCOLORS2;
|
|
26210
|
-
const
|
|
26259
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
26211
26260
|
for (const m of indicators.values()) {
|
|
26212
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
26261
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
26213
26262
|
}
|
|
26214
|
-
return
|
|
26263
|
+
return map2;
|
|
26215
26264
|
}
|
|
26216
26265
|
|
|
26217
26266
|
// src/renderers/shared/drawing-geometry.ts
|
|
@@ -26963,6 +27012,32 @@ void main() {
|
|
|
26963
27012
|
}
|
|
26964
27013
|
};
|
|
26965
27014
|
|
|
27015
|
+
// src/renderers/native/chrome/tz.ts
|
|
27016
|
+
function tzOffsetMs(ms, timeZone) {
|
|
27017
|
+
if (!timeZone || timeZone === "UTC") return 0;
|
|
27018
|
+
try {
|
|
27019
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
27020
|
+
timeZone,
|
|
27021
|
+
hourCycle: "h23",
|
|
27022
|
+
year: "numeric",
|
|
27023
|
+
month: "2-digit",
|
|
27024
|
+
day: "2-digit",
|
|
27025
|
+
hour: "2-digit",
|
|
27026
|
+
minute: "2-digit",
|
|
27027
|
+
second: "2-digit"
|
|
27028
|
+
});
|
|
27029
|
+
const parts3 = dtf.formatToParts(new Date(ms));
|
|
27030
|
+
const get = (t) => Number(parts3.find((p) => p.type === t)?.value);
|
|
27031
|
+
const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
|
|
27032
|
+
return asUTC - ms;
|
|
27033
|
+
} catch {
|
|
27034
|
+
return 0;
|
|
27035
|
+
}
|
|
27036
|
+
}
|
|
27037
|
+
function zonedDate(ms, timeZone) {
|
|
27038
|
+
return new Date(ms + tzOffsetMs(ms, timeZone));
|
|
27039
|
+
}
|
|
27040
|
+
|
|
26966
27041
|
// src/renderers/native/chrome/ticks.ts
|
|
26967
27042
|
function priceTicks(min2, max2, target = 6) {
|
|
26968
27043
|
if (!(max2 > min2) || !Number.isFinite(min2) || !Number.isFinite(max2)) return [];
|
|
@@ -27110,7 +27185,14 @@ void main() {
|
|
|
27110
27185
|
YEAR
|
|
27111
27186
|
];
|
|
27112
27187
|
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
27188
|
+
var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
27113
27189
|
var pad2 = (n) => n < 10 ? `0${n}` : String(n);
|
|
27190
|
+
function formatTimeStamp(ms, timeZone, barIntervalMs) {
|
|
27191
|
+
const d = zonedDate(ms, timeZone);
|
|
27192
|
+
const date = `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} '${pad2(d.getUTCFullYear() % 100)}`;
|
|
27193
|
+
if (barIntervalMs >= DAY) return date;
|
|
27194
|
+
return `${date} ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}`;
|
|
27195
|
+
}
|
|
27114
27196
|
function pickStep(targetMs) {
|
|
27115
27197
|
for (const step of STEP_LADDER) if (step >= targetMs) return step;
|
|
27116
27198
|
return STEP_LADDER[STEP_LADDER.length - 1];
|
|
@@ -27152,32 +27234,6 @@ void main() {
|
|
|
27152
27234
|
return out;
|
|
27153
27235
|
}
|
|
27154
27236
|
|
|
27155
|
-
// src/renderers/native/chrome/tz.ts
|
|
27156
|
-
function tzOffsetMs(ms, timeZone) {
|
|
27157
|
-
if (!timeZone || timeZone === "UTC") return 0;
|
|
27158
|
-
try {
|
|
27159
|
-
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
27160
|
-
timeZone,
|
|
27161
|
-
hourCycle: "h23",
|
|
27162
|
-
year: "numeric",
|
|
27163
|
-
month: "2-digit",
|
|
27164
|
-
day: "2-digit",
|
|
27165
|
-
hour: "2-digit",
|
|
27166
|
-
minute: "2-digit",
|
|
27167
|
-
second: "2-digit"
|
|
27168
|
-
});
|
|
27169
|
-
const parts3 = dtf.formatToParts(new Date(ms));
|
|
27170
|
-
const get = (t) => Number(parts3.find((p) => p.type === t)?.value);
|
|
27171
|
-
const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
|
|
27172
|
-
return asUTC - ms;
|
|
27173
|
-
} catch {
|
|
27174
|
-
return 0;
|
|
27175
|
-
}
|
|
27176
|
-
}
|
|
27177
|
-
function zonedDate(ms, timeZone) {
|
|
27178
|
-
return new Date(ms + tzOffsetMs(ms, timeZone));
|
|
27179
|
-
}
|
|
27180
|
-
|
|
27181
27237
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
27182
27238
|
var ChromeRenderer = class {
|
|
27183
27239
|
constructor() {
|
|
@@ -27624,7 +27680,7 @@ void main() {
|
|
|
27624
27680
|
const price = coords.yToPrice(ch.y, pane.scale, pane.bounds);
|
|
27625
27681
|
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);
|
|
27626
27682
|
}
|
|
27627
|
-
this.chip(ctx, x, dataH + 1,
|
|
27683
|
+
this.chip(ctx, x, dataH + 1, formatTimeStamp(coords.logicalToTime(logical), scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
27628
27684
|
}
|
|
27629
27685
|
destroy() {
|
|
27630
27686
|
this.canvas = null;
|
|
@@ -27672,7 +27728,7 @@ void main() {
|
|
|
27672
27728
|
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);
|
|
27673
27729
|
}
|
|
27674
27730
|
}
|
|
27675
|
-
this.chip(ctx, x, dataH + 1,
|
|
27731
|
+
this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
27676
27732
|
ctx.globalAlpha = 1;
|
|
27677
27733
|
}
|
|
27678
27734
|
/** A soft band + a brighter crisp center line over the hovered separator, so it reads as
|
|
@@ -27705,11 +27761,6 @@ void main() {
|
|
|
27705
27761
|
else if (style === "dotted") ctx.setLineDash([2, 3]);
|
|
27706
27762
|
else ctx.setLineDash([]);
|
|
27707
27763
|
}
|
|
27708
|
-
function formatStamp(ms, timeZone) {
|
|
27709
|
-
const d = zonedDate(ms, timeZone);
|
|
27710
|
-
const pad = (n) => n < 10 ? `0${n}` : String(n);
|
|
27711
|
-
return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
27712
|
-
}
|
|
27713
27764
|
|
|
27714
27765
|
// src/core/timezones.ts
|
|
27715
27766
|
var TIMEZONES = [
|
|
@@ -27925,9 +27976,9 @@ void main() {
|
|
|
27925
27976
|
};
|
|
27926
27977
|
if (section.rows) addRows(section.rows);
|
|
27927
27978
|
for (const inst of section.instances ?? []) addRows(inst.rows);
|
|
27928
|
-
for (const
|
|
27929
|
-
ids.add(`${scope}.${settingsIdSlug(
|
|
27930
|
-
addRows(
|
|
27979
|
+
for (const sub2 of section.subsections ?? []) {
|
|
27980
|
+
ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
|
|
27981
|
+
addRows(sub2.rows);
|
|
27931
27982
|
}
|
|
27932
27983
|
}
|
|
27933
27984
|
for (const hs of hostSections) {
|
|
@@ -28486,14 +28537,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
28486
28537
|
};
|
|
28487
28538
|
if (section.rows) seed(section.rows);
|
|
28488
28539
|
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
28489
|
-
for (const
|
|
28540
|
+
for (const sub2 of section.subsections ?? []) seed(sub2.rows);
|
|
28490
28541
|
for (const inst of section.instances ?? []) {
|
|
28491
28542
|
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
28492
28543
|
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
28493
28544
|
}
|
|
28494
|
-
for (const
|
|
28495
|
-
if (!
|
|
28496
|
-
bag[
|
|
28545
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28546
|
+
if (!sub2.enableKey || sub2.enableKey in bag) continue;
|
|
28547
|
+
bag[sub2.enableKey] = values[sub2.enableKey] === true;
|
|
28497
28548
|
}
|
|
28498
28549
|
const refreshers = [];
|
|
28499
28550
|
const put = (key, v) => {
|
|
@@ -28510,16 +28561,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
28510
28561
|
if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
|
|
28511
28562
|
else this.flatTypeRows(rows, bag, put, refreshers, body);
|
|
28512
28563
|
}
|
|
28513
|
-
for (const
|
|
28514
|
-
if (settingsIdHidden(`${scope}.${settingsIdSlug(
|
|
28515
|
-
const rows = filterHiddenRows(
|
|
28564
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28565
|
+
if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
|
|
28566
|
+
const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
|
|
28516
28567
|
if (rows.length === 0) continue;
|
|
28517
|
-
const subMarker = this.section(
|
|
28568
|
+
const subMarker = this.section(sub2.title);
|
|
28518
28569
|
subMarker.dataset.sdStyle = typeId;
|
|
28519
28570
|
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
28520
28571
|
subMarker.dataset.sdSub = "1";
|
|
28521
28572
|
body.append(subMarker);
|
|
28522
|
-
body.append(this.groupedRows(`${typeId}/${
|
|
28573
|
+
body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
|
|
28523
28574
|
}
|
|
28524
28575
|
for (const r of refreshers) r();
|
|
28525
28576
|
}
|
|
@@ -30627,11 +30678,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30627
30678
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
30628
30679
|
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
30629
30680
|
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
30630
|
-
down(x, y, mode = "off",
|
|
30681
|
+
down(x, y, mode = "off", shift4 = false) {
|
|
30631
30682
|
const proj = this.deps.projector();
|
|
30632
30683
|
if (this.state.kind === "placing") {
|
|
30633
30684
|
if (this.state.draft.placementMode() !== "click") return;
|
|
30634
|
-
const pivot =
|
|
30685
|
+
const pivot = shift4 ? this.placingPivot(this.state.draft) : null;
|
|
30635
30686
|
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
30636
30687
|
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
30637
30688
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
@@ -30650,7 +30701,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30650
30701
|
}
|
|
30651
30702
|
const hit = this.hitAt(x, y);
|
|
30652
30703
|
if (hit) {
|
|
30653
|
-
if (
|
|
30704
|
+
if (shift4) {
|
|
30654
30705
|
this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
|
|
30655
30706
|
return;
|
|
30656
30707
|
}
|
|
@@ -30660,7 +30711,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30660
30711
|
}
|
|
30661
30712
|
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
30662
30713
|
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
30663
|
-
move(x, y, mode = "off",
|
|
30714
|
+
move(x, y, mode = "off", shift4 = false) {
|
|
30664
30715
|
if (this.state.kind === "idle") {
|
|
30665
30716
|
const tool = this.deps.activeTool();
|
|
30666
30717
|
const had = this.snapAt != null;
|
|
@@ -30674,7 +30725,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30674
30725
|
this.captureFreehand(x, y);
|
|
30675
30726
|
return;
|
|
30676
30727
|
}
|
|
30677
|
-
const pivot =
|
|
30728
|
+
const pivot = shift4 ? this.placingPivot(this.state.draft) : null;
|
|
30678
30729
|
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
30679
30730
|
this.deps.changed();
|
|
30680
30731
|
return;
|
|
@@ -30687,7 +30738,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30687
30738
|
if (this.state.handle >= 0) {
|
|
30688
30739
|
const a = d.anchors[this.state.handle];
|
|
30689
30740
|
if (a) {
|
|
30690
|
-
const pivot =
|
|
30741
|
+
const pivot = shift4 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
30691
30742
|
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
30692
30743
|
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
30693
30744
|
}
|
|
@@ -31385,21 +31436,21 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
31385
31436
|
bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
|
|
31386
31437
|
bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
|
|
31387
31438
|
}
|
|
31388
|
-
const
|
|
31389
|
-
if (paths.has("vwap.midColor") &&
|
|
31439
|
+
const vwap2 = drawing.vwap;
|
|
31440
|
+
if (paths.has("vwap.midColor") && vwap2) {
|
|
31390
31441
|
const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
|
|
31391
31442
|
const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
|
|
31392
|
-
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON,
|
|
31393
|
-
bar.appendChild(this.dropdown("VWAP style", styles,
|
|
31443
|
+
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
|
|
31444
|
+
bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
|
|
31394
31445
|
bar.appendChild(
|
|
31395
|
-
this.dropdown("Band multiplier", MULTS,
|
|
31446
|
+
this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
|
|
31396
31447
|
label: (v) => `${v}\u03C3`,
|
|
31397
31448
|
labelInTrigger: true
|
|
31398
31449
|
})
|
|
31399
31450
|
);
|
|
31400
|
-
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON,
|
|
31401
|
-
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON,
|
|
31402
|
-
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON,
|
|
31451
|
+
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
|
|
31452
|
+
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
|
|
31453
|
+
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
|
|
31403
31454
|
}
|
|
31404
31455
|
if (paths.has("maxCurvature")) {
|
|
31405
31456
|
const cur = drawing.maxCurvature ?? 24;
|
|
@@ -32145,8 +32196,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32145
32196
|
}
|
|
32146
32197
|
/** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
|
|
32147
32198
|
* right edge of the flyout rows, beside the favorite star. */
|
|
32148
|
-
setShortcuts(
|
|
32149
|
-
this.shortcuts = new Map(Object.entries(
|
|
32199
|
+
setShortcuts(map2) {
|
|
32200
|
+
this.shortcuts = new Map(Object.entries(map2));
|
|
32150
32201
|
}
|
|
32151
32202
|
/** Paint one star for the current favorite state (filled + gold, or outline). */
|
|
32152
32203
|
paintStar(el, type) {
|
|
@@ -33048,8 +33099,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33048
33099
|
this.toolbar.setFavorites(types);
|
|
33049
33100
|
}
|
|
33050
33101
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
33051
|
-
setToolShortcuts(
|
|
33052
|
-
this.toolbar.setShortcuts(
|
|
33102
|
+
setToolShortcuts(map2) {
|
|
33103
|
+
this.toolbar.setShortcuts(map2);
|
|
33053
33104
|
}
|
|
33054
33105
|
/** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
|
|
33055
33106
|
* in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
|
|
@@ -33167,7 +33218,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33167
33218
|
this.render();
|
|
33168
33219
|
}
|
|
33169
33220
|
}
|
|
33170
|
-
pointerDown(x, y, snap = "off",
|
|
33221
|
+
pointerDown(x, y, snap = "off", shift4 = false) {
|
|
33171
33222
|
if (this.eraserMode) {
|
|
33172
33223
|
this.erasing = true;
|
|
33173
33224
|
this.deleteAt(x, y);
|
|
@@ -33187,9 +33238,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33187
33238
|
return;
|
|
33188
33239
|
}
|
|
33189
33240
|
}
|
|
33190
|
-
this.interaction.down(x, y, snap,
|
|
33241
|
+
this.interaction.down(x, y, snap, shift4);
|
|
33191
33242
|
}
|
|
33192
|
-
pointerMove(x, y, snap = "off",
|
|
33243
|
+
pointerMove(x, y, snap = "off", shift4 = false) {
|
|
33193
33244
|
if (this.eraserMode) {
|
|
33194
33245
|
if (this.erasing) this.deleteAt(x, y);
|
|
33195
33246
|
return;
|
|
@@ -33200,7 +33251,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33200
33251
|
this.render();
|
|
33201
33252
|
return;
|
|
33202
33253
|
}
|
|
33203
|
-
this.interaction.move(x, y, snap,
|
|
33254
|
+
this.interaction.move(x, y, snap, shift4);
|
|
33204
33255
|
this.updateHover(x, y);
|
|
33205
33256
|
}
|
|
33206
33257
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -33877,14 +33928,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33877
33928
|
}
|
|
33878
33929
|
for (const { box, w: w2, h: h2 } of spanning) {
|
|
33879
33930
|
if (box.cs > 1) {
|
|
33880
|
-
let
|
|
33881
|
-
for (let c = box.c; c < box.c + box.cs; c += 1)
|
|
33882
|
-
if (w2 >
|
|
33931
|
+
let sum2 = 0;
|
|
33932
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
|
|
33933
|
+
if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
|
|
33883
33934
|
}
|
|
33884
33935
|
if (box.rs > 1) {
|
|
33885
|
-
let
|
|
33886
|
-
for (let r = box.r; r < box.r + box.rs; r += 1)
|
|
33887
|
-
if (h2 >
|
|
33936
|
+
let sum2 = 0;
|
|
33937
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
|
|
33938
|
+
if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
|
|
33888
33939
|
}
|
|
33889
33940
|
}
|
|
33890
33941
|
let w = 0;
|
|
@@ -34198,6 +34249,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34198
34249
|
const end = Math.min(right, x2);
|
|
34199
34250
|
return end > x ? { x, width: end - x } : null;
|
|
34200
34251
|
}
|
|
34252
|
+
function bandEdgeSlot(logical) {
|
|
34253
|
+
return Math.ceil(logical) - 0.5;
|
|
34254
|
+
}
|
|
34201
34255
|
var BackdropRenderer = class {
|
|
34202
34256
|
constructor() {
|
|
34203
34257
|
this.canvas = null;
|
|
@@ -34228,21 +34282,22 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34228
34282
|
this.drawGrid(ctx, scene, coords, theme, coords.width, gridAlpha);
|
|
34229
34283
|
}
|
|
34230
34284
|
/** Renderer-owned session highlight bands: full-height (all panes), behind the grid.
|
|
34231
|
-
* Session-zone washes
|
|
34285
|
+
* Session-zone washes paint first (edges snapped to bar-slot boundaries so a band
|
|
34286
|
+
* never cuts a candle in two), host highlights on top (continuous time mapping —
|
|
34287
|
+
* hosts may paint sub-bar spans). */
|
|
34232
34288
|
drawHighlights(ctx, scene, coords) {
|
|
34233
34289
|
const sessions = scene.sessionHighlightBands();
|
|
34234
34290
|
if (sessions.length > 0) {
|
|
34235
34291
|
const left = Math.max(0, coords.logicalToX(-0.5));
|
|
34236
34292
|
const right = Math.min(coords.width, coords.logicalToX(coords.barCount - 0.5));
|
|
34237
|
-
this.drawHighlightSet(ctx, sessions, coords, left, right);
|
|
34293
|
+
this.drawHighlightSet(ctx, sessions, coords, left, right, true);
|
|
34238
34294
|
}
|
|
34239
|
-
this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width);
|
|
34295
|
+
this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width, false);
|
|
34240
34296
|
}
|
|
34241
|
-
drawHighlightSet(ctx, bands, coords, left, right) {
|
|
34297
|
+
drawHighlightSet(ctx, bands, coords, left, right, snapToSlots) {
|
|
34298
|
+
const edgeX = (ms) => snapToSlots ? coords.logicalToX(bandEdgeSlot(coords.timeToLogical(ms))) : coords.timeToX(ms);
|
|
34242
34299
|
for (const band of bands) {
|
|
34243
|
-
const
|
|
34244
|
-
const x2 = coords.timeToX(band.to);
|
|
34245
|
-
const rect = clipHighlightRect(x1, x2, left, right);
|
|
34300
|
+
const rect = clipHighlightRect(edgeX(band.from), edgeX(band.to), left, right);
|
|
34246
34301
|
if (!rect) continue;
|
|
34247
34302
|
ctx.fillStyle = band.color;
|
|
34248
34303
|
ctx.fillRect(rect.x, 0, rect.width, coords.height);
|
|
@@ -34501,6 +34556,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34501
34556
|
});
|
|
34502
34557
|
el.style.color = attributionMarkColor(background);
|
|
34503
34558
|
el.innerHTML = html;
|
|
34559
|
+
el.dataset.velaScreenshot = "1";
|
|
34504
34560
|
return el;
|
|
34505
34561
|
}
|
|
34506
34562
|
function createAttributionMark(doc, background) {
|
|
@@ -34519,6 +34575,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34519
34575
|
cursor: "pointer"
|
|
34520
34576
|
});
|
|
34521
34577
|
applyAttributionMarkTheme(a, background);
|
|
34578
|
+
a.dataset.velaScreenshot = "1";
|
|
34522
34579
|
const symbol = doc.createElement("span");
|
|
34523
34580
|
symbol.className = "vela-attr-symbol";
|
|
34524
34581
|
symbol.setAttribute("aria-hidden", "true");
|
|
@@ -34601,24 +34658,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34601
34658
|
drawSvgShapes(el, alpha);
|
|
34602
34659
|
continue;
|
|
34603
34660
|
}
|
|
34604
|
-
const
|
|
34661
|
+
const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
|
|
34605
34662
|
let path = null;
|
|
34606
34663
|
if (tag === "path") {
|
|
34607
34664
|
const d = el.getAttribute("d");
|
|
34608
34665
|
if (d) path = new Path2D(d);
|
|
34609
34666
|
} else if (tag === "circle") {
|
|
34610
34667
|
path = new Path2D();
|
|
34611
|
-
path.arc(
|
|
34668
|
+
path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
|
|
34612
34669
|
} else if (tag === "ellipse") {
|
|
34613
34670
|
path = new Path2D();
|
|
34614
|
-
path.ellipse(
|
|
34671
|
+
path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
|
|
34615
34672
|
} else if (tag === "rect") {
|
|
34616
34673
|
path = new Path2D();
|
|
34617
|
-
path.rect(
|
|
34674
|
+
path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
|
|
34618
34675
|
} else if (tag === "line") {
|
|
34619
34676
|
path = new Path2D();
|
|
34620
|
-
path.moveTo(
|
|
34621
|
-
path.lineTo(
|
|
34677
|
+
path.moveTo(num4("x1"), num4("y1"));
|
|
34678
|
+
path.lineTo(num4("x2"), num4("y2"));
|
|
34622
34679
|
} else if (tag === "polyline" || tag === "polygon") {
|
|
34623
34680
|
const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
|
|
34624
34681
|
if (pts.length < 4 || pts.some(Number.isNaN)) continue;
|
|
@@ -35523,7 +35580,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35523
35580
|
};
|
|
35524
35581
|
})(),
|
|
35525
35582
|
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
|
|
35526
|
-
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
|
|
35583
|
+
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor, extendedColor: s.sessions.extendedColor }
|
|
35527
35584
|
};
|
|
35528
35585
|
}
|
|
35529
35586
|
/**
|
|
@@ -35605,7 +35662,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35605
35662
|
width: next2.baseline.width,
|
|
35606
35663
|
baselineLevel: next2.baseline.baselineLevel
|
|
35607
35664
|
};
|
|
35608
|
-
s.sessions = { premarketColor: next2.sessions.premarketColor, postmarketColor: next2.sessions.postmarketColor };
|
|
35665
|
+
s.sessions = { premarketColor: next2.sessions.premarketColor, postmarketColor: next2.sessions.postmarketColor, extendedColor: next2.sessions.extendedColor };
|
|
35609
35666
|
this.setPriceStyle(next2.series.style);
|
|
35610
35667
|
this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
|
|
35611
35668
|
this.scene.baselineValue = next2.series.baseline;
|
|
@@ -35827,9 +35884,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35827
35884
|
* opts in with a `data-vela-screenshot` attribute on the mount container's
|
|
35828
35885
|
* subtree (the widget marks its status line, and its symbol watermark with
|
|
35829
35886
|
* `"under"` — drawn beneath the canvases, where it sits on screen). Only the
|
|
35830
|
-
* crosshair (L2) is intentionally excluded.
|
|
35887
|
+
* crosshair (L2) is intentionally excluded. {@link screenshot} is this canvas
|
|
35888
|
+
* as a PNG data URL.
|
|
35831
35889
|
*/
|
|
35832
|
-
|
|
35890
|
+
screenshotCanvas() {
|
|
35833
35891
|
if (!this.dataCanvas) return null;
|
|
35834
35892
|
this.computeScales();
|
|
35835
35893
|
this.paintData();
|
|
@@ -35857,7 +35915,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35857
35915
|
if (el.getAttribute("data-vela-screenshot") !== "under") rasterizeOverlay(ctx, el, frame);
|
|
35858
35916
|
}
|
|
35859
35917
|
}
|
|
35860
|
-
return out
|
|
35918
|
+
return out;
|
|
35919
|
+
}
|
|
35920
|
+
screenshot() {
|
|
35921
|
+
return this.screenshotCanvas()?.toDataURL("image/png") ?? null;
|
|
35861
35922
|
}
|
|
35862
35923
|
/**
|
|
35863
35924
|
* The candles-first-appear reveal — animates the **candles themselves** (no extra
|
|
@@ -36031,8 +36092,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36031
36092
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
36032
36093
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
36033
36094
|
drawingsSnapMode: () => this.snapMode,
|
|
36034
|
-
drawingsPointerDown: (x, y, snap,
|
|
36035
|
-
drawingsPointerMove: (x, y, snap,
|
|
36095
|
+
drawingsPointerDown: (x, y, snap, shift4) => this.userDrawings?.pointerDown(x, y, snap, shift4),
|
|
36096
|
+
drawingsPointerMove: (x, y, snap, shift4) => this.userDrawings?.pointerMove(x, y, snap, shift4),
|
|
36036
36097
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
36037
36098
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
36038
36099
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -36349,18 +36410,18 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36349
36410
|
for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
|
|
36350
36411
|
if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
|
|
36351
36412
|
const newHeadTime = this.bars[0].time;
|
|
36352
|
-
let
|
|
36413
|
+
let shift4 = 0;
|
|
36353
36414
|
if (newHeadTime !== prevHeadTime) {
|
|
36354
36415
|
const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
|
|
36355
|
-
if (prepended > 0)
|
|
36416
|
+
if (prepended > 0) shift4 = prepended;
|
|
36356
36417
|
else {
|
|
36357
36418
|
const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
|
|
36358
|
-
if (trimmed > 0)
|
|
36419
|
+
if (trimmed > 0) shift4 = -trimmed;
|
|
36359
36420
|
}
|
|
36360
36421
|
}
|
|
36361
|
-
if (
|
|
36362
|
-
this.zoomAnchorLogical +=
|
|
36363
|
-
if (this.hoverLogical != null) this.hoverLogical +=
|
|
36422
|
+
if (shift4 !== 0) {
|
|
36423
|
+
this.zoomAnchorLogical += shift4;
|
|
36424
|
+
if (this.hoverLogical != null) this.hoverLogical += shift4;
|
|
36364
36425
|
}
|
|
36365
36426
|
}
|
|
36366
36427
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
@@ -37252,7 +37313,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
37252
37313
|
const pricePane = this.dataWindowPricePane();
|
|
37253
37314
|
const ohlc = this.dataWindowOHLC(bar, pricePane);
|
|
37254
37315
|
const parts3 = [];
|
|
37255
|
-
if (bar) parts3.push(
|
|
37316
|
+
if (bar) parts3.push(formatStamp(bar.time, this.scene.timezone));
|
|
37256
37317
|
if (ohlc) parts3.push(`Open ${ohlc.o}, High ${ohlc.h}, Low ${ohlc.l}, Close ${ohlc.c}`);
|
|
37257
37318
|
for (const group of this.dataWindowGroups(idx, pricePane)) {
|
|
37258
37319
|
for (const row of group.rows) parts3.push(`${row.label} ${row.value}`);
|
|
@@ -37966,15 +38027,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
37966
38027
|
/** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
|
|
37967
38028
|
* strip — merged own-scale indicators are hidden while collapsed. */
|
|
37968
38029
|
collapsedMasterMap() {
|
|
37969
|
-
const
|
|
38030
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
37970
38031
|
for (const pane of this.scene.panes.values()) {
|
|
37971
38032
|
if (!pane.collapsed) continue;
|
|
37972
38033
|
const models = this.scene.orderedIndicatorsForPane(pane.id);
|
|
37973
38034
|
const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
|
|
37974
38035
|
const master = models.find((m) => !merged.has(m.id)) ?? models[0];
|
|
37975
|
-
|
|
38036
|
+
map2.set(pane.id, master?.id ?? null);
|
|
37976
38037
|
}
|
|
37977
|
-
return
|
|
38038
|
+
return map2;
|
|
37978
38039
|
}
|
|
37979
38040
|
/** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
|
|
37980
38041
|
setToolbarGutter(px) {
|
|
@@ -38139,7 +38200,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38139
38200
|
time: `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`
|
|
38140
38201
|
};
|
|
38141
38202
|
}
|
|
38142
|
-
function
|
|
38203
|
+
function formatStamp(ms, timeZone) {
|
|
38143
38204
|
const { date, time } = formatStampParts(ms, timeZone);
|
|
38144
38205
|
return `${date} ${time}`;
|
|
38145
38206
|
}
|
|
@@ -38170,7 +38231,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38170
38231
|
}
|
|
38171
38232
|
return out.sort((a, b) => a[0] - b[0]);
|
|
38172
38233
|
};
|
|
38173
|
-
return { pre: windows(v.pre), post: windows(v.post) };
|
|
38234
|
+
return { pre: windows(v.pre), post: windows(v.post), extended: windows(v.extended) };
|
|
38174
38235
|
}
|
|
38175
38236
|
function clamp012(v) {
|
|
38176
38237
|
return Math.max(0, Math.min(1, v));
|
|
@@ -38370,12 +38431,2328 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38370
38431
|
registerNativeIndicator(vpvrDescriptor);
|
|
38371
38432
|
}
|
|
38372
38433
|
|
|
38434
|
+
// src/core/model/identity.ts
|
|
38435
|
+
function stableSeriesId(parts3) {
|
|
38436
|
+
const normTitle = parts3.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
38437
|
+
return `${parts3.instanceId}:${parts3.kind}:${normTitle}#${parts3.ordinal}`;
|
|
38438
|
+
}
|
|
38439
|
+
|
|
38440
|
+
// src/core/native-indicators/classics/define.ts
|
|
38441
|
+
function num3(inputs, key, fallback) {
|
|
38442
|
+
const v = inputs[key];
|
|
38443
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
38444
|
+
}
|
|
38445
|
+
function str3(inputs, key, fallback) {
|
|
38446
|
+
const v = inputs[key];
|
|
38447
|
+
return typeof v === "string" && v !== "" ? v : fallback;
|
|
38448
|
+
}
|
|
38449
|
+
function toPoints(bars, values, colors) {
|
|
38450
|
+
const pts = new Array(bars.length);
|
|
38451
|
+
for (let i = 0; i < bars.length; i++) {
|
|
38452
|
+
const v = values[i];
|
|
38453
|
+
const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
|
|
38454
|
+
const c = colors?.[i];
|
|
38455
|
+
if (c != null) p.color = c;
|
|
38456
|
+
pts[i] = p;
|
|
38457
|
+
}
|
|
38458
|
+
return pts;
|
|
38459
|
+
}
|
|
38460
|
+
var ClassicIndicator = class {
|
|
38461
|
+
constructor(spec) {
|
|
38462
|
+
this.spec = spec;
|
|
38463
|
+
this.inputs = {};
|
|
38464
|
+
}
|
|
38465
|
+
start(ctx, inputs) {
|
|
38466
|
+
this.ctx = ctx;
|
|
38467
|
+
this.inputs = inputs;
|
|
38468
|
+
this.recompute();
|
|
38469
|
+
}
|
|
38470
|
+
onBars() {
|
|
38471
|
+
this.recompute();
|
|
38472
|
+
}
|
|
38473
|
+
onViewport() {
|
|
38474
|
+
}
|
|
38475
|
+
setInputs(inputs) {
|
|
38476
|
+
this.inputs = inputs;
|
|
38477
|
+
this.recompute();
|
|
38478
|
+
}
|
|
38479
|
+
/** Nothing to free — the compute reads the live bar accessor on demand. */
|
|
38480
|
+
suspend() {
|
|
38481
|
+
}
|
|
38482
|
+
resume() {
|
|
38483
|
+
this.recompute();
|
|
38484
|
+
}
|
|
38485
|
+
stop() {
|
|
38486
|
+
}
|
|
38487
|
+
recompute() {
|
|
38488
|
+
const bars = this.ctx.bars();
|
|
38489
|
+
const out = this.spec.compute(bars, this.inputs);
|
|
38490
|
+
const instanceId = this.spec.type;
|
|
38491
|
+
const series = [];
|
|
38492
|
+
const idsByKey = /* @__PURE__ */ new Map();
|
|
38493
|
+
out.plots.forEach((plot, ordinal) => {
|
|
38494
|
+
const kind = plot.kind ?? "line";
|
|
38495
|
+
const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
|
|
38496
|
+
idsByKey.set(plot.key, id);
|
|
38497
|
+
series.push({
|
|
38498
|
+
id,
|
|
38499
|
+
title: plot.title,
|
|
38500
|
+
paneId: "",
|
|
38501
|
+
// stamped by the orchestrator
|
|
38502
|
+
kind,
|
|
38503
|
+
points: toPoints(bars, plot.values, plot.colors),
|
|
38504
|
+
style: {
|
|
38505
|
+
color: plot.color,
|
|
38506
|
+
width: plot.width ?? 1,
|
|
38507
|
+
lineStyle: plot.lineStyle ?? "solid",
|
|
38508
|
+
...plot.base != null ? { base: plot.base } : {}
|
|
38509
|
+
},
|
|
38510
|
+
...plot.overlay ? { overlay: true } : {}
|
|
38511
|
+
});
|
|
38512
|
+
});
|
|
38513
|
+
if (out.markers && out.markers.length > 0) {
|
|
38514
|
+
series.push({
|
|
38515
|
+
id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
|
|
38516
|
+
title: this.spec.shortTitle ?? this.spec.title,
|
|
38517
|
+
paneId: "",
|
|
38518
|
+
kind: "markers",
|
|
38519
|
+
markers: out.markers
|
|
38520
|
+
});
|
|
38521
|
+
}
|
|
38522
|
+
const fills = (out.bands ?? []).flatMap((band, ordinal) => {
|
|
38523
|
+
const from = idsByKey.get(band.from);
|
|
38524
|
+
const to = idsByKey.get(band.to);
|
|
38525
|
+
if (!from || !to) return [];
|
|
38526
|
+
return [
|
|
38527
|
+
{
|
|
38528
|
+
id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
|
|
38529
|
+
paneId: "",
|
|
38530
|
+
fromSeriesId: from,
|
|
38531
|
+
toSeriesId: to,
|
|
38532
|
+
color: band.color
|
|
38533
|
+
}
|
|
38534
|
+
];
|
|
38535
|
+
});
|
|
38536
|
+
const priceLines = (out.levels ?? []).map((level, ordinal) => ({
|
|
38537
|
+
id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
|
|
38538
|
+
paneId: "",
|
|
38539
|
+
price: level.price,
|
|
38540
|
+
...level.color != null ? { color: level.color } : {},
|
|
38541
|
+
lineStyle: level.lineStyle ?? "dashed",
|
|
38542
|
+
width: 1,
|
|
38543
|
+
...level.title != null ? { title: level.title } : {}
|
|
38544
|
+
}));
|
|
38545
|
+
const polylines = (out.polylines ?? []).map((poly, ordinal) => {
|
|
38546
|
+
const { key, ...rest } = poly;
|
|
38547
|
+
return {
|
|
38548
|
+
id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
|
|
38549
|
+
paneId: "",
|
|
38550
|
+
...rest
|
|
38551
|
+
};
|
|
38552
|
+
});
|
|
38553
|
+
this.ctx.emit({ series, fills, priceLines, polylines });
|
|
38554
|
+
this.ctx.setStatus("idle");
|
|
38555
|
+
}
|
|
38556
|
+
};
|
|
38557
|
+
function classicDescriptor(spec) {
|
|
38558
|
+
return {
|
|
38559
|
+
type: spec.type,
|
|
38560
|
+
title: spec.title,
|
|
38561
|
+
...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
|
|
38562
|
+
paneHint: spec.overlay ? "price" : "new",
|
|
38563
|
+
overlay: spec.overlay,
|
|
38564
|
+
// Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
|
|
38565
|
+
multiInstance: true,
|
|
38566
|
+
inputsSchema: () => spec.inputs,
|
|
38567
|
+
defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
|
|
38568
|
+
create: () => new ClassicIndicator(spec)
|
|
38569
|
+
};
|
|
38570
|
+
}
|
|
38571
|
+
|
|
38572
|
+
// src/core/native-indicators/classics/math.ts
|
|
38573
|
+
var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
|
|
38574
|
+
function sourceValues(bars, source) {
|
|
38575
|
+
const out = new Array(bars.length);
|
|
38576
|
+
for (let i = 0; i < bars.length; i++) {
|
|
38577
|
+
const b = bars[i];
|
|
38578
|
+
switch (source) {
|
|
38579
|
+
case "Open":
|
|
38580
|
+
out[i] = b.open;
|
|
38581
|
+
break;
|
|
38582
|
+
case "High":
|
|
38583
|
+
out[i] = b.high;
|
|
38584
|
+
break;
|
|
38585
|
+
case "Low":
|
|
38586
|
+
out[i] = b.low;
|
|
38587
|
+
break;
|
|
38588
|
+
case "HL2":
|
|
38589
|
+
out[i] = (b.high + b.low) / 2;
|
|
38590
|
+
break;
|
|
38591
|
+
case "HLC3":
|
|
38592
|
+
out[i] = (b.high + b.low + b.close) / 3;
|
|
38593
|
+
break;
|
|
38594
|
+
case "OHLC4":
|
|
38595
|
+
out[i] = (b.open + b.high + b.low + b.close) / 4;
|
|
38596
|
+
break;
|
|
38597
|
+
case "HLCC4":
|
|
38598
|
+
out[i] = (b.high + b.low + b.close + b.close) / 4;
|
|
38599
|
+
break;
|
|
38600
|
+
default:
|
|
38601
|
+
out[i] = b.close;
|
|
38602
|
+
}
|
|
38603
|
+
}
|
|
38604
|
+
return out;
|
|
38605
|
+
}
|
|
38606
|
+
function volumes(bars) {
|
|
38607
|
+
return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
|
|
38608
|
+
}
|
|
38609
|
+
function highs(bars) {
|
|
38610
|
+
return bars.map((b) => b.high);
|
|
38611
|
+
}
|
|
38612
|
+
function lows(bars) {
|
|
38613
|
+
return bars.map((b) => b.low);
|
|
38614
|
+
}
|
|
38615
|
+
function closes(bars) {
|
|
38616
|
+
return bars.map((b) => b.close);
|
|
38617
|
+
}
|
|
38618
|
+
function opens(bars) {
|
|
38619
|
+
return bars.map((b) => b.open);
|
|
38620
|
+
}
|
|
38621
|
+
function zip(a, b, f) {
|
|
38622
|
+
const out = new Array(a.length);
|
|
38623
|
+
for (let i = 0; i < a.length; i++) {
|
|
38624
|
+
const x = a[i];
|
|
38625
|
+
const y = b[i];
|
|
38626
|
+
out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
|
|
38627
|
+
}
|
|
38628
|
+
return out;
|
|
38629
|
+
}
|
|
38630
|
+
function map(a, f) {
|
|
38631
|
+
const out = new Array(a.length);
|
|
38632
|
+
for (let i = 0; i < a.length; i++) {
|
|
38633
|
+
const x = a[i];
|
|
38634
|
+
out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
|
|
38635
|
+
}
|
|
38636
|
+
return out;
|
|
38637
|
+
}
|
|
38638
|
+
function sub(a, b) {
|
|
38639
|
+
return zip(a, b, (x, y) => x - y);
|
|
38640
|
+
}
|
|
38641
|
+
function windowed(v, len, fold) {
|
|
38642
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38643
|
+
if (len <= 0) return out;
|
|
38644
|
+
let run = 0;
|
|
38645
|
+
for (let i = 0; i < v.length; i++) {
|
|
38646
|
+
run = Number.isFinite(v[i]) ? run + 1 : 0;
|
|
38647
|
+
if (run >= len) out[i] = fold(v, i - len + 1, i);
|
|
38648
|
+
}
|
|
38649
|
+
return out;
|
|
38650
|
+
}
|
|
38651
|
+
function sum(v, len) {
|
|
38652
|
+
return windowed(v, len, (w, from, to) => {
|
|
38653
|
+
let s = 0;
|
|
38654
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
38655
|
+
return s;
|
|
38656
|
+
});
|
|
38657
|
+
}
|
|
38658
|
+
function sma(v, len) {
|
|
38659
|
+
return windowed(v, len, (w, from, to) => {
|
|
38660
|
+
let s = 0;
|
|
38661
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
38662
|
+
return s / len;
|
|
38663
|
+
});
|
|
38664
|
+
}
|
|
38665
|
+
function wma(v, len) {
|
|
38666
|
+
const norm = len * (len + 1) / 2;
|
|
38667
|
+
return windowed(v, len, (w, from, to) => {
|
|
38668
|
+
let s = 0;
|
|
38669
|
+
for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
|
|
38670
|
+
return s / norm;
|
|
38671
|
+
});
|
|
38672
|
+
}
|
|
38673
|
+
function vwma(src, vol, len) {
|
|
38674
|
+
const pv = zip(src, vol, (p, q) => p * q);
|
|
38675
|
+
return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
|
|
38676
|
+
}
|
|
38677
|
+
function stdev(v, len) {
|
|
38678
|
+
return windowed(v, len, (w, from, to) => {
|
|
38679
|
+
let s = 0;
|
|
38680
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
38681
|
+
const mean = s / len;
|
|
38682
|
+
let sq = 0;
|
|
38683
|
+
for (let k = from; k <= to; k++) {
|
|
38684
|
+
const d = w[k] - mean;
|
|
38685
|
+
sq += d * d;
|
|
38686
|
+
}
|
|
38687
|
+
return Math.sqrt(sq / len);
|
|
38688
|
+
});
|
|
38689
|
+
}
|
|
38690
|
+
function highest(v, len) {
|
|
38691
|
+
return windowed(v, len, (w, from, to) => {
|
|
38692
|
+
let m = -Infinity;
|
|
38693
|
+
for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
|
|
38694
|
+
return m;
|
|
38695
|
+
});
|
|
38696
|
+
}
|
|
38697
|
+
function lowest(v, len) {
|
|
38698
|
+
return windowed(v, len, (w, from, to) => {
|
|
38699
|
+
let m = Infinity;
|
|
38700
|
+
for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
|
|
38701
|
+
return m;
|
|
38702
|
+
});
|
|
38703
|
+
}
|
|
38704
|
+
function barsSinceHighest(v, len) {
|
|
38705
|
+
return windowed(v, len, (w, from, to) => {
|
|
38706
|
+
let m = -Infinity;
|
|
38707
|
+
let at = to;
|
|
38708
|
+
for (let k = from; k <= to; k++) {
|
|
38709
|
+
if (w[k] >= m) {
|
|
38710
|
+
m = w[k];
|
|
38711
|
+
at = k;
|
|
38712
|
+
}
|
|
38713
|
+
}
|
|
38714
|
+
return to - at;
|
|
38715
|
+
});
|
|
38716
|
+
}
|
|
38717
|
+
function barsSinceLowest(v, len) {
|
|
38718
|
+
return windowed(v, len, (w, from, to) => {
|
|
38719
|
+
let m = Infinity;
|
|
38720
|
+
let at = to;
|
|
38721
|
+
for (let k = from; k <= to; k++) {
|
|
38722
|
+
if (w[k] <= m) {
|
|
38723
|
+
m = w[k];
|
|
38724
|
+
at = k;
|
|
38725
|
+
}
|
|
38726
|
+
}
|
|
38727
|
+
return to - at;
|
|
38728
|
+
});
|
|
38729
|
+
}
|
|
38730
|
+
function recursive(v, len, alpha) {
|
|
38731
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38732
|
+
let run = 0;
|
|
38733
|
+
let prev3 = Number.NaN;
|
|
38734
|
+
for (let i = 0; i < v.length; i++) {
|
|
38735
|
+
const x = v[i];
|
|
38736
|
+
if (!Number.isFinite(x)) {
|
|
38737
|
+
run = 0;
|
|
38738
|
+
prev3 = Number.NaN;
|
|
38739
|
+
continue;
|
|
38740
|
+
}
|
|
38741
|
+
run += 1;
|
|
38742
|
+
if (Number.isFinite(prev3)) {
|
|
38743
|
+
prev3 = alpha * x + (1 - alpha) * prev3;
|
|
38744
|
+
out[i] = prev3;
|
|
38745
|
+
} else if (run >= len) {
|
|
38746
|
+
let s = 0;
|
|
38747
|
+
for (let k = i - len + 1; k <= i; k++) s += v[k];
|
|
38748
|
+
prev3 = s / len;
|
|
38749
|
+
out[i] = prev3;
|
|
38750
|
+
}
|
|
38751
|
+
}
|
|
38752
|
+
return out;
|
|
38753
|
+
}
|
|
38754
|
+
function ema(v, len) {
|
|
38755
|
+
return recursive(v, len, 2 / (len + 1));
|
|
38756
|
+
}
|
|
38757
|
+
function rma(v, len) {
|
|
38758
|
+
return recursive(v, len, 1 / len);
|
|
38759
|
+
}
|
|
38760
|
+
function change(v, n = 1) {
|
|
38761
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38762
|
+
for (let i = n; i < v.length; i++) {
|
|
38763
|
+
const a = v[i];
|
|
38764
|
+
const b = v[i - n];
|
|
38765
|
+
if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
|
|
38766
|
+
}
|
|
38767
|
+
return out;
|
|
38768
|
+
}
|
|
38769
|
+
function roc(v, len) {
|
|
38770
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38771
|
+
for (let i = len; i < v.length; i++) {
|
|
38772
|
+
const a = v[i];
|
|
38773
|
+
const b = v[i - len];
|
|
38774
|
+
if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
|
|
38775
|
+
}
|
|
38776
|
+
return out;
|
|
38777
|
+
}
|
|
38778
|
+
function shift3(v, n) {
|
|
38779
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38780
|
+
for (let i = 0; i < v.length; i++) {
|
|
38781
|
+
const j = i - n;
|
|
38782
|
+
if (j >= 0 && j < v.length) out[i] = v[j];
|
|
38783
|
+
}
|
|
38784
|
+
return out;
|
|
38785
|
+
}
|
|
38786
|
+
function trueRange(bars) {
|
|
38787
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
38788
|
+
for (let i = 0; i < bars.length; i++) {
|
|
38789
|
+
const b = bars[i];
|
|
38790
|
+
if (i === 0) {
|
|
38791
|
+
out[i] = b.high - b.low;
|
|
38792
|
+
continue;
|
|
38793
|
+
}
|
|
38794
|
+
const pc = bars[i - 1].close;
|
|
38795
|
+
out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
|
|
38796
|
+
}
|
|
38797
|
+
return out;
|
|
38798
|
+
}
|
|
38799
|
+
function atr(bars, len) {
|
|
38800
|
+
return rma(trueRange(bars), len);
|
|
38801
|
+
}
|
|
38802
|
+
function rsi(v, len) {
|
|
38803
|
+
const d = change(v);
|
|
38804
|
+
const up = map(d, (x) => Math.max(x, 0));
|
|
38805
|
+
const dn = map(d, (x) => Math.max(-x, 0));
|
|
38806
|
+
return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
|
|
38807
|
+
}
|
|
38808
|
+
function stoch(v, hi, lo, len) {
|
|
38809
|
+
const hh = highest(hi, len);
|
|
38810
|
+
const ll = lowest(lo, len);
|
|
38811
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38812
|
+
for (let i = 0; i < v.length; i++) {
|
|
38813
|
+
const x = v[i];
|
|
38814
|
+
const h = hh[i];
|
|
38815
|
+
const l = ll[i];
|
|
38816
|
+
if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
38817
|
+
out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
|
|
38818
|
+
}
|
|
38819
|
+
return out;
|
|
38820
|
+
}
|
|
38821
|
+
function linreg(v, len) {
|
|
38822
|
+
const sx = (len - 1) * len / 2;
|
|
38823
|
+
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
38824
|
+
const denom = len * sxx - sx * sx;
|
|
38825
|
+
return windowed(v, len, (w, from, to) => {
|
|
38826
|
+
let sy = 0;
|
|
38827
|
+
let sxy = 0;
|
|
38828
|
+
for (let k = from; k <= to; k++) {
|
|
38829
|
+
const x = k - from;
|
|
38830
|
+
sy += w[k];
|
|
38831
|
+
sxy += x * w[k];
|
|
38832
|
+
}
|
|
38833
|
+
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
38834
|
+
const intercept = (sy - slope * sx) / len;
|
|
38835
|
+
return intercept + slope * (len - 1);
|
|
38836
|
+
});
|
|
38837
|
+
}
|
|
38838
|
+
function swma(v) {
|
|
38839
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38840
|
+
for (let i = 3; i < v.length; i++) {
|
|
38841
|
+
const a = v[i - 3];
|
|
38842
|
+
const b = v[i - 2];
|
|
38843
|
+
const c = v[i - 1];
|
|
38844
|
+
const d = v[i];
|
|
38845
|
+
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
|
|
38846
|
+
out[i] = (a + 2 * b + 2 * c + d) / 6;
|
|
38847
|
+
}
|
|
38848
|
+
}
|
|
38849
|
+
return out;
|
|
38850
|
+
}
|
|
38851
|
+
function percentRank(v, len) {
|
|
38852
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38853
|
+
for (let i = len; i < v.length; i++) {
|
|
38854
|
+
const x = v[i];
|
|
38855
|
+
if (!Number.isFinite(x)) continue;
|
|
38856
|
+
let below = 0;
|
|
38857
|
+
let count = 0;
|
|
38858
|
+
for (let k = i - len; k < i; k++) {
|
|
38859
|
+
const y = v[k];
|
|
38860
|
+
if (!Number.isFinite(y)) {
|
|
38861
|
+
count = -1;
|
|
38862
|
+
break;
|
|
38863
|
+
}
|
|
38864
|
+
count += 1;
|
|
38865
|
+
if (y <= x) below += 1;
|
|
38866
|
+
}
|
|
38867
|
+
if (count === len) out[i] = 100 * below / len;
|
|
38868
|
+
}
|
|
38869
|
+
return out;
|
|
38870
|
+
}
|
|
38871
|
+
function cumSum(v) {
|
|
38872
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38873
|
+
let acc = 0;
|
|
38874
|
+
let seen = false;
|
|
38875
|
+
for (let i = 0; i < v.length; i++) {
|
|
38876
|
+
const x = v[i];
|
|
38877
|
+
if (Number.isFinite(x)) {
|
|
38878
|
+
acc += x;
|
|
38879
|
+
seen = true;
|
|
38880
|
+
}
|
|
38881
|
+
if (seen) out[i] = acc;
|
|
38882
|
+
}
|
|
38883
|
+
return out;
|
|
38884
|
+
}
|
|
38885
|
+
|
|
38886
|
+
// src/core/native-indicators/classics/shared.ts
|
|
38887
|
+
function lengthInput(defval, key = "length", title = "Length", max2 = 5e3) {
|
|
38888
|
+
return { key, title, type: "int", defval, min: 1, max: max2, step: 1 };
|
|
38889
|
+
}
|
|
38890
|
+
function sourceInput(defval = "Close") {
|
|
38891
|
+
return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
|
|
38892
|
+
}
|
|
38893
|
+
function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
|
|
38894
|
+
return { key, title, type: "color", defval };
|
|
38895
|
+
}
|
|
38896
|
+
function withAlpha2(hex, alpha = 0.08) {
|
|
38897
|
+
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
38898
|
+
if (!m) return hex;
|
|
38899
|
+
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
38900
|
+
return `#${m[1]}${a}`;
|
|
38901
|
+
}
|
|
38902
|
+
|
|
38903
|
+
// src/core/native-indicators/classics/averages.ts
|
|
38904
|
+
var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
|
|
38905
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
|
|
38906
|
+
function fixedAverage(type, title, shortTitle, smooth) {
|
|
38907
|
+
return {
|
|
38908
|
+
type,
|
|
38909
|
+
title,
|
|
38910
|
+
shortTitle,
|
|
38911
|
+
overlay: true,
|
|
38912
|
+
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
38913
|
+
compute: (bars, inputs) => {
|
|
38914
|
+
const len = num3(inputs, "length", 20);
|
|
38915
|
+
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
38916
|
+
const offset3 = Math.trunc(num3(inputs, "offset", 0));
|
|
38917
|
+
if (offset3 !== 0) values = shift3(values, offset3);
|
|
38918
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38919
|
+
}
|
|
38920
|
+
};
|
|
38921
|
+
}
|
|
38922
|
+
var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
|
|
38923
|
+
var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
|
|
38924
|
+
var movingAverage = {
|
|
38925
|
+
type: "moving-average",
|
|
38926
|
+
title: "Moving Average",
|
|
38927
|
+
shortTitle: "MA",
|
|
38928
|
+
overlay: true,
|
|
38929
|
+
inputs: [
|
|
38930
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
|
|
38931
|
+
lengthInput(20),
|
|
38932
|
+
sourceInput(),
|
|
38933
|
+
offsetInput,
|
|
38934
|
+
colorInput()
|
|
38935
|
+
],
|
|
38936
|
+
compute: (bars, inputs) => {
|
|
38937
|
+
const len = num3(inputs, "length", 20);
|
|
38938
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38939
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
38940
|
+
let values;
|
|
38941
|
+
switch (kind) {
|
|
38942
|
+
case "EMA":
|
|
38943
|
+
values = ema(src, len);
|
|
38944
|
+
break;
|
|
38945
|
+
case "WMA":
|
|
38946
|
+
values = wma(src, len);
|
|
38947
|
+
break;
|
|
38948
|
+
case "RMA":
|
|
38949
|
+
values = rma(src, len);
|
|
38950
|
+
break;
|
|
38951
|
+
case "VWMA":
|
|
38952
|
+
values = vwma(src, volumes(bars), len);
|
|
38953
|
+
break;
|
|
38954
|
+
default:
|
|
38955
|
+
values = sma(src, len);
|
|
38956
|
+
}
|
|
38957
|
+
const offset3 = Math.trunc(num3(inputs, "offset", 0));
|
|
38958
|
+
if (offset3 !== 0) values = shift3(values, offset3);
|
|
38959
|
+
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38960
|
+
}
|
|
38961
|
+
};
|
|
38962
|
+
var smoothedMa = {
|
|
38963
|
+
type: "rma",
|
|
38964
|
+
title: "Smoothed Moving Average",
|
|
38965
|
+
shortTitle: "RMA",
|
|
38966
|
+
overlay: true,
|
|
38967
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
38968
|
+
compute: (bars, inputs) => ({
|
|
38969
|
+
plots: [
|
|
38970
|
+
{
|
|
38971
|
+
key: "rma",
|
|
38972
|
+
title: "RMA",
|
|
38973
|
+
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
38974
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
38975
|
+
width: 2
|
|
38976
|
+
}
|
|
38977
|
+
]
|
|
38978
|
+
})
|
|
38979
|
+
};
|
|
38980
|
+
var zlema = {
|
|
38981
|
+
type: "zlema",
|
|
38982
|
+
title: "Zero-Lag Exponential Moving Average",
|
|
38983
|
+
shortTitle: "ZLEMA",
|
|
38984
|
+
overlay: true,
|
|
38985
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
38986
|
+
compute: (bars, inputs) => {
|
|
38987
|
+
const len = num3(inputs, "length", 14);
|
|
38988
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38989
|
+
const lag = Math.floor((len - 1) / 2);
|
|
38990
|
+
const lagged = shift3(src, lag);
|
|
38991
|
+
const delagged = zip(src, lagged, (a, b) => 2 * a - b);
|
|
38992
|
+
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38993
|
+
}
|
|
38994
|
+
};
|
|
38995
|
+
var vidya = {
|
|
38996
|
+
type: "vidya",
|
|
38997
|
+
title: "Variable Index Dynamic Average",
|
|
38998
|
+
shortTitle: "VIDYA",
|
|
38999
|
+
overlay: true,
|
|
39000
|
+
inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
|
|
39001
|
+
compute: (bars, inputs) => {
|
|
39002
|
+
const len = num3(inputs, "length", 14);
|
|
39003
|
+
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
39004
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39005
|
+
const d = change(src);
|
|
39006
|
+
const su = new Array(src.length).fill(Number.NaN);
|
|
39007
|
+
const sd = new Array(src.length).fill(Number.NaN);
|
|
39008
|
+
for (let i = cmoLen; i < src.length; i++) {
|
|
39009
|
+
let u = 0;
|
|
39010
|
+
let w = 0;
|
|
39011
|
+
let ok = true;
|
|
39012
|
+
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
39013
|
+
const x = d[k];
|
|
39014
|
+
if (!Number.isFinite(x)) {
|
|
39015
|
+
ok = false;
|
|
39016
|
+
break;
|
|
39017
|
+
}
|
|
39018
|
+
if (x > 0) u += x;
|
|
39019
|
+
else w -= x;
|
|
39020
|
+
}
|
|
39021
|
+
if (ok) {
|
|
39022
|
+
su[i] = u;
|
|
39023
|
+
sd[i] = w;
|
|
39024
|
+
}
|
|
39025
|
+
}
|
|
39026
|
+
const alpha = 2 / (len + 1);
|
|
39027
|
+
const out = new Array(src.length).fill(Number.NaN);
|
|
39028
|
+
let prev3 = Number.NaN;
|
|
39029
|
+
for (let i = 0; i < src.length; i++) {
|
|
39030
|
+
const x = src[i];
|
|
39031
|
+
const u = su[i];
|
|
39032
|
+
const w = sd[i];
|
|
39033
|
+
if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
|
|
39034
|
+
const total = u + w;
|
|
39035
|
+
const k = total === 0 ? 0 : Math.abs((u - w) / total);
|
|
39036
|
+
prev3 = Number.isFinite(prev3) ? alpha * k * x + (1 - alpha * k) * prev3 : x;
|
|
39037
|
+
out[i] = prev3;
|
|
39038
|
+
}
|
|
39039
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39040
|
+
}
|
|
39041
|
+
};
|
|
39042
|
+
var maEnvelope = {
|
|
39043
|
+
type: "ma-envelope",
|
|
39044
|
+
title: "Moving Average Envelope",
|
|
39045
|
+
shortTitle: "MA Env",
|
|
39046
|
+
overlay: true,
|
|
39047
|
+
inputs: [
|
|
39048
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
|
|
39049
|
+
lengthInput(20),
|
|
39050
|
+
sourceInput(),
|
|
39051
|
+
{ key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
|
|
39052
|
+
colorInput()
|
|
39053
|
+
],
|
|
39054
|
+
compute: (bars, inputs) => {
|
|
39055
|
+
const len = num3(inputs, "length", 20);
|
|
39056
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39057
|
+
const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
|
|
39058
|
+
const k = num3(inputs, "percent", 2.5) / 100;
|
|
39059
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
39060
|
+
return {
|
|
39061
|
+
plots: [
|
|
39062
|
+
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
39063
|
+
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
39064
|
+
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
39065
|
+
],
|
|
39066
|
+
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
39067
|
+
};
|
|
39068
|
+
}
|
|
39069
|
+
};
|
|
39070
|
+
var linearRegression = {
|
|
39071
|
+
type: "linear-regression",
|
|
39072
|
+
title: "Linear Regression Curve",
|
|
39073
|
+
shortTitle: "LinReg",
|
|
39074
|
+
overlay: true,
|
|
39075
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
39076
|
+
compute: (bars, inputs) => ({
|
|
39077
|
+
plots: [
|
|
39078
|
+
{
|
|
39079
|
+
key: "linreg",
|
|
39080
|
+
title: "LinReg",
|
|
39081
|
+
values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
39082
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
39083
|
+
width: 2
|
|
39084
|
+
}
|
|
39085
|
+
]
|
|
39086
|
+
})
|
|
39087
|
+
};
|
|
39088
|
+
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
39089
|
+
|
|
39090
|
+
// src/core/native-indicators/classics/bands.ts
|
|
39091
|
+
function bollinger(bars, inputs) {
|
|
39092
|
+
const len = num3(inputs, "length", 20);
|
|
39093
|
+
const mult = num3(inputs, "mult", 2);
|
|
39094
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39095
|
+
const basis = sma(src, len);
|
|
39096
|
+
const dev = map(stdev(src, len), (x) => x * mult);
|
|
39097
|
+
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
39098
|
+
}
|
|
39099
|
+
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
39100
|
+
var bollingerBands = {
|
|
39101
|
+
type: "bollinger-bands",
|
|
39102
|
+
title: "Bollinger Bands",
|
|
39103
|
+
shortTitle: "BB",
|
|
39104
|
+
overlay: true,
|
|
39105
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
39106
|
+
compute: (bars, inputs) => {
|
|
39107
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
39108
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
39109
|
+
return {
|
|
39110
|
+
plots: [
|
|
39111
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
39112
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
39113
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
39114
|
+
],
|
|
39115
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
39116
|
+
};
|
|
39117
|
+
}
|
|
39118
|
+
};
|
|
39119
|
+
var percentB = {
|
|
39120
|
+
type: "percent-b",
|
|
39121
|
+
title: "Bollinger Bands %B",
|
|
39122
|
+
shortTitle: "%B",
|
|
39123
|
+
overlay: false,
|
|
39124
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
39125
|
+
compute: (bars, inputs) => {
|
|
39126
|
+
const { upper, lower } = bollinger(bars, inputs);
|
|
39127
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39128
|
+
const values = src.map((x, i) => {
|
|
39129
|
+
const u = upper[i];
|
|
39130
|
+
const l = lower[i];
|
|
39131
|
+
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
39132
|
+
});
|
|
39133
|
+
return {
|
|
39134
|
+
plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39135
|
+
levels: [
|
|
39136
|
+
{ key: "upper", price: 1, color: NEUTRAL },
|
|
39137
|
+
{ key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
|
|
39138
|
+
{ key: "lower", price: 0, color: NEUTRAL }
|
|
39139
|
+
]
|
|
39140
|
+
};
|
|
39141
|
+
}
|
|
39142
|
+
};
|
|
39143
|
+
var bandwidth = {
|
|
39144
|
+
type: "bandwidth",
|
|
39145
|
+
title: "Bollinger Bands Width",
|
|
39146
|
+
shortTitle: "BandWidth",
|
|
39147
|
+
overlay: false,
|
|
39148
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
39149
|
+
compute: (bars, inputs) => {
|
|
39150
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
39151
|
+
const values = basis.map((b, i) => {
|
|
39152
|
+
const u = upper[i];
|
|
39153
|
+
const l = lower[i];
|
|
39154
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
|
|
39155
|
+
});
|
|
39156
|
+
return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39157
|
+
}
|
|
39158
|
+
};
|
|
39159
|
+
var keltner = {
|
|
39160
|
+
type: "keltner-channels",
|
|
39161
|
+
title: "Keltner Channels",
|
|
39162
|
+
shortTitle: "KC",
|
|
39163
|
+
overlay: true,
|
|
39164
|
+
inputs: [
|
|
39165
|
+
lengthInput(20),
|
|
39166
|
+
{ key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
39167
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
39168
|
+
sourceInput(),
|
|
39169
|
+
colorInput()
|
|
39170
|
+
],
|
|
39171
|
+
compute: (bars, inputs) => {
|
|
39172
|
+
const len = num3(inputs, "length", 20);
|
|
39173
|
+
const mult = num3(inputs, "mult", 2);
|
|
39174
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
39175
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
|
|
39176
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
39177
|
+
return {
|
|
39178
|
+
plots: [
|
|
39179
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
39180
|
+
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
39181
|
+
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
39182
|
+
],
|
|
39183
|
+
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
39184
|
+
};
|
|
39185
|
+
}
|
|
39186
|
+
};
|
|
39187
|
+
var donchian = {
|
|
39188
|
+
type: "donchian-channels",
|
|
39189
|
+
title: "Donchian Channels",
|
|
39190
|
+
shortTitle: "DC",
|
|
39191
|
+
overlay: true,
|
|
39192
|
+
inputs: [lengthInput(20), colorInput()],
|
|
39193
|
+
compute: (bars, inputs) => {
|
|
39194
|
+
const len = num3(inputs, "length", 20);
|
|
39195
|
+
const upper = highest(highs(bars), len);
|
|
39196
|
+
const lower = lowest(lows(bars), len);
|
|
39197
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
39198
|
+
return {
|
|
39199
|
+
plots: [
|
|
39200
|
+
{ key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
|
|
39201
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
39202
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
39203
|
+
],
|
|
39204
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
39205
|
+
};
|
|
39206
|
+
}
|
|
39207
|
+
};
|
|
39208
|
+
var chandelier = {
|
|
39209
|
+
type: "chandelier-exit",
|
|
39210
|
+
title: "Chandelier Exit",
|
|
39211
|
+
shortTitle: "CE",
|
|
39212
|
+
overlay: true,
|
|
39213
|
+
inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
|
|
39214
|
+
compute: (bars, inputs) => {
|
|
39215
|
+
const len = num3(inputs, "length", 22);
|
|
39216
|
+
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
39217
|
+
const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
39218
|
+
const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
39219
|
+
return {
|
|
39220
|
+
plots: [
|
|
39221
|
+
{ key: "long", title: "Long stop", values: long, color: BULLISH },
|
|
39222
|
+
{ key: "short", title: "Short stop", values: short, color: BEARISH }
|
|
39223
|
+
]
|
|
39224
|
+
};
|
|
39225
|
+
}
|
|
39226
|
+
};
|
|
39227
|
+
var chandeKroll = {
|
|
39228
|
+
type: "chande-kroll-stop",
|
|
39229
|
+
title: "Chande Kroll Stop",
|
|
39230
|
+
shortTitle: "CKS",
|
|
39231
|
+
overlay: true,
|
|
39232
|
+
inputs: [
|
|
39233
|
+
{ key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
39234
|
+
{ key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
|
|
39235
|
+
{ key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
|
|
39236
|
+
],
|
|
39237
|
+
compute: (bars, inputs) => {
|
|
39238
|
+
const p = num3(inputs, "p", 10);
|
|
39239
|
+
const x = num3(inputs, "x", 1);
|
|
39240
|
+
const q = num3(inputs, "q", 9);
|
|
39241
|
+
const range = map(atr(bars, p), (v) => v * x);
|
|
39242
|
+
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
39243
|
+
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
39244
|
+
return {
|
|
39245
|
+
plots: [
|
|
39246
|
+
{ key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
|
|
39247
|
+
{ key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
|
|
39248
|
+
]
|
|
39249
|
+
};
|
|
39250
|
+
}
|
|
39251
|
+
};
|
|
39252
|
+
var supertrend = {
|
|
39253
|
+
type: "supertrend",
|
|
39254
|
+
title: "SuperTrend",
|
|
39255
|
+
shortTitle: "SuperTrend",
|
|
39256
|
+
overlay: true,
|
|
39257
|
+
inputs: [
|
|
39258
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
39259
|
+
{ key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
|
|
39260
|
+
],
|
|
39261
|
+
compute: (bars, inputs) => {
|
|
39262
|
+
const len = num3(inputs, "atrLength", 10);
|
|
39263
|
+
const mult = num3(inputs, "mult", 3);
|
|
39264
|
+
const range = atr(bars, len);
|
|
39265
|
+
const n = bars.length;
|
|
39266
|
+
const st = new Array(n).fill(Number.NaN);
|
|
39267
|
+
const dir = new Array(n).fill(1);
|
|
39268
|
+
let up = Number.NaN;
|
|
39269
|
+
let dn = Number.NaN;
|
|
39270
|
+
let trend = 1;
|
|
39271
|
+
for (let i = 0; i < n; i++) {
|
|
39272
|
+
const r = range[i];
|
|
39273
|
+
if (!Number.isFinite(r)) continue;
|
|
39274
|
+
const b = bars[i];
|
|
39275
|
+
const mid = (b.high + b.low) / 2;
|
|
39276
|
+
const basicUp = mid - mult * r;
|
|
39277
|
+
const basicDn = mid + mult * r;
|
|
39278
|
+
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
39279
|
+
up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
|
|
39280
|
+
dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
|
|
39281
|
+
if (trend === 1 && b.close < up) trend = -1;
|
|
39282
|
+
else if (trend === -1 && b.close > dn) trend = 1;
|
|
39283
|
+
dir[i] = trend;
|
|
39284
|
+
st[i] = trend === 1 ? up : dn;
|
|
39285
|
+
}
|
|
39286
|
+
const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
|
|
39287
|
+
return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
|
|
39288
|
+
}
|
|
39289
|
+
};
|
|
39290
|
+
var alligator = {
|
|
39291
|
+
type: "williams-alligator",
|
|
39292
|
+
title: "Williams Alligator",
|
|
39293
|
+
shortTitle: "Alligator",
|
|
39294
|
+
overlay: true,
|
|
39295
|
+
inputs: [
|
|
39296
|
+
{ key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
|
|
39297
|
+
{ key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
|
|
39298
|
+
{ key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
|
|
39299
|
+
{ key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
|
|
39300
|
+
{ key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
|
|
39301
|
+
{ key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
|
|
39302
|
+
],
|
|
39303
|
+
compute: (bars, inputs) => {
|
|
39304
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39305
|
+
const jaw = shift3(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
39306
|
+
const teeth = shift3(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
39307
|
+
const lips = shift3(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
39308
|
+
return {
|
|
39309
|
+
plots: [
|
|
39310
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
|
|
39311
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
|
|
39312
|
+
{ key: "lips", title: "Lips", values: lips, color: BULLISH }
|
|
39313
|
+
]
|
|
39314
|
+
};
|
|
39315
|
+
}
|
|
39316
|
+
};
|
|
39317
|
+
var gator = {
|
|
39318
|
+
type: "gator-oscillator",
|
|
39319
|
+
title: "Gator Oscillator",
|
|
39320
|
+
shortTitle: "Gator Oscillator",
|
|
39321
|
+
overlay: false,
|
|
39322
|
+
inputs: [],
|
|
39323
|
+
compute: (bars) => {
|
|
39324
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39325
|
+
const jaw = shift3(rma(hl2, 13), 8);
|
|
39326
|
+
const teeth = shift3(rma(hl2, 8), 5);
|
|
39327
|
+
const lips = shift3(rma(hl2, 5), 3);
|
|
39328
|
+
const upper = map(sub(jaw, teeth), Math.abs);
|
|
39329
|
+
const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
|
|
39330
|
+
const colorByExpansion = (v) => v.map((x, i) => {
|
|
39331
|
+
if (!Number.isFinite(x)) return null;
|
|
39332
|
+
const prev3 = i > 0 ? v[i - 1] : Number.NaN;
|
|
39333
|
+
return Number.isFinite(prev3) && Math.abs(x) > Math.abs(prev3) ? BULLISH : BEARISH;
|
|
39334
|
+
});
|
|
39335
|
+
return {
|
|
39336
|
+
plots: [
|
|
39337
|
+
{ key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
|
|
39338
|
+
{ key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
|
|
39339
|
+
]
|
|
39340
|
+
};
|
|
39341
|
+
}
|
|
39342
|
+
};
|
|
39343
|
+
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
39344
|
+
|
|
39345
|
+
// src/core/native-indicators/classics/oscillators.ts
|
|
39346
|
+
function guideLevels(low, high, mid) {
|
|
39347
|
+
const levels = [
|
|
39348
|
+
{ key: "upper", price: high, color: NEUTRAL },
|
|
39349
|
+
{ key: "lower", price: low, color: NEUTRAL }
|
|
39350
|
+
];
|
|
39351
|
+
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
39352
|
+
return levels;
|
|
39353
|
+
}
|
|
39354
|
+
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
39355
|
+
function histColors(values) {
|
|
39356
|
+
return values.map((x, i) => {
|
|
39357
|
+
if (!Number.isFinite(x)) return null;
|
|
39358
|
+
const prev3 = i > 0 ? values[i - 1] : Number.NaN;
|
|
39359
|
+
const rising = Number.isFinite(prev3) ? x >= prev3 : true;
|
|
39360
|
+
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
39361
|
+
return rising ? `${BEARISH}80` : BEARISH;
|
|
39362
|
+
});
|
|
39363
|
+
}
|
|
39364
|
+
var rsiSpec = {
|
|
39365
|
+
type: "rsi",
|
|
39366
|
+
title: "Relative Strength Index",
|
|
39367
|
+
shortTitle: "RSI",
|
|
39368
|
+
overlay: false,
|
|
39369
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
39370
|
+
compute: (bars, inputs) => ({
|
|
39371
|
+
plots: [
|
|
39372
|
+
{
|
|
39373
|
+
key: "rsi",
|
|
39374
|
+
title: "RSI",
|
|
39375
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
39376
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
39377
|
+
width: 2
|
|
39378
|
+
}
|
|
39379
|
+
],
|
|
39380
|
+
levels: guideLevels(30, 70, 50)
|
|
39381
|
+
})
|
|
39382
|
+
};
|
|
39383
|
+
var stochastic = {
|
|
39384
|
+
type: "stochastic",
|
|
39385
|
+
title: "Stochastic",
|
|
39386
|
+
shortTitle: "Stoch",
|
|
39387
|
+
overlay: false,
|
|
39388
|
+
inputs: [
|
|
39389
|
+
lengthInput(14, "kLength", "%K length"),
|
|
39390
|
+
lengthInput(1, "kSmoothing", "%K smoothing", 500),
|
|
39391
|
+
lengthInput(3, "dLength", "%D smoothing", 500)
|
|
39392
|
+
],
|
|
39393
|
+
compute: (bars, inputs) => {
|
|
39394
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
|
|
39395
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
39396
|
+
return {
|
|
39397
|
+
plots: [
|
|
39398
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
39399
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
39400
|
+
],
|
|
39401
|
+
levels: guideLevels(20, 80)
|
|
39402
|
+
};
|
|
39403
|
+
}
|
|
39404
|
+
};
|
|
39405
|
+
var stochasticRsi = {
|
|
39406
|
+
type: "stochastic-rsi",
|
|
39407
|
+
title: "Stochastic RSI",
|
|
39408
|
+
shortTitle: "Stoch RSI",
|
|
39409
|
+
overlay: false,
|
|
39410
|
+
inputs: [
|
|
39411
|
+
lengthInput(14, "rsiLength", "RSI length"),
|
|
39412
|
+
lengthInput(14, "stochLength", "Stochastic length"),
|
|
39413
|
+
lengthInput(3, "kSmoothing", "%K smoothing", 500),
|
|
39414
|
+
lengthInput(3, "dLength", "%D smoothing", 500),
|
|
39415
|
+
sourceInput()
|
|
39416
|
+
],
|
|
39417
|
+
compute: (bars, inputs) => {
|
|
39418
|
+
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
39419
|
+
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
39420
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
39421
|
+
return {
|
|
39422
|
+
plots: [
|
|
39423
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
39424
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
39425
|
+
],
|
|
39426
|
+
levels: guideLevels(20, 80)
|
|
39427
|
+
};
|
|
39428
|
+
}
|
|
39429
|
+
};
|
|
39430
|
+
var macd = {
|
|
39431
|
+
type: "macd",
|
|
39432
|
+
title: "MACD",
|
|
39433
|
+
overlay: false,
|
|
39434
|
+
inputs: [
|
|
39435
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
39436
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
39437
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
39438
|
+
sourceInput()
|
|
39439
|
+
],
|
|
39440
|
+
compute: (bars, inputs) => {
|
|
39441
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39442
|
+
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
39443
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
39444
|
+
const hist = sub(line, signal);
|
|
39445
|
+
return {
|
|
39446
|
+
plots: [
|
|
39447
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
39448
|
+
{ key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
|
|
39449
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
39450
|
+
],
|
|
39451
|
+
levels: ZERO_LEVEL
|
|
39452
|
+
};
|
|
39453
|
+
}
|
|
39454
|
+
};
|
|
39455
|
+
var ppo = {
|
|
39456
|
+
type: "ppo",
|
|
39457
|
+
title: "Percentage Price Oscillator",
|
|
39458
|
+
shortTitle: "PPO",
|
|
39459
|
+
overlay: false,
|
|
39460
|
+
inputs: [
|
|
39461
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
39462
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
39463
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
39464
|
+
sourceInput()
|
|
39465
|
+
],
|
|
39466
|
+
compute: (bars, inputs) => {
|
|
39467
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39468
|
+
const fast = ema(src, num3(inputs, "fastLength", 12));
|
|
39469
|
+
const slow = ema(src, num3(inputs, "slowLength", 26));
|
|
39470
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
39471
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
39472
|
+
const hist = sub(line, signal);
|
|
39473
|
+
return {
|
|
39474
|
+
plots: [
|
|
39475
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
39476
|
+
{ key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
|
|
39477
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
39478
|
+
],
|
|
39479
|
+
levels: ZERO_LEVEL
|
|
39480
|
+
};
|
|
39481
|
+
}
|
|
39482
|
+
};
|
|
39483
|
+
var awesome = {
|
|
39484
|
+
type: "awesome-oscillator",
|
|
39485
|
+
title: "Awesome Oscillator",
|
|
39486
|
+
shortTitle: "AO",
|
|
39487
|
+
overlay: false,
|
|
39488
|
+
inputs: [],
|
|
39489
|
+
compute: (bars) => {
|
|
39490
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39491
|
+
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
39492
|
+
const colors = ao.map((x, i) => {
|
|
39493
|
+
if (!Number.isFinite(x)) return null;
|
|
39494
|
+
const prev3 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
39495
|
+
return Number.isFinite(prev3) && x < prev3 ? BEARISH : BULLISH;
|
|
39496
|
+
});
|
|
39497
|
+
return {
|
|
39498
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
|
|
39499
|
+
levels: ZERO_LEVEL
|
|
39500
|
+
};
|
|
39501
|
+
}
|
|
39502
|
+
};
|
|
39503
|
+
var cci = {
|
|
39504
|
+
type: "commodity-channel-index",
|
|
39505
|
+
title: "Commodity Channel Index",
|
|
39506
|
+
shortTitle: "CCI",
|
|
39507
|
+
overlay: false,
|
|
39508
|
+
inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
|
|
39509
|
+
compute: (bars, inputs) => {
|
|
39510
|
+
const len = num3(inputs, "length", 20);
|
|
39511
|
+
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
39512
|
+
const basis = sma(src, len);
|
|
39513
|
+
const dev = new Array(src.length).fill(Number.NaN);
|
|
39514
|
+
for (let i = len - 1; i < src.length; i++) {
|
|
39515
|
+
const mean = basis[i];
|
|
39516
|
+
if (!Number.isFinite(mean)) continue;
|
|
39517
|
+
let d = 0;
|
|
39518
|
+
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
39519
|
+
dev[i] = d / len;
|
|
39520
|
+
}
|
|
39521
|
+
const values = src.map((x, i) => {
|
|
39522
|
+
const b = basis[i];
|
|
39523
|
+
const d = dev[i];
|
|
39524
|
+
return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
|
|
39525
|
+
});
|
|
39526
|
+
return {
|
|
39527
|
+
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39528
|
+
levels: guideLevels(-100, 100, 0)
|
|
39529
|
+
};
|
|
39530
|
+
}
|
|
39531
|
+
};
|
|
39532
|
+
var williamsR = {
|
|
39533
|
+
type: "williams-percent-r",
|
|
39534
|
+
title: "Williams %R",
|
|
39535
|
+
shortTitle: "%R",
|
|
39536
|
+
overlay: false,
|
|
39537
|
+
inputs: [lengthInput(14), colorInput()],
|
|
39538
|
+
compute: (bars, inputs) => {
|
|
39539
|
+
const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
|
|
39540
|
+
return {
|
|
39541
|
+
plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39542
|
+
levels: guideLevels(-80, -20)
|
|
39543
|
+
};
|
|
39544
|
+
}
|
|
39545
|
+
};
|
|
39546
|
+
var cmo = {
|
|
39547
|
+
type: "chande-momentum-oscillator",
|
|
39548
|
+
title: "Chande Momentum Oscillator",
|
|
39549
|
+
shortTitle: "CMO",
|
|
39550
|
+
overlay: false,
|
|
39551
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
39552
|
+
compute: (bars, inputs) => {
|
|
39553
|
+
const len = num3(inputs, "length", 9);
|
|
39554
|
+
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
39555
|
+
const su = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
39556
|
+
const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
39557
|
+
const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
39558
|
+
return {
|
|
39559
|
+
plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39560
|
+
levels: guideLevels(-50, 50, 0)
|
|
39561
|
+
};
|
|
39562
|
+
}
|
|
39563
|
+
};
|
|
39564
|
+
var connorsRsi = {
|
|
39565
|
+
type: "connors-rsi",
|
|
39566
|
+
title: "Connors RSI",
|
|
39567
|
+
shortTitle: "CRSI",
|
|
39568
|
+
overlay: false,
|
|
39569
|
+
inputs: [
|
|
39570
|
+
lengthInput(3, "rsiLength", "RSI length"),
|
|
39571
|
+
lengthInput(2, "streakLength", "Streak RSI length"),
|
|
39572
|
+
lengthInput(100, "rankLength", "Percent-rank length"),
|
|
39573
|
+
colorInput()
|
|
39574
|
+
],
|
|
39575
|
+
compute: (bars, inputs) => {
|
|
39576
|
+
const c = closes(bars);
|
|
39577
|
+
const streak = new Array(c.length).fill(0);
|
|
39578
|
+
for (let i = 1; i < c.length; i++) {
|
|
39579
|
+
const d = c[i] - c[i - 1];
|
|
39580
|
+
streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
|
|
39581
|
+
}
|
|
39582
|
+
const a = rsi(c, num3(inputs, "rsiLength", 3));
|
|
39583
|
+
const b = rsi(streak, num3(inputs, "streakLength", 2));
|
|
39584
|
+
const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
|
|
39585
|
+
const values = c.map((_, i) => {
|
|
39586
|
+
const x = a[i];
|
|
39587
|
+
const y = b[i];
|
|
39588
|
+
const z = r[i];
|
|
39589
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
39590
|
+
});
|
|
39591
|
+
return {
|
|
39592
|
+
plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39593
|
+
levels: guideLevels(20, 80, 50)
|
|
39594
|
+
};
|
|
39595
|
+
}
|
|
39596
|
+
};
|
|
39597
|
+
var fisher = {
|
|
39598
|
+
type: "fisher-transform",
|
|
39599
|
+
title: "Fisher Transform",
|
|
39600
|
+
shortTitle: "Fisher",
|
|
39601
|
+
overlay: false,
|
|
39602
|
+
inputs: [lengthInput(9)],
|
|
39603
|
+
compute: (bars, inputs) => {
|
|
39604
|
+
const len = num3(inputs, "length", 9);
|
|
39605
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39606
|
+
const hi = highest(hl2, len);
|
|
39607
|
+
const lo = lowest(hl2, len);
|
|
39608
|
+
const n = bars.length;
|
|
39609
|
+
const out = new Array(n).fill(Number.NaN);
|
|
39610
|
+
let value = 0;
|
|
39611
|
+
let fish = 0;
|
|
39612
|
+
for (let i = 0; i < n; i++) {
|
|
39613
|
+
const h = hi[i];
|
|
39614
|
+
const l = lo[i];
|
|
39615
|
+
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
39616
|
+
const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
|
|
39617
|
+
value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
|
|
39618
|
+
fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
|
|
39619
|
+
out[i] = fish;
|
|
39620
|
+
}
|
|
39621
|
+
return {
|
|
39622
|
+
plots: [
|
|
39623
|
+
{ key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
|
|
39624
|
+
{ key: "trigger", title: "Trigger", values: shift3(out, 1), color: WARNING }
|
|
39625
|
+
],
|
|
39626
|
+
levels: ZERO_LEVEL
|
|
39627
|
+
};
|
|
39628
|
+
}
|
|
39629
|
+
};
|
|
39630
|
+
var trix = {
|
|
39631
|
+
type: "trix",
|
|
39632
|
+
title: "TRIX",
|
|
39633
|
+
overlay: false,
|
|
39634
|
+
inputs: [lengthInput(18), colorInput()],
|
|
39635
|
+
compute: (bars, inputs) => {
|
|
39636
|
+
const len = num3(inputs, "length", 18);
|
|
39637
|
+
const logClose = map(closes(bars), Math.log);
|
|
39638
|
+
const smooth = ema(ema(ema(logClose, len), len), len);
|
|
39639
|
+
const values = map(change(smooth), (x) => x * 1e4);
|
|
39640
|
+
return {
|
|
39641
|
+
plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39642
|
+
levels: ZERO_LEVEL
|
|
39643
|
+
};
|
|
39644
|
+
}
|
|
39645
|
+
};
|
|
39646
|
+
function tsiLine(src, long, short) {
|
|
39647
|
+
const mom = change(src);
|
|
39648
|
+
const numer = ema(ema(mom, long), short);
|
|
39649
|
+
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
39650
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
|
|
39651
|
+
}
|
|
39652
|
+
var tsi = {
|
|
39653
|
+
type: "true-strength-index",
|
|
39654
|
+
title: "True Strength Index",
|
|
39655
|
+
shortTitle: "TSI",
|
|
39656
|
+
overlay: false,
|
|
39657
|
+
inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
|
|
39658
|
+
compute: (bars, inputs) => {
|
|
39659
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
|
|
39660
|
+
return {
|
|
39661
|
+
plots: [
|
|
39662
|
+
{ key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
|
|
39663
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
39664
|
+
],
|
|
39665
|
+
levels: ZERO_LEVEL
|
|
39666
|
+
};
|
|
39667
|
+
}
|
|
39668
|
+
};
|
|
39669
|
+
var smiErgodic = {
|
|
39670
|
+
type: "smi-ergodic",
|
|
39671
|
+
title: "SMI Ergodic Oscillator",
|
|
39672
|
+
shortTitle: "SMIE",
|
|
39673
|
+
overlay: false,
|
|
39674
|
+
inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
|
|
39675
|
+
compute: (bars, inputs) => {
|
|
39676
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
39677
|
+
const signal = ema(line, num3(inputs, "signalLength", 5));
|
|
39678
|
+
const osc = sub(line, signal);
|
|
39679
|
+
return {
|
|
39680
|
+
plots: [
|
|
39681
|
+
{ key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
|
|
39682
|
+
{ key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
|
|
39683
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
39684
|
+
],
|
|
39685
|
+
levels: ZERO_LEVEL
|
|
39686
|
+
};
|
|
39687
|
+
}
|
|
39688
|
+
};
|
|
39689
|
+
var stc = {
|
|
39690
|
+
type: "schaff-trend-cycle",
|
|
39691
|
+
title: "Schaff Trend Cycle",
|
|
39692
|
+
shortTitle: "Schaff Trend Cycle",
|
|
39693
|
+
overlay: false,
|
|
39694
|
+
inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
|
|
39695
|
+
compute: (bars, inputs) => {
|
|
39696
|
+
const len = num3(inputs, "cycleLength", 10);
|
|
39697
|
+
const src = closes(bars);
|
|
39698
|
+
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
39699
|
+
const stochOf = (v) => {
|
|
39700
|
+
const lo = lowest(v, len);
|
|
39701
|
+
const hi = highest(v, len);
|
|
39702
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
39703
|
+
let prevRaw = Number.NaN;
|
|
39704
|
+
let smoothed = Number.NaN;
|
|
39705
|
+
for (let i = 0; i < v.length; i++) {
|
|
39706
|
+
const x = v[i];
|
|
39707
|
+
const l = lo[i];
|
|
39708
|
+
const h = hi[i];
|
|
39709
|
+
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
39710
|
+
const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
|
|
39711
|
+
prevRaw = raw;
|
|
39712
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
|
|
39713
|
+
out[i] = smoothed;
|
|
39714
|
+
}
|
|
39715
|
+
return out;
|
|
39716
|
+
};
|
|
39717
|
+
const values = stochOf(stochOf(macdLine));
|
|
39718
|
+
return {
|
|
39719
|
+
plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
|
|
39720
|
+
levels: guideLevels(25, 75)
|
|
39721
|
+
};
|
|
39722
|
+
}
|
|
39723
|
+
};
|
|
39724
|
+
var kst = {
|
|
39725
|
+
type: "know-sure-thing",
|
|
39726
|
+
title: "Know Sure Thing",
|
|
39727
|
+
shortTitle: "KST",
|
|
39728
|
+
overlay: false,
|
|
39729
|
+
inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
|
|
39730
|
+
compute: (bars, inputs) => {
|
|
39731
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39732
|
+
const line = [
|
|
39733
|
+
sma(roc(src, 10), 10),
|
|
39734
|
+
sma(roc(src, 15), 10),
|
|
39735
|
+
sma(roc(src, 20), 10),
|
|
39736
|
+
sma(roc(src, 30), 15)
|
|
39737
|
+
].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
|
|
39738
|
+
return {
|
|
39739
|
+
plots: [
|
|
39740
|
+
{ key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
|
|
39741
|
+
{ key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
|
|
39742
|
+
],
|
|
39743
|
+
levels: ZERO_LEVEL
|
|
39744
|
+
};
|
|
39745
|
+
}
|
|
39746
|
+
};
|
|
39747
|
+
var coppock = {
|
|
39748
|
+
type: "coppock-curve",
|
|
39749
|
+
title: "Coppock Curve",
|
|
39750
|
+
shortTitle: "Coppock",
|
|
39751
|
+
overlay: false,
|
|
39752
|
+
inputs: [
|
|
39753
|
+
lengthInput(10, "wmaLength", "WMA length"),
|
|
39754
|
+
lengthInput(14, "longRoc", "Long RoC length"),
|
|
39755
|
+
lengthInput(11, "shortRoc", "Short RoC length"),
|
|
39756
|
+
sourceInput(),
|
|
39757
|
+
colorInput()
|
|
39758
|
+
],
|
|
39759
|
+
compute: (bars, inputs) => {
|
|
39760
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39761
|
+
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
39762
|
+
return {
|
|
39763
|
+
plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39764
|
+
levels: ZERO_LEVEL
|
|
39765
|
+
};
|
|
39766
|
+
}
|
|
39767
|
+
};
|
|
39768
|
+
var dpo = {
|
|
39769
|
+
type: "detrended-price-oscillator",
|
|
39770
|
+
title: "Detrended Price Oscillator",
|
|
39771
|
+
shortTitle: "DPO",
|
|
39772
|
+
overlay: false,
|
|
39773
|
+
inputs: [lengthInput(21), colorInput()],
|
|
39774
|
+
compute: (bars, inputs) => {
|
|
39775
|
+
const len = num3(inputs, "length", 21);
|
|
39776
|
+
const barsback = Math.floor(len / 2) + 1;
|
|
39777
|
+
const values = sub(closes(bars), shift3(sma(closes(bars), len), barsback));
|
|
39778
|
+
return {
|
|
39779
|
+
plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39780
|
+
levels: ZERO_LEVEL
|
|
39781
|
+
};
|
|
39782
|
+
}
|
|
39783
|
+
};
|
|
39784
|
+
var rocSpec = {
|
|
39785
|
+
type: "rate-of-change",
|
|
39786
|
+
title: "Rate of Change",
|
|
39787
|
+
shortTitle: "ROC",
|
|
39788
|
+
overlay: false,
|
|
39789
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
39790
|
+
compute: (bars, inputs) => ({
|
|
39791
|
+
plots: [
|
|
39792
|
+
{
|
|
39793
|
+
key: "roc",
|
|
39794
|
+
title: "RoC",
|
|
39795
|
+
values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
|
|
39796
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
39797
|
+
width: 2
|
|
39798
|
+
}
|
|
39799
|
+
],
|
|
39800
|
+
levels: ZERO_LEVEL
|
|
39801
|
+
})
|
|
39802
|
+
};
|
|
39803
|
+
var ultimate = {
|
|
39804
|
+
type: "ultimate-oscillator",
|
|
39805
|
+
title: "Ultimate Oscillator",
|
|
39806
|
+
shortTitle: "UO",
|
|
39807
|
+
overlay: false,
|
|
39808
|
+
inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
|
|
39809
|
+
compute: (bars, inputs) => {
|
|
39810
|
+
const n = bars.length;
|
|
39811
|
+
const bp = new Array(n).fill(Number.NaN);
|
|
39812
|
+
const tr = new Array(n).fill(Number.NaN);
|
|
39813
|
+
for (let i = 0; i < n; i++) {
|
|
39814
|
+
const b = bars[i];
|
|
39815
|
+
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
39816
|
+
const lo = Math.min(b.low, pc);
|
|
39817
|
+
const hi = Math.max(b.high, pc);
|
|
39818
|
+
bp[i] = b.close - lo;
|
|
39819
|
+
tr[i] = hi - lo;
|
|
39820
|
+
}
|
|
39821
|
+
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
39822
|
+
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
39823
|
+
const a14 = avg(num3(inputs, "middleLength", 14));
|
|
39824
|
+
const a28 = avg(num3(inputs, "slowLength", 28));
|
|
39825
|
+
const values = a7.map((x, i) => {
|
|
39826
|
+
const y = a14[i];
|
|
39827
|
+
const z = a28[i];
|
|
39828
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
39829
|
+
});
|
|
39830
|
+
return {
|
|
39831
|
+
plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39832
|
+
levels: guideLevels(30, 70, 50)
|
|
39833
|
+
};
|
|
39834
|
+
}
|
|
39835
|
+
};
|
|
39836
|
+
var rvgi = {
|
|
39837
|
+
type: "relative-vigor-index",
|
|
39838
|
+
title: "Relative Vigor Index",
|
|
39839
|
+
shortTitle: "RVGI",
|
|
39840
|
+
overlay: false,
|
|
39841
|
+
inputs: [lengthInput(10)],
|
|
39842
|
+
compute: (bars, inputs) => {
|
|
39843
|
+
const len = num3(inputs, "length", 10);
|
|
39844
|
+
const co = swma(sub(closes(bars), opens(bars)));
|
|
39845
|
+
const hl = swma(sub(highs(bars), lows(bars)));
|
|
39846
|
+
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
39847
|
+
return {
|
|
39848
|
+
plots: [
|
|
39849
|
+
{ key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
|
|
39850
|
+
{ key: "signal", title: "Signal", values: swma(line), color: WARNING }
|
|
39851
|
+
],
|
|
39852
|
+
levels: ZERO_LEVEL
|
|
39853
|
+
};
|
|
39854
|
+
}
|
|
39855
|
+
};
|
|
39856
|
+
var relVolatility = {
|
|
39857
|
+
type: "relative-volatility-index",
|
|
39858
|
+
title: "Relative Volatility Index",
|
|
39859
|
+
shortTitle: "RVI",
|
|
39860
|
+
overlay: false,
|
|
39861
|
+
inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
|
|
39862
|
+
compute: (bars, inputs) => {
|
|
39863
|
+
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
39864
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39865
|
+
const dev = stdev(src, num3(inputs, "stdevLength", 10));
|
|
39866
|
+
const d = change(src);
|
|
39867
|
+
const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
|
|
39868
|
+
const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
|
|
39869
|
+
const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
|
|
39870
|
+
return {
|
|
39871
|
+
plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39872
|
+
levels: guideLevels(20, 80, 50)
|
|
39873
|
+
};
|
|
39874
|
+
}
|
|
39875
|
+
};
|
|
39876
|
+
var bop = {
|
|
39877
|
+
type: "balance-of-power",
|
|
39878
|
+
title: "Balance of Power",
|
|
39879
|
+
shortTitle: "BOP",
|
|
39880
|
+
overlay: false,
|
|
39881
|
+
inputs: [colorInput()],
|
|
39882
|
+
compute: (bars, inputs) => {
|
|
39883
|
+
const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
39884
|
+
return {
|
|
39885
|
+
plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39886
|
+
levels: ZERO_LEVEL
|
|
39887
|
+
};
|
|
39888
|
+
}
|
|
39889
|
+
};
|
|
39890
|
+
var elderRay = {
|
|
39891
|
+
type: "elder-ray",
|
|
39892
|
+
title: "Elder Ray",
|
|
39893
|
+
overlay: false,
|
|
39894
|
+
inputs: [lengthInput(13)],
|
|
39895
|
+
compute: (bars, inputs) => {
|
|
39896
|
+
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
39897
|
+
return {
|
|
39898
|
+
plots: [
|
|
39899
|
+
{ key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
|
|
39900
|
+
{ key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
|
|
39901
|
+
],
|
|
39902
|
+
levels: ZERO_LEVEL
|
|
39903
|
+
};
|
|
39904
|
+
}
|
|
39905
|
+
};
|
|
39906
|
+
var ttmSqueeze = {
|
|
39907
|
+
type: "ttm-squeeze",
|
|
39908
|
+
title: "TTM Squeeze",
|
|
39909
|
+
overlay: false,
|
|
39910
|
+
inputs: [
|
|
39911
|
+
lengthInput(20),
|
|
39912
|
+
{ key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
39913
|
+
{ key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
|
|
39914
|
+
],
|
|
39915
|
+
compute: (bars, inputs) => {
|
|
39916
|
+
const len = num3(inputs, "length", 20);
|
|
39917
|
+
const c = closes(bars);
|
|
39918
|
+
const basis = sma(c, len);
|
|
39919
|
+
const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
39920
|
+
const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
39921
|
+
const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
39922
|
+
const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
|
|
39923
|
+
const mom = linreg(sub(c, anchor), len);
|
|
39924
|
+
const squeezed = c.map((_, i) => {
|
|
39925
|
+
const d = dev[i];
|
|
39926
|
+
const r = kcRange[i];
|
|
39927
|
+
return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
|
|
39928
|
+
});
|
|
39929
|
+
const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
|
|
39930
|
+
return {
|
|
39931
|
+
plots: [
|
|
39932
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
|
|
39933
|
+
{ key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
|
|
39934
|
+
]
|
|
39935
|
+
};
|
|
39936
|
+
}
|
|
39937
|
+
};
|
|
39938
|
+
var oscillatorSpecs = [
|
|
39939
|
+
rsiSpec,
|
|
39940
|
+
stochastic,
|
|
39941
|
+
stochasticRsi,
|
|
39942
|
+
macd,
|
|
39943
|
+
ppo,
|
|
39944
|
+
awesome,
|
|
39945
|
+
cci,
|
|
39946
|
+
williamsR,
|
|
39947
|
+
cmo,
|
|
39948
|
+
connorsRsi,
|
|
39949
|
+
fisher,
|
|
39950
|
+
trix,
|
|
39951
|
+
tsi,
|
|
39952
|
+
smiErgodic,
|
|
39953
|
+
stc,
|
|
39954
|
+
kst,
|
|
39955
|
+
coppock,
|
|
39956
|
+
dpo,
|
|
39957
|
+
rocSpec,
|
|
39958
|
+
ultimate,
|
|
39959
|
+
rvgi,
|
|
39960
|
+
relVolatility,
|
|
39961
|
+
bop,
|
|
39962
|
+
elderRay,
|
|
39963
|
+
ttmSqueeze
|
|
39964
|
+
];
|
|
39965
|
+
|
|
39966
|
+
// src/core/native-indicators/classics/trend.ts
|
|
39967
|
+
var aroon = {
|
|
39968
|
+
type: "aroon",
|
|
39969
|
+
title: "Aroon",
|
|
39970
|
+
overlay: false,
|
|
39971
|
+
inputs: [lengthInput(14)],
|
|
39972
|
+
compute: (bars, inputs) => {
|
|
39973
|
+
const len = num3(inputs, "length", 14);
|
|
39974
|
+
const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
39975
|
+
const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
39976
|
+
return {
|
|
39977
|
+
plots: [
|
|
39978
|
+
{ key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
|
|
39979
|
+
{ key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
|
|
39980
|
+
],
|
|
39981
|
+
levels: [
|
|
39982
|
+
{ key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
|
|
39983
|
+
{ key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
|
|
39984
|
+
]
|
|
39985
|
+
};
|
|
39986
|
+
}
|
|
39987
|
+
};
|
|
39988
|
+
var adx = {
|
|
39989
|
+
type: "average-directional-index",
|
|
39990
|
+
title: "Average Directional Index",
|
|
39991
|
+
shortTitle: "ADX",
|
|
39992
|
+
overlay: false,
|
|
39993
|
+
inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
|
|
39994
|
+
compute: (bars, inputs) => {
|
|
39995
|
+
const diLen = num3(inputs, "diLength", 14);
|
|
39996
|
+
const adxLen = num3(inputs, "adxLength", 14);
|
|
39997
|
+
const n = bars.length;
|
|
39998
|
+
const plusDm = new Array(n).fill(Number.NaN);
|
|
39999
|
+
const minusDm = new Array(n).fill(Number.NaN);
|
|
40000
|
+
for (let i = 1; i < n; i++) {
|
|
40001
|
+
const up = bars[i].high - bars[i - 1].high;
|
|
40002
|
+
const dn = bars[i - 1].low - bars[i].low;
|
|
40003
|
+
plusDm[i] = up > dn && up > 0 ? up : 0;
|
|
40004
|
+
minusDm[i] = dn > up && dn > 0 ? dn : 0;
|
|
40005
|
+
}
|
|
40006
|
+
const atrLine = rma(trueRange(bars), diLen);
|
|
40007
|
+
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
40008
|
+
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
40009
|
+
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
40010
|
+
return {
|
|
40011
|
+
plots: [
|
|
40012
|
+
{ key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
|
|
40013
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
|
|
40014
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
|
|
40015
|
+
],
|
|
40016
|
+
levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
|
|
40017
|
+
};
|
|
40018
|
+
}
|
|
40019
|
+
};
|
|
40020
|
+
var vortex = {
|
|
40021
|
+
type: "vortex-indicator",
|
|
40022
|
+
title: "Vortex Indicator",
|
|
40023
|
+
shortTitle: "VI",
|
|
40024
|
+
overlay: false,
|
|
40025
|
+
inputs: [lengthInput(14)],
|
|
40026
|
+
compute: (bars, inputs) => {
|
|
40027
|
+
const len = num3(inputs, "length", 14);
|
|
40028
|
+
const n = bars.length;
|
|
40029
|
+
const vmPlus = new Array(n).fill(Number.NaN);
|
|
40030
|
+
const vmMinus = new Array(n).fill(Number.NaN);
|
|
40031
|
+
for (let i = 1; i < n; i++) {
|
|
40032
|
+
vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
|
|
40033
|
+
vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
|
|
40034
|
+
}
|
|
40035
|
+
const trSum = sum(trueRange(bars), len);
|
|
40036
|
+
return {
|
|
40037
|
+
plots: [
|
|
40038
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
|
|
40039
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
|
|
40040
|
+
],
|
|
40041
|
+
levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
|
|
40042
|
+
};
|
|
40043
|
+
}
|
|
40044
|
+
};
|
|
40045
|
+
var trendSpecs = [aroon, adx, vortex];
|
|
40046
|
+
|
|
40047
|
+
// src/core/native-indicators/classics/volatility.ts
|
|
40048
|
+
var atrSpec = {
|
|
40049
|
+
type: "average-true-range",
|
|
40050
|
+
title: "Average True Range",
|
|
40051
|
+
shortTitle: "ATR",
|
|
40052
|
+
overlay: false,
|
|
40053
|
+
inputs: [lengthInput(14), colorInput()],
|
|
40054
|
+
compute: (bars, inputs) => ({
|
|
40055
|
+
plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
40056
|
+
})
|
|
40057
|
+
};
|
|
40058
|
+
var historicalVolatility = {
|
|
40059
|
+
type: "historical-volatility",
|
|
40060
|
+
title: "Historical Volatility",
|
|
40061
|
+
shortTitle: "HV",
|
|
40062
|
+
overlay: false,
|
|
40063
|
+
inputs: [
|
|
40064
|
+
lengthInput(10),
|
|
40065
|
+
{ 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)" },
|
|
40066
|
+
colorInput()
|
|
40067
|
+
],
|
|
40068
|
+
compute: (bars, inputs) => {
|
|
40069
|
+
const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
|
|
40070
|
+
const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
|
|
40071
|
+
return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
40072
|
+
}
|
|
40073
|
+
};
|
|
40074
|
+
var chaikinVolatility = {
|
|
40075
|
+
type: "chaikin-volatility",
|
|
40076
|
+
title: "Chaikin Volatility",
|
|
40077
|
+
shortTitle: "CHV",
|
|
40078
|
+
overlay: false,
|
|
40079
|
+
inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
|
|
40080
|
+
compute: (bars, inputs) => {
|
|
40081
|
+
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
40082
|
+
return {
|
|
40083
|
+
plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40084
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40085
|
+
};
|
|
40086
|
+
}
|
|
40087
|
+
};
|
|
40088
|
+
var massIndex = {
|
|
40089
|
+
type: "mass-index",
|
|
40090
|
+
title: "Mass Index",
|
|
40091
|
+
shortTitle: "MI",
|
|
40092
|
+
overlay: false,
|
|
40093
|
+
inputs: [lengthInput(25), colorInput()],
|
|
40094
|
+
compute: (bars, inputs) => {
|
|
40095
|
+
const range = sub(highs(bars), lows(bars));
|
|
40096
|
+
const single = ema(range, 9);
|
|
40097
|
+
const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
40098
|
+
return {
|
|
40099
|
+
plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40100
|
+
levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
|
|
40101
|
+
};
|
|
40102
|
+
}
|
|
40103
|
+
};
|
|
40104
|
+
var standardDeviation = {
|
|
40105
|
+
type: "standard-deviation",
|
|
40106
|
+
title: "Standard Deviation",
|
|
40107
|
+
shortTitle: "StdDev",
|
|
40108
|
+
overlay: false,
|
|
40109
|
+
inputs: [lengthInput(20), sourceInput(), colorInput()],
|
|
40110
|
+
compute: (bars, inputs) => ({
|
|
40111
|
+
plots: [
|
|
40112
|
+
{
|
|
40113
|
+
key: "stdev",
|
|
40114
|
+
title: "StdDev",
|
|
40115
|
+
values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
|
|
40116
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
40117
|
+
width: 2
|
|
40118
|
+
}
|
|
40119
|
+
]
|
|
40120
|
+
})
|
|
40121
|
+
};
|
|
40122
|
+
var ulcerIndex = {
|
|
40123
|
+
type: "ulcer-index",
|
|
40124
|
+
title: "Ulcer Index",
|
|
40125
|
+
overlay: false,
|
|
40126
|
+
inputs: [lengthInput(14), colorInput()],
|
|
40127
|
+
compute: (bars, inputs) => {
|
|
40128
|
+
const len = num3(inputs, "length", 14);
|
|
40129
|
+
const c = closes(bars);
|
|
40130
|
+
const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
|
|
40131
|
+
const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
|
|
40132
|
+
return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
40133
|
+
}
|
|
40134
|
+
};
|
|
40135
|
+
var choppiness = {
|
|
40136
|
+
type: "choppiness-index",
|
|
40137
|
+
title: "Choppiness Index",
|
|
40138
|
+
shortTitle: "CHOP",
|
|
40139
|
+
overlay: false,
|
|
40140
|
+
inputs: [lengthInput(14), colorInput()],
|
|
40141
|
+
compute: (bars, inputs) => {
|
|
40142
|
+
const len = num3(inputs, "length", 14);
|
|
40143
|
+
const trSum = sum(trueRange(bars), len);
|
|
40144
|
+
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
40145
|
+
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
40146
|
+
return {
|
|
40147
|
+
plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40148
|
+
levels: [
|
|
40149
|
+
{ key: "upper", price: 61.8, color: NEUTRAL },
|
|
40150
|
+
{ key: "lower", price: 38.2, color: NEUTRAL }
|
|
40151
|
+
]
|
|
40152
|
+
};
|
|
40153
|
+
}
|
|
40154
|
+
};
|
|
40155
|
+
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
40156
|
+
|
|
40157
|
+
// src/core/native-indicators/classics/volume.ts
|
|
40158
|
+
function moneyFlowVolume(bars) {
|
|
40159
|
+
return bars.map((b) => {
|
|
40160
|
+
if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
40161
|
+
if (b.high === b.low) return 0;
|
|
40162
|
+
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
40163
|
+
});
|
|
40164
|
+
}
|
|
40165
|
+
var obv = {
|
|
40166
|
+
type: "on-balance-volume",
|
|
40167
|
+
title: "On Balance Volume",
|
|
40168
|
+
shortTitle: "OBV",
|
|
40169
|
+
overlay: false,
|
|
40170
|
+
inputs: [colorInput()],
|
|
40171
|
+
compute: (bars, inputs) => {
|
|
40172
|
+
const vol = volumes(bars);
|
|
40173
|
+
const d = change(closes(bars));
|
|
40174
|
+
const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
|
|
40175
|
+
return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
40176
|
+
}
|
|
40177
|
+
};
|
|
40178
|
+
var accDist = {
|
|
40179
|
+
type: "accumulation-distribution",
|
|
40180
|
+
title: "Accumulation / Distribution",
|
|
40181
|
+
shortTitle: "A/D",
|
|
40182
|
+
overlay: false,
|
|
40183
|
+
inputs: [colorInput()],
|
|
40184
|
+
compute: (bars, inputs) => ({
|
|
40185
|
+
plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
40186
|
+
})
|
|
40187
|
+
};
|
|
40188
|
+
var cmf = {
|
|
40189
|
+
type: "chaikin-money-flow",
|
|
40190
|
+
title: "Chaikin Money Flow",
|
|
40191
|
+
shortTitle: "CMF",
|
|
40192
|
+
overlay: false,
|
|
40193
|
+
inputs: [lengthInput(20), colorInput()],
|
|
40194
|
+
compute: (bars, inputs) => {
|
|
40195
|
+
const len = num3(inputs, "length", 20);
|
|
40196
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
40197
|
+
return {
|
|
40198
|
+
plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40199
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40200
|
+
};
|
|
40201
|
+
}
|
|
40202
|
+
};
|
|
40203
|
+
var chaikinOscillator = {
|
|
40204
|
+
type: "chaikin-oscillator",
|
|
40205
|
+
title: "Chaikin Oscillator",
|
|
40206
|
+
shortTitle: "Chaikin Osc",
|
|
40207
|
+
overlay: false,
|
|
40208
|
+
inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
40209
|
+
compute: (bars, inputs) => {
|
|
40210
|
+
const ad = cumSum(moneyFlowVolume(bars));
|
|
40211
|
+
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
40212
|
+
return {
|
|
40213
|
+
plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40214
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40215
|
+
};
|
|
40216
|
+
}
|
|
40217
|
+
};
|
|
40218
|
+
var eom = {
|
|
40219
|
+
type: "ease-of-movement",
|
|
40220
|
+
title: "Ease of Movement",
|
|
40221
|
+
shortTitle: "EOM",
|
|
40222
|
+
overlay: false,
|
|
40223
|
+
inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
|
|
40224
|
+
compute: (bars, inputs) => {
|
|
40225
|
+
const div = num3(inputs, "divisor", 1e4);
|
|
40226
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
40227
|
+
const move = change(hl2);
|
|
40228
|
+
const raw = bars.map((b, i) => {
|
|
40229
|
+
const m = move[i];
|
|
40230
|
+
if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
|
|
40231
|
+
return div * m * (b.high - b.low) / b.volume;
|
|
40232
|
+
});
|
|
40233
|
+
return {
|
|
40234
|
+
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40235
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40236
|
+
};
|
|
40237
|
+
}
|
|
40238
|
+
};
|
|
40239
|
+
var forceIndex = {
|
|
40240
|
+
type: "force-index",
|
|
40241
|
+
title: "Force Index",
|
|
40242
|
+
shortTitle: "FI",
|
|
40243
|
+
overlay: false,
|
|
40244
|
+
inputs: [lengthInput(13), colorInput()],
|
|
40245
|
+
compute: (bars, inputs) => {
|
|
40246
|
+
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
40247
|
+
return {
|
|
40248
|
+
plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40249
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40250
|
+
};
|
|
40251
|
+
}
|
|
40252
|
+
};
|
|
40253
|
+
var klinger = {
|
|
40254
|
+
type: "klinger-oscillator",
|
|
40255
|
+
title: "Klinger Oscillator",
|
|
40256
|
+
shortTitle: "KVO",
|
|
40257
|
+
overlay: false,
|
|
40258
|
+
inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
|
|
40259
|
+
compute: (bars, inputs) => {
|
|
40260
|
+
const hlc3 = sourceValues(bars, "HLC3");
|
|
40261
|
+
const trend = change(hlc3);
|
|
40262
|
+
const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
|
|
40263
|
+
const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
|
|
40264
|
+
return {
|
|
40265
|
+
plots: [
|
|
40266
|
+
{ key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
|
|
40267
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
40268
|
+
],
|
|
40269
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40270
|
+
};
|
|
40271
|
+
}
|
|
40272
|
+
};
|
|
40273
|
+
var mfi = {
|
|
40274
|
+
type: "money-flow-index",
|
|
40275
|
+
title: "Money Flow Index",
|
|
40276
|
+
shortTitle: "MFI",
|
|
40277
|
+
overlay: false,
|
|
40278
|
+
inputs: [lengthInput(14), colorInput()],
|
|
40279
|
+
compute: (bars, inputs) => {
|
|
40280
|
+
const len = num3(inputs, "length", 14);
|
|
40281
|
+
const tp = sourceValues(bars, "HLC3");
|
|
40282
|
+
const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
|
|
40283
|
+
const d = change(tp);
|
|
40284
|
+
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
40285
|
+
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
40286
|
+
const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
|
|
40287
|
+
return {
|
|
40288
|
+
plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40289
|
+
levels: [
|
|
40290
|
+
{ key: "upper", price: 80, color: NEUTRAL },
|
|
40291
|
+
{ key: "lower", price: 20, color: NEUTRAL }
|
|
40292
|
+
]
|
|
40293
|
+
};
|
|
40294
|
+
}
|
|
40295
|
+
};
|
|
40296
|
+
function volumeIndex(bars, when) {
|
|
40297
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
40298
|
+
let index = 1e3;
|
|
40299
|
+
let started = false;
|
|
40300
|
+
for (let i = 1; i < bars.length; i++) {
|
|
40301
|
+
const v = bars[i].volume;
|
|
40302
|
+
const pv = bars[i - 1].volume;
|
|
40303
|
+
const pc = bars[i - 1].close;
|
|
40304
|
+
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
40305
|
+
if (started) out[i] = index;
|
|
40306
|
+
continue;
|
|
40307
|
+
}
|
|
40308
|
+
started = true;
|
|
40309
|
+
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
40310
|
+
out[i] = index;
|
|
40311
|
+
}
|
|
40312
|
+
return out;
|
|
40313
|
+
}
|
|
40314
|
+
var nvi = {
|
|
40315
|
+
type: "negative-volume-index",
|
|
40316
|
+
title: "Negative Volume Index",
|
|
40317
|
+
shortTitle: "Negative Volume Index",
|
|
40318
|
+
overlay: false,
|
|
40319
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
40320
|
+
compute: (bars, inputs) => {
|
|
40321
|
+
const line = volumeIndex(bars, (v, pv) => v < pv);
|
|
40322
|
+
return {
|
|
40323
|
+
plots: [
|
|
40324
|
+
{ key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
40325
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
40326
|
+
]
|
|
40327
|
+
};
|
|
40328
|
+
}
|
|
40329
|
+
};
|
|
40330
|
+
var pvi = {
|
|
40331
|
+
type: "positive-volume-index",
|
|
40332
|
+
title: "Positive Volume Index",
|
|
40333
|
+
shortTitle: "Positive Volume Index",
|
|
40334
|
+
overlay: false,
|
|
40335
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
40336
|
+
compute: (bars, inputs) => {
|
|
40337
|
+
const line = volumeIndex(bars, (v, pv) => v > pv);
|
|
40338
|
+
return {
|
|
40339
|
+
plots: [
|
|
40340
|
+
{ key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
40341
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
40342
|
+
]
|
|
40343
|
+
};
|
|
40344
|
+
}
|
|
40345
|
+
};
|
|
40346
|
+
var pvt = {
|
|
40347
|
+
type: "price-volume-trend",
|
|
40348
|
+
title: "Price Volume Trend",
|
|
40349
|
+
shortTitle: "PVT",
|
|
40350
|
+
overlay: false,
|
|
40351
|
+
inputs: [colorInput()],
|
|
40352
|
+
compute: (bars, inputs) => {
|
|
40353
|
+
const c = closes(bars);
|
|
40354
|
+
const raw = bars.map((b, i) => {
|
|
40355
|
+
if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
40356
|
+
const pc = c[i - 1];
|
|
40357
|
+
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
40358
|
+
});
|
|
40359
|
+
return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
40360
|
+
}
|
|
40361
|
+
};
|
|
40362
|
+
var volumeOscillator = {
|
|
40363
|
+
type: "volume-oscillator",
|
|
40364
|
+
title: "Volume Oscillator",
|
|
40365
|
+
shortTitle: "Vol Osc",
|
|
40366
|
+
overlay: false,
|
|
40367
|
+
inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
40368
|
+
compute: (bars, inputs) => {
|
|
40369
|
+
const vol = volumes(bars);
|
|
40370
|
+
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
40371
|
+
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
40372
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
40373
|
+
return {
|
|
40374
|
+
plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40375
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40376
|
+
};
|
|
40377
|
+
}
|
|
40378
|
+
};
|
|
40379
|
+
var pvo = {
|
|
40380
|
+
type: "pvo",
|
|
40381
|
+
title: "Percentage Volume Oscillator",
|
|
40382
|
+
shortTitle: "PVO",
|
|
40383
|
+
overlay: false,
|
|
40384
|
+
inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
|
|
40385
|
+
compute: (bars, inputs) => {
|
|
40386
|
+
const vol = volumes(bars);
|
|
40387
|
+
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
40388
|
+
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
40389
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
40390
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
40391
|
+
const hist = sub(line, signal);
|
|
40392
|
+
return {
|
|
40393
|
+
plots: [
|
|
40394
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
|
|
40395
|
+
{ key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
|
|
40396
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
40397
|
+
],
|
|
40398
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40399
|
+
};
|
|
40400
|
+
}
|
|
40401
|
+
};
|
|
40402
|
+
var vfi = {
|
|
40403
|
+
type: "volume-flow-indicator",
|
|
40404
|
+
title: "Volume Flow Indicator",
|
|
40405
|
+
shortTitle: "Volume Flow Indicator",
|
|
40406
|
+
overlay: false,
|
|
40407
|
+
inputs: [
|
|
40408
|
+
lengthInput(130),
|
|
40409
|
+
{ key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
|
|
40410
|
+
{ key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
|
|
40411
|
+
lengthInput(3, "smoothLength", "Smoothing length")
|
|
40412
|
+
],
|
|
40413
|
+
compute: (bars, inputs) => {
|
|
40414
|
+
const len = num3(inputs, "length", 130);
|
|
40415
|
+
const coef = num3(inputs, "coef", 0.2);
|
|
40416
|
+
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
40417
|
+
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
40418
|
+
const inter = change(typical);
|
|
40419
|
+
const vInter = stdev(inter, 30);
|
|
40420
|
+
const vol = volumes(bars);
|
|
40421
|
+
const vAve = sma(vol, len);
|
|
40422
|
+
const mf = change(sourceValues(bars, "HLC3"));
|
|
40423
|
+
const raw = bars.map((b, i) => {
|
|
40424
|
+
const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
|
|
40425
|
+
const v = vol[i];
|
|
40426
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
40427
|
+
const m = mf[i];
|
|
40428
|
+
if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
|
|
40429
|
+
const capped = Math.min(v, ave * volCoef);
|
|
40430
|
+
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
40431
|
+
});
|
|
40432
|
+
const values = bars.map((_, i) => {
|
|
40433
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
40434
|
+
return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
|
|
40435
|
+
});
|
|
40436
|
+
const flowSum = sum(raw, len);
|
|
40437
|
+
const line = zip(flowSum, values, (f, a) => f / a);
|
|
40438
|
+
return {
|
|
40439
|
+
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
40440
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40441
|
+
};
|
|
40442
|
+
}
|
|
40443
|
+
};
|
|
40444
|
+
var intradayIntensity = {
|
|
40445
|
+
type: "intraday-intensity",
|
|
40446
|
+
title: "Intraday Intensity",
|
|
40447
|
+
shortTitle: "Intraday Intensity",
|
|
40448
|
+
overlay: false,
|
|
40449
|
+
inputs: [lengthInput(21), colorInput()],
|
|
40450
|
+
compute: (bars, inputs) => {
|
|
40451
|
+
const len = num3(inputs, "length", 21);
|
|
40452
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
|
|
40453
|
+
return {
|
|
40454
|
+
plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40455
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40456
|
+
};
|
|
40457
|
+
}
|
|
40458
|
+
};
|
|
40459
|
+
var volumeSpecs = [
|
|
40460
|
+
obv,
|
|
40461
|
+
accDist,
|
|
40462
|
+
cmf,
|
|
40463
|
+
chaikinOscillator,
|
|
40464
|
+
eom,
|
|
40465
|
+
forceIndex,
|
|
40466
|
+
klinger,
|
|
40467
|
+
mfi,
|
|
40468
|
+
nvi,
|
|
40469
|
+
pvi,
|
|
40470
|
+
pvt,
|
|
40471
|
+
volumeOscillator,
|
|
40472
|
+
pvo,
|
|
40473
|
+
vfi,
|
|
40474
|
+
intradayIntensity
|
|
40475
|
+
];
|
|
40476
|
+
|
|
40477
|
+
// src/core/native-indicators/classics/overlays.ts
|
|
40478
|
+
var DAY_MS2 = 864e5;
|
|
40479
|
+
function periodKey(anchor, time) {
|
|
40480
|
+
if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
|
|
40481
|
+
if (anchor === "Month") {
|
|
40482
|
+
const d = new Date(time);
|
|
40483
|
+
return d.getUTCFullYear() * 12 + d.getUTCMonth();
|
|
40484
|
+
}
|
|
40485
|
+
return Math.floor(time / DAY_MS2);
|
|
40486
|
+
}
|
|
40487
|
+
var parabolicSar = {
|
|
40488
|
+
type: "parabolic-sar",
|
|
40489
|
+
title: "Parabolic SAR",
|
|
40490
|
+
shortTitle: "SAR",
|
|
40491
|
+
overlay: true,
|
|
40492
|
+
inputs: [
|
|
40493
|
+
{ key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
40494
|
+
{ key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
40495
|
+
{ key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
|
|
40496
|
+
],
|
|
40497
|
+
compute: (bars, inputs) => {
|
|
40498
|
+
const start = num3(inputs, "start", 0.02);
|
|
40499
|
+
const inc = num3(inputs, "increment", 0.02);
|
|
40500
|
+
const max2 = num3(inputs, "maximum", 0.2);
|
|
40501
|
+
const n = bars.length;
|
|
40502
|
+
const values = new Array(n).fill(Number.NaN);
|
|
40503
|
+
const colors = new Array(n).fill(null);
|
|
40504
|
+
if (n >= 2) {
|
|
40505
|
+
let long = bars[1].close >= bars[0].close;
|
|
40506
|
+
let sar = long ? bars[0].low : bars[0].high;
|
|
40507
|
+
let ep = long ? bars[0].high : bars[0].low;
|
|
40508
|
+
let af = start;
|
|
40509
|
+
for (let i = 1; i < n; i++) {
|
|
40510
|
+
const b = bars[i];
|
|
40511
|
+
sar += af * (ep - sar);
|
|
40512
|
+
if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
|
|
40513
|
+
else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
|
|
40514
|
+
const reversed = long ? b.low < sar : b.high > sar;
|
|
40515
|
+
if (reversed) {
|
|
40516
|
+
long = !long;
|
|
40517
|
+
sar = ep;
|
|
40518
|
+
ep = long ? b.high : b.low;
|
|
40519
|
+
af = start;
|
|
40520
|
+
} else if (long && b.high > ep) {
|
|
40521
|
+
ep = b.high;
|
|
40522
|
+
af = Math.min(max2, af + inc);
|
|
40523
|
+
} else if (!long && b.low < ep) {
|
|
40524
|
+
ep = b.low;
|
|
40525
|
+
af = Math.min(max2, af + inc);
|
|
40526
|
+
}
|
|
40527
|
+
values[i] = sar;
|
|
40528
|
+
colors[i] = long ? BULLISH : BEARISH;
|
|
40529
|
+
}
|
|
40530
|
+
}
|
|
40531
|
+
return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
|
|
40532
|
+
}
|
|
40533
|
+
};
|
|
40534
|
+
var vwap = {
|
|
40535
|
+
type: "vwap",
|
|
40536
|
+
title: "Volume Weighted Average Price",
|
|
40537
|
+
shortTitle: "VWAP",
|
|
40538
|
+
overlay: true,
|
|
40539
|
+
inputs: [
|
|
40540
|
+
{ key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
|
|
40541
|
+
{ key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
|
|
40542
|
+
colorInput(INFO)
|
|
40543
|
+
],
|
|
40544
|
+
compute: (bars, inputs) => {
|
|
40545
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
40546
|
+
const source = str3(inputs, "source", "HLC3");
|
|
40547
|
+
const values = new Array(bars.length).fill(Number.NaN);
|
|
40548
|
+
let period = Number.NaN;
|
|
40549
|
+
let cumPV = 0;
|
|
40550
|
+
let cumV = 0;
|
|
40551
|
+
for (let i = 0; i < bars.length; i++) {
|
|
40552
|
+
const b = bars[i];
|
|
40553
|
+
const key = periodKey(anchor, b.time);
|
|
40554
|
+
if (key !== period) {
|
|
40555
|
+
period = key;
|
|
40556
|
+
cumPV = 0;
|
|
40557
|
+
cumV = 0;
|
|
40558
|
+
}
|
|
40559
|
+
const v = b.volume;
|
|
40560
|
+
if (v != null && Number.isFinite(v) && v > 0) {
|
|
40561
|
+
const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
|
|
40562
|
+
cumPV += tp * v;
|
|
40563
|
+
cumV += v;
|
|
40564
|
+
}
|
|
40565
|
+
if (cumV > 0) values[i] = cumPV / cumV;
|
|
40566
|
+
}
|
|
40567
|
+
return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
40568
|
+
}
|
|
40569
|
+
};
|
|
40570
|
+
var pivotPoints = {
|
|
40571
|
+
type: "pivot-points",
|
|
40572
|
+
title: "Pivot Points",
|
|
40573
|
+
shortTitle: "Pivots",
|
|
40574
|
+
overlay: true,
|
|
40575
|
+
inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
|
|
40576
|
+
compute: (bars, inputs) => {
|
|
40577
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
40578
|
+
const aggs = /* @__PURE__ */ new Map();
|
|
40579
|
+
for (const b of bars) {
|
|
40580
|
+
const key = periodKey(anchor, b.time);
|
|
40581
|
+
const a = aggs.get(key);
|
|
40582
|
+
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
40583
|
+
else {
|
|
40584
|
+
a.high = Math.max(a.high, b.high);
|
|
40585
|
+
a.low = Math.min(a.low, b.low);
|
|
40586
|
+
a.close = b.close;
|
|
40587
|
+
}
|
|
40588
|
+
}
|
|
40589
|
+
const n = bars.length;
|
|
40590
|
+
const mk = () => new Array(n).fill(Number.NaN);
|
|
40591
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
40592
|
+
for (let i = 0; i < n; i++) {
|
|
40593
|
+
const prev3 = aggs.get(periodKey(anchor, bars[i].time) - 1);
|
|
40594
|
+
if (!prev3) continue;
|
|
40595
|
+
const p = (prev3.high + prev3.low + prev3.close) / 3;
|
|
40596
|
+
levels.p[i] = p;
|
|
40597
|
+
levels.r1[i] = 2 * p - prev3.low;
|
|
40598
|
+
levels.s1[i] = 2 * p - prev3.high;
|
|
40599
|
+
levels.r2[i] = p + (prev3.high - prev3.low);
|
|
40600
|
+
levels.s2[i] = p - (prev3.high - prev3.low);
|
|
40601
|
+
levels.r3[i] = prev3.high + 2 * (p - prev3.low);
|
|
40602
|
+
levels.s3[i] = prev3.low - 2 * (prev3.high - p);
|
|
40603
|
+
}
|
|
40604
|
+
const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
|
|
40605
|
+
return {
|
|
40606
|
+
plots: [
|
|
40607
|
+
plot("p", "P", WARNING),
|
|
40608
|
+
plot("r1", "R1", BEARISH),
|
|
40609
|
+
plot("s1", "S1", BULLISH),
|
|
40610
|
+
plot("r2", "R2", BEARISH),
|
|
40611
|
+
plot("s2", "S2", BULLISH),
|
|
40612
|
+
plot("r3", "R3", BEARISH),
|
|
40613
|
+
plot("s3", "S3", BULLISH)
|
|
40614
|
+
]
|
|
40615
|
+
};
|
|
40616
|
+
}
|
|
40617
|
+
};
|
|
40618
|
+
var fiftyTwoWeek = {
|
|
40619
|
+
type: "52-week-high-low",
|
|
40620
|
+
title: "52 Week High/Low",
|
|
40621
|
+
shortTitle: "52W H/L",
|
|
40622
|
+
overlay: true,
|
|
40623
|
+
inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
|
|
40624
|
+
compute: (bars, inputs) => {
|
|
40625
|
+
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
40626
|
+
const n = bars.length;
|
|
40627
|
+
const hi = new Array(n).fill(Number.NaN);
|
|
40628
|
+
const lo = new Array(n).fill(Number.NaN);
|
|
40629
|
+
const maxIdx = [];
|
|
40630
|
+
const minIdx = [];
|
|
40631
|
+
let from = 0;
|
|
40632
|
+
for (let i = 0; i < n; i++) {
|
|
40633
|
+
const b = bars[i];
|
|
40634
|
+
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
40635
|
+
maxIdx.push(i);
|
|
40636
|
+
while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
|
|
40637
|
+
minIdx.push(i);
|
|
40638
|
+
while (bars[from].time < b.time - span) from += 1;
|
|
40639
|
+
while (maxIdx[0] < from) maxIdx.shift();
|
|
40640
|
+
while (minIdx[0] < from) minIdx.shift();
|
|
40641
|
+
hi[i] = bars[maxIdx[0]].high;
|
|
40642
|
+
lo[i] = bars[minIdx[0]].low;
|
|
40643
|
+
}
|
|
40644
|
+
return {
|
|
40645
|
+
plots: [
|
|
40646
|
+
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
40647
|
+
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
40648
|
+
]
|
|
40649
|
+
};
|
|
40650
|
+
}
|
|
40651
|
+
};
|
|
40652
|
+
var zigzag = {
|
|
40653
|
+
type: "zigzag",
|
|
40654
|
+
title: "ZigZag",
|
|
40655
|
+
overlay: true,
|
|
40656
|
+
inputs: [
|
|
40657
|
+
{ key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
|
|
40658
|
+
{ key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
40659
|
+
colorInput()
|
|
40660
|
+
],
|
|
40661
|
+
compute: (bars, inputs) => {
|
|
40662
|
+
const dev = num3(inputs, "deviation", 5) / 100;
|
|
40663
|
+
const depth = num3(inputs, "depth", 10);
|
|
40664
|
+
const pivots = [];
|
|
40665
|
+
if (bars.length > 1) {
|
|
40666
|
+
let up = bars[1].close >= bars[0].close;
|
|
40667
|
+
let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
|
|
40668
|
+
for (let i = 1; i < bars.length; i++) {
|
|
40669
|
+
const b = bars[i];
|
|
40670
|
+
if (up) {
|
|
40671
|
+
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
40672
|
+
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
40673
|
+
pivots.push(ext);
|
|
40674
|
+
up = false;
|
|
40675
|
+
ext = { i, price: b.low };
|
|
40676
|
+
}
|
|
40677
|
+
} else if (b.low <= ext.price) {
|
|
40678
|
+
ext = { i, price: b.low };
|
|
40679
|
+
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
40680
|
+
pivots.push(ext);
|
|
40681
|
+
up = true;
|
|
40682
|
+
ext = { i, price: b.high };
|
|
40683
|
+
}
|
|
40684
|
+
}
|
|
40685
|
+
pivots.push(ext);
|
|
40686
|
+
}
|
|
40687
|
+
return {
|
|
40688
|
+
plots: [],
|
|
40689
|
+
polylines: [
|
|
40690
|
+
{
|
|
40691
|
+
key: "zigzag",
|
|
40692
|
+
points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
|
|
40693
|
+
curved: false,
|
|
40694
|
+
closed: false,
|
|
40695
|
+
lineColor: str3(inputs, "color", SERIES_LINE),
|
|
40696
|
+
lineWidth: 2,
|
|
40697
|
+
lineStyle: "solid",
|
|
40698
|
+
arrowLeft: false,
|
|
40699
|
+
arrowRight: false
|
|
40700
|
+
}
|
|
40701
|
+
]
|
|
40702
|
+
};
|
|
40703
|
+
}
|
|
40704
|
+
};
|
|
40705
|
+
var williamsFractal = {
|
|
40706
|
+
type: "williams-fractal",
|
|
40707
|
+
title: "Williams Fractal",
|
|
40708
|
+
shortTitle: "Fractals",
|
|
40709
|
+
overlay: true,
|
|
40710
|
+
inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
|
|
40711
|
+
compute: (bars, inputs) => {
|
|
40712
|
+
const p = num3(inputs, "periods", 2);
|
|
40713
|
+
const markers = [];
|
|
40714
|
+
const isExtreme = (i, pick, better) => {
|
|
40715
|
+
const v = pick(bars[i]);
|
|
40716
|
+
for (let k = i - p; k <= i + p; k++) {
|
|
40717
|
+
if (k === i) continue;
|
|
40718
|
+
if (!better(v, pick(bars[k]))) return false;
|
|
40719
|
+
}
|
|
40720
|
+
return true;
|
|
40721
|
+
};
|
|
40722
|
+
for (let i = p; i < bars.length - p; i++) {
|
|
40723
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
|
|
40724
|
+
markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
|
|
40725
|
+
}
|
|
40726
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
40727
|
+
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
40728
|
+
}
|
|
40729
|
+
}
|
|
40730
|
+
return { plots: [], markers };
|
|
40731
|
+
}
|
|
40732
|
+
};
|
|
40733
|
+
var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
|
|
40734
|
+
|
|
40735
|
+
// src/core/native-indicators/classics/index.ts
|
|
40736
|
+
var classicSpecs = [
|
|
40737
|
+
...averageSpecs,
|
|
40738
|
+
...bandSpecs,
|
|
40739
|
+
...oscillatorSpecs,
|
|
40740
|
+
...trendSpecs,
|
|
40741
|
+
...volatilitySpecs,
|
|
40742
|
+
...volumeSpecs,
|
|
40743
|
+
...overlaySpecs
|
|
40744
|
+
];
|
|
40745
|
+
function registerClassicIndicators() {
|
|
40746
|
+
for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
|
|
40747
|
+
}
|
|
40748
|
+
|
|
38373
40749
|
// src/Vela.ts
|
|
38374
40750
|
var Vela = class {
|
|
38375
40751
|
constructor(container, options = {}, deps = {}) {
|
|
38376
40752
|
registerBuiltinChartTypes();
|
|
38377
40753
|
registerVolume();
|
|
38378
40754
|
registerVpvr();
|
|
40755
|
+
registerClassicIndicators();
|
|
38379
40756
|
const element = resolveElement(container);
|
|
38380
40757
|
const theme = resolveTheme(options.theme);
|
|
38381
40758
|
let animZoom = true;
|
|
@@ -38450,25 +40827,28 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38450
40827
|
}
|
|
38451
40828
|
/**
|
|
38452
40829
|
* Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
|
|
38453
|
-
* e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
38454
|
-
* events)
|
|
38455
|
-
*
|
|
40830
|
+
* e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
40831
|
+
* remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
|
|
40832
|
+
* call returns the existing handle; the classic studies allow several instances, so each call
|
|
40833
|
+
* adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
|
|
38456
40834
|
*/
|
|
38457
40835
|
addNativeIndicator(type, options) {
|
|
38458
40836
|
return this.orchestrator.addNativeIndicator(type, options);
|
|
38459
40837
|
}
|
|
38460
40838
|
/**
|
|
38461
40839
|
* The catalog of built-in native indicators with their live state on this chart — each entry's
|
|
38462
|
-
* `type`, `title`, whether it `supported`s the current symbol,
|
|
38463
|
-
*
|
|
38464
|
-
* Lets a host "add indicator" UI list them, gate unsupported
|
|
38465
|
-
*
|
|
40840
|
+
* `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
|
|
40841
|
+
* and whether it allows several instances (`multiInstance`; otherwise a second
|
|
40842
|
+
* `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
|
|
40843
|
+
* ones, and de-duplicate the single-instance ones. Async because support may probe the
|
|
40844
|
+
* provider (a type may need data the symbol lacks).
|
|
38466
40845
|
*/
|
|
38467
40846
|
availableNativeIndicators() {
|
|
38468
40847
|
return this.orchestrator.availableNativeIndicators();
|
|
38469
40848
|
}
|
|
38470
40849
|
/**
|
|
38471
|
-
* The native-indicator types PRESENT on the chart right now
|
|
40850
|
+
* The native-indicator types PRESENT on the chart right now, one entry per instance (a
|
|
40851
|
+
* multi-instance type repeats) — the synchronous slice of
|
|
38472
40852
|
* {@link availableNativeIndicators} (only support probing is async; presence never is).
|
|
38473
40853
|
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
38474
40854
|
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|
|
@@ -38835,12 +41215,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38835
41215
|
return Math.round(Math.min(Math.max(px, lo), hi));
|
|
38836
41216
|
}
|
|
38837
41217
|
|
|
38838
|
-
// src/core/model/identity.ts
|
|
38839
|
-
function stableSeriesId(parts3) {
|
|
38840
|
-
const normTitle = parts3.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
38841
|
-
return `${parts3.instanceId}:${parts3.kind}:${normTitle}#${parts3.ordinal}`;
|
|
38842
|
-
}
|
|
38843
|
-
|
|
38844
41218
|
// src/data/symbol-base.ts
|
|
38845
41219
|
function baseOf(d) {
|
|
38846
41220
|
const fromDesc = d.description?.split("/")[0]?.trim();
|
|
@@ -38942,9 +41316,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38942
41316
|
for (const b of bars) byTime.set(b.time, b);
|
|
38943
41317
|
return [...byTime.values()].sort((a, b) => a.time - b.time);
|
|
38944
41318
|
}
|
|
38945
|
-
function aggregate(
|
|
41319
|
+
function aggregate(sub2, bucketMs) {
|
|
38946
41320
|
const buckets = /* @__PURE__ */ new Map();
|
|
38947
|
-
for (const b of
|
|
41321
|
+
for (const b of sub2) {
|
|
38948
41322
|
const key = Math.floor(b.time / bucketMs) * bucketMs;
|
|
38949
41323
|
const cur = buckets.get(key);
|
|
38950
41324
|
if (!cur) {
|
|
@@ -38996,8 +41370,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38996
41370
|
}
|
|
38997
41371
|
const ratio = targetMin / subMin;
|
|
38998
41372
|
const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
|
|
38999
|
-
const
|
|
39000
|
-
const agg = aggregate(
|
|
41373
|
+
const sub2 = klinesToOHLCV(await this.fetchKlines(base, apiSymbol, MIN_TO_INTERVAL[subMin], subRange));
|
|
41374
|
+
const agg = aggregate(sub2, targetMin * 6e4);
|
|
39001
41375
|
return range.limit != null && agg.length > range.limit ? agg.slice(-range.limit) : agg;
|
|
39002
41376
|
} catch (e) {
|
|
39003
41377
|
console.warn(`[vela] Binance: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -39328,9 +41702,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39328
41702
|
for (const b of bars) byTime.set(b.time, b);
|
|
39329
41703
|
return [...byTime.values()].sort((a, b) => a.time - b.time);
|
|
39330
41704
|
}
|
|
39331
|
-
function aggregate2(
|
|
41705
|
+
function aggregate2(sub2, bucketMs) {
|
|
39332
41706
|
const buckets = /* @__PURE__ */ new Map();
|
|
39333
|
-
for (const b of
|
|
41707
|
+
for (const b of sub2) {
|
|
39334
41708
|
const key = Math.floor(b.time / bucketMs) * bucketMs;
|
|
39335
41709
|
const cur = buckets.get(key);
|
|
39336
41710
|
if (!cur) {
|
|
@@ -39381,8 +41755,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39381
41755
|
}
|
|
39382
41756
|
const ratio = targetMin / subMin;
|
|
39383
41757
|
const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
|
|
39384
|
-
const
|
|
39385
|
-
const agg = aggregate2(
|
|
41758
|
+
const sub2 = (await this.fetchCandles(coin, MIN_TO_INTERVAL2[subMin], subMin * 6e4, subRange)).map(candleToOHLCV);
|
|
41759
|
+
const agg = aggregate2(sub2, targetMin * 6e4);
|
|
39386
41760
|
return range.limit != null && agg.length > range.limit ? agg.slice(-range.limit) : agg;
|
|
39387
41761
|
} catch (e) {
|
|
39388
41762
|
console.warn(`[vela] Hyperliquid: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -39734,15 +42108,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39734
42108
|
for (const b of bars) byTime.set(b.time, b);
|
|
39735
42109
|
return [...byTime.values()].sort((a, b) => a.time - b.time);
|
|
39736
42110
|
}
|
|
39737
|
-
function aggregate3(
|
|
39738
|
-
return aggregateBy(
|
|
42111
|
+
function aggregate3(sub2, bucketMs) {
|
|
42112
|
+
return aggregateBy(sub2, (t) => Math.floor(t / bucketMs) * bucketMs);
|
|
39739
42113
|
}
|
|
39740
|
-
function aggregateCalendar(
|
|
39741
|
-
return aggregateBy(
|
|
42114
|
+
function aggregateCalendar(sub2, unit) {
|
|
42115
|
+
return aggregateBy(sub2, unit === "W" ? weekStartUTC : monthStartUTC);
|
|
39742
42116
|
}
|
|
39743
|
-
function aggregateBy(
|
|
42117
|
+
function aggregateBy(sub2, keyMs) {
|
|
39744
42118
|
const buckets = /* @__PURE__ */ new Map();
|
|
39745
|
-
for (const b of
|
|
42119
|
+
for (const b of sub2) {
|
|
39746
42120
|
const key = keyMs(b.time);
|
|
39747
42121
|
const cur = buckets.get(key);
|
|
39748
42122
|
if (!cur) {
|
|
@@ -39796,8 +42170,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39796
42170
|
if (tf === "W" || tf === "M") {
|
|
39797
42171
|
const span = tf === "W" ? 7 * MS_PER_DAY : 31 * MS_PER_DAY;
|
|
39798
42172
|
const subRange2 = range.from != null ? range : { ...range, limit: range.limit != null ? Math.ceil(range.limit * span / MS_PER_DAY) + 31 : void 0 };
|
|
39799
|
-
const
|
|
39800
|
-
return clampLimit(aggregateCalendar(
|
|
42173
|
+
const sub3 = await this.fetchCandles(product, 86400, subRange2);
|
|
42174
|
+
return clampLimit(aggregateCalendar(sub3, tf), range.limit);
|
|
39801
42175
|
}
|
|
39802
42176
|
const targetMin = /^\d+$/.test(tf) ? parseInt(tf, 10) : null;
|
|
39803
42177
|
const subMin = targetMin != null ? selectSubTf3(targetMin) : null;
|
|
@@ -39807,8 +42181,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39807
42181
|
}
|
|
39808
42182
|
const ratio = targetMin / subMin;
|
|
39809
42183
|
const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
|
|
39810
|
-
const
|
|
39811
|
-
return clampLimit(aggregate3(
|
|
42184
|
+
const sub2 = await this.fetchCandles(product, MIN_TO_GRAN[subMin], subRange);
|
|
42185
|
+
return clampLimit(aggregate3(sub2, targetMin * 6e4), range.limit);
|
|
39812
42186
|
} catch (e) {
|
|
39813
42187
|
console.warn(`[vela] Coinbase: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
39814
42188
|
return [];
|
|
@@ -40078,6 +42452,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
40078
42452
|
exports.BarStore = BarStore;
|
|
40079
42453
|
exports.BinanceProvider = BinanceProvider;
|
|
40080
42454
|
exports.CATEGORICAL = CATEGORICAL;
|
|
42455
|
+
exports.CHIP_PLATE = CHIP_PLATE;
|
|
40081
42456
|
exports.CROSSHAIR = CROSSHAIR;
|
|
40082
42457
|
exports.CachingDataFeed = CachingDataFeed;
|
|
40083
42458
|
exports.CoinbaseProvider = CoinbaseProvider;
|