@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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS } from './chunk-
|
|
2
|
-
import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, CalloutBubble, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-
|
|
3
|
-
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BEARISH, BULLISH, isDarkColor, SERIES_LINE,
|
|
1
|
+
import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS, stableSeriesId } from './chunk-I77F2SVE.js';
|
|
2
|
+
import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, CalloutBubble, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-MGU3USTP.js';
|
|
3
|
+
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BEARISH, BULLISH, isDarkColor, SERIES_LINE, CHIP_PLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, SLATE, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, INFO, svg24, svg24Solid } from './chunk-6DPAYEGO.js';
|
|
4
4
|
|
|
5
5
|
// src/core/events/EventBus.ts
|
|
6
6
|
var TypedEventBus = class {
|
|
@@ -468,6 +468,15 @@ var RendererControl = class {
|
|
|
468
468
|
console.warn(`[vela] renderer "${this.renderer.name}" does not support screenshot().`);
|
|
469
469
|
return null;
|
|
470
470
|
}
|
|
471
|
+
/**
|
|
472
|
+
* Raster of the current chart onto a canvas (same pixels as {@link screenshot}),
|
|
473
|
+
* or null if the renderer has no canvas export. Silent — a host compositing
|
|
474
|
+
* several charts skips a renderer that cannot contribute.
|
|
475
|
+
*/
|
|
476
|
+
screenshotCanvas() {
|
|
477
|
+
if (typeof this.renderer.screenshotCanvas === "function") return this.renderer.screenshotCanvas();
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
471
480
|
/**
|
|
472
481
|
* The active renderer's full cosmetic config as a serializable, versioned JSON
|
|
473
482
|
* document — persist it (templates, saved settings) and feed it back to
|
|
@@ -1127,7 +1136,7 @@ function attachForming(closed, forming) {
|
|
|
1127
1136
|
|
|
1128
1137
|
// src/data/MultiProviderFeed.ts
|
|
1129
1138
|
function intervalMs(timeframe) {
|
|
1130
|
-
const
|
|
1139
|
+
const map2 = {
|
|
1131
1140
|
"1m": 6e4,
|
|
1132
1141
|
"3m": 18e4,
|
|
1133
1142
|
"5m": 3e5,
|
|
@@ -1149,7 +1158,7 @@ function intervalMs(timeframe) {
|
|
|
1149
1158
|
W: 6048e5,
|
|
1150
1159
|
M: 2592e6
|
|
1151
1160
|
};
|
|
1152
|
-
return
|
|
1161
|
+
return map2[timeframe] ?? 36e5;
|
|
1153
1162
|
}
|
|
1154
1163
|
var MultiProviderFeed = class {
|
|
1155
1164
|
constructor(store = sharedBarStore) {
|
|
@@ -1536,8 +1545,8 @@ var DrawingsControl = class {
|
|
|
1536
1545
|
/** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
|
|
1537
1546
|
* display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
|
|
1538
1547
|
* platform formatting, so hints always match the host's actual bindings. */
|
|
1539
|
-
setToolShortcuts(
|
|
1540
|
-
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(
|
|
1548
|
+
setToolShortcuts(map2) {
|
|
1549
|
+
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
|
|
1541
1550
|
return this;
|
|
1542
1551
|
}
|
|
1543
1552
|
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
@@ -1978,8 +1987,8 @@ var DrawingController = class {
|
|
|
1978
1987
|
this.port?.setExternalGhost?.(doc);
|
|
1979
1988
|
}
|
|
1980
1989
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
1981
|
-
setToolShortcuts(
|
|
1982
|
-
this.port?.setToolShortcuts?.(
|
|
1990
|
+
setToolShortcuts(map2) {
|
|
1991
|
+
this.port?.setToolShortcuts?.(map2);
|
|
1983
1992
|
}
|
|
1984
1993
|
// ── programmatic CRUD (facade-facing) ── each is one undo step
|
|
1985
1994
|
add(type, init = {}) {
|
|
@@ -2466,8 +2475,8 @@ var DrawingToolbar = class {
|
|
|
2466
2475
|
}
|
|
2467
2476
|
/** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
|
|
2468
2477
|
* right edge of the flyout rows, beside the favorite star. */
|
|
2469
|
-
setShortcuts(
|
|
2470
|
-
this.shortcuts = new Map(Object.entries(
|
|
2478
|
+
setShortcuts(map2) {
|
|
2479
|
+
this.shortcuts = new Map(Object.entries(map2));
|
|
2471
2480
|
}
|
|
2472
2481
|
/** Paint one star for the current favorite state (filled + gold, or outline). */
|
|
2473
2482
|
paintStar(el, type) {
|
|
@@ -4318,8 +4327,8 @@ var InputsUI = class {
|
|
|
4318
4327
|
lg.style.top = `${top}px`;
|
|
4319
4328
|
}
|
|
4320
4329
|
/** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
|
|
4321
|
-
setCollapsedPanes(
|
|
4322
|
-
this.paneCollapse =
|
|
4330
|
+
setCollapsedPanes(map2) {
|
|
4331
|
+
this.paneCollapse = map2;
|
|
4323
4332
|
}
|
|
4324
4333
|
/**
|
|
4325
4334
|
* Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
|
|
@@ -5080,6 +5089,66 @@ function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
|
5080
5089
|
center: (wickLeftDev + wickDev / 2) / dpr
|
|
5081
5090
|
};
|
|
5082
5091
|
}
|
|
5092
|
+
function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
|
|
5093
|
+
const out = [];
|
|
5094
|
+
let col = NaN;
|
|
5095
|
+
let intervals = [];
|
|
5096
|
+
const flush = () => {
|
|
5097
|
+
if (intervals.length === 0) return;
|
|
5098
|
+
const merged = [intervals[0]];
|
|
5099
|
+
for (let k = 1; k < intervals.length; k += 1) {
|
|
5100
|
+
const prev2 = merged[merged.length - 1];
|
|
5101
|
+
const next = intervals[k];
|
|
5102
|
+
if (Math.abs(yOf(prev2.hi) - yOf(next.lo)) < 1) {
|
|
5103
|
+
prev2.hi = next.hi;
|
|
5104
|
+
if (next.headIdx < prev2.headIdx) {
|
|
5105
|
+
prev2.headIdx = next.headIdx;
|
|
5106
|
+
prev2.headTime = next.headTime;
|
|
5107
|
+
prev2.open = next.open;
|
|
5108
|
+
}
|
|
5109
|
+
if (next.lastIdx > prev2.lastIdx) {
|
|
5110
|
+
prev2.lastIdx = next.lastIdx;
|
|
5111
|
+
prev2.close = next.close;
|
|
5112
|
+
}
|
|
5113
|
+
} else {
|
|
5114
|
+
merged.push(next);
|
|
5115
|
+
}
|
|
5116
|
+
}
|
|
5117
|
+
for (const iv of merged) out.push({ x: col, hi: iv.hi, lo: iv.lo, headTime: iv.headTime, open: iv.open, close: iv.close });
|
|
5118
|
+
intervals = [];
|
|
5119
|
+
};
|
|
5120
|
+
for (let i = i0; i <= i1; i += 1) {
|
|
5121
|
+
const b = bars[i];
|
|
5122
|
+
if (!b || b.high <= b.low) continue;
|
|
5123
|
+
const x = Math.round(xOf(i));
|
|
5124
|
+
if (x !== col) {
|
|
5125
|
+
flush();
|
|
5126
|
+
col = x;
|
|
5127
|
+
}
|
|
5128
|
+
let lo = b.low;
|
|
5129
|
+
let hi = b.high;
|
|
5130
|
+
let headIdx = i;
|
|
5131
|
+
let headTime = b.time;
|
|
5132
|
+
let open = b.open;
|
|
5133
|
+
for (let k = intervals.length - 1; k >= 0; k -= 1) {
|
|
5134
|
+
const iv = intervals[k];
|
|
5135
|
+
if (iv.lo > hi || iv.hi < lo) continue;
|
|
5136
|
+
if (iv.lo < lo) lo = iv.lo;
|
|
5137
|
+
if (iv.hi > hi) hi = iv.hi;
|
|
5138
|
+
if (iv.headIdx < headIdx) {
|
|
5139
|
+
headIdx = iv.headIdx;
|
|
5140
|
+
headTime = iv.headTime;
|
|
5141
|
+
open = iv.open;
|
|
5142
|
+
}
|
|
5143
|
+
intervals.splice(k, 1);
|
|
5144
|
+
}
|
|
5145
|
+
let insertAt = 0;
|
|
5146
|
+
while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
|
|
5147
|
+
intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open, lastIdx: i, close: b.close });
|
|
5148
|
+
}
|
|
5149
|
+
flush();
|
|
5150
|
+
return out;
|
|
5151
|
+
}
|
|
5083
5152
|
|
|
5084
5153
|
// src/renderers/native/core/chartConfig.ts
|
|
5085
5154
|
var CHART_CONFIG_VERSION = 1;
|
|
@@ -5090,6 +5159,7 @@ var BASELINE_FILL_ALPHA_FAR = 0.05;
|
|
|
5090
5159
|
var BASELINE_LEVEL_DEFAULT = 50;
|
|
5091
5160
|
var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
|
|
5092
5161
|
var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
|
|
5162
|
+
var EXTENDED_SHADE = POSTMARKET_SHADE;
|
|
5093
5163
|
function defaultChartStyle() {
|
|
5094
5164
|
return {
|
|
5095
5165
|
chartTypes: {},
|
|
@@ -5098,7 +5168,7 @@ function defaultChartStyle() {
|
|
|
5098
5168
|
gridHorz: { visible: true, color: null },
|
|
5099
5169
|
borderColor: null,
|
|
5100
5170
|
separatorColor: null,
|
|
5101
|
-
crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground:
|
|
5171
|
+
crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: CHIP_PLATE },
|
|
5102
5172
|
candle: {
|
|
5103
5173
|
bodyVisible: true,
|
|
5104
5174
|
borderVisible: false,
|
|
@@ -5121,7 +5191,7 @@ function defaultChartStyle() {
|
|
|
5121
5191
|
width: 2,
|
|
5122
5192
|
baselineLevel: BASELINE_LEVEL_DEFAULT
|
|
5123
5193
|
},
|
|
5124
|
-
sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
|
|
5194
|
+
sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE, extendedColor: EXTENDED_SHADE }
|
|
5125
5195
|
};
|
|
5126
5196
|
}
|
|
5127
5197
|
var LINE_STYLES = ["solid", "dashed", "dotted"];
|
|
@@ -5221,9 +5291,9 @@ function factoryResetConfig(factory) {
|
|
|
5221
5291
|
if (inst.enableKey) defaults[inst.enableKey] = false;
|
|
5222
5292
|
addRows(inst.rows);
|
|
5223
5293
|
}
|
|
5224
|
-
for (const
|
|
5225
|
-
if (
|
|
5226
|
-
addRows(
|
|
5294
|
+
for (const sub2 of section.subsections ?? []) {
|
|
5295
|
+
if (sub2.enableKey) defaults[sub2.enableKey] = false;
|
|
5296
|
+
addRows(sub2.rows);
|
|
5227
5297
|
}
|
|
5228
5298
|
if (!section.instances) addRows(section.rows);
|
|
5229
5299
|
bag[t.id] = defaults;
|
|
@@ -5350,7 +5420,8 @@ function mergeConfig(base, patch) {
|
|
|
5350
5420
|
},
|
|
5351
5421
|
sessions: {
|
|
5352
5422
|
premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
|
|
5353
|
-
postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
|
|
5423
|
+
postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor,
|
|
5424
|
+
extendedColor: isColor(sessions.extendedColor) ? sessions.extendedColor : base.sessions.extendedColor
|
|
5354
5425
|
},
|
|
5355
5426
|
stacking: {
|
|
5356
5427
|
candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
|
|
@@ -6335,44 +6406,19 @@ var WebGL2Backend = class {
|
|
|
6335
6406
|
}
|
|
6336
6407
|
}
|
|
6337
6408
|
/**
|
|
6338
|
-
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated):
|
|
6339
|
-
*
|
|
6340
|
-
*
|
|
6409
|
+
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
|
|
6410
|
+
* rounded pixel column collapse into high-low sticks — one per contiguous
|
|
6411
|
+
* coverage run (see {@link aggregateCandleColumns}), so a price gap inside the
|
|
6412
|
+
* column stays a void. Draw cost stays bounded by screen width, not bar count;
|
|
6413
|
+
* stick color follows its own first-open→last-close.
|
|
6341
6414
|
*/
|
|
6342
6415
|
emitCandlesAggregated(b, bars, i0, i1, coords, pane, up, down, barColors) {
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
let headTime = 0;
|
|
6349
|
-
let active = false;
|
|
6350
|
-
const flush = () => {
|
|
6351
|
-
if (!active) return;
|
|
6352
|
-
const c = parseColor(barColors.get(headTime) ?? (closeV >= openV ? up : down));
|
|
6353
|
-
const hY = coords.priceToY(hi, pane.scale, pane.bounds);
|
|
6354
|
-
const lY = coords.priceToY(lo, pane.scale, pane.bounds);
|
|
6355
|
-
b.rect(px - 0.5, hY, 1, lY - hY, c);
|
|
6356
|
-
};
|
|
6357
|
-
for (let i = i0; i <= i1; i += 1) {
|
|
6358
|
-
const bar = bars[i];
|
|
6359
|
-
if (!bar || bar.high <= bar.low) continue;
|
|
6360
|
-
const x = Math.round(coords.logicalToX(i));
|
|
6361
|
-
if (!active || x !== px) {
|
|
6362
|
-
flush();
|
|
6363
|
-
px = x;
|
|
6364
|
-
lo = bar.low;
|
|
6365
|
-
hi = bar.high;
|
|
6366
|
-
openV = bar.open;
|
|
6367
|
-
headTime = bar.time;
|
|
6368
|
-
active = true;
|
|
6369
|
-
} else {
|
|
6370
|
-
if (bar.low < lo) lo = bar.low;
|
|
6371
|
-
if (bar.high > hi) hi = bar.high;
|
|
6372
|
-
}
|
|
6373
|
-
closeV = bar.close;
|
|
6416
|
+
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
6417
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
6418
|
+
const c = parseColor(barColors.get(s.headTime) ?? (s.close >= s.open ? up : down));
|
|
6419
|
+
const hY = yOf(s.hi);
|
|
6420
|
+
b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
|
|
6374
6421
|
}
|
|
6375
|
-
flush();
|
|
6376
6422
|
}
|
|
6377
6423
|
emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
6378
6424
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
@@ -6576,11 +6622,11 @@ function mergeBarColors(indicators) {
|
|
|
6576
6622
|
}
|
|
6577
6623
|
}
|
|
6578
6624
|
if (!any) return EMPTY_BARCOLORS;
|
|
6579
|
-
const
|
|
6625
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
6580
6626
|
for (const m of indicators.values()) {
|
|
6581
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
6627
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
6582
6628
|
}
|
|
6583
|
-
return
|
|
6629
|
+
return map2;
|
|
6584
6630
|
}
|
|
6585
6631
|
|
|
6586
6632
|
// src/renderers/native/core/ViewportState.ts
|
|
@@ -6877,9 +6923,9 @@ function isHorizontalWheel(deltaX, deltaY) {
|
|
|
6877
6923
|
function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
|
|
6878
6924
|
return rightOffset + deltaX / barSpacing;
|
|
6879
6925
|
}
|
|
6880
|
-
function wheelPanDelta(deltaX, deltaY,
|
|
6926
|
+
function wheelPanDelta(deltaX, deltaY, shift2) {
|
|
6881
6927
|
if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
|
|
6882
|
-
if (
|
|
6928
|
+
if (shift2) return deltaY;
|
|
6883
6929
|
return null;
|
|
6884
6930
|
}
|
|
6885
6931
|
function effectiveSnapMode(momentaryOverride, sticky) {
|
|
@@ -7670,13 +7716,15 @@ var SceneGraph = class {
|
|
|
7670
7716
|
if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
|
|
7671
7717
|
return this.orderedCache;
|
|
7672
7718
|
}
|
|
7673
|
-
/** The session zones resolved into colored bands (pre/post-market
|
|
7674
|
-
* config's session colors) — consumed by the same painting path as
|
|
7719
|
+
/** The session zones resolved into colored bands (pre/post-market or extended-hours
|
|
7720
|
+
* washes from the config's session colors) — consumed by the same painting path as
|
|
7721
|
+
* {@link highlights}. */
|
|
7675
7722
|
sessionHighlightBands() {
|
|
7676
7723
|
if (!this.sessionZones) return [];
|
|
7677
7724
|
const out = [];
|
|
7678
7725
|
for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
|
|
7679
7726
|
for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
|
|
7727
|
+
for (const [from, to] of this.sessionZones.extended) out.push({ from, to, color: this.style.sessions.extendedColor });
|
|
7680
7728
|
return out;
|
|
7681
7729
|
}
|
|
7682
7730
|
indicatorsForPane(paneId) {
|
|
@@ -8215,50 +8263,23 @@ var Canvas2dBackend = class {
|
|
|
8215
8263
|
}
|
|
8216
8264
|
}
|
|
8217
8265
|
/**
|
|
8218
|
-
* Sub-pixel LOD:
|
|
8219
|
-
*
|
|
8220
|
-
*
|
|
8221
|
-
*
|
|
8266
|
+
* Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
|
|
8267
|
+
* sticks (one per contiguous coverage run — see {@link aggregateCandleColumns}),
|
|
8268
|
+
* so draw cost is bounded by screen width (not bar count) when zoomed far out
|
|
8269
|
+
* and a price gap inside the column stays a void. Each stick's color follows
|
|
8270
|
+
* its first-open→last-close direction (a barcolor() on the head bar still wins).
|
|
8222
8271
|
*/
|
|
8223
8272
|
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, up, down, barColors) {
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
let headTime = 0;
|
|
8230
|
-
let active = false;
|
|
8231
|
-
const flush = () => {
|
|
8232
|
-
if (!active) return;
|
|
8233
|
-
const color = barColors.get(headTime) ?? (closeV >= openV ? up : down);
|
|
8234
|
-
const hY = coords.priceToY(hi, pane.scale, pane.bounds);
|
|
8235
|
-
const lY = coords.priceToY(lo, pane.scale, pane.bounds);
|
|
8236
|
-
ctx.strokeStyle = color;
|
|
8237
|
-
ctx.lineWidth = 1;
|
|
8273
|
+
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
8274
|
+
ctx.lineWidth = 1;
|
|
8275
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
8276
|
+
const x = s.x + 0.5;
|
|
8277
|
+
ctx.strokeStyle = barColors.get(s.headTime) ?? (s.close >= s.open ? up : down);
|
|
8238
8278
|
ctx.beginPath();
|
|
8239
|
-
ctx.moveTo(
|
|
8240
|
-
ctx.lineTo(
|
|
8279
|
+
ctx.moveTo(x, yOf(s.hi));
|
|
8280
|
+
ctx.lineTo(x, yOf(s.lo));
|
|
8241
8281
|
ctx.stroke();
|
|
8242
|
-
};
|
|
8243
|
-
for (let i = i0; i <= i1; i += 1) {
|
|
8244
|
-
const b = bars[i];
|
|
8245
|
-
if (!b || b.high <= b.low) continue;
|
|
8246
|
-
const x = Math.round(coords.logicalToX(i)) + 0.5;
|
|
8247
|
-
if (!active || x !== px) {
|
|
8248
|
-
flush();
|
|
8249
|
-
px = x;
|
|
8250
|
-
lo = b.low;
|
|
8251
|
-
hi = b.high;
|
|
8252
|
-
openV = b.open;
|
|
8253
|
-
headTime = b.time;
|
|
8254
|
-
active = true;
|
|
8255
|
-
} else {
|
|
8256
|
-
if (b.low < lo) lo = b.low;
|
|
8257
|
-
if (b.high > hi) hi = b.high;
|
|
8258
|
-
}
|
|
8259
|
-
closeV = b.close;
|
|
8260
8282
|
}
|
|
8261
|
-
flush();
|
|
8262
8283
|
}
|
|
8263
8284
|
drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
8264
8285
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
@@ -8526,11 +8547,11 @@ function mergeBarColors2(indicators) {
|
|
|
8526
8547
|
}
|
|
8527
8548
|
}
|
|
8528
8549
|
if (!any) return EMPTY_BARCOLORS2;
|
|
8529
|
-
const
|
|
8550
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
8530
8551
|
for (const m of indicators.values()) {
|
|
8531
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
8552
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
8532
8553
|
}
|
|
8533
|
-
return
|
|
8554
|
+
return map2;
|
|
8534
8555
|
}
|
|
8535
8556
|
|
|
8536
8557
|
// src/renderers/shared/drawing-geometry.ts
|
|
@@ -9282,6 +9303,32 @@ var DrawingSceneRenderer = class {
|
|
|
9282
9303
|
}
|
|
9283
9304
|
};
|
|
9284
9305
|
|
|
9306
|
+
// src/renderers/native/chrome/tz.ts
|
|
9307
|
+
function tzOffsetMs(ms, timeZone) {
|
|
9308
|
+
if (!timeZone || timeZone === "UTC") return 0;
|
|
9309
|
+
try {
|
|
9310
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
9311
|
+
timeZone,
|
|
9312
|
+
hourCycle: "h23",
|
|
9313
|
+
year: "numeric",
|
|
9314
|
+
month: "2-digit",
|
|
9315
|
+
day: "2-digit",
|
|
9316
|
+
hour: "2-digit",
|
|
9317
|
+
minute: "2-digit",
|
|
9318
|
+
second: "2-digit"
|
|
9319
|
+
});
|
|
9320
|
+
const parts = dtf.formatToParts(new Date(ms));
|
|
9321
|
+
const get = (t) => Number(parts.find((p) => p.type === t)?.value);
|
|
9322
|
+
const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
|
|
9323
|
+
return asUTC - ms;
|
|
9324
|
+
} catch {
|
|
9325
|
+
return 0;
|
|
9326
|
+
}
|
|
9327
|
+
}
|
|
9328
|
+
function zonedDate(ms, timeZone) {
|
|
9329
|
+
return new Date(ms + tzOffsetMs(ms, timeZone));
|
|
9330
|
+
}
|
|
9331
|
+
|
|
9285
9332
|
// src/renderers/native/chrome/ticks.ts
|
|
9286
9333
|
function priceTicks(min, max, target = 6) {
|
|
9287
9334
|
if (!(max > min) || !Number.isFinite(min) || !Number.isFinite(max)) return [];
|
|
@@ -9429,7 +9476,14 @@ var STEP_LADDER = [
|
|
|
9429
9476
|
YEAR
|
|
9430
9477
|
];
|
|
9431
9478
|
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
9479
|
+
var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
9432
9480
|
var pad2 = (n) => n < 10 ? `0${n}` : String(n);
|
|
9481
|
+
function formatTimeStamp(ms, timeZone, barIntervalMs) {
|
|
9482
|
+
const d = zonedDate(ms, timeZone);
|
|
9483
|
+
const date = `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} '${pad2(d.getUTCFullYear() % 100)}`;
|
|
9484
|
+
if (barIntervalMs >= DAY) return date;
|
|
9485
|
+
return `${date} ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}`;
|
|
9486
|
+
}
|
|
9433
9487
|
function pickStep(targetMs) {
|
|
9434
9488
|
for (const step of STEP_LADDER) if (step >= targetMs) return step;
|
|
9435
9489
|
return STEP_LADDER[STEP_LADDER.length - 1];
|
|
@@ -9471,32 +9525,6 @@ function timeTicks(fromMs, toMs, target = 8, offsetMs = 0) {
|
|
|
9471
9525
|
return out;
|
|
9472
9526
|
}
|
|
9473
9527
|
|
|
9474
|
-
// src/renderers/native/chrome/tz.ts
|
|
9475
|
-
function tzOffsetMs(ms, timeZone) {
|
|
9476
|
-
if (!timeZone || timeZone === "UTC") return 0;
|
|
9477
|
-
try {
|
|
9478
|
-
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
9479
|
-
timeZone,
|
|
9480
|
-
hourCycle: "h23",
|
|
9481
|
-
year: "numeric",
|
|
9482
|
-
month: "2-digit",
|
|
9483
|
-
day: "2-digit",
|
|
9484
|
-
hour: "2-digit",
|
|
9485
|
-
minute: "2-digit",
|
|
9486
|
-
second: "2-digit"
|
|
9487
|
-
});
|
|
9488
|
-
const parts = dtf.formatToParts(new Date(ms));
|
|
9489
|
-
const get = (t) => Number(parts.find((p) => p.type === t)?.value);
|
|
9490
|
-
const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
|
|
9491
|
-
return asUTC - ms;
|
|
9492
|
-
} catch {
|
|
9493
|
-
return 0;
|
|
9494
|
-
}
|
|
9495
|
-
}
|
|
9496
|
-
function zonedDate(ms, timeZone) {
|
|
9497
|
-
return new Date(ms + tzOffsetMs(ms, timeZone));
|
|
9498
|
-
}
|
|
9499
|
-
|
|
9500
9528
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
9501
9529
|
var ChromeRenderer = class {
|
|
9502
9530
|
constructor() {
|
|
@@ -9943,7 +9971,7 @@ var CrosshairRenderer = class {
|
|
|
9943
9971
|
const price = coords.yToPrice(ch.y, pane.scale, pane.bounds);
|
|
9944
9972
|
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);
|
|
9945
9973
|
}
|
|
9946
|
-
this.chip(ctx, x, dataH + 1,
|
|
9974
|
+
this.chip(ctx, x, dataH + 1, formatTimeStamp(coords.logicalToTime(logical), scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
9947
9975
|
}
|
|
9948
9976
|
destroy() {
|
|
9949
9977
|
this.canvas = null;
|
|
@@ -9991,7 +10019,7 @@ var CrosshairRenderer = class {
|
|
|
9991
10019
|
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);
|
|
9992
10020
|
}
|
|
9993
10021
|
}
|
|
9994
|
-
this.chip(ctx, x, dataH + 1,
|
|
10022
|
+
this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
9995
10023
|
ctx.globalAlpha = 1;
|
|
9996
10024
|
}
|
|
9997
10025
|
/** A soft band + a brighter crisp center line over the hovered separator, so it reads as
|
|
@@ -10024,11 +10052,6 @@ function setDash3(ctx, style) {
|
|
|
10024
10052
|
else if (style === "dotted") ctx.setLineDash([2, 3]);
|
|
10025
10053
|
else ctx.setLineDash([]);
|
|
10026
10054
|
}
|
|
10027
|
-
function formatStamp(ms, timeZone) {
|
|
10028
|
-
const d = zonedDate(ms, timeZone);
|
|
10029
|
-
const pad = (n) => n < 10 ? `0${n}` : String(n);
|
|
10030
|
-
return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
10031
|
-
}
|
|
10032
10055
|
|
|
10033
10056
|
// src/renderers/native/chrome/settings-visibility.ts
|
|
10034
10057
|
function settingsIdSlug(label) {
|
|
@@ -10167,9 +10190,9 @@ function settingsIdCatalog(hostSections) {
|
|
|
10167
10190
|
};
|
|
10168
10191
|
if (section.rows) addRows(section.rows);
|
|
10169
10192
|
for (const inst of section.instances ?? []) addRows(inst.rows);
|
|
10170
|
-
for (const
|
|
10171
|
-
ids.add(`${scope}.${settingsIdSlug(
|
|
10172
|
-
addRows(
|
|
10193
|
+
for (const sub2 of section.subsections ?? []) {
|
|
10194
|
+
ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
|
|
10195
|
+
addRows(sub2.rows);
|
|
10173
10196
|
}
|
|
10174
10197
|
}
|
|
10175
10198
|
for (const hs of hostSections) {
|
|
@@ -10728,14 +10751,14 @@ var SettingsDialog = class {
|
|
|
10728
10751
|
};
|
|
10729
10752
|
if (section.rows) seed(section.rows);
|
|
10730
10753
|
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
10731
|
-
for (const
|
|
10754
|
+
for (const sub2 of section.subsections ?? []) seed(sub2.rows);
|
|
10732
10755
|
for (const inst of section.instances ?? []) {
|
|
10733
10756
|
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
10734
10757
|
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
10735
10758
|
}
|
|
10736
|
-
for (const
|
|
10737
|
-
if (!
|
|
10738
|
-
bag[
|
|
10759
|
+
for (const sub2 of section.subsections ?? []) {
|
|
10760
|
+
if (!sub2.enableKey || sub2.enableKey in bag) continue;
|
|
10761
|
+
bag[sub2.enableKey] = values[sub2.enableKey] === true;
|
|
10739
10762
|
}
|
|
10740
10763
|
const refreshers = [];
|
|
10741
10764
|
const put = (key, v) => {
|
|
@@ -10752,16 +10775,16 @@ var SettingsDialog = class {
|
|
|
10752
10775
|
if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
|
|
10753
10776
|
else this.flatTypeRows(rows, bag, put, refreshers, body);
|
|
10754
10777
|
}
|
|
10755
|
-
for (const
|
|
10756
|
-
if (settingsIdHidden(`${scope}.${settingsIdSlug(
|
|
10757
|
-
const rows = filterHiddenRows(
|
|
10778
|
+
for (const sub2 of section.subsections ?? []) {
|
|
10779
|
+
if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
|
|
10780
|
+
const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
|
|
10758
10781
|
if (rows.length === 0) continue;
|
|
10759
|
-
const subMarker = this.section(
|
|
10782
|
+
const subMarker = this.section(sub2.title);
|
|
10760
10783
|
subMarker.dataset.sdStyle = typeId;
|
|
10761
10784
|
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
10762
10785
|
subMarker.dataset.sdSub = "1";
|
|
10763
10786
|
body.append(subMarker);
|
|
10764
|
-
body.append(this.groupedRows(`${typeId}/${
|
|
10787
|
+
body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
|
|
10765
10788
|
}
|
|
10766
10789
|
for (const r of refreshers) r();
|
|
10767
10790
|
}
|
|
@@ -12868,11 +12891,11 @@ var DrawingInteraction = class {
|
|
|
12868
12891
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
12869
12892
|
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
12870
12893
|
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
12871
|
-
down(x, y, mode = "off",
|
|
12894
|
+
down(x, y, mode = "off", shift2 = false) {
|
|
12872
12895
|
const proj = this.deps.projector();
|
|
12873
12896
|
if (this.state.kind === "placing") {
|
|
12874
12897
|
if (this.state.draft.placementMode() !== "click") return;
|
|
12875
|
-
const pivot =
|
|
12898
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
12876
12899
|
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
12877
12900
|
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
12878
12901
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
@@ -12891,7 +12914,7 @@ var DrawingInteraction = class {
|
|
|
12891
12914
|
}
|
|
12892
12915
|
const hit = this.hitAt(x, y);
|
|
12893
12916
|
if (hit) {
|
|
12894
|
-
if (
|
|
12917
|
+
if (shift2) {
|
|
12895
12918
|
this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
|
|
12896
12919
|
return;
|
|
12897
12920
|
}
|
|
@@ -12901,7 +12924,7 @@ var DrawingInteraction = class {
|
|
|
12901
12924
|
}
|
|
12902
12925
|
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
12903
12926
|
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
12904
|
-
move(x, y, mode = "off",
|
|
12927
|
+
move(x, y, mode = "off", shift2 = false) {
|
|
12905
12928
|
if (this.state.kind === "idle") {
|
|
12906
12929
|
const tool = this.deps.activeTool();
|
|
12907
12930
|
const had = this.snapAt != null;
|
|
@@ -12915,7 +12938,7 @@ var DrawingInteraction = class {
|
|
|
12915
12938
|
this.captureFreehand(x, y);
|
|
12916
12939
|
return;
|
|
12917
12940
|
}
|
|
12918
|
-
const pivot =
|
|
12941
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
12919
12942
|
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
12920
12943
|
this.deps.changed();
|
|
12921
12944
|
return;
|
|
@@ -12928,7 +12951,7 @@ var DrawingInteraction = class {
|
|
|
12928
12951
|
if (this.state.handle >= 0) {
|
|
12929
12952
|
const a = d.anchors[this.state.handle];
|
|
12930
12953
|
if (a) {
|
|
12931
|
-
const pivot =
|
|
12954
|
+
const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
12932
12955
|
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
12933
12956
|
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
12934
12957
|
}
|
|
@@ -13626,21 +13649,21 @@ var DrawingSettingsPopup = class {
|
|
|
13626
13649
|
bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
|
|
13627
13650
|
bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
|
|
13628
13651
|
}
|
|
13629
|
-
const
|
|
13630
|
-
if (paths.has("vwap.midColor") &&
|
|
13652
|
+
const vwap2 = drawing.vwap;
|
|
13653
|
+
if (paths.has("vwap.midColor") && vwap2) {
|
|
13631
13654
|
const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
|
|
13632
13655
|
const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
|
|
13633
|
-
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON,
|
|
13634
|
-
bar.appendChild(this.dropdown("VWAP style", styles,
|
|
13656
|
+
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
|
|
13657
|
+
bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
|
|
13635
13658
|
bar.appendChild(
|
|
13636
|
-
this.dropdown("Band multiplier", MULTS,
|
|
13659
|
+
this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
|
|
13637
13660
|
label: (v) => `${v}\u03C3`,
|
|
13638
13661
|
labelInTrigger: true
|
|
13639
13662
|
})
|
|
13640
13663
|
);
|
|
13641
|
-
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON,
|
|
13642
|
-
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON,
|
|
13643
|
-
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON,
|
|
13664
|
+
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
|
|
13665
|
+
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
|
|
13666
|
+
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
|
|
13644
13667
|
}
|
|
13645
13668
|
if (paths.has("maxCurvature")) {
|
|
13646
13669
|
const cur = drawing.maxCurvature ?? 24;
|
|
@@ -14657,8 +14680,8 @@ var UserDrawingController = class {
|
|
|
14657
14680
|
this.toolbar.setFavorites(types);
|
|
14658
14681
|
}
|
|
14659
14682
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
14660
|
-
setToolShortcuts(
|
|
14661
|
-
this.toolbar.setShortcuts(
|
|
14683
|
+
setToolShortcuts(map2) {
|
|
14684
|
+
this.toolbar.setShortcuts(map2);
|
|
14662
14685
|
}
|
|
14663
14686
|
/** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
|
|
14664
14687
|
* in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
|
|
@@ -14776,7 +14799,7 @@ var UserDrawingController = class {
|
|
|
14776
14799
|
this.render();
|
|
14777
14800
|
}
|
|
14778
14801
|
}
|
|
14779
|
-
pointerDown(x, y, snap = "off",
|
|
14802
|
+
pointerDown(x, y, snap = "off", shift2 = false) {
|
|
14780
14803
|
if (this.eraserMode) {
|
|
14781
14804
|
this.erasing = true;
|
|
14782
14805
|
this.deleteAt(x, y);
|
|
@@ -14796,9 +14819,9 @@ var UserDrawingController = class {
|
|
|
14796
14819
|
return;
|
|
14797
14820
|
}
|
|
14798
14821
|
}
|
|
14799
|
-
this.interaction.down(x, y, snap,
|
|
14822
|
+
this.interaction.down(x, y, snap, shift2);
|
|
14800
14823
|
}
|
|
14801
|
-
pointerMove(x, y, snap = "off",
|
|
14824
|
+
pointerMove(x, y, snap = "off", shift2 = false) {
|
|
14802
14825
|
if (this.eraserMode) {
|
|
14803
14826
|
if (this.erasing) this.deleteAt(x, y);
|
|
14804
14827
|
return;
|
|
@@ -14809,7 +14832,7 @@ var UserDrawingController = class {
|
|
|
14809
14832
|
this.render();
|
|
14810
14833
|
return;
|
|
14811
14834
|
}
|
|
14812
|
-
this.interaction.move(x, y, snap,
|
|
14835
|
+
this.interaction.move(x, y, snap, shift2);
|
|
14813
14836
|
this.updateHover(x, y);
|
|
14814
14837
|
}
|
|
14815
14838
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -15486,14 +15509,14 @@ function layoutTable(ctx, t, args) {
|
|
|
15486
15509
|
}
|
|
15487
15510
|
for (const { box, w: w2, h: h2 } of spanning) {
|
|
15488
15511
|
if (box.cs > 1) {
|
|
15489
|
-
let
|
|
15490
|
-
for (let c = box.c; c < box.c + box.cs; c += 1)
|
|
15491
|
-
if (w2 >
|
|
15512
|
+
let sum2 = 0;
|
|
15513
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
|
|
15514
|
+
if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
|
|
15492
15515
|
}
|
|
15493
15516
|
if (box.rs > 1) {
|
|
15494
|
-
let
|
|
15495
|
-
for (let r = box.r; r < box.r + box.rs; r += 1)
|
|
15496
|
-
if (h2 >
|
|
15517
|
+
let sum2 = 0;
|
|
15518
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
|
|
15519
|
+
if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
|
|
15497
15520
|
}
|
|
15498
15521
|
}
|
|
15499
15522
|
let w = 0;
|
|
@@ -15807,6 +15830,9 @@ function clipHighlightRect(x1, x2, left, right) {
|
|
|
15807
15830
|
const end = Math.min(right, x2);
|
|
15808
15831
|
return end > x ? { x, width: end - x } : null;
|
|
15809
15832
|
}
|
|
15833
|
+
function bandEdgeSlot(logical) {
|
|
15834
|
+
return Math.ceil(logical) - 0.5;
|
|
15835
|
+
}
|
|
15810
15836
|
var BackdropRenderer = class {
|
|
15811
15837
|
constructor() {
|
|
15812
15838
|
this.canvas = null;
|
|
@@ -15837,21 +15863,22 @@ var BackdropRenderer = class {
|
|
|
15837
15863
|
this.drawGrid(ctx, scene, coords, theme, coords.width, gridAlpha);
|
|
15838
15864
|
}
|
|
15839
15865
|
/** Renderer-owned session highlight bands: full-height (all panes), behind the grid.
|
|
15840
|
-
* Session-zone washes
|
|
15866
|
+
* Session-zone washes paint first (edges snapped to bar-slot boundaries so a band
|
|
15867
|
+
* never cuts a candle in two), host highlights on top (continuous time mapping —
|
|
15868
|
+
* hosts may paint sub-bar spans). */
|
|
15841
15869
|
drawHighlights(ctx, scene, coords) {
|
|
15842
15870
|
const sessions = scene.sessionHighlightBands();
|
|
15843
15871
|
if (sessions.length > 0) {
|
|
15844
15872
|
const left = Math.max(0, coords.logicalToX(-0.5));
|
|
15845
15873
|
const right = Math.min(coords.width, coords.logicalToX(coords.barCount - 0.5));
|
|
15846
|
-
this.drawHighlightSet(ctx, sessions, coords, left, right);
|
|
15874
|
+
this.drawHighlightSet(ctx, sessions, coords, left, right, true);
|
|
15847
15875
|
}
|
|
15848
|
-
this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width);
|
|
15876
|
+
this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width, false);
|
|
15849
15877
|
}
|
|
15850
|
-
drawHighlightSet(ctx, bands, coords, left, right) {
|
|
15878
|
+
drawHighlightSet(ctx, bands, coords, left, right, snapToSlots) {
|
|
15879
|
+
const edgeX = (ms) => snapToSlots ? coords.logicalToX(bandEdgeSlot(coords.timeToLogical(ms))) : coords.timeToX(ms);
|
|
15851
15880
|
for (const band of bands) {
|
|
15852
|
-
const
|
|
15853
|
-
const x2 = coords.timeToX(band.to);
|
|
15854
|
-
const rect = clipHighlightRect(x1, x2, left, right);
|
|
15881
|
+
const rect = clipHighlightRect(edgeX(band.from), edgeX(band.to), left, right);
|
|
15855
15882
|
if (!rect) continue;
|
|
15856
15883
|
ctx.fillStyle = band.color;
|
|
15857
15884
|
ctx.fillRect(rect.x, 0, rect.width, coords.height);
|
|
@@ -16084,6 +16111,7 @@ function createCustomMark(doc, html, background) {
|
|
|
16084
16111
|
});
|
|
16085
16112
|
el.style.color = attributionMarkColor(background);
|
|
16086
16113
|
el.innerHTML = html;
|
|
16114
|
+
el.dataset.velaScreenshot = "1";
|
|
16087
16115
|
return el;
|
|
16088
16116
|
}
|
|
16089
16117
|
function createAttributionMark(doc, background) {
|
|
@@ -16102,6 +16130,7 @@ function createAttributionMark(doc, background) {
|
|
|
16102
16130
|
cursor: "pointer"
|
|
16103
16131
|
});
|
|
16104
16132
|
applyAttributionMarkTheme(a, background);
|
|
16133
|
+
a.dataset.velaScreenshot = "1";
|
|
16105
16134
|
const symbol = doc.createElement("span");
|
|
16106
16135
|
symbol.className = "vela-attr-symbol";
|
|
16107
16136
|
symbol.setAttribute("aria-hidden", "true");
|
|
@@ -16184,24 +16213,24 @@ function rasterizeOverlay(ctx, root, frame) {
|
|
|
16184
16213
|
drawSvgShapes(el, alpha);
|
|
16185
16214
|
continue;
|
|
16186
16215
|
}
|
|
16187
|
-
const
|
|
16216
|
+
const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
|
|
16188
16217
|
let path = null;
|
|
16189
16218
|
if (tag === "path") {
|
|
16190
16219
|
const d = el.getAttribute("d");
|
|
16191
16220
|
if (d) path = new Path2D(d);
|
|
16192
16221
|
} else if (tag === "circle") {
|
|
16193
16222
|
path = new Path2D();
|
|
16194
|
-
path.arc(
|
|
16223
|
+
path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
|
|
16195
16224
|
} else if (tag === "ellipse") {
|
|
16196
16225
|
path = new Path2D();
|
|
16197
|
-
path.ellipse(
|
|
16226
|
+
path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
|
|
16198
16227
|
} else if (tag === "rect") {
|
|
16199
16228
|
path = new Path2D();
|
|
16200
|
-
path.rect(
|
|
16229
|
+
path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
|
|
16201
16230
|
} else if (tag === "line") {
|
|
16202
16231
|
path = new Path2D();
|
|
16203
|
-
path.moveTo(
|
|
16204
|
-
path.lineTo(
|
|
16232
|
+
path.moveTo(num4("x1"), num4("y1"));
|
|
16233
|
+
path.lineTo(num4("x2"), num4("y2"));
|
|
16205
16234
|
} else if (tag === "polyline" || tag === "polygon") {
|
|
16206
16235
|
const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
|
|
16207
16236
|
if (pts.length < 4 || pts.some(Number.isNaN)) continue;
|
|
@@ -17106,7 +17135,7 @@ var NativeRenderer = class {
|
|
|
17106
17135
|
};
|
|
17107
17136
|
})(),
|
|
17108
17137
|
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
|
|
17109
|
-
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
|
|
17138
|
+
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor, extendedColor: s.sessions.extendedColor }
|
|
17110
17139
|
};
|
|
17111
17140
|
}
|
|
17112
17141
|
/**
|
|
@@ -17188,7 +17217,7 @@ var NativeRenderer = class {
|
|
|
17188
17217
|
width: next.baseline.width,
|
|
17189
17218
|
baselineLevel: next.baseline.baselineLevel
|
|
17190
17219
|
};
|
|
17191
|
-
s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
|
|
17220
|
+
s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor, extendedColor: next.sessions.extendedColor };
|
|
17192
17221
|
this.setPriceStyle(next.series.style);
|
|
17193
17222
|
this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
|
|
17194
17223
|
this.scene.baselineValue = next.series.baseline;
|
|
@@ -17410,9 +17439,10 @@ var NativeRenderer = class {
|
|
|
17410
17439
|
* opts in with a `data-vela-screenshot` attribute on the mount container's
|
|
17411
17440
|
* subtree (the widget marks its status line, and its symbol watermark with
|
|
17412
17441
|
* `"under"` — drawn beneath the canvases, where it sits on screen). Only the
|
|
17413
|
-
* crosshair (L2) is intentionally excluded.
|
|
17442
|
+
* crosshair (L2) is intentionally excluded. {@link screenshot} is this canvas
|
|
17443
|
+
* as a PNG data URL.
|
|
17414
17444
|
*/
|
|
17415
|
-
|
|
17445
|
+
screenshotCanvas() {
|
|
17416
17446
|
if (!this.dataCanvas) return null;
|
|
17417
17447
|
this.computeScales();
|
|
17418
17448
|
this.paintData();
|
|
@@ -17440,7 +17470,10 @@ var NativeRenderer = class {
|
|
|
17440
17470
|
if (el.getAttribute("data-vela-screenshot") !== "under") rasterizeOverlay(ctx, el, frame);
|
|
17441
17471
|
}
|
|
17442
17472
|
}
|
|
17443
|
-
return out
|
|
17473
|
+
return out;
|
|
17474
|
+
}
|
|
17475
|
+
screenshot() {
|
|
17476
|
+
return this.screenshotCanvas()?.toDataURL("image/png") ?? null;
|
|
17444
17477
|
}
|
|
17445
17478
|
/**
|
|
17446
17479
|
* The candles-first-appear reveal — animates the **candles themselves** (no extra
|
|
@@ -17614,8 +17647,8 @@ var NativeRenderer = class {
|
|
|
17614
17647
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
17615
17648
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
17616
17649
|
drawingsSnapMode: () => this.snapMode,
|
|
17617
|
-
drawingsPointerDown: (x, y, snap,
|
|
17618
|
-
drawingsPointerMove: (x, y, snap,
|
|
17650
|
+
drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
|
|
17651
|
+
drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
|
|
17619
17652
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
17620
17653
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
17621
17654
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -17932,18 +17965,18 @@ var NativeRenderer = class {
|
|
|
17932
17965
|
for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
|
|
17933
17966
|
if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
|
|
17934
17967
|
const newHeadTime = this.bars[0].time;
|
|
17935
|
-
let
|
|
17968
|
+
let shift2 = 0;
|
|
17936
17969
|
if (newHeadTime !== prevHeadTime) {
|
|
17937
17970
|
const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
|
|
17938
|
-
if (prepended > 0)
|
|
17971
|
+
if (prepended > 0) shift2 = prepended;
|
|
17939
17972
|
else {
|
|
17940
17973
|
const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
|
|
17941
|
-
if (trimmed > 0)
|
|
17974
|
+
if (trimmed > 0) shift2 = -trimmed;
|
|
17942
17975
|
}
|
|
17943
17976
|
}
|
|
17944
|
-
if (
|
|
17945
|
-
this.zoomAnchorLogical +=
|
|
17946
|
-
if (this.hoverLogical != null) this.hoverLogical +=
|
|
17977
|
+
if (shift2 !== 0) {
|
|
17978
|
+
this.zoomAnchorLogical += shift2;
|
|
17979
|
+
if (this.hoverLogical != null) this.hoverLogical += shift2;
|
|
17947
17980
|
}
|
|
17948
17981
|
}
|
|
17949
17982
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
@@ -18835,7 +18868,7 @@ var NativeRenderer = class {
|
|
|
18835
18868
|
const pricePane = this.dataWindowPricePane();
|
|
18836
18869
|
const ohlc = this.dataWindowOHLC(bar, pricePane);
|
|
18837
18870
|
const parts = [];
|
|
18838
|
-
if (bar) parts.push(
|
|
18871
|
+
if (bar) parts.push(formatStamp(bar.time, this.scene.timezone));
|
|
18839
18872
|
if (ohlc) parts.push(`Open ${ohlc.o}, High ${ohlc.h}, Low ${ohlc.l}, Close ${ohlc.c}`);
|
|
18840
18873
|
for (const group of this.dataWindowGroups(idx, pricePane)) {
|
|
18841
18874
|
for (const row of group.rows) parts.push(`${row.label} ${row.value}`);
|
|
@@ -19549,15 +19582,15 @@ var NativeRenderer = class {
|
|
|
19549
19582
|
/** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
|
|
19550
19583
|
* strip — merged own-scale indicators are hidden while collapsed. */
|
|
19551
19584
|
collapsedMasterMap() {
|
|
19552
|
-
const
|
|
19585
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
19553
19586
|
for (const pane of this.scene.panes.values()) {
|
|
19554
19587
|
if (!pane.collapsed) continue;
|
|
19555
19588
|
const models = this.scene.orderedIndicatorsForPane(pane.id);
|
|
19556
19589
|
const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
|
|
19557
19590
|
const master = models.find((m) => !merged.has(m.id)) ?? models[0];
|
|
19558
|
-
|
|
19591
|
+
map2.set(pane.id, master?.id ?? null);
|
|
19559
19592
|
}
|
|
19560
|
-
return
|
|
19593
|
+
return map2;
|
|
19561
19594
|
}
|
|
19562
19595
|
/** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
|
|
19563
19596
|
setToolbarGutter(px) {
|
|
@@ -19722,7 +19755,7 @@ function formatStampParts(ms, timeZone) {
|
|
|
19722
19755
|
time: `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`
|
|
19723
19756
|
};
|
|
19724
19757
|
}
|
|
19725
|
-
function
|
|
19758
|
+
function formatStamp(ms, timeZone) {
|
|
19726
19759
|
const { date, time } = formatStampParts(ms, timeZone);
|
|
19727
19760
|
return `${date} ${time}`;
|
|
19728
19761
|
}
|
|
@@ -19753,7 +19786,7 @@ function sanitizeSessionZones(value) {
|
|
|
19753
19786
|
}
|
|
19754
19787
|
return out.sort((a, b) => a[0] - b[0]);
|
|
19755
19788
|
};
|
|
19756
|
-
return { pre: windows(v.pre), post: windows(v.post) };
|
|
19789
|
+
return { pre: windows(v.pre), post: windows(v.post), extended: windows(v.extended) };
|
|
19757
19790
|
}
|
|
19758
19791
|
function clamp012(v) {
|
|
19759
19792
|
return Math.max(0, Math.min(1, v));
|
|
@@ -19829,7 +19862,7 @@ var LiveSession = class {
|
|
|
19829
19862
|
|
|
19830
19863
|
// src/core/engine/IndicatorHandleImpl.ts
|
|
19831
19864
|
var IndicatorHandleImpl = class {
|
|
19832
|
-
constructor(id, title, controller, source) {
|
|
19865
|
+
constructor(id, title, controller, source, nativeType) {
|
|
19833
19866
|
this.controller = controller;
|
|
19834
19867
|
this.schema = [];
|
|
19835
19868
|
this.propsSchema = [];
|
|
@@ -19838,6 +19871,7 @@ var IndicatorHandleImpl = class {
|
|
|
19838
19871
|
this.id = id;
|
|
19839
19872
|
this.title = title;
|
|
19840
19873
|
if (source !== void 0) this.source = source;
|
|
19874
|
+
if (nativeType !== void 0) this.nativeType = nativeType;
|
|
19841
19875
|
}
|
|
19842
19876
|
get inputs() {
|
|
19843
19877
|
return this.schema;
|
|
@@ -19928,24 +19962,24 @@ function summarizeModel(model) {
|
|
|
19928
19962
|
}
|
|
19929
19963
|
function inspectModels(models) {
|
|
19930
19964
|
const indicators = models.map(summarizeModel);
|
|
19931
|
-
const
|
|
19965
|
+
const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
|
|
19932
19966
|
return {
|
|
19933
19967
|
indicators,
|
|
19934
19968
|
totals: {
|
|
19935
19969
|
panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
|
|
19936
|
-
series:
|
|
19937
|
-
fills:
|
|
19938
|
-
backgrounds:
|
|
19939
|
-
priceLines:
|
|
19940
|
-
lines:
|
|
19941
|
-
boxes:
|
|
19942
|
-
labels:
|
|
19943
|
-
polylines:
|
|
19944
|
-
linefills:
|
|
19945
|
-
tables:
|
|
19946
|
-
barColors:
|
|
19947
|
-
trades:
|
|
19948
|
-
forcedOverlay:
|
|
19970
|
+
series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
|
|
19971
|
+
fills: sum2((s) => s.fills),
|
|
19972
|
+
backgrounds: sum2((s) => s.backgrounds),
|
|
19973
|
+
priceLines: sum2((s) => s.priceLines),
|
|
19974
|
+
lines: sum2((s) => s.lines),
|
|
19975
|
+
boxes: sum2((s) => s.boxes),
|
|
19976
|
+
labels: sum2((s) => s.labels),
|
|
19977
|
+
polylines: sum2((s) => s.polylines),
|
|
19978
|
+
linefills: sum2((s) => s.linefills),
|
|
19979
|
+
tables: sum2((s) => s.tables),
|
|
19980
|
+
barColors: sum2((s) => s.barColors),
|
|
19981
|
+
trades: sum2((s) => s.trades),
|
|
19982
|
+
forcedOverlay: sum2((s) => s.forcedOverlay)
|
|
19949
19983
|
}
|
|
19950
19984
|
};
|
|
19951
19985
|
}
|
|
@@ -21002,16 +21036,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21002
21036
|
/**
|
|
21003
21037
|
* Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
|
|
21004
21038
|
* indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
|
|
21005
|
-
* INSTANCE per type
|
|
21006
|
-
*
|
|
21039
|
+
* INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
|
|
21040
|
+
* single-instance type returns the existing handle; a multi-instance type gets a fresh
|
|
21041
|
+
* instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
|
|
21042
|
+
* addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
|
|
21007
21043
|
*/
|
|
21008
21044
|
addNativeIndicator(type, options = {}) {
|
|
21009
|
-
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
21010
|
-
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
|
|
21011
21045
|
const descriptor = getNativeIndicator(type);
|
|
21046
|
+
if (!descriptor?.multiInstance) {
|
|
21047
|
+
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
21048
|
+
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
|
|
21049
|
+
}
|
|
21012
21050
|
const id = this.registry.nextId("native");
|
|
21013
21051
|
const title = descriptor?.title ?? type;
|
|
21014
|
-
const handle = new IndicatorHandleImpl(id, title, this);
|
|
21052
|
+
const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
|
|
21015
21053
|
this.handles.set(id, handle);
|
|
21016
21054
|
if (!descriptor) {
|
|
21017
21055
|
console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
|
|
@@ -21023,16 +21061,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21023
21061
|
void this.startNativeIndicator(id, handle);
|
|
21024
21062
|
return handle;
|
|
21025
21063
|
}
|
|
21026
|
-
/**
|
|
21027
|
-
*
|
|
21028
|
-
* entry's `supported` (applies to the current symbol) and `present` (already added — a second
|
|
21029
|
-
* add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
|
|
21030
|
-
* because a descriptor's `isSupported` may probe the provider for a required capability.
|
|
21031
|
-
*/
|
|
21032
|
-
/** The native types present on the chart — SYNC (registry state; no support probe). */
|
|
21064
|
+
/** The native types present on the chart, one entry per INSTANCE in registry order (a
|
|
21065
|
+
* multi-instance type repeats) — SYNC (registry state; no support probe). */
|
|
21033
21066
|
presentNativeIndicators() {
|
|
21034
21067
|
return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
|
|
21035
21068
|
}
|
|
21069
|
+
/**
|
|
21070
|
+
* The catalog of registered native-indicator types with their live state on THIS chart: each
|
|
21071
|
+
* entry's `supported` (applies to the current symbol) and `present` (at least one instance
|
|
21072
|
+
* added — a second add of a single-instance type is a no-op). Powers a host "add native
|
|
21073
|
+
* indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
|
|
21074
|
+
* probe the provider for a required capability.
|
|
21075
|
+
*/
|
|
21036
21076
|
async availableNativeIndicators() {
|
|
21037
21077
|
const symbol = this.qualifiedSymbol();
|
|
21038
21078
|
const present = new Set(this.presentNativeIndicators());
|
|
@@ -21042,7 +21082,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21042
21082
|
title: d.title,
|
|
21043
21083
|
supported: await this.isNativeSupported(d, symbol),
|
|
21044
21084
|
present: present.has(d.type),
|
|
21045
|
-
beta: d.beta
|
|
21085
|
+
beta: d.beta,
|
|
21086
|
+
...d.multiInstance ? { multiInstance: true } : {}
|
|
21046
21087
|
}))
|
|
21047
21088
|
);
|
|
21048
21089
|
}
|
|
@@ -21982,12 +22023,2322 @@ function registerVpvr() {
|
|
|
21982
22023
|
registerNativeIndicator(vpvrDescriptor);
|
|
21983
22024
|
}
|
|
21984
22025
|
|
|
22026
|
+
// src/core/native-indicators/classics/define.ts
|
|
22027
|
+
function num3(inputs, key, fallback) {
|
|
22028
|
+
const v = inputs[key];
|
|
22029
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
22030
|
+
}
|
|
22031
|
+
function str3(inputs, key, fallback) {
|
|
22032
|
+
const v = inputs[key];
|
|
22033
|
+
return typeof v === "string" && v !== "" ? v : fallback;
|
|
22034
|
+
}
|
|
22035
|
+
function toPoints(bars, values, colors) {
|
|
22036
|
+
const pts = new Array(bars.length);
|
|
22037
|
+
for (let i = 0; i < bars.length; i++) {
|
|
22038
|
+
const v = values[i];
|
|
22039
|
+
const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
|
|
22040
|
+
const c = colors?.[i];
|
|
22041
|
+
if (c != null) p.color = c;
|
|
22042
|
+
pts[i] = p;
|
|
22043
|
+
}
|
|
22044
|
+
return pts;
|
|
22045
|
+
}
|
|
22046
|
+
var ClassicIndicator = class {
|
|
22047
|
+
constructor(spec) {
|
|
22048
|
+
this.spec = spec;
|
|
22049
|
+
this.inputs = {};
|
|
22050
|
+
}
|
|
22051
|
+
start(ctx, inputs) {
|
|
22052
|
+
this.ctx = ctx;
|
|
22053
|
+
this.inputs = inputs;
|
|
22054
|
+
this.recompute();
|
|
22055
|
+
}
|
|
22056
|
+
onBars() {
|
|
22057
|
+
this.recompute();
|
|
22058
|
+
}
|
|
22059
|
+
onViewport() {
|
|
22060
|
+
}
|
|
22061
|
+
setInputs(inputs) {
|
|
22062
|
+
this.inputs = inputs;
|
|
22063
|
+
this.recompute();
|
|
22064
|
+
}
|
|
22065
|
+
/** Nothing to free — the compute reads the live bar accessor on demand. */
|
|
22066
|
+
suspend() {
|
|
22067
|
+
}
|
|
22068
|
+
resume() {
|
|
22069
|
+
this.recompute();
|
|
22070
|
+
}
|
|
22071
|
+
stop() {
|
|
22072
|
+
}
|
|
22073
|
+
recompute() {
|
|
22074
|
+
const bars = this.ctx.bars();
|
|
22075
|
+
const out = this.spec.compute(bars, this.inputs);
|
|
22076
|
+
const instanceId = this.spec.type;
|
|
22077
|
+
const series = [];
|
|
22078
|
+
const idsByKey = /* @__PURE__ */ new Map();
|
|
22079
|
+
out.plots.forEach((plot, ordinal) => {
|
|
22080
|
+
const kind = plot.kind ?? "line";
|
|
22081
|
+
const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
|
|
22082
|
+
idsByKey.set(plot.key, id);
|
|
22083
|
+
series.push({
|
|
22084
|
+
id,
|
|
22085
|
+
title: plot.title,
|
|
22086
|
+
paneId: "",
|
|
22087
|
+
// stamped by the orchestrator
|
|
22088
|
+
kind,
|
|
22089
|
+
points: toPoints(bars, plot.values, plot.colors),
|
|
22090
|
+
style: {
|
|
22091
|
+
color: plot.color,
|
|
22092
|
+
width: plot.width ?? 1,
|
|
22093
|
+
lineStyle: plot.lineStyle ?? "solid",
|
|
22094
|
+
...plot.base != null ? { base: plot.base } : {}
|
|
22095
|
+
},
|
|
22096
|
+
...plot.overlay ? { overlay: true } : {}
|
|
22097
|
+
});
|
|
22098
|
+
});
|
|
22099
|
+
if (out.markers && out.markers.length > 0) {
|
|
22100
|
+
series.push({
|
|
22101
|
+
id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
|
|
22102
|
+
title: this.spec.shortTitle ?? this.spec.title,
|
|
22103
|
+
paneId: "",
|
|
22104
|
+
kind: "markers",
|
|
22105
|
+
markers: out.markers
|
|
22106
|
+
});
|
|
22107
|
+
}
|
|
22108
|
+
const fills = (out.bands ?? []).flatMap((band, ordinal) => {
|
|
22109
|
+
const from = idsByKey.get(band.from);
|
|
22110
|
+
const to = idsByKey.get(band.to);
|
|
22111
|
+
if (!from || !to) return [];
|
|
22112
|
+
return [
|
|
22113
|
+
{
|
|
22114
|
+
id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
|
|
22115
|
+
paneId: "",
|
|
22116
|
+
fromSeriesId: from,
|
|
22117
|
+
toSeriesId: to,
|
|
22118
|
+
color: band.color
|
|
22119
|
+
}
|
|
22120
|
+
];
|
|
22121
|
+
});
|
|
22122
|
+
const priceLines = (out.levels ?? []).map((level, ordinal) => ({
|
|
22123
|
+
id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
|
|
22124
|
+
paneId: "",
|
|
22125
|
+
price: level.price,
|
|
22126
|
+
...level.color != null ? { color: level.color } : {},
|
|
22127
|
+
lineStyle: level.lineStyle ?? "dashed",
|
|
22128
|
+
width: 1,
|
|
22129
|
+
...level.title != null ? { title: level.title } : {}
|
|
22130
|
+
}));
|
|
22131
|
+
const polylines = (out.polylines ?? []).map((poly, ordinal) => {
|
|
22132
|
+
const { key, ...rest } = poly;
|
|
22133
|
+
return {
|
|
22134
|
+
id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
|
|
22135
|
+
paneId: "",
|
|
22136
|
+
...rest
|
|
22137
|
+
};
|
|
22138
|
+
});
|
|
22139
|
+
this.ctx.emit({ series, fills, priceLines, polylines });
|
|
22140
|
+
this.ctx.setStatus("idle");
|
|
22141
|
+
}
|
|
22142
|
+
};
|
|
22143
|
+
function classicDescriptor(spec) {
|
|
22144
|
+
return {
|
|
22145
|
+
type: spec.type,
|
|
22146
|
+
title: spec.title,
|
|
22147
|
+
...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
|
|
22148
|
+
paneHint: spec.overlay ? "price" : "new",
|
|
22149
|
+
overlay: spec.overlay,
|
|
22150
|
+
// Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
|
|
22151
|
+
multiInstance: true,
|
|
22152
|
+
inputsSchema: () => spec.inputs,
|
|
22153
|
+
defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
|
|
22154
|
+
create: () => new ClassicIndicator(spec)
|
|
22155
|
+
};
|
|
22156
|
+
}
|
|
22157
|
+
|
|
22158
|
+
// src/core/native-indicators/classics/math.ts
|
|
22159
|
+
var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
|
|
22160
|
+
function sourceValues(bars, source) {
|
|
22161
|
+
const out = new Array(bars.length);
|
|
22162
|
+
for (let i = 0; i < bars.length; i++) {
|
|
22163
|
+
const b = bars[i];
|
|
22164
|
+
switch (source) {
|
|
22165
|
+
case "Open":
|
|
22166
|
+
out[i] = b.open;
|
|
22167
|
+
break;
|
|
22168
|
+
case "High":
|
|
22169
|
+
out[i] = b.high;
|
|
22170
|
+
break;
|
|
22171
|
+
case "Low":
|
|
22172
|
+
out[i] = b.low;
|
|
22173
|
+
break;
|
|
22174
|
+
case "HL2":
|
|
22175
|
+
out[i] = (b.high + b.low) / 2;
|
|
22176
|
+
break;
|
|
22177
|
+
case "HLC3":
|
|
22178
|
+
out[i] = (b.high + b.low + b.close) / 3;
|
|
22179
|
+
break;
|
|
22180
|
+
case "OHLC4":
|
|
22181
|
+
out[i] = (b.open + b.high + b.low + b.close) / 4;
|
|
22182
|
+
break;
|
|
22183
|
+
case "HLCC4":
|
|
22184
|
+
out[i] = (b.high + b.low + b.close + b.close) / 4;
|
|
22185
|
+
break;
|
|
22186
|
+
default:
|
|
22187
|
+
out[i] = b.close;
|
|
22188
|
+
}
|
|
22189
|
+
}
|
|
22190
|
+
return out;
|
|
22191
|
+
}
|
|
22192
|
+
function volumes(bars) {
|
|
22193
|
+
return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
|
|
22194
|
+
}
|
|
22195
|
+
function highs(bars) {
|
|
22196
|
+
return bars.map((b) => b.high);
|
|
22197
|
+
}
|
|
22198
|
+
function lows(bars) {
|
|
22199
|
+
return bars.map((b) => b.low);
|
|
22200
|
+
}
|
|
22201
|
+
function closes(bars) {
|
|
22202
|
+
return bars.map((b) => b.close);
|
|
22203
|
+
}
|
|
22204
|
+
function opens(bars) {
|
|
22205
|
+
return bars.map((b) => b.open);
|
|
22206
|
+
}
|
|
22207
|
+
function zip(a, b, f) {
|
|
22208
|
+
const out = new Array(a.length);
|
|
22209
|
+
for (let i = 0; i < a.length; i++) {
|
|
22210
|
+
const x = a[i];
|
|
22211
|
+
const y = b[i];
|
|
22212
|
+
out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
|
|
22213
|
+
}
|
|
22214
|
+
return out;
|
|
22215
|
+
}
|
|
22216
|
+
function map(a, f) {
|
|
22217
|
+
const out = new Array(a.length);
|
|
22218
|
+
for (let i = 0; i < a.length; i++) {
|
|
22219
|
+
const x = a[i];
|
|
22220
|
+
out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
|
|
22221
|
+
}
|
|
22222
|
+
return out;
|
|
22223
|
+
}
|
|
22224
|
+
function sub(a, b) {
|
|
22225
|
+
return zip(a, b, (x, y) => x - y);
|
|
22226
|
+
}
|
|
22227
|
+
function windowed(v, len, fold) {
|
|
22228
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22229
|
+
if (len <= 0) return out;
|
|
22230
|
+
let run = 0;
|
|
22231
|
+
for (let i = 0; i < v.length; i++) {
|
|
22232
|
+
run = Number.isFinite(v[i]) ? run + 1 : 0;
|
|
22233
|
+
if (run >= len) out[i] = fold(v, i - len + 1, i);
|
|
22234
|
+
}
|
|
22235
|
+
return out;
|
|
22236
|
+
}
|
|
22237
|
+
function sum(v, len) {
|
|
22238
|
+
return windowed(v, len, (w, from, to) => {
|
|
22239
|
+
let s = 0;
|
|
22240
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
22241
|
+
return s;
|
|
22242
|
+
});
|
|
22243
|
+
}
|
|
22244
|
+
function sma(v, len) {
|
|
22245
|
+
return windowed(v, len, (w, from, to) => {
|
|
22246
|
+
let s = 0;
|
|
22247
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
22248
|
+
return s / len;
|
|
22249
|
+
});
|
|
22250
|
+
}
|
|
22251
|
+
function wma(v, len) {
|
|
22252
|
+
const norm = len * (len + 1) / 2;
|
|
22253
|
+
return windowed(v, len, (w, from, to) => {
|
|
22254
|
+
let s = 0;
|
|
22255
|
+
for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
|
|
22256
|
+
return s / norm;
|
|
22257
|
+
});
|
|
22258
|
+
}
|
|
22259
|
+
function vwma(src, vol, len) {
|
|
22260
|
+
const pv = zip(src, vol, (p, q) => p * q);
|
|
22261
|
+
return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
|
|
22262
|
+
}
|
|
22263
|
+
function stdev(v, len) {
|
|
22264
|
+
return windowed(v, len, (w, from, to) => {
|
|
22265
|
+
let s = 0;
|
|
22266
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
22267
|
+
const mean = s / len;
|
|
22268
|
+
let sq = 0;
|
|
22269
|
+
for (let k = from; k <= to; k++) {
|
|
22270
|
+
const d = w[k] - mean;
|
|
22271
|
+
sq += d * d;
|
|
22272
|
+
}
|
|
22273
|
+
return Math.sqrt(sq / len);
|
|
22274
|
+
});
|
|
22275
|
+
}
|
|
22276
|
+
function highest(v, len) {
|
|
22277
|
+
return windowed(v, len, (w, from, to) => {
|
|
22278
|
+
let m = -Infinity;
|
|
22279
|
+
for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
|
|
22280
|
+
return m;
|
|
22281
|
+
});
|
|
22282
|
+
}
|
|
22283
|
+
function lowest(v, len) {
|
|
22284
|
+
return windowed(v, len, (w, from, to) => {
|
|
22285
|
+
let m = Infinity;
|
|
22286
|
+
for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
|
|
22287
|
+
return m;
|
|
22288
|
+
});
|
|
22289
|
+
}
|
|
22290
|
+
function barsSinceHighest(v, len) {
|
|
22291
|
+
return windowed(v, len, (w, from, to) => {
|
|
22292
|
+
let m = -Infinity;
|
|
22293
|
+
let at = to;
|
|
22294
|
+
for (let k = from; k <= to; k++) {
|
|
22295
|
+
if (w[k] >= m) {
|
|
22296
|
+
m = w[k];
|
|
22297
|
+
at = k;
|
|
22298
|
+
}
|
|
22299
|
+
}
|
|
22300
|
+
return to - at;
|
|
22301
|
+
});
|
|
22302
|
+
}
|
|
22303
|
+
function barsSinceLowest(v, len) {
|
|
22304
|
+
return windowed(v, len, (w, from, to) => {
|
|
22305
|
+
let m = Infinity;
|
|
22306
|
+
let at = to;
|
|
22307
|
+
for (let k = from; k <= to; k++) {
|
|
22308
|
+
if (w[k] <= m) {
|
|
22309
|
+
m = w[k];
|
|
22310
|
+
at = k;
|
|
22311
|
+
}
|
|
22312
|
+
}
|
|
22313
|
+
return to - at;
|
|
22314
|
+
});
|
|
22315
|
+
}
|
|
22316
|
+
function recursive(v, len, alpha) {
|
|
22317
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22318
|
+
let run = 0;
|
|
22319
|
+
let prev2 = Number.NaN;
|
|
22320
|
+
for (let i = 0; i < v.length; i++) {
|
|
22321
|
+
const x = v[i];
|
|
22322
|
+
if (!Number.isFinite(x)) {
|
|
22323
|
+
run = 0;
|
|
22324
|
+
prev2 = Number.NaN;
|
|
22325
|
+
continue;
|
|
22326
|
+
}
|
|
22327
|
+
run += 1;
|
|
22328
|
+
if (Number.isFinite(prev2)) {
|
|
22329
|
+
prev2 = alpha * x + (1 - alpha) * prev2;
|
|
22330
|
+
out[i] = prev2;
|
|
22331
|
+
} else if (run >= len) {
|
|
22332
|
+
let s = 0;
|
|
22333
|
+
for (let k = i - len + 1; k <= i; k++) s += v[k];
|
|
22334
|
+
prev2 = s / len;
|
|
22335
|
+
out[i] = prev2;
|
|
22336
|
+
}
|
|
22337
|
+
}
|
|
22338
|
+
return out;
|
|
22339
|
+
}
|
|
22340
|
+
function ema(v, len) {
|
|
22341
|
+
return recursive(v, len, 2 / (len + 1));
|
|
22342
|
+
}
|
|
22343
|
+
function rma(v, len) {
|
|
22344
|
+
return recursive(v, len, 1 / len);
|
|
22345
|
+
}
|
|
22346
|
+
function change(v, n = 1) {
|
|
22347
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22348
|
+
for (let i = n; i < v.length; i++) {
|
|
22349
|
+
const a = v[i];
|
|
22350
|
+
const b = v[i - n];
|
|
22351
|
+
if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
|
|
22352
|
+
}
|
|
22353
|
+
return out;
|
|
22354
|
+
}
|
|
22355
|
+
function roc(v, len) {
|
|
22356
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22357
|
+
for (let i = len; i < v.length; i++) {
|
|
22358
|
+
const a = v[i];
|
|
22359
|
+
const b = v[i - len];
|
|
22360
|
+
if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
|
|
22361
|
+
}
|
|
22362
|
+
return out;
|
|
22363
|
+
}
|
|
22364
|
+
function shift(v, n) {
|
|
22365
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22366
|
+
for (let i = 0; i < v.length; i++) {
|
|
22367
|
+
const j = i - n;
|
|
22368
|
+
if (j >= 0 && j < v.length) out[i] = v[j];
|
|
22369
|
+
}
|
|
22370
|
+
return out;
|
|
22371
|
+
}
|
|
22372
|
+
function trueRange(bars) {
|
|
22373
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
22374
|
+
for (let i = 0; i < bars.length; i++) {
|
|
22375
|
+
const b = bars[i];
|
|
22376
|
+
if (i === 0) {
|
|
22377
|
+
out[i] = b.high - b.low;
|
|
22378
|
+
continue;
|
|
22379
|
+
}
|
|
22380
|
+
const pc = bars[i - 1].close;
|
|
22381
|
+
out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
|
|
22382
|
+
}
|
|
22383
|
+
return out;
|
|
22384
|
+
}
|
|
22385
|
+
function atr(bars, len) {
|
|
22386
|
+
return rma(trueRange(bars), len);
|
|
22387
|
+
}
|
|
22388
|
+
function rsi(v, len) {
|
|
22389
|
+
const d = change(v);
|
|
22390
|
+
const up = map(d, (x) => Math.max(x, 0));
|
|
22391
|
+
const dn = map(d, (x) => Math.max(-x, 0));
|
|
22392
|
+
return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
|
|
22393
|
+
}
|
|
22394
|
+
function stoch(v, hi, lo, len) {
|
|
22395
|
+
const hh = highest(hi, len);
|
|
22396
|
+
const ll = lowest(lo, len);
|
|
22397
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22398
|
+
for (let i = 0; i < v.length; i++) {
|
|
22399
|
+
const x = v[i];
|
|
22400
|
+
const h = hh[i];
|
|
22401
|
+
const l = ll[i];
|
|
22402
|
+
if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
22403
|
+
out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
|
|
22404
|
+
}
|
|
22405
|
+
return out;
|
|
22406
|
+
}
|
|
22407
|
+
function linreg(v, len) {
|
|
22408
|
+
const sx = (len - 1) * len / 2;
|
|
22409
|
+
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
22410
|
+
const denom = len * sxx - sx * sx;
|
|
22411
|
+
return windowed(v, len, (w, from, to) => {
|
|
22412
|
+
let sy = 0;
|
|
22413
|
+
let sxy = 0;
|
|
22414
|
+
for (let k = from; k <= to; k++) {
|
|
22415
|
+
const x = k - from;
|
|
22416
|
+
sy += w[k];
|
|
22417
|
+
sxy += x * w[k];
|
|
22418
|
+
}
|
|
22419
|
+
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
22420
|
+
const intercept = (sy - slope * sx) / len;
|
|
22421
|
+
return intercept + slope * (len - 1);
|
|
22422
|
+
});
|
|
22423
|
+
}
|
|
22424
|
+
function swma(v) {
|
|
22425
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22426
|
+
for (let i = 3; i < v.length; i++) {
|
|
22427
|
+
const a = v[i - 3];
|
|
22428
|
+
const b = v[i - 2];
|
|
22429
|
+
const c = v[i - 1];
|
|
22430
|
+
const d = v[i];
|
|
22431
|
+
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
|
|
22432
|
+
out[i] = (a + 2 * b + 2 * c + d) / 6;
|
|
22433
|
+
}
|
|
22434
|
+
}
|
|
22435
|
+
return out;
|
|
22436
|
+
}
|
|
22437
|
+
function percentRank(v, len) {
|
|
22438
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22439
|
+
for (let i = len; i < v.length; i++) {
|
|
22440
|
+
const x = v[i];
|
|
22441
|
+
if (!Number.isFinite(x)) continue;
|
|
22442
|
+
let below = 0;
|
|
22443
|
+
let count = 0;
|
|
22444
|
+
for (let k = i - len; k < i; k++) {
|
|
22445
|
+
const y = v[k];
|
|
22446
|
+
if (!Number.isFinite(y)) {
|
|
22447
|
+
count = -1;
|
|
22448
|
+
break;
|
|
22449
|
+
}
|
|
22450
|
+
count += 1;
|
|
22451
|
+
if (y <= x) below += 1;
|
|
22452
|
+
}
|
|
22453
|
+
if (count === len) out[i] = 100 * below / len;
|
|
22454
|
+
}
|
|
22455
|
+
return out;
|
|
22456
|
+
}
|
|
22457
|
+
function cumSum(v) {
|
|
22458
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22459
|
+
let acc = 0;
|
|
22460
|
+
let seen = false;
|
|
22461
|
+
for (let i = 0; i < v.length; i++) {
|
|
22462
|
+
const x = v[i];
|
|
22463
|
+
if (Number.isFinite(x)) {
|
|
22464
|
+
acc += x;
|
|
22465
|
+
seen = true;
|
|
22466
|
+
}
|
|
22467
|
+
if (seen) out[i] = acc;
|
|
22468
|
+
}
|
|
22469
|
+
return out;
|
|
22470
|
+
}
|
|
22471
|
+
|
|
22472
|
+
// src/core/native-indicators/classics/shared.ts
|
|
22473
|
+
function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
|
|
22474
|
+
return { key, title, type: "int", defval, min: 1, max, step: 1 };
|
|
22475
|
+
}
|
|
22476
|
+
function sourceInput(defval = "Close") {
|
|
22477
|
+
return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
|
|
22478
|
+
}
|
|
22479
|
+
function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
|
|
22480
|
+
return { key, title, type: "color", defval };
|
|
22481
|
+
}
|
|
22482
|
+
function withAlpha2(hex, alpha = 0.08) {
|
|
22483
|
+
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
22484
|
+
if (!m) return hex;
|
|
22485
|
+
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
22486
|
+
return `#${m[1]}${a}`;
|
|
22487
|
+
}
|
|
22488
|
+
|
|
22489
|
+
// src/core/native-indicators/classics/averages.ts
|
|
22490
|
+
var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
|
|
22491
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
|
|
22492
|
+
function fixedAverage(type, title, shortTitle, smooth) {
|
|
22493
|
+
return {
|
|
22494
|
+
type,
|
|
22495
|
+
title,
|
|
22496
|
+
shortTitle,
|
|
22497
|
+
overlay: true,
|
|
22498
|
+
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
22499
|
+
compute: (bars, inputs) => {
|
|
22500
|
+
const len = num3(inputs, "length", 20);
|
|
22501
|
+
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
22502
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
22503
|
+
if (offset !== 0) values = shift(values, offset);
|
|
22504
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22505
|
+
}
|
|
22506
|
+
};
|
|
22507
|
+
}
|
|
22508
|
+
var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
|
|
22509
|
+
var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
|
|
22510
|
+
var movingAverage = {
|
|
22511
|
+
type: "moving-average",
|
|
22512
|
+
title: "Moving Average",
|
|
22513
|
+
shortTitle: "MA",
|
|
22514
|
+
overlay: true,
|
|
22515
|
+
inputs: [
|
|
22516
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
|
|
22517
|
+
lengthInput(20),
|
|
22518
|
+
sourceInput(),
|
|
22519
|
+
offsetInput,
|
|
22520
|
+
colorInput()
|
|
22521
|
+
],
|
|
22522
|
+
compute: (bars, inputs) => {
|
|
22523
|
+
const len = num3(inputs, "length", 20);
|
|
22524
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22525
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
22526
|
+
let values;
|
|
22527
|
+
switch (kind) {
|
|
22528
|
+
case "EMA":
|
|
22529
|
+
values = ema(src, len);
|
|
22530
|
+
break;
|
|
22531
|
+
case "WMA":
|
|
22532
|
+
values = wma(src, len);
|
|
22533
|
+
break;
|
|
22534
|
+
case "RMA":
|
|
22535
|
+
values = rma(src, len);
|
|
22536
|
+
break;
|
|
22537
|
+
case "VWMA":
|
|
22538
|
+
values = vwma(src, volumes(bars), len);
|
|
22539
|
+
break;
|
|
22540
|
+
default:
|
|
22541
|
+
values = sma(src, len);
|
|
22542
|
+
}
|
|
22543
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
22544
|
+
if (offset !== 0) values = shift(values, offset);
|
|
22545
|
+
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22546
|
+
}
|
|
22547
|
+
};
|
|
22548
|
+
var smoothedMa = {
|
|
22549
|
+
type: "rma",
|
|
22550
|
+
title: "Smoothed Moving Average",
|
|
22551
|
+
shortTitle: "RMA",
|
|
22552
|
+
overlay: true,
|
|
22553
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
22554
|
+
compute: (bars, inputs) => ({
|
|
22555
|
+
plots: [
|
|
22556
|
+
{
|
|
22557
|
+
key: "rma",
|
|
22558
|
+
title: "RMA",
|
|
22559
|
+
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
22560
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
22561
|
+
width: 2
|
|
22562
|
+
}
|
|
22563
|
+
]
|
|
22564
|
+
})
|
|
22565
|
+
};
|
|
22566
|
+
var zlema = {
|
|
22567
|
+
type: "zlema",
|
|
22568
|
+
title: "Zero-Lag Exponential Moving Average",
|
|
22569
|
+
shortTitle: "ZLEMA",
|
|
22570
|
+
overlay: true,
|
|
22571
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
22572
|
+
compute: (bars, inputs) => {
|
|
22573
|
+
const len = num3(inputs, "length", 14);
|
|
22574
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22575
|
+
const lag = Math.floor((len - 1) / 2);
|
|
22576
|
+
const lagged = shift(src, lag);
|
|
22577
|
+
const delagged = zip(src, lagged, (a, b) => 2 * a - b);
|
|
22578
|
+
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22579
|
+
}
|
|
22580
|
+
};
|
|
22581
|
+
var vidya = {
|
|
22582
|
+
type: "vidya",
|
|
22583
|
+
title: "Variable Index Dynamic Average",
|
|
22584
|
+
shortTitle: "VIDYA",
|
|
22585
|
+
overlay: true,
|
|
22586
|
+
inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
|
|
22587
|
+
compute: (bars, inputs) => {
|
|
22588
|
+
const len = num3(inputs, "length", 14);
|
|
22589
|
+
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
22590
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22591
|
+
const d = change(src);
|
|
22592
|
+
const su = new Array(src.length).fill(Number.NaN);
|
|
22593
|
+
const sd = new Array(src.length).fill(Number.NaN);
|
|
22594
|
+
for (let i = cmoLen; i < src.length; i++) {
|
|
22595
|
+
let u = 0;
|
|
22596
|
+
let w = 0;
|
|
22597
|
+
let ok = true;
|
|
22598
|
+
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
22599
|
+
const x = d[k];
|
|
22600
|
+
if (!Number.isFinite(x)) {
|
|
22601
|
+
ok = false;
|
|
22602
|
+
break;
|
|
22603
|
+
}
|
|
22604
|
+
if (x > 0) u += x;
|
|
22605
|
+
else w -= x;
|
|
22606
|
+
}
|
|
22607
|
+
if (ok) {
|
|
22608
|
+
su[i] = u;
|
|
22609
|
+
sd[i] = w;
|
|
22610
|
+
}
|
|
22611
|
+
}
|
|
22612
|
+
const alpha = 2 / (len + 1);
|
|
22613
|
+
const out = new Array(src.length).fill(Number.NaN);
|
|
22614
|
+
let prev2 = Number.NaN;
|
|
22615
|
+
for (let i = 0; i < src.length; i++) {
|
|
22616
|
+
const x = src[i];
|
|
22617
|
+
const u = su[i];
|
|
22618
|
+
const w = sd[i];
|
|
22619
|
+
if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
|
|
22620
|
+
const total = u + w;
|
|
22621
|
+
const k = total === 0 ? 0 : Math.abs((u - w) / total);
|
|
22622
|
+
prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
|
|
22623
|
+
out[i] = prev2;
|
|
22624
|
+
}
|
|
22625
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22626
|
+
}
|
|
22627
|
+
};
|
|
22628
|
+
var maEnvelope = {
|
|
22629
|
+
type: "ma-envelope",
|
|
22630
|
+
title: "Moving Average Envelope",
|
|
22631
|
+
shortTitle: "MA Env",
|
|
22632
|
+
overlay: true,
|
|
22633
|
+
inputs: [
|
|
22634
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
|
|
22635
|
+
lengthInput(20),
|
|
22636
|
+
sourceInput(),
|
|
22637
|
+
{ key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
|
|
22638
|
+
colorInput()
|
|
22639
|
+
],
|
|
22640
|
+
compute: (bars, inputs) => {
|
|
22641
|
+
const len = num3(inputs, "length", 20);
|
|
22642
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22643
|
+
const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
|
|
22644
|
+
const k = num3(inputs, "percent", 2.5) / 100;
|
|
22645
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
22646
|
+
return {
|
|
22647
|
+
plots: [
|
|
22648
|
+
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
22649
|
+
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
22650
|
+
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
22651
|
+
],
|
|
22652
|
+
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
22653
|
+
};
|
|
22654
|
+
}
|
|
22655
|
+
};
|
|
22656
|
+
var linearRegression = {
|
|
22657
|
+
type: "linear-regression",
|
|
22658
|
+
title: "Linear Regression Curve",
|
|
22659
|
+
shortTitle: "LinReg",
|
|
22660
|
+
overlay: true,
|
|
22661
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
22662
|
+
compute: (bars, inputs) => ({
|
|
22663
|
+
plots: [
|
|
22664
|
+
{
|
|
22665
|
+
key: "linreg",
|
|
22666
|
+
title: "LinReg",
|
|
22667
|
+
values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
22668
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
22669
|
+
width: 2
|
|
22670
|
+
}
|
|
22671
|
+
]
|
|
22672
|
+
})
|
|
22673
|
+
};
|
|
22674
|
+
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
22675
|
+
|
|
22676
|
+
// src/core/native-indicators/classics/bands.ts
|
|
22677
|
+
function bollinger(bars, inputs) {
|
|
22678
|
+
const len = num3(inputs, "length", 20);
|
|
22679
|
+
const mult = num3(inputs, "mult", 2);
|
|
22680
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22681
|
+
const basis = sma(src, len);
|
|
22682
|
+
const dev = map(stdev(src, len), (x) => x * mult);
|
|
22683
|
+
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
22684
|
+
}
|
|
22685
|
+
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
22686
|
+
var bollingerBands = {
|
|
22687
|
+
type: "bollinger-bands",
|
|
22688
|
+
title: "Bollinger Bands",
|
|
22689
|
+
shortTitle: "BB",
|
|
22690
|
+
overlay: true,
|
|
22691
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
22692
|
+
compute: (bars, inputs) => {
|
|
22693
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
22694
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
22695
|
+
return {
|
|
22696
|
+
plots: [
|
|
22697
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
22698
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
22699
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
22700
|
+
],
|
|
22701
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
22702
|
+
};
|
|
22703
|
+
}
|
|
22704
|
+
};
|
|
22705
|
+
var percentB = {
|
|
22706
|
+
type: "percent-b",
|
|
22707
|
+
title: "Bollinger Bands %B",
|
|
22708
|
+
shortTitle: "%B",
|
|
22709
|
+
overlay: false,
|
|
22710
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
22711
|
+
compute: (bars, inputs) => {
|
|
22712
|
+
const { upper, lower } = bollinger(bars, inputs);
|
|
22713
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22714
|
+
const values = src.map((x, i) => {
|
|
22715
|
+
const u = upper[i];
|
|
22716
|
+
const l = lower[i];
|
|
22717
|
+
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
22718
|
+
});
|
|
22719
|
+
return {
|
|
22720
|
+
plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
22721
|
+
levels: [
|
|
22722
|
+
{ key: "upper", price: 1, color: NEUTRAL },
|
|
22723
|
+
{ key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
|
|
22724
|
+
{ key: "lower", price: 0, color: NEUTRAL }
|
|
22725
|
+
]
|
|
22726
|
+
};
|
|
22727
|
+
}
|
|
22728
|
+
};
|
|
22729
|
+
var bandwidth = {
|
|
22730
|
+
type: "bandwidth",
|
|
22731
|
+
title: "Bollinger Bands Width",
|
|
22732
|
+
shortTitle: "BandWidth",
|
|
22733
|
+
overlay: false,
|
|
22734
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
22735
|
+
compute: (bars, inputs) => {
|
|
22736
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
22737
|
+
const values = basis.map((b, i) => {
|
|
22738
|
+
const u = upper[i];
|
|
22739
|
+
const l = lower[i];
|
|
22740
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
|
|
22741
|
+
});
|
|
22742
|
+
return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22743
|
+
}
|
|
22744
|
+
};
|
|
22745
|
+
var keltner = {
|
|
22746
|
+
type: "keltner-channels",
|
|
22747
|
+
title: "Keltner Channels",
|
|
22748
|
+
shortTitle: "KC",
|
|
22749
|
+
overlay: true,
|
|
22750
|
+
inputs: [
|
|
22751
|
+
lengthInput(20),
|
|
22752
|
+
{ key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
22753
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
22754
|
+
sourceInput(),
|
|
22755
|
+
colorInput()
|
|
22756
|
+
],
|
|
22757
|
+
compute: (bars, inputs) => {
|
|
22758
|
+
const len = num3(inputs, "length", 20);
|
|
22759
|
+
const mult = num3(inputs, "mult", 2);
|
|
22760
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
22761
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
|
|
22762
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
22763
|
+
return {
|
|
22764
|
+
plots: [
|
|
22765
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
22766
|
+
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
22767
|
+
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
22768
|
+
],
|
|
22769
|
+
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
22770
|
+
};
|
|
22771
|
+
}
|
|
22772
|
+
};
|
|
22773
|
+
var donchian = {
|
|
22774
|
+
type: "donchian-channels",
|
|
22775
|
+
title: "Donchian Channels",
|
|
22776
|
+
shortTitle: "DC",
|
|
22777
|
+
overlay: true,
|
|
22778
|
+
inputs: [lengthInput(20), colorInput()],
|
|
22779
|
+
compute: (bars, inputs) => {
|
|
22780
|
+
const len = num3(inputs, "length", 20);
|
|
22781
|
+
const upper = highest(highs(bars), len);
|
|
22782
|
+
const lower = lowest(lows(bars), len);
|
|
22783
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
22784
|
+
return {
|
|
22785
|
+
plots: [
|
|
22786
|
+
{ key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
|
|
22787
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
22788
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
22789
|
+
],
|
|
22790
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
22791
|
+
};
|
|
22792
|
+
}
|
|
22793
|
+
};
|
|
22794
|
+
var chandelier = {
|
|
22795
|
+
type: "chandelier-exit",
|
|
22796
|
+
title: "Chandelier Exit",
|
|
22797
|
+
shortTitle: "CE",
|
|
22798
|
+
overlay: true,
|
|
22799
|
+
inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
|
|
22800
|
+
compute: (bars, inputs) => {
|
|
22801
|
+
const len = num3(inputs, "length", 22);
|
|
22802
|
+
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
22803
|
+
const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
22804
|
+
const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
22805
|
+
return {
|
|
22806
|
+
plots: [
|
|
22807
|
+
{ key: "long", title: "Long stop", values: long, color: BULLISH },
|
|
22808
|
+
{ key: "short", title: "Short stop", values: short, color: BEARISH }
|
|
22809
|
+
]
|
|
22810
|
+
};
|
|
22811
|
+
}
|
|
22812
|
+
};
|
|
22813
|
+
var chandeKroll = {
|
|
22814
|
+
type: "chande-kroll-stop",
|
|
22815
|
+
title: "Chande Kroll Stop",
|
|
22816
|
+
shortTitle: "CKS",
|
|
22817
|
+
overlay: true,
|
|
22818
|
+
inputs: [
|
|
22819
|
+
{ key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
22820
|
+
{ key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
|
|
22821
|
+
{ key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
|
|
22822
|
+
],
|
|
22823
|
+
compute: (bars, inputs) => {
|
|
22824
|
+
const p = num3(inputs, "p", 10);
|
|
22825
|
+
const x = num3(inputs, "x", 1);
|
|
22826
|
+
const q = num3(inputs, "q", 9);
|
|
22827
|
+
const range = map(atr(bars, p), (v) => v * x);
|
|
22828
|
+
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
22829
|
+
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
22830
|
+
return {
|
|
22831
|
+
plots: [
|
|
22832
|
+
{ key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
|
|
22833
|
+
{ key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
|
|
22834
|
+
]
|
|
22835
|
+
};
|
|
22836
|
+
}
|
|
22837
|
+
};
|
|
22838
|
+
var supertrend = {
|
|
22839
|
+
type: "supertrend",
|
|
22840
|
+
title: "SuperTrend",
|
|
22841
|
+
shortTitle: "SuperTrend",
|
|
22842
|
+
overlay: true,
|
|
22843
|
+
inputs: [
|
|
22844
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
22845
|
+
{ key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
|
|
22846
|
+
],
|
|
22847
|
+
compute: (bars, inputs) => {
|
|
22848
|
+
const len = num3(inputs, "atrLength", 10);
|
|
22849
|
+
const mult = num3(inputs, "mult", 3);
|
|
22850
|
+
const range = atr(bars, len);
|
|
22851
|
+
const n = bars.length;
|
|
22852
|
+
const st = new Array(n).fill(Number.NaN);
|
|
22853
|
+
const dir = new Array(n).fill(1);
|
|
22854
|
+
let up = Number.NaN;
|
|
22855
|
+
let dn = Number.NaN;
|
|
22856
|
+
let trend = 1;
|
|
22857
|
+
for (let i = 0; i < n; i++) {
|
|
22858
|
+
const r = range[i];
|
|
22859
|
+
if (!Number.isFinite(r)) continue;
|
|
22860
|
+
const b = bars[i];
|
|
22861
|
+
const mid = (b.high + b.low) / 2;
|
|
22862
|
+
const basicUp = mid - mult * r;
|
|
22863
|
+
const basicDn = mid + mult * r;
|
|
22864
|
+
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
22865
|
+
up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
|
|
22866
|
+
dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
|
|
22867
|
+
if (trend === 1 && b.close < up) trend = -1;
|
|
22868
|
+
else if (trend === -1 && b.close > dn) trend = 1;
|
|
22869
|
+
dir[i] = trend;
|
|
22870
|
+
st[i] = trend === 1 ? up : dn;
|
|
22871
|
+
}
|
|
22872
|
+
const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
|
|
22873
|
+
return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
|
|
22874
|
+
}
|
|
22875
|
+
};
|
|
22876
|
+
var alligator = {
|
|
22877
|
+
type: "williams-alligator",
|
|
22878
|
+
title: "Williams Alligator",
|
|
22879
|
+
shortTitle: "Alligator",
|
|
22880
|
+
overlay: true,
|
|
22881
|
+
inputs: [
|
|
22882
|
+
{ key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
|
|
22883
|
+
{ key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
|
|
22884
|
+
{ key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
|
|
22885
|
+
{ key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
|
|
22886
|
+
{ key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
|
|
22887
|
+
{ key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
|
|
22888
|
+
],
|
|
22889
|
+
compute: (bars, inputs) => {
|
|
22890
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
22891
|
+
const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
22892
|
+
const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
22893
|
+
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
22894
|
+
return {
|
|
22895
|
+
plots: [
|
|
22896
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
|
|
22897
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
|
|
22898
|
+
{ key: "lips", title: "Lips", values: lips, color: BULLISH }
|
|
22899
|
+
]
|
|
22900
|
+
};
|
|
22901
|
+
}
|
|
22902
|
+
};
|
|
22903
|
+
var gator = {
|
|
22904
|
+
type: "gator-oscillator",
|
|
22905
|
+
title: "Gator Oscillator",
|
|
22906
|
+
shortTitle: "Gator Oscillator",
|
|
22907
|
+
overlay: false,
|
|
22908
|
+
inputs: [],
|
|
22909
|
+
compute: (bars) => {
|
|
22910
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
22911
|
+
const jaw = shift(rma(hl2, 13), 8);
|
|
22912
|
+
const teeth = shift(rma(hl2, 8), 5);
|
|
22913
|
+
const lips = shift(rma(hl2, 5), 3);
|
|
22914
|
+
const upper = map(sub(jaw, teeth), Math.abs);
|
|
22915
|
+
const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
|
|
22916
|
+
const colorByExpansion = (v) => v.map((x, i) => {
|
|
22917
|
+
if (!Number.isFinite(x)) return null;
|
|
22918
|
+
const prev2 = i > 0 ? v[i - 1] : Number.NaN;
|
|
22919
|
+
return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
|
|
22920
|
+
});
|
|
22921
|
+
return {
|
|
22922
|
+
plots: [
|
|
22923
|
+
{ key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
|
|
22924
|
+
{ key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
|
|
22925
|
+
]
|
|
22926
|
+
};
|
|
22927
|
+
}
|
|
22928
|
+
};
|
|
22929
|
+
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
22930
|
+
|
|
22931
|
+
// src/core/native-indicators/classics/oscillators.ts
|
|
22932
|
+
function guideLevels(low, high, mid) {
|
|
22933
|
+
const levels = [
|
|
22934
|
+
{ key: "upper", price: high, color: NEUTRAL },
|
|
22935
|
+
{ key: "lower", price: low, color: NEUTRAL }
|
|
22936
|
+
];
|
|
22937
|
+
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
22938
|
+
return levels;
|
|
22939
|
+
}
|
|
22940
|
+
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
22941
|
+
function histColors(values) {
|
|
22942
|
+
return values.map((x, i) => {
|
|
22943
|
+
if (!Number.isFinite(x)) return null;
|
|
22944
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
22945
|
+
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
22946
|
+
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
22947
|
+
return rising ? `${BEARISH}80` : BEARISH;
|
|
22948
|
+
});
|
|
22949
|
+
}
|
|
22950
|
+
var rsiSpec = {
|
|
22951
|
+
type: "rsi",
|
|
22952
|
+
title: "Relative Strength Index",
|
|
22953
|
+
shortTitle: "RSI",
|
|
22954
|
+
overlay: false,
|
|
22955
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
22956
|
+
compute: (bars, inputs) => ({
|
|
22957
|
+
plots: [
|
|
22958
|
+
{
|
|
22959
|
+
key: "rsi",
|
|
22960
|
+
title: "RSI",
|
|
22961
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
22962
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
22963
|
+
width: 2
|
|
22964
|
+
}
|
|
22965
|
+
],
|
|
22966
|
+
levels: guideLevels(30, 70, 50)
|
|
22967
|
+
})
|
|
22968
|
+
};
|
|
22969
|
+
var stochastic = {
|
|
22970
|
+
type: "stochastic",
|
|
22971
|
+
title: "Stochastic",
|
|
22972
|
+
shortTitle: "Stoch",
|
|
22973
|
+
overlay: false,
|
|
22974
|
+
inputs: [
|
|
22975
|
+
lengthInput(14, "kLength", "%K length"),
|
|
22976
|
+
lengthInput(1, "kSmoothing", "%K smoothing", 500),
|
|
22977
|
+
lengthInput(3, "dLength", "%D smoothing", 500)
|
|
22978
|
+
],
|
|
22979
|
+
compute: (bars, inputs) => {
|
|
22980
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
|
|
22981
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
22982
|
+
return {
|
|
22983
|
+
plots: [
|
|
22984
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
22985
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
22986
|
+
],
|
|
22987
|
+
levels: guideLevels(20, 80)
|
|
22988
|
+
};
|
|
22989
|
+
}
|
|
22990
|
+
};
|
|
22991
|
+
var stochasticRsi = {
|
|
22992
|
+
type: "stochastic-rsi",
|
|
22993
|
+
title: "Stochastic RSI",
|
|
22994
|
+
shortTitle: "Stoch RSI",
|
|
22995
|
+
overlay: false,
|
|
22996
|
+
inputs: [
|
|
22997
|
+
lengthInput(14, "rsiLength", "RSI length"),
|
|
22998
|
+
lengthInput(14, "stochLength", "Stochastic length"),
|
|
22999
|
+
lengthInput(3, "kSmoothing", "%K smoothing", 500),
|
|
23000
|
+
lengthInput(3, "dLength", "%D smoothing", 500),
|
|
23001
|
+
sourceInput()
|
|
23002
|
+
],
|
|
23003
|
+
compute: (bars, inputs) => {
|
|
23004
|
+
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
23005
|
+
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
23006
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
23007
|
+
return {
|
|
23008
|
+
plots: [
|
|
23009
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
23010
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
23011
|
+
],
|
|
23012
|
+
levels: guideLevels(20, 80)
|
|
23013
|
+
};
|
|
23014
|
+
}
|
|
23015
|
+
};
|
|
23016
|
+
var macd = {
|
|
23017
|
+
type: "macd",
|
|
23018
|
+
title: "MACD",
|
|
23019
|
+
overlay: false,
|
|
23020
|
+
inputs: [
|
|
23021
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
23022
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
23023
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
23024
|
+
sourceInput()
|
|
23025
|
+
],
|
|
23026
|
+
compute: (bars, inputs) => {
|
|
23027
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23028
|
+
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
23029
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
23030
|
+
const hist = sub(line, signal);
|
|
23031
|
+
return {
|
|
23032
|
+
plots: [
|
|
23033
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
23034
|
+
{ key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
|
|
23035
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
23036
|
+
],
|
|
23037
|
+
levels: ZERO_LEVEL
|
|
23038
|
+
};
|
|
23039
|
+
}
|
|
23040
|
+
};
|
|
23041
|
+
var ppo = {
|
|
23042
|
+
type: "ppo",
|
|
23043
|
+
title: "Percentage Price Oscillator",
|
|
23044
|
+
shortTitle: "PPO",
|
|
23045
|
+
overlay: false,
|
|
23046
|
+
inputs: [
|
|
23047
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
23048
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
23049
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
23050
|
+
sourceInput()
|
|
23051
|
+
],
|
|
23052
|
+
compute: (bars, inputs) => {
|
|
23053
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23054
|
+
const fast = ema(src, num3(inputs, "fastLength", 12));
|
|
23055
|
+
const slow = ema(src, num3(inputs, "slowLength", 26));
|
|
23056
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
23057
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
23058
|
+
const hist = sub(line, signal);
|
|
23059
|
+
return {
|
|
23060
|
+
plots: [
|
|
23061
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
23062
|
+
{ key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
|
|
23063
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
23064
|
+
],
|
|
23065
|
+
levels: ZERO_LEVEL
|
|
23066
|
+
};
|
|
23067
|
+
}
|
|
23068
|
+
};
|
|
23069
|
+
var awesome = {
|
|
23070
|
+
type: "awesome-oscillator",
|
|
23071
|
+
title: "Awesome Oscillator",
|
|
23072
|
+
shortTitle: "AO",
|
|
23073
|
+
overlay: false,
|
|
23074
|
+
inputs: [],
|
|
23075
|
+
compute: (bars) => {
|
|
23076
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
23077
|
+
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
23078
|
+
const colors = ao.map((x, i) => {
|
|
23079
|
+
if (!Number.isFinite(x)) return null;
|
|
23080
|
+
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
23081
|
+
return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
|
|
23082
|
+
});
|
|
23083
|
+
return {
|
|
23084
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
|
|
23085
|
+
levels: ZERO_LEVEL
|
|
23086
|
+
};
|
|
23087
|
+
}
|
|
23088
|
+
};
|
|
23089
|
+
var cci = {
|
|
23090
|
+
type: "commodity-channel-index",
|
|
23091
|
+
title: "Commodity Channel Index",
|
|
23092
|
+
shortTitle: "CCI",
|
|
23093
|
+
overlay: false,
|
|
23094
|
+
inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
|
|
23095
|
+
compute: (bars, inputs) => {
|
|
23096
|
+
const len = num3(inputs, "length", 20);
|
|
23097
|
+
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
23098
|
+
const basis = sma(src, len);
|
|
23099
|
+
const dev = new Array(src.length).fill(Number.NaN);
|
|
23100
|
+
for (let i = len - 1; i < src.length; i++) {
|
|
23101
|
+
const mean = basis[i];
|
|
23102
|
+
if (!Number.isFinite(mean)) continue;
|
|
23103
|
+
let d = 0;
|
|
23104
|
+
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
23105
|
+
dev[i] = d / len;
|
|
23106
|
+
}
|
|
23107
|
+
const values = src.map((x, i) => {
|
|
23108
|
+
const b = basis[i];
|
|
23109
|
+
const d = dev[i];
|
|
23110
|
+
return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
|
|
23111
|
+
});
|
|
23112
|
+
return {
|
|
23113
|
+
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23114
|
+
levels: guideLevels(-100, 100, 0)
|
|
23115
|
+
};
|
|
23116
|
+
}
|
|
23117
|
+
};
|
|
23118
|
+
var williamsR = {
|
|
23119
|
+
type: "williams-percent-r",
|
|
23120
|
+
title: "Williams %R",
|
|
23121
|
+
shortTitle: "%R",
|
|
23122
|
+
overlay: false,
|
|
23123
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23124
|
+
compute: (bars, inputs) => {
|
|
23125
|
+
const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
|
|
23126
|
+
return {
|
|
23127
|
+
plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23128
|
+
levels: guideLevels(-80, -20)
|
|
23129
|
+
};
|
|
23130
|
+
}
|
|
23131
|
+
};
|
|
23132
|
+
var cmo = {
|
|
23133
|
+
type: "chande-momentum-oscillator",
|
|
23134
|
+
title: "Chande Momentum Oscillator",
|
|
23135
|
+
shortTitle: "CMO",
|
|
23136
|
+
overlay: false,
|
|
23137
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
23138
|
+
compute: (bars, inputs) => {
|
|
23139
|
+
const len = num3(inputs, "length", 9);
|
|
23140
|
+
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
23141
|
+
const su = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
23142
|
+
const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
23143
|
+
const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
23144
|
+
return {
|
|
23145
|
+
plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23146
|
+
levels: guideLevels(-50, 50, 0)
|
|
23147
|
+
};
|
|
23148
|
+
}
|
|
23149
|
+
};
|
|
23150
|
+
var connorsRsi = {
|
|
23151
|
+
type: "connors-rsi",
|
|
23152
|
+
title: "Connors RSI",
|
|
23153
|
+
shortTitle: "CRSI",
|
|
23154
|
+
overlay: false,
|
|
23155
|
+
inputs: [
|
|
23156
|
+
lengthInput(3, "rsiLength", "RSI length"),
|
|
23157
|
+
lengthInput(2, "streakLength", "Streak RSI length"),
|
|
23158
|
+
lengthInput(100, "rankLength", "Percent-rank length"),
|
|
23159
|
+
colorInput()
|
|
23160
|
+
],
|
|
23161
|
+
compute: (bars, inputs) => {
|
|
23162
|
+
const c = closes(bars);
|
|
23163
|
+
const streak = new Array(c.length).fill(0);
|
|
23164
|
+
for (let i = 1; i < c.length; i++) {
|
|
23165
|
+
const d = c[i] - c[i - 1];
|
|
23166
|
+
streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
|
|
23167
|
+
}
|
|
23168
|
+
const a = rsi(c, num3(inputs, "rsiLength", 3));
|
|
23169
|
+
const b = rsi(streak, num3(inputs, "streakLength", 2));
|
|
23170
|
+
const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
|
|
23171
|
+
const values = c.map((_, i) => {
|
|
23172
|
+
const x = a[i];
|
|
23173
|
+
const y = b[i];
|
|
23174
|
+
const z = r[i];
|
|
23175
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
23176
|
+
});
|
|
23177
|
+
return {
|
|
23178
|
+
plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23179
|
+
levels: guideLevels(20, 80, 50)
|
|
23180
|
+
};
|
|
23181
|
+
}
|
|
23182
|
+
};
|
|
23183
|
+
var fisher = {
|
|
23184
|
+
type: "fisher-transform",
|
|
23185
|
+
title: "Fisher Transform",
|
|
23186
|
+
shortTitle: "Fisher",
|
|
23187
|
+
overlay: false,
|
|
23188
|
+
inputs: [lengthInput(9)],
|
|
23189
|
+
compute: (bars, inputs) => {
|
|
23190
|
+
const len = num3(inputs, "length", 9);
|
|
23191
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
23192
|
+
const hi = highest(hl2, len);
|
|
23193
|
+
const lo = lowest(hl2, len);
|
|
23194
|
+
const n = bars.length;
|
|
23195
|
+
const out = new Array(n).fill(Number.NaN);
|
|
23196
|
+
let value = 0;
|
|
23197
|
+
let fish = 0;
|
|
23198
|
+
for (let i = 0; i < n; i++) {
|
|
23199
|
+
const h = hi[i];
|
|
23200
|
+
const l = lo[i];
|
|
23201
|
+
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
23202
|
+
const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
|
|
23203
|
+
value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
|
|
23204
|
+
fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
|
|
23205
|
+
out[i] = fish;
|
|
23206
|
+
}
|
|
23207
|
+
return {
|
|
23208
|
+
plots: [
|
|
23209
|
+
{ key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
|
|
23210
|
+
{ key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
|
|
23211
|
+
],
|
|
23212
|
+
levels: ZERO_LEVEL
|
|
23213
|
+
};
|
|
23214
|
+
}
|
|
23215
|
+
};
|
|
23216
|
+
var trix = {
|
|
23217
|
+
type: "trix",
|
|
23218
|
+
title: "TRIX",
|
|
23219
|
+
overlay: false,
|
|
23220
|
+
inputs: [lengthInput(18), colorInput()],
|
|
23221
|
+
compute: (bars, inputs) => {
|
|
23222
|
+
const len = num3(inputs, "length", 18);
|
|
23223
|
+
const logClose = map(closes(bars), Math.log);
|
|
23224
|
+
const smooth = ema(ema(ema(logClose, len), len), len);
|
|
23225
|
+
const values = map(change(smooth), (x) => x * 1e4);
|
|
23226
|
+
return {
|
|
23227
|
+
plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23228
|
+
levels: ZERO_LEVEL
|
|
23229
|
+
};
|
|
23230
|
+
}
|
|
23231
|
+
};
|
|
23232
|
+
function tsiLine(src, long, short) {
|
|
23233
|
+
const mom = change(src);
|
|
23234
|
+
const numer = ema(ema(mom, long), short);
|
|
23235
|
+
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
23236
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
|
|
23237
|
+
}
|
|
23238
|
+
var tsi = {
|
|
23239
|
+
type: "true-strength-index",
|
|
23240
|
+
title: "True Strength Index",
|
|
23241
|
+
shortTitle: "TSI",
|
|
23242
|
+
overlay: false,
|
|
23243
|
+
inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
|
|
23244
|
+
compute: (bars, inputs) => {
|
|
23245
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
|
|
23246
|
+
return {
|
|
23247
|
+
plots: [
|
|
23248
|
+
{ key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
|
|
23249
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
23250
|
+
],
|
|
23251
|
+
levels: ZERO_LEVEL
|
|
23252
|
+
};
|
|
23253
|
+
}
|
|
23254
|
+
};
|
|
23255
|
+
var smiErgodic = {
|
|
23256
|
+
type: "smi-ergodic",
|
|
23257
|
+
title: "SMI Ergodic Oscillator",
|
|
23258
|
+
shortTitle: "SMIE",
|
|
23259
|
+
overlay: false,
|
|
23260
|
+
inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
|
|
23261
|
+
compute: (bars, inputs) => {
|
|
23262
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
23263
|
+
const signal = ema(line, num3(inputs, "signalLength", 5));
|
|
23264
|
+
const osc = sub(line, signal);
|
|
23265
|
+
return {
|
|
23266
|
+
plots: [
|
|
23267
|
+
{ key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
|
|
23268
|
+
{ key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
|
|
23269
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
23270
|
+
],
|
|
23271
|
+
levels: ZERO_LEVEL
|
|
23272
|
+
};
|
|
23273
|
+
}
|
|
23274
|
+
};
|
|
23275
|
+
var stc = {
|
|
23276
|
+
type: "schaff-trend-cycle",
|
|
23277
|
+
title: "Schaff Trend Cycle",
|
|
23278
|
+
shortTitle: "Schaff Trend Cycle",
|
|
23279
|
+
overlay: false,
|
|
23280
|
+
inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
|
|
23281
|
+
compute: (bars, inputs) => {
|
|
23282
|
+
const len = num3(inputs, "cycleLength", 10);
|
|
23283
|
+
const src = closes(bars);
|
|
23284
|
+
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
23285
|
+
const stochOf = (v) => {
|
|
23286
|
+
const lo = lowest(v, len);
|
|
23287
|
+
const hi = highest(v, len);
|
|
23288
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
23289
|
+
let prevRaw = Number.NaN;
|
|
23290
|
+
let smoothed = Number.NaN;
|
|
23291
|
+
for (let i = 0; i < v.length; i++) {
|
|
23292
|
+
const x = v[i];
|
|
23293
|
+
const l = lo[i];
|
|
23294
|
+
const h = hi[i];
|
|
23295
|
+
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
23296
|
+
const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
|
|
23297
|
+
prevRaw = raw;
|
|
23298
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
|
|
23299
|
+
out[i] = smoothed;
|
|
23300
|
+
}
|
|
23301
|
+
return out;
|
|
23302
|
+
};
|
|
23303
|
+
const values = stochOf(stochOf(macdLine));
|
|
23304
|
+
return {
|
|
23305
|
+
plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
|
|
23306
|
+
levels: guideLevels(25, 75)
|
|
23307
|
+
};
|
|
23308
|
+
}
|
|
23309
|
+
};
|
|
23310
|
+
var kst = {
|
|
23311
|
+
type: "know-sure-thing",
|
|
23312
|
+
title: "Know Sure Thing",
|
|
23313
|
+
shortTitle: "KST",
|
|
23314
|
+
overlay: false,
|
|
23315
|
+
inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
|
|
23316
|
+
compute: (bars, inputs) => {
|
|
23317
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23318
|
+
const line = [
|
|
23319
|
+
sma(roc(src, 10), 10),
|
|
23320
|
+
sma(roc(src, 15), 10),
|
|
23321
|
+
sma(roc(src, 20), 10),
|
|
23322
|
+
sma(roc(src, 30), 15)
|
|
23323
|
+
].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
|
|
23324
|
+
return {
|
|
23325
|
+
plots: [
|
|
23326
|
+
{ key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
|
|
23327
|
+
{ key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
|
|
23328
|
+
],
|
|
23329
|
+
levels: ZERO_LEVEL
|
|
23330
|
+
};
|
|
23331
|
+
}
|
|
23332
|
+
};
|
|
23333
|
+
var coppock = {
|
|
23334
|
+
type: "coppock-curve",
|
|
23335
|
+
title: "Coppock Curve",
|
|
23336
|
+
shortTitle: "Coppock",
|
|
23337
|
+
overlay: false,
|
|
23338
|
+
inputs: [
|
|
23339
|
+
lengthInput(10, "wmaLength", "WMA length"),
|
|
23340
|
+
lengthInput(14, "longRoc", "Long RoC length"),
|
|
23341
|
+
lengthInput(11, "shortRoc", "Short RoC length"),
|
|
23342
|
+
sourceInput(),
|
|
23343
|
+
colorInput()
|
|
23344
|
+
],
|
|
23345
|
+
compute: (bars, inputs) => {
|
|
23346
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23347
|
+
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
23348
|
+
return {
|
|
23349
|
+
plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23350
|
+
levels: ZERO_LEVEL
|
|
23351
|
+
};
|
|
23352
|
+
}
|
|
23353
|
+
};
|
|
23354
|
+
var dpo = {
|
|
23355
|
+
type: "detrended-price-oscillator",
|
|
23356
|
+
title: "Detrended Price Oscillator",
|
|
23357
|
+
shortTitle: "DPO",
|
|
23358
|
+
overlay: false,
|
|
23359
|
+
inputs: [lengthInput(21), colorInput()],
|
|
23360
|
+
compute: (bars, inputs) => {
|
|
23361
|
+
const len = num3(inputs, "length", 21);
|
|
23362
|
+
const barsback = Math.floor(len / 2) + 1;
|
|
23363
|
+
const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
|
|
23364
|
+
return {
|
|
23365
|
+
plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23366
|
+
levels: ZERO_LEVEL
|
|
23367
|
+
};
|
|
23368
|
+
}
|
|
23369
|
+
};
|
|
23370
|
+
var rocSpec = {
|
|
23371
|
+
type: "rate-of-change",
|
|
23372
|
+
title: "Rate of Change",
|
|
23373
|
+
shortTitle: "ROC",
|
|
23374
|
+
overlay: false,
|
|
23375
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
23376
|
+
compute: (bars, inputs) => ({
|
|
23377
|
+
plots: [
|
|
23378
|
+
{
|
|
23379
|
+
key: "roc",
|
|
23380
|
+
title: "RoC",
|
|
23381
|
+
values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
|
|
23382
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
23383
|
+
width: 2
|
|
23384
|
+
}
|
|
23385
|
+
],
|
|
23386
|
+
levels: ZERO_LEVEL
|
|
23387
|
+
})
|
|
23388
|
+
};
|
|
23389
|
+
var ultimate = {
|
|
23390
|
+
type: "ultimate-oscillator",
|
|
23391
|
+
title: "Ultimate Oscillator",
|
|
23392
|
+
shortTitle: "UO",
|
|
23393
|
+
overlay: false,
|
|
23394
|
+
inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
|
|
23395
|
+
compute: (bars, inputs) => {
|
|
23396
|
+
const n = bars.length;
|
|
23397
|
+
const bp = new Array(n).fill(Number.NaN);
|
|
23398
|
+
const tr = new Array(n).fill(Number.NaN);
|
|
23399
|
+
for (let i = 0; i < n; i++) {
|
|
23400
|
+
const b = bars[i];
|
|
23401
|
+
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
23402
|
+
const lo = Math.min(b.low, pc);
|
|
23403
|
+
const hi = Math.max(b.high, pc);
|
|
23404
|
+
bp[i] = b.close - lo;
|
|
23405
|
+
tr[i] = hi - lo;
|
|
23406
|
+
}
|
|
23407
|
+
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
23408
|
+
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
23409
|
+
const a14 = avg(num3(inputs, "middleLength", 14));
|
|
23410
|
+
const a28 = avg(num3(inputs, "slowLength", 28));
|
|
23411
|
+
const values = a7.map((x, i) => {
|
|
23412
|
+
const y = a14[i];
|
|
23413
|
+
const z = a28[i];
|
|
23414
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
23415
|
+
});
|
|
23416
|
+
return {
|
|
23417
|
+
plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23418
|
+
levels: guideLevels(30, 70, 50)
|
|
23419
|
+
};
|
|
23420
|
+
}
|
|
23421
|
+
};
|
|
23422
|
+
var rvgi = {
|
|
23423
|
+
type: "relative-vigor-index",
|
|
23424
|
+
title: "Relative Vigor Index",
|
|
23425
|
+
shortTitle: "RVGI",
|
|
23426
|
+
overlay: false,
|
|
23427
|
+
inputs: [lengthInput(10)],
|
|
23428
|
+
compute: (bars, inputs) => {
|
|
23429
|
+
const len = num3(inputs, "length", 10);
|
|
23430
|
+
const co = swma(sub(closes(bars), opens(bars)));
|
|
23431
|
+
const hl = swma(sub(highs(bars), lows(bars)));
|
|
23432
|
+
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
23433
|
+
return {
|
|
23434
|
+
plots: [
|
|
23435
|
+
{ key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
|
|
23436
|
+
{ key: "signal", title: "Signal", values: swma(line), color: WARNING }
|
|
23437
|
+
],
|
|
23438
|
+
levels: ZERO_LEVEL
|
|
23439
|
+
};
|
|
23440
|
+
}
|
|
23441
|
+
};
|
|
23442
|
+
var relVolatility = {
|
|
23443
|
+
type: "relative-volatility-index",
|
|
23444
|
+
title: "Relative Volatility Index",
|
|
23445
|
+
shortTitle: "RVI",
|
|
23446
|
+
overlay: false,
|
|
23447
|
+
inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
|
|
23448
|
+
compute: (bars, inputs) => {
|
|
23449
|
+
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
23450
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23451
|
+
const dev = stdev(src, num3(inputs, "stdevLength", 10));
|
|
23452
|
+
const d = change(src);
|
|
23453
|
+
const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
|
|
23454
|
+
const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
|
|
23455
|
+
const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
|
|
23456
|
+
return {
|
|
23457
|
+
plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23458
|
+
levels: guideLevels(20, 80, 50)
|
|
23459
|
+
};
|
|
23460
|
+
}
|
|
23461
|
+
};
|
|
23462
|
+
var bop = {
|
|
23463
|
+
type: "balance-of-power",
|
|
23464
|
+
title: "Balance of Power",
|
|
23465
|
+
shortTitle: "BOP",
|
|
23466
|
+
overlay: false,
|
|
23467
|
+
inputs: [colorInput()],
|
|
23468
|
+
compute: (bars, inputs) => {
|
|
23469
|
+
const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
23470
|
+
return {
|
|
23471
|
+
plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23472
|
+
levels: ZERO_LEVEL
|
|
23473
|
+
};
|
|
23474
|
+
}
|
|
23475
|
+
};
|
|
23476
|
+
var elderRay = {
|
|
23477
|
+
type: "elder-ray",
|
|
23478
|
+
title: "Elder Ray",
|
|
23479
|
+
overlay: false,
|
|
23480
|
+
inputs: [lengthInput(13)],
|
|
23481
|
+
compute: (bars, inputs) => {
|
|
23482
|
+
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
23483
|
+
return {
|
|
23484
|
+
plots: [
|
|
23485
|
+
{ key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
|
|
23486
|
+
{ key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
|
|
23487
|
+
],
|
|
23488
|
+
levels: ZERO_LEVEL
|
|
23489
|
+
};
|
|
23490
|
+
}
|
|
23491
|
+
};
|
|
23492
|
+
var ttmSqueeze = {
|
|
23493
|
+
type: "ttm-squeeze",
|
|
23494
|
+
title: "TTM Squeeze",
|
|
23495
|
+
overlay: false,
|
|
23496
|
+
inputs: [
|
|
23497
|
+
lengthInput(20),
|
|
23498
|
+
{ key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
23499
|
+
{ key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
|
|
23500
|
+
],
|
|
23501
|
+
compute: (bars, inputs) => {
|
|
23502
|
+
const len = num3(inputs, "length", 20);
|
|
23503
|
+
const c = closes(bars);
|
|
23504
|
+
const basis = sma(c, len);
|
|
23505
|
+
const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
23506
|
+
const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
23507
|
+
const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
23508
|
+
const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
|
|
23509
|
+
const mom = linreg(sub(c, anchor), len);
|
|
23510
|
+
const squeezed = c.map((_, i) => {
|
|
23511
|
+
const d = dev[i];
|
|
23512
|
+
const r = kcRange[i];
|
|
23513
|
+
return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
|
|
23514
|
+
});
|
|
23515
|
+
const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
|
|
23516
|
+
return {
|
|
23517
|
+
plots: [
|
|
23518
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
|
|
23519
|
+
{ key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
|
|
23520
|
+
]
|
|
23521
|
+
};
|
|
23522
|
+
}
|
|
23523
|
+
};
|
|
23524
|
+
var oscillatorSpecs = [
|
|
23525
|
+
rsiSpec,
|
|
23526
|
+
stochastic,
|
|
23527
|
+
stochasticRsi,
|
|
23528
|
+
macd,
|
|
23529
|
+
ppo,
|
|
23530
|
+
awesome,
|
|
23531
|
+
cci,
|
|
23532
|
+
williamsR,
|
|
23533
|
+
cmo,
|
|
23534
|
+
connorsRsi,
|
|
23535
|
+
fisher,
|
|
23536
|
+
trix,
|
|
23537
|
+
tsi,
|
|
23538
|
+
smiErgodic,
|
|
23539
|
+
stc,
|
|
23540
|
+
kst,
|
|
23541
|
+
coppock,
|
|
23542
|
+
dpo,
|
|
23543
|
+
rocSpec,
|
|
23544
|
+
ultimate,
|
|
23545
|
+
rvgi,
|
|
23546
|
+
relVolatility,
|
|
23547
|
+
bop,
|
|
23548
|
+
elderRay,
|
|
23549
|
+
ttmSqueeze
|
|
23550
|
+
];
|
|
23551
|
+
|
|
23552
|
+
// src/core/native-indicators/classics/trend.ts
|
|
23553
|
+
var aroon = {
|
|
23554
|
+
type: "aroon",
|
|
23555
|
+
title: "Aroon",
|
|
23556
|
+
overlay: false,
|
|
23557
|
+
inputs: [lengthInput(14)],
|
|
23558
|
+
compute: (bars, inputs) => {
|
|
23559
|
+
const len = num3(inputs, "length", 14);
|
|
23560
|
+
const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
23561
|
+
const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
23562
|
+
return {
|
|
23563
|
+
plots: [
|
|
23564
|
+
{ key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
|
|
23565
|
+
{ key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
|
|
23566
|
+
],
|
|
23567
|
+
levels: [
|
|
23568
|
+
{ key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
|
|
23569
|
+
{ key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
|
|
23570
|
+
]
|
|
23571
|
+
};
|
|
23572
|
+
}
|
|
23573
|
+
};
|
|
23574
|
+
var adx = {
|
|
23575
|
+
type: "average-directional-index",
|
|
23576
|
+
title: "Average Directional Index",
|
|
23577
|
+
shortTitle: "ADX",
|
|
23578
|
+
overlay: false,
|
|
23579
|
+
inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
|
|
23580
|
+
compute: (bars, inputs) => {
|
|
23581
|
+
const diLen = num3(inputs, "diLength", 14);
|
|
23582
|
+
const adxLen = num3(inputs, "adxLength", 14);
|
|
23583
|
+
const n = bars.length;
|
|
23584
|
+
const plusDm = new Array(n).fill(Number.NaN);
|
|
23585
|
+
const minusDm = new Array(n).fill(Number.NaN);
|
|
23586
|
+
for (let i = 1; i < n; i++) {
|
|
23587
|
+
const up = bars[i].high - bars[i - 1].high;
|
|
23588
|
+
const dn = bars[i - 1].low - bars[i].low;
|
|
23589
|
+
plusDm[i] = up > dn && up > 0 ? up : 0;
|
|
23590
|
+
minusDm[i] = dn > up && dn > 0 ? dn : 0;
|
|
23591
|
+
}
|
|
23592
|
+
const atrLine = rma(trueRange(bars), diLen);
|
|
23593
|
+
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
23594
|
+
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
23595
|
+
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
23596
|
+
return {
|
|
23597
|
+
plots: [
|
|
23598
|
+
{ key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
|
|
23599
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
|
|
23600
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
|
|
23601
|
+
],
|
|
23602
|
+
levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
|
|
23603
|
+
};
|
|
23604
|
+
}
|
|
23605
|
+
};
|
|
23606
|
+
var vortex = {
|
|
23607
|
+
type: "vortex-indicator",
|
|
23608
|
+
title: "Vortex Indicator",
|
|
23609
|
+
shortTitle: "VI",
|
|
23610
|
+
overlay: false,
|
|
23611
|
+
inputs: [lengthInput(14)],
|
|
23612
|
+
compute: (bars, inputs) => {
|
|
23613
|
+
const len = num3(inputs, "length", 14);
|
|
23614
|
+
const n = bars.length;
|
|
23615
|
+
const vmPlus = new Array(n).fill(Number.NaN);
|
|
23616
|
+
const vmMinus = new Array(n).fill(Number.NaN);
|
|
23617
|
+
for (let i = 1; i < n; i++) {
|
|
23618
|
+
vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
|
|
23619
|
+
vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
|
|
23620
|
+
}
|
|
23621
|
+
const trSum = sum(trueRange(bars), len);
|
|
23622
|
+
return {
|
|
23623
|
+
plots: [
|
|
23624
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
|
|
23625
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
|
|
23626
|
+
],
|
|
23627
|
+
levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
|
|
23628
|
+
};
|
|
23629
|
+
}
|
|
23630
|
+
};
|
|
23631
|
+
var trendSpecs = [aroon, adx, vortex];
|
|
23632
|
+
|
|
23633
|
+
// src/core/native-indicators/classics/volatility.ts
|
|
23634
|
+
var atrSpec = {
|
|
23635
|
+
type: "average-true-range",
|
|
23636
|
+
title: "Average True Range",
|
|
23637
|
+
shortTitle: "ATR",
|
|
23638
|
+
overlay: false,
|
|
23639
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23640
|
+
compute: (bars, inputs) => ({
|
|
23641
|
+
plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
23642
|
+
})
|
|
23643
|
+
};
|
|
23644
|
+
var historicalVolatility = {
|
|
23645
|
+
type: "historical-volatility",
|
|
23646
|
+
title: "Historical Volatility",
|
|
23647
|
+
shortTitle: "HV",
|
|
23648
|
+
overlay: false,
|
|
23649
|
+
inputs: [
|
|
23650
|
+
lengthInput(10),
|
|
23651
|
+
{ 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)" },
|
|
23652
|
+
colorInput()
|
|
23653
|
+
],
|
|
23654
|
+
compute: (bars, inputs) => {
|
|
23655
|
+
const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
|
|
23656
|
+
const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
|
|
23657
|
+
return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
23658
|
+
}
|
|
23659
|
+
};
|
|
23660
|
+
var chaikinVolatility = {
|
|
23661
|
+
type: "chaikin-volatility",
|
|
23662
|
+
title: "Chaikin Volatility",
|
|
23663
|
+
shortTitle: "CHV",
|
|
23664
|
+
overlay: false,
|
|
23665
|
+
inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
|
|
23666
|
+
compute: (bars, inputs) => {
|
|
23667
|
+
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
23668
|
+
return {
|
|
23669
|
+
plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23670
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23671
|
+
};
|
|
23672
|
+
}
|
|
23673
|
+
};
|
|
23674
|
+
var massIndex = {
|
|
23675
|
+
type: "mass-index",
|
|
23676
|
+
title: "Mass Index",
|
|
23677
|
+
shortTitle: "MI",
|
|
23678
|
+
overlay: false,
|
|
23679
|
+
inputs: [lengthInput(25), colorInput()],
|
|
23680
|
+
compute: (bars, inputs) => {
|
|
23681
|
+
const range = sub(highs(bars), lows(bars));
|
|
23682
|
+
const single = ema(range, 9);
|
|
23683
|
+
const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
23684
|
+
return {
|
|
23685
|
+
plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23686
|
+
levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
|
|
23687
|
+
};
|
|
23688
|
+
}
|
|
23689
|
+
};
|
|
23690
|
+
var standardDeviation = {
|
|
23691
|
+
type: "standard-deviation",
|
|
23692
|
+
title: "Standard Deviation",
|
|
23693
|
+
shortTitle: "StdDev",
|
|
23694
|
+
overlay: false,
|
|
23695
|
+
inputs: [lengthInput(20), sourceInput(), colorInput()],
|
|
23696
|
+
compute: (bars, inputs) => ({
|
|
23697
|
+
plots: [
|
|
23698
|
+
{
|
|
23699
|
+
key: "stdev",
|
|
23700
|
+
title: "StdDev",
|
|
23701
|
+
values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
|
|
23702
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
23703
|
+
width: 2
|
|
23704
|
+
}
|
|
23705
|
+
]
|
|
23706
|
+
})
|
|
23707
|
+
};
|
|
23708
|
+
var ulcerIndex = {
|
|
23709
|
+
type: "ulcer-index",
|
|
23710
|
+
title: "Ulcer Index",
|
|
23711
|
+
overlay: false,
|
|
23712
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23713
|
+
compute: (bars, inputs) => {
|
|
23714
|
+
const len = num3(inputs, "length", 14);
|
|
23715
|
+
const c = closes(bars);
|
|
23716
|
+
const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
|
|
23717
|
+
const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
|
|
23718
|
+
return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
23719
|
+
}
|
|
23720
|
+
};
|
|
23721
|
+
var choppiness = {
|
|
23722
|
+
type: "choppiness-index",
|
|
23723
|
+
title: "Choppiness Index",
|
|
23724
|
+
shortTitle: "CHOP",
|
|
23725
|
+
overlay: false,
|
|
23726
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23727
|
+
compute: (bars, inputs) => {
|
|
23728
|
+
const len = num3(inputs, "length", 14);
|
|
23729
|
+
const trSum = sum(trueRange(bars), len);
|
|
23730
|
+
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
23731
|
+
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
23732
|
+
return {
|
|
23733
|
+
plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23734
|
+
levels: [
|
|
23735
|
+
{ key: "upper", price: 61.8, color: NEUTRAL },
|
|
23736
|
+
{ key: "lower", price: 38.2, color: NEUTRAL }
|
|
23737
|
+
]
|
|
23738
|
+
};
|
|
23739
|
+
}
|
|
23740
|
+
};
|
|
23741
|
+
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
23742
|
+
|
|
23743
|
+
// src/core/native-indicators/classics/volume.ts
|
|
23744
|
+
function moneyFlowVolume(bars) {
|
|
23745
|
+
return bars.map((b) => {
|
|
23746
|
+
if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
23747
|
+
if (b.high === b.low) return 0;
|
|
23748
|
+
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
23749
|
+
});
|
|
23750
|
+
}
|
|
23751
|
+
var obv = {
|
|
23752
|
+
type: "on-balance-volume",
|
|
23753
|
+
title: "On Balance Volume",
|
|
23754
|
+
shortTitle: "OBV",
|
|
23755
|
+
overlay: false,
|
|
23756
|
+
inputs: [colorInput()],
|
|
23757
|
+
compute: (bars, inputs) => {
|
|
23758
|
+
const vol = volumes(bars);
|
|
23759
|
+
const d = change(closes(bars));
|
|
23760
|
+
const signed2 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
|
|
23761
|
+
return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed2), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
23762
|
+
}
|
|
23763
|
+
};
|
|
23764
|
+
var accDist = {
|
|
23765
|
+
type: "accumulation-distribution",
|
|
23766
|
+
title: "Accumulation / Distribution",
|
|
23767
|
+
shortTitle: "A/D",
|
|
23768
|
+
overlay: false,
|
|
23769
|
+
inputs: [colorInput()],
|
|
23770
|
+
compute: (bars, inputs) => ({
|
|
23771
|
+
plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
23772
|
+
})
|
|
23773
|
+
};
|
|
23774
|
+
var cmf = {
|
|
23775
|
+
type: "chaikin-money-flow",
|
|
23776
|
+
title: "Chaikin Money Flow",
|
|
23777
|
+
shortTitle: "CMF",
|
|
23778
|
+
overlay: false,
|
|
23779
|
+
inputs: [lengthInput(20), colorInput()],
|
|
23780
|
+
compute: (bars, inputs) => {
|
|
23781
|
+
const len = num3(inputs, "length", 20);
|
|
23782
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
23783
|
+
return {
|
|
23784
|
+
plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23785
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23786
|
+
};
|
|
23787
|
+
}
|
|
23788
|
+
};
|
|
23789
|
+
var chaikinOscillator = {
|
|
23790
|
+
type: "chaikin-oscillator",
|
|
23791
|
+
title: "Chaikin Oscillator",
|
|
23792
|
+
shortTitle: "Chaikin Osc",
|
|
23793
|
+
overlay: false,
|
|
23794
|
+
inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
23795
|
+
compute: (bars, inputs) => {
|
|
23796
|
+
const ad = cumSum(moneyFlowVolume(bars));
|
|
23797
|
+
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
23798
|
+
return {
|
|
23799
|
+
plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23800
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23801
|
+
};
|
|
23802
|
+
}
|
|
23803
|
+
};
|
|
23804
|
+
var eom = {
|
|
23805
|
+
type: "ease-of-movement",
|
|
23806
|
+
title: "Ease of Movement",
|
|
23807
|
+
shortTitle: "EOM",
|
|
23808
|
+
overlay: false,
|
|
23809
|
+
inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
|
|
23810
|
+
compute: (bars, inputs) => {
|
|
23811
|
+
const div = num3(inputs, "divisor", 1e4);
|
|
23812
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
23813
|
+
const move = change(hl2);
|
|
23814
|
+
const raw = bars.map((b, i) => {
|
|
23815
|
+
const m = move[i];
|
|
23816
|
+
if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
|
|
23817
|
+
return div * m * (b.high - b.low) / b.volume;
|
|
23818
|
+
});
|
|
23819
|
+
return {
|
|
23820
|
+
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23821
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23822
|
+
};
|
|
23823
|
+
}
|
|
23824
|
+
};
|
|
23825
|
+
var forceIndex = {
|
|
23826
|
+
type: "force-index",
|
|
23827
|
+
title: "Force Index",
|
|
23828
|
+
shortTitle: "FI",
|
|
23829
|
+
overlay: false,
|
|
23830
|
+
inputs: [lengthInput(13), colorInput()],
|
|
23831
|
+
compute: (bars, inputs) => {
|
|
23832
|
+
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
23833
|
+
return {
|
|
23834
|
+
plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23835
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23836
|
+
};
|
|
23837
|
+
}
|
|
23838
|
+
};
|
|
23839
|
+
var klinger = {
|
|
23840
|
+
type: "klinger-oscillator",
|
|
23841
|
+
title: "Klinger Oscillator",
|
|
23842
|
+
shortTitle: "KVO",
|
|
23843
|
+
overlay: false,
|
|
23844
|
+
inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
|
|
23845
|
+
compute: (bars, inputs) => {
|
|
23846
|
+
const hlc3 = sourceValues(bars, "HLC3");
|
|
23847
|
+
const trend = change(hlc3);
|
|
23848
|
+
const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
|
|
23849
|
+
const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
|
|
23850
|
+
return {
|
|
23851
|
+
plots: [
|
|
23852
|
+
{ key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
|
|
23853
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
23854
|
+
],
|
|
23855
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23856
|
+
};
|
|
23857
|
+
}
|
|
23858
|
+
};
|
|
23859
|
+
var mfi = {
|
|
23860
|
+
type: "money-flow-index",
|
|
23861
|
+
title: "Money Flow Index",
|
|
23862
|
+
shortTitle: "MFI",
|
|
23863
|
+
overlay: false,
|
|
23864
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23865
|
+
compute: (bars, inputs) => {
|
|
23866
|
+
const len = num3(inputs, "length", 14);
|
|
23867
|
+
const tp = sourceValues(bars, "HLC3");
|
|
23868
|
+
const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
|
|
23869
|
+
const d = change(tp);
|
|
23870
|
+
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
23871
|
+
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
23872
|
+
const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
|
|
23873
|
+
return {
|
|
23874
|
+
plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23875
|
+
levels: [
|
|
23876
|
+
{ key: "upper", price: 80, color: NEUTRAL },
|
|
23877
|
+
{ key: "lower", price: 20, color: NEUTRAL }
|
|
23878
|
+
]
|
|
23879
|
+
};
|
|
23880
|
+
}
|
|
23881
|
+
};
|
|
23882
|
+
function volumeIndex(bars, when) {
|
|
23883
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
23884
|
+
let index = 1e3;
|
|
23885
|
+
let started = false;
|
|
23886
|
+
for (let i = 1; i < bars.length; i++) {
|
|
23887
|
+
const v = bars[i].volume;
|
|
23888
|
+
const pv = bars[i - 1].volume;
|
|
23889
|
+
const pc = bars[i - 1].close;
|
|
23890
|
+
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
23891
|
+
if (started) out[i] = index;
|
|
23892
|
+
continue;
|
|
23893
|
+
}
|
|
23894
|
+
started = true;
|
|
23895
|
+
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
23896
|
+
out[i] = index;
|
|
23897
|
+
}
|
|
23898
|
+
return out;
|
|
23899
|
+
}
|
|
23900
|
+
var nvi = {
|
|
23901
|
+
type: "negative-volume-index",
|
|
23902
|
+
title: "Negative Volume Index",
|
|
23903
|
+
shortTitle: "Negative Volume Index",
|
|
23904
|
+
overlay: false,
|
|
23905
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
23906
|
+
compute: (bars, inputs) => {
|
|
23907
|
+
const line = volumeIndex(bars, (v, pv) => v < pv);
|
|
23908
|
+
return {
|
|
23909
|
+
plots: [
|
|
23910
|
+
{ key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
23911
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
23912
|
+
]
|
|
23913
|
+
};
|
|
23914
|
+
}
|
|
23915
|
+
};
|
|
23916
|
+
var pvi = {
|
|
23917
|
+
type: "positive-volume-index",
|
|
23918
|
+
title: "Positive Volume Index",
|
|
23919
|
+
shortTitle: "Positive Volume Index",
|
|
23920
|
+
overlay: false,
|
|
23921
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
23922
|
+
compute: (bars, inputs) => {
|
|
23923
|
+
const line = volumeIndex(bars, (v, pv) => v > pv);
|
|
23924
|
+
return {
|
|
23925
|
+
plots: [
|
|
23926
|
+
{ key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
23927
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
23928
|
+
]
|
|
23929
|
+
};
|
|
23930
|
+
}
|
|
23931
|
+
};
|
|
23932
|
+
var pvt = {
|
|
23933
|
+
type: "price-volume-trend",
|
|
23934
|
+
title: "Price Volume Trend",
|
|
23935
|
+
shortTitle: "PVT",
|
|
23936
|
+
overlay: false,
|
|
23937
|
+
inputs: [colorInput()],
|
|
23938
|
+
compute: (bars, inputs) => {
|
|
23939
|
+
const c = closes(bars);
|
|
23940
|
+
const raw = bars.map((b, i) => {
|
|
23941
|
+
if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
23942
|
+
const pc = c[i - 1];
|
|
23943
|
+
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
23944
|
+
});
|
|
23945
|
+
return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
23946
|
+
}
|
|
23947
|
+
};
|
|
23948
|
+
var volumeOscillator = {
|
|
23949
|
+
type: "volume-oscillator",
|
|
23950
|
+
title: "Volume Oscillator",
|
|
23951
|
+
shortTitle: "Vol Osc",
|
|
23952
|
+
overlay: false,
|
|
23953
|
+
inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
23954
|
+
compute: (bars, inputs) => {
|
|
23955
|
+
const vol = volumes(bars);
|
|
23956
|
+
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
23957
|
+
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
23958
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
23959
|
+
return {
|
|
23960
|
+
plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23961
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23962
|
+
};
|
|
23963
|
+
}
|
|
23964
|
+
};
|
|
23965
|
+
var pvo = {
|
|
23966
|
+
type: "pvo",
|
|
23967
|
+
title: "Percentage Volume Oscillator",
|
|
23968
|
+
shortTitle: "PVO",
|
|
23969
|
+
overlay: false,
|
|
23970
|
+
inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
|
|
23971
|
+
compute: (bars, inputs) => {
|
|
23972
|
+
const vol = volumes(bars);
|
|
23973
|
+
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
23974
|
+
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
23975
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
23976
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
23977
|
+
const hist = sub(line, signal);
|
|
23978
|
+
return {
|
|
23979
|
+
plots: [
|
|
23980
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
|
|
23981
|
+
{ key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
|
|
23982
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
23983
|
+
],
|
|
23984
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23985
|
+
};
|
|
23986
|
+
}
|
|
23987
|
+
};
|
|
23988
|
+
var vfi = {
|
|
23989
|
+
type: "volume-flow-indicator",
|
|
23990
|
+
title: "Volume Flow Indicator",
|
|
23991
|
+
shortTitle: "Volume Flow Indicator",
|
|
23992
|
+
overlay: false,
|
|
23993
|
+
inputs: [
|
|
23994
|
+
lengthInput(130),
|
|
23995
|
+
{ key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
|
|
23996
|
+
{ key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
|
|
23997
|
+
lengthInput(3, "smoothLength", "Smoothing length")
|
|
23998
|
+
],
|
|
23999
|
+
compute: (bars, inputs) => {
|
|
24000
|
+
const len = num3(inputs, "length", 130);
|
|
24001
|
+
const coef = num3(inputs, "coef", 0.2);
|
|
24002
|
+
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
24003
|
+
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
24004
|
+
const inter = change(typical);
|
|
24005
|
+
const vInter = stdev(inter, 30);
|
|
24006
|
+
const vol = volumes(bars);
|
|
24007
|
+
const vAve = sma(vol, len);
|
|
24008
|
+
const mf = change(sourceValues(bars, "HLC3"));
|
|
24009
|
+
const raw = bars.map((b, i) => {
|
|
24010
|
+
const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
|
|
24011
|
+
const v = vol[i];
|
|
24012
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
24013
|
+
const m = mf[i];
|
|
24014
|
+
if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
|
|
24015
|
+
const capped = Math.min(v, ave * volCoef);
|
|
24016
|
+
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
24017
|
+
});
|
|
24018
|
+
const values = bars.map((_, i) => {
|
|
24019
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
24020
|
+
return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
|
|
24021
|
+
});
|
|
24022
|
+
const flowSum = sum(raw, len);
|
|
24023
|
+
const line = zip(flowSum, values, (f, a) => f / a);
|
|
24024
|
+
return {
|
|
24025
|
+
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
24026
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
24027
|
+
};
|
|
24028
|
+
}
|
|
24029
|
+
};
|
|
24030
|
+
var intradayIntensity = {
|
|
24031
|
+
type: "intraday-intensity",
|
|
24032
|
+
title: "Intraday Intensity",
|
|
24033
|
+
shortTitle: "Intraday Intensity",
|
|
24034
|
+
overlay: false,
|
|
24035
|
+
inputs: [lengthInput(21), colorInput()],
|
|
24036
|
+
compute: (bars, inputs) => {
|
|
24037
|
+
const len = num3(inputs, "length", 21);
|
|
24038
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
|
|
24039
|
+
return {
|
|
24040
|
+
plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
24041
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
24042
|
+
};
|
|
24043
|
+
}
|
|
24044
|
+
};
|
|
24045
|
+
var volumeSpecs = [
|
|
24046
|
+
obv,
|
|
24047
|
+
accDist,
|
|
24048
|
+
cmf,
|
|
24049
|
+
chaikinOscillator,
|
|
24050
|
+
eom,
|
|
24051
|
+
forceIndex,
|
|
24052
|
+
klinger,
|
|
24053
|
+
mfi,
|
|
24054
|
+
nvi,
|
|
24055
|
+
pvi,
|
|
24056
|
+
pvt,
|
|
24057
|
+
volumeOscillator,
|
|
24058
|
+
pvo,
|
|
24059
|
+
vfi,
|
|
24060
|
+
intradayIntensity
|
|
24061
|
+
];
|
|
24062
|
+
|
|
24063
|
+
// src/core/native-indicators/classics/overlays.ts
|
|
24064
|
+
var DAY_MS2 = 864e5;
|
|
24065
|
+
function periodKey(anchor, time) {
|
|
24066
|
+
if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
|
|
24067
|
+
if (anchor === "Month") {
|
|
24068
|
+
const d = new Date(time);
|
|
24069
|
+
return d.getUTCFullYear() * 12 + d.getUTCMonth();
|
|
24070
|
+
}
|
|
24071
|
+
return Math.floor(time / DAY_MS2);
|
|
24072
|
+
}
|
|
24073
|
+
var parabolicSar = {
|
|
24074
|
+
type: "parabolic-sar",
|
|
24075
|
+
title: "Parabolic SAR",
|
|
24076
|
+
shortTitle: "SAR",
|
|
24077
|
+
overlay: true,
|
|
24078
|
+
inputs: [
|
|
24079
|
+
{ key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
24080
|
+
{ key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
24081
|
+
{ key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
|
|
24082
|
+
],
|
|
24083
|
+
compute: (bars, inputs) => {
|
|
24084
|
+
const start = num3(inputs, "start", 0.02);
|
|
24085
|
+
const inc = num3(inputs, "increment", 0.02);
|
|
24086
|
+
const max = num3(inputs, "maximum", 0.2);
|
|
24087
|
+
const n = bars.length;
|
|
24088
|
+
const values = new Array(n).fill(Number.NaN);
|
|
24089
|
+
const colors = new Array(n).fill(null);
|
|
24090
|
+
if (n >= 2) {
|
|
24091
|
+
let long = bars[1].close >= bars[0].close;
|
|
24092
|
+
let sar = long ? bars[0].low : bars[0].high;
|
|
24093
|
+
let ep = long ? bars[0].high : bars[0].low;
|
|
24094
|
+
let af = start;
|
|
24095
|
+
for (let i = 1; i < n; i++) {
|
|
24096
|
+
const b = bars[i];
|
|
24097
|
+
sar += af * (ep - sar);
|
|
24098
|
+
if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
|
|
24099
|
+
else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
|
|
24100
|
+
const reversed = long ? b.low < sar : b.high > sar;
|
|
24101
|
+
if (reversed) {
|
|
24102
|
+
long = !long;
|
|
24103
|
+
sar = ep;
|
|
24104
|
+
ep = long ? b.high : b.low;
|
|
24105
|
+
af = start;
|
|
24106
|
+
} else if (long && b.high > ep) {
|
|
24107
|
+
ep = b.high;
|
|
24108
|
+
af = Math.min(max, af + inc);
|
|
24109
|
+
} else if (!long && b.low < ep) {
|
|
24110
|
+
ep = b.low;
|
|
24111
|
+
af = Math.min(max, af + inc);
|
|
24112
|
+
}
|
|
24113
|
+
values[i] = sar;
|
|
24114
|
+
colors[i] = long ? BULLISH : BEARISH;
|
|
24115
|
+
}
|
|
24116
|
+
}
|
|
24117
|
+
return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
|
|
24118
|
+
}
|
|
24119
|
+
};
|
|
24120
|
+
var vwap = {
|
|
24121
|
+
type: "vwap",
|
|
24122
|
+
title: "Volume Weighted Average Price",
|
|
24123
|
+
shortTitle: "VWAP",
|
|
24124
|
+
overlay: true,
|
|
24125
|
+
inputs: [
|
|
24126
|
+
{ key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
|
|
24127
|
+
{ key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
|
|
24128
|
+
colorInput(INFO)
|
|
24129
|
+
],
|
|
24130
|
+
compute: (bars, inputs) => {
|
|
24131
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
24132
|
+
const source = str3(inputs, "source", "HLC3");
|
|
24133
|
+
const values = new Array(bars.length).fill(Number.NaN);
|
|
24134
|
+
let period = Number.NaN;
|
|
24135
|
+
let cumPV = 0;
|
|
24136
|
+
let cumV = 0;
|
|
24137
|
+
for (let i = 0; i < bars.length; i++) {
|
|
24138
|
+
const b = bars[i];
|
|
24139
|
+
const key = periodKey(anchor, b.time);
|
|
24140
|
+
if (key !== period) {
|
|
24141
|
+
period = key;
|
|
24142
|
+
cumPV = 0;
|
|
24143
|
+
cumV = 0;
|
|
24144
|
+
}
|
|
24145
|
+
const v = b.volume;
|
|
24146
|
+
if (v != null && Number.isFinite(v) && v > 0) {
|
|
24147
|
+
const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
|
|
24148
|
+
cumPV += tp * v;
|
|
24149
|
+
cumV += v;
|
|
24150
|
+
}
|
|
24151
|
+
if (cumV > 0) values[i] = cumPV / cumV;
|
|
24152
|
+
}
|
|
24153
|
+
return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
24154
|
+
}
|
|
24155
|
+
};
|
|
24156
|
+
var pivotPoints = {
|
|
24157
|
+
type: "pivot-points",
|
|
24158
|
+
title: "Pivot Points",
|
|
24159
|
+
shortTitle: "Pivots",
|
|
24160
|
+
overlay: true,
|
|
24161
|
+
inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
|
|
24162
|
+
compute: (bars, inputs) => {
|
|
24163
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
24164
|
+
const aggs = /* @__PURE__ */ new Map();
|
|
24165
|
+
for (const b of bars) {
|
|
24166
|
+
const key = periodKey(anchor, b.time);
|
|
24167
|
+
const a = aggs.get(key);
|
|
24168
|
+
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
24169
|
+
else {
|
|
24170
|
+
a.high = Math.max(a.high, b.high);
|
|
24171
|
+
a.low = Math.min(a.low, b.low);
|
|
24172
|
+
a.close = b.close;
|
|
24173
|
+
}
|
|
24174
|
+
}
|
|
24175
|
+
const n = bars.length;
|
|
24176
|
+
const mk = () => new Array(n).fill(Number.NaN);
|
|
24177
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
24178
|
+
for (let i = 0; i < n; i++) {
|
|
24179
|
+
const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
|
|
24180
|
+
if (!prev2) continue;
|
|
24181
|
+
const p = (prev2.high + prev2.low + prev2.close) / 3;
|
|
24182
|
+
levels.p[i] = p;
|
|
24183
|
+
levels.r1[i] = 2 * p - prev2.low;
|
|
24184
|
+
levels.s1[i] = 2 * p - prev2.high;
|
|
24185
|
+
levels.r2[i] = p + (prev2.high - prev2.low);
|
|
24186
|
+
levels.s2[i] = p - (prev2.high - prev2.low);
|
|
24187
|
+
levels.r3[i] = prev2.high + 2 * (p - prev2.low);
|
|
24188
|
+
levels.s3[i] = prev2.low - 2 * (prev2.high - p);
|
|
24189
|
+
}
|
|
24190
|
+
const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
|
|
24191
|
+
return {
|
|
24192
|
+
plots: [
|
|
24193
|
+
plot("p", "P", WARNING),
|
|
24194
|
+
plot("r1", "R1", BEARISH),
|
|
24195
|
+
plot("s1", "S1", BULLISH),
|
|
24196
|
+
plot("r2", "R2", BEARISH),
|
|
24197
|
+
plot("s2", "S2", BULLISH),
|
|
24198
|
+
plot("r3", "R3", BEARISH),
|
|
24199
|
+
plot("s3", "S3", BULLISH)
|
|
24200
|
+
]
|
|
24201
|
+
};
|
|
24202
|
+
}
|
|
24203
|
+
};
|
|
24204
|
+
var fiftyTwoWeek = {
|
|
24205
|
+
type: "52-week-high-low",
|
|
24206
|
+
title: "52 Week High/Low",
|
|
24207
|
+
shortTitle: "52W H/L",
|
|
24208
|
+
overlay: true,
|
|
24209
|
+
inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
|
|
24210
|
+
compute: (bars, inputs) => {
|
|
24211
|
+
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
24212
|
+
const n = bars.length;
|
|
24213
|
+
const hi = new Array(n).fill(Number.NaN);
|
|
24214
|
+
const lo = new Array(n).fill(Number.NaN);
|
|
24215
|
+
const maxIdx = [];
|
|
24216
|
+
const minIdx = [];
|
|
24217
|
+
let from = 0;
|
|
24218
|
+
for (let i = 0; i < n; i++) {
|
|
24219
|
+
const b = bars[i];
|
|
24220
|
+
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
24221
|
+
maxIdx.push(i);
|
|
24222
|
+
while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
|
|
24223
|
+
minIdx.push(i);
|
|
24224
|
+
while (bars[from].time < b.time - span) from += 1;
|
|
24225
|
+
while (maxIdx[0] < from) maxIdx.shift();
|
|
24226
|
+
while (minIdx[0] < from) minIdx.shift();
|
|
24227
|
+
hi[i] = bars[maxIdx[0]].high;
|
|
24228
|
+
lo[i] = bars[minIdx[0]].low;
|
|
24229
|
+
}
|
|
24230
|
+
return {
|
|
24231
|
+
plots: [
|
|
24232
|
+
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
24233
|
+
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
24234
|
+
]
|
|
24235
|
+
};
|
|
24236
|
+
}
|
|
24237
|
+
};
|
|
24238
|
+
var zigzag = {
|
|
24239
|
+
type: "zigzag",
|
|
24240
|
+
title: "ZigZag",
|
|
24241
|
+
overlay: true,
|
|
24242
|
+
inputs: [
|
|
24243
|
+
{ key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
|
|
24244
|
+
{ key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
24245
|
+
colorInput()
|
|
24246
|
+
],
|
|
24247
|
+
compute: (bars, inputs) => {
|
|
24248
|
+
const dev = num3(inputs, "deviation", 5) / 100;
|
|
24249
|
+
const depth = num3(inputs, "depth", 10);
|
|
24250
|
+
const pivots = [];
|
|
24251
|
+
if (bars.length > 1) {
|
|
24252
|
+
let up = bars[1].close >= bars[0].close;
|
|
24253
|
+
let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
|
|
24254
|
+
for (let i = 1; i < bars.length; i++) {
|
|
24255
|
+
const b = bars[i];
|
|
24256
|
+
if (up) {
|
|
24257
|
+
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
24258
|
+
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
24259
|
+
pivots.push(ext);
|
|
24260
|
+
up = false;
|
|
24261
|
+
ext = { i, price: b.low };
|
|
24262
|
+
}
|
|
24263
|
+
} else if (b.low <= ext.price) {
|
|
24264
|
+
ext = { i, price: b.low };
|
|
24265
|
+
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
24266
|
+
pivots.push(ext);
|
|
24267
|
+
up = true;
|
|
24268
|
+
ext = { i, price: b.high };
|
|
24269
|
+
}
|
|
24270
|
+
}
|
|
24271
|
+
pivots.push(ext);
|
|
24272
|
+
}
|
|
24273
|
+
return {
|
|
24274
|
+
plots: [],
|
|
24275
|
+
polylines: [
|
|
24276
|
+
{
|
|
24277
|
+
key: "zigzag",
|
|
24278
|
+
points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
|
|
24279
|
+
curved: false,
|
|
24280
|
+
closed: false,
|
|
24281
|
+
lineColor: str3(inputs, "color", SERIES_LINE),
|
|
24282
|
+
lineWidth: 2,
|
|
24283
|
+
lineStyle: "solid",
|
|
24284
|
+
arrowLeft: false,
|
|
24285
|
+
arrowRight: false
|
|
24286
|
+
}
|
|
24287
|
+
]
|
|
24288
|
+
};
|
|
24289
|
+
}
|
|
24290
|
+
};
|
|
24291
|
+
var williamsFractal = {
|
|
24292
|
+
type: "williams-fractal",
|
|
24293
|
+
title: "Williams Fractal",
|
|
24294
|
+
shortTitle: "Fractals",
|
|
24295
|
+
overlay: true,
|
|
24296
|
+
inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
|
|
24297
|
+
compute: (bars, inputs) => {
|
|
24298
|
+
const p = num3(inputs, "periods", 2);
|
|
24299
|
+
const markers = [];
|
|
24300
|
+
const isExtreme = (i, pick, better) => {
|
|
24301
|
+
const v = pick(bars[i]);
|
|
24302
|
+
for (let k = i - p; k <= i + p; k++) {
|
|
24303
|
+
if (k === i) continue;
|
|
24304
|
+
if (!better(v, pick(bars[k]))) return false;
|
|
24305
|
+
}
|
|
24306
|
+
return true;
|
|
24307
|
+
};
|
|
24308
|
+
for (let i = p; i < bars.length - p; i++) {
|
|
24309
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
|
|
24310
|
+
markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
|
|
24311
|
+
}
|
|
24312
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
24313
|
+
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
24314
|
+
}
|
|
24315
|
+
}
|
|
24316
|
+
return { plots: [], markers };
|
|
24317
|
+
}
|
|
24318
|
+
};
|
|
24319
|
+
var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
|
|
24320
|
+
|
|
24321
|
+
// src/core/native-indicators/classics/index.ts
|
|
24322
|
+
var classicSpecs = [
|
|
24323
|
+
...averageSpecs,
|
|
24324
|
+
...bandSpecs,
|
|
24325
|
+
...oscillatorSpecs,
|
|
24326
|
+
...trendSpecs,
|
|
24327
|
+
...volatilitySpecs,
|
|
24328
|
+
...volumeSpecs,
|
|
24329
|
+
...overlaySpecs
|
|
24330
|
+
];
|
|
24331
|
+
function registerClassicIndicators() {
|
|
24332
|
+
for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
|
|
24333
|
+
}
|
|
24334
|
+
|
|
21985
24335
|
// src/Vela.ts
|
|
21986
24336
|
var Vela = class {
|
|
21987
24337
|
constructor(container, options = {}, deps = {}) {
|
|
21988
24338
|
registerBuiltinChartTypes();
|
|
21989
24339
|
registerVolume();
|
|
21990
24340
|
registerVpvr();
|
|
24341
|
+
registerClassicIndicators();
|
|
21991
24342
|
const element = resolveElement(container);
|
|
21992
24343
|
const theme = resolveTheme(options.theme);
|
|
21993
24344
|
let animZoom = true;
|
|
@@ -22062,25 +24413,28 @@ var Vela = class {
|
|
|
22062
24413
|
}
|
|
22063
24414
|
/**
|
|
22064
24415
|
* Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
|
|
22065
|
-
* e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
22066
|
-
* events)
|
|
22067
|
-
*
|
|
24416
|
+
* e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
24417
|
+
* remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
|
|
24418
|
+
* call returns the existing handle; the classic studies allow several instances, so each call
|
|
24419
|
+
* adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
|
|
22068
24420
|
*/
|
|
22069
24421
|
addNativeIndicator(type, options) {
|
|
22070
24422
|
return this.orchestrator.addNativeIndicator(type, options);
|
|
22071
24423
|
}
|
|
22072
24424
|
/**
|
|
22073
24425
|
* The catalog of built-in native indicators with their live state on this chart — each entry's
|
|
22074
|
-
* `type`, `title`, whether it `supported`s the current symbol,
|
|
22075
|
-
*
|
|
22076
|
-
* Lets a host "add indicator" UI list them, gate unsupported
|
|
22077
|
-
*
|
|
24426
|
+
* `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
|
|
24427
|
+
* and whether it allows several instances (`multiInstance`; otherwise a second
|
|
24428
|
+
* `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
|
|
24429
|
+
* ones, and de-duplicate the single-instance ones. Async because support may probe the
|
|
24430
|
+
* provider (a type may need data the symbol lacks).
|
|
22078
24431
|
*/
|
|
22079
24432
|
availableNativeIndicators() {
|
|
22080
24433
|
return this.orchestrator.availableNativeIndicators();
|
|
22081
24434
|
}
|
|
22082
24435
|
/**
|
|
22083
|
-
* The native-indicator types PRESENT on the chart right now
|
|
24436
|
+
* The native-indicator types PRESENT on the chart right now, one entry per instance (a
|
|
24437
|
+
* multi-instance type repeats) — the synchronous slice of
|
|
22084
24438
|
* {@link availableNativeIndicators} (only support probing is async; presence never is).
|
|
22085
24439
|
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
22086
24440
|
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|