hyperprop-charting-library 0.1.133 → 0.1.135
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/dist/hyperprop-charting-library.cjs +1690 -1479
- package/dist/hyperprop-charting-library.d.ts +82 -64
- package/dist/hyperprop-charting-library.js +1689 -1480
- package/dist/index.cjs +1690 -1479
- package/dist/index.d.cts +82 -64
- package/dist/index.d.ts +82 -64
- package/dist/index.js +1689 -1480
- package/docs/API.md +18 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/types.ts
|
|
2
2
|
var POSITION_DEFAULT_COLORS = ["#26a69a", "#ef5350", "#ffffff"];
|
|
3
3
|
var FIB_DEFAULT_PALETTE = [
|
|
4
4
|
"#787b86",
|
|
@@ -10,387 +10,127 @@ var FIB_DEFAULT_PALETTE = [
|
|
|
10
10
|
"#2962ff",
|
|
11
11
|
"#9c27b0"
|
|
12
12
|
];
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
sessionSeparators: false,
|
|
22
|
-
sessionSeparatorColor: "#3b4150",
|
|
23
|
-
sessionSeparatorOpacity: 0.55
|
|
13
|
+
|
|
14
|
+
// src/indicators.ts
|
|
15
|
+
var getIndicatorSourceValue = (point, source) => {
|
|
16
|
+
if (source === "open") return point.o;
|
|
17
|
+
if (source === "high") return point.h;
|
|
18
|
+
if (source === "low") return point.l;
|
|
19
|
+
if (source === "hl2") return (point.h + point.l) / 2;
|
|
20
|
+
return point.c;
|
|
24
21
|
};
|
|
25
|
-
var
|
|
26
|
-
|
|
27
|
-
textColor: "#a9adb6",
|
|
28
|
-
fontSize: 12,
|
|
29
|
-
lineWidth: 1
|
|
22
|
+
var clampIndicatorLength = (value, fallback) => {
|
|
23
|
+
return Math.max(1, Math.round(Number(value) || fallback));
|
|
30
24
|
};
|
|
31
|
-
var
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
sideHintRight: "",
|
|
58
|
-
sideHintLeftColor: "#26a69a",
|
|
59
|
-
sideHintRightColor: "#ef5350"
|
|
25
|
+
var parseColorToRgb = (value) => {
|
|
26
|
+
if (!value) return null;
|
|
27
|
+
const normalized = value.trim().toLowerCase();
|
|
28
|
+
if (normalized === "white") return { r: 255, g: 255, b: 255 };
|
|
29
|
+
if (normalized === "black") return { r: 0, g: 0, b: 0 };
|
|
30
|
+
const hex3 = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/.exec(normalized);
|
|
31
|
+
if (hex3) {
|
|
32
|
+
return {
|
|
33
|
+
r: parseInt(hex3[1] + hex3[1], 16),
|
|
34
|
+
g: parseInt(hex3[2] + hex3[2], 16),
|
|
35
|
+
b: parseInt(hex3[3] + hex3[3], 16)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const hex = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})(?:[0-9a-f]{2})?$/.exec(normalized);
|
|
39
|
+
if (hex) {
|
|
40
|
+
return {
|
|
41
|
+
r: parseInt(hex[1], 16),
|
|
42
|
+
g: parseInt(hex[2], 16),
|
|
43
|
+
b: parseInt(hex[3], 16)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const rgb = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*[\d.]+\s*)?\)$/.exec(normalized);
|
|
47
|
+
if (rgb) {
|
|
48
|
+
return { r: Number(rgb[1]), g: Number(rgb[2]), b: Number(rgb[3]) };
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
60
51
|
};
|
|
61
|
-
var
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
fontSize: 92,
|
|
67
|
-
fontWeight: 700,
|
|
68
|
-
thickness: 0,
|
|
69
|
-
imageSrc: "",
|
|
70
|
-
imageScale: 1,
|
|
71
|
-
imageMaxWidthRatio: 0.42,
|
|
72
|
-
imageMaxHeightRatio: 0.3,
|
|
73
|
-
imageTintColor: "",
|
|
74
|
-
imageTintOpacity: 1
|
|
52
|
+
var labelTextColorOn = (background, textColor) => {
|
|
53
|
+
const rgb = parseColorToRgb(background);
|
|
54
|
+
if (!rgb) return textColor;
|
|
55
|
+
const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
|
|
56
|
+
return luminance >= 0.62 ? "#000000" : textColor;
|
|
75
57
|
};
|
|
76
|
-
var
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
58
|
+
var computeSmaSeries = (data, length, source) => {
|
|
59
|
+
const result = new Array(data.length).fill(null);
|
|
60
|
+
let sum = 0;
|
|
61
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
62
|
+
const point = data[i];
|
|
63
|
+
if (!point) continue;
|
|
64
|
+
const value = getIndicatorSourceValue(point, source);
|
|
65
|
+
sum += value;
|
|
66
|
+
if (i >= length) {
|
|
67
|
+
const oldPoint = data[i - length];
|
|
68
|
+
if (oldPoint) {
|
|
69
|
+
sum -= getIndicatorSourceValue(oldPoint, source);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (i >= length - 1) {
|
|
73
|
+
result[i] = sum / length;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
83
77
|
};
|
|
84
|
-
var
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
indicatorLegendPosition: "top-left",
|
|
99
|
-
indicatorLegendOffsetX: 10,
|
|
100
|
-
indicatorLegendOffsetY: 10,
|
|
101
|
-
showCountdownToBarClose: false,
|
|
102
|
-
noOverlapping: true,
|
|
103
|
-
backgroundColor: "#0b1220",
|
|
104
|
-
textColor: "#cbd5e1",
|
|
105
|
-
mutedTextColor: "#94a3b8",
|
|
106
|
-
symbolNameBackgroundColor: "#1f2937",
|
|
107
|
-
symbolNameTextColor: "#e5e7eb",
|
|
108
|
-
previousCloseColor: "#94a3b8",
|
|
109
|
-
highLowColor: "#a78bfa",
|
|
110
|
-
bidColor: "#ef4444",
|
|
111
|
-
askColor: "#22c55e",
|
|
112
|
-
indicatorTextColor: "#cbd5e1",
|
|
113
|
-
borderRadius: 3,
|
|
114
|
-
labelHeight: 20,
|
|
115
|
-
labelPaddingX: 8
|
|
78
|
+
var computeEmaSeries = (data, length, source) => {
|
|
79
|
+
const result = new Array(data.length).fill(null);
|
|
80
|
+
const alpha = 2 / (length + 1);
|
|
81
|
+
let prev = null;
|
|
82
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
83
|
+
const point = data[i];
|
|
84
|
+
if (!point) continue;
|
|
85
|
+
const value = getIndicatorSourceValue(point, source);
|
|
86
|
+
prev = prev === null ? value : alpha * value + (1 - alpha) * prev;
|
|
87
|
+
if (i >= length - 1) {
|
|
88
|
+
result[i] = prev;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
116
92
|
};
|
|
117
|
-
var
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
93
|
+
var computeRmaSeries = (data, length, source) => {
|
|
94
|
+
const result = new Array(data.length).fill(null);
|
|
95
|
+
let prev = null;
|
|
96
|
+
let seedSum = 0;
|
|
97
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
98
|
+
const point = data[i];
|
|
99
|
+
if (!point) continue;
|
|
100
|
+
const value = getIndicatorSourceValue(point, source);
|
|
101
|
+
if (i < length) {
|
|
102
|
+
seedSum += value;
|
|
103
|
+
if (i === length - 1) {
|
|
104
|
+
prev = seedSum / length;
|
|
105
|
+
result[i] = prev;
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
prev = prev === null ? value : (prev * (length - 1) + value) / length;
|
|
110
|
+
result[i] = prev;
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
127
113
|
};
|
|
128
|
-
var
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
actionButtonFontWeight: 500,
|
|
149
|
-
actionButtonBorderColor: "#2563eb",
|
|
150
|
-
actionButtonBorderStyle: "solid",
|
|
151
|
-
actionButtonsInnerGap: 6,
|
|
152
|
-
actionButtonsGroupGap: 8,
|
|
153
|
-
actionButtons: [],
|
|
154
|
-
connectorToPrice: Number.NaN,
|
|
155
|
-
connectorColor: "#2563eb",
|
|
156
|
-
connectorStyle: "dotted",
|
|
157
|
-
connectorThickness: 1,
|
|
158
|
-
connectorAnchorPaddingRight: 10,
|
|
159
|
-
fillToPrice: Number.NaN,
|
|
160
|
-
fillColor: "rgba(37,99,235,0.18)",
|
|
161
|
-
pinOutOfRange: false
|
|
162
|
-
};
|
|
163
|
-
var DEFAULT_OPTIONS = {
|
|
164
|
-
width: 720,
|
|
165
|
-
height: 360,
|
|
166
|
-
backgroundColor: "#101114",
|
|
167
|
-
axisColor: "#7f8289",
|
|
168
|
-
axis: DEFAULT_AXIS_OPTIONS,
|
|
169
|
-
xAxis: DEFAULT_AXIS_OPTIONS,
|
|
170
|
-
yAxis: DEFAULT_AXIS_OPTIONS,
|
|
171
|
-
priceDecimals: 2,
|
|
172
|
-
stabilizePriceLabels: true,
|
|
173
|
-
priceLabelMinIntegerDigits: 3,
|
|
174
|
-
priceLabelWidthTemplate: "",
|
|
175
|
-
initialViewport: "latest",
|
|
176
|
-
initialVisibleBars: 60,
|
|
177
|
-
minVisibleBars: 5,
|
|
178
|
-
maxVisibleBars: 2e4,
|
|
179
|
-
maxPanBars: 1e6,
|
|
180
|
-
rightEdgePaddingBars: 2,
|
|
181
|
-
preserveViewportOnDataUpdate: true,
|
|
182
|
-
upColor: "#2fb171",
|
|
183
|
-
downColor: "#d35a5a",
|
|
184
|
-
gridColor: "#252932",
|
|
185
|
-
fontFamily: "Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif",
|
|
186
|
-
candleBodyWidthRatio: 0.7,
|
|
187
|
-
candleMinWidth: 0.5,
|
|
188
|
-
candleWickWidth: 1,
|
|
189
|
-
tickSize: 0,
|
|
190
|
-
candleColorMode: "openClose",
|
|
191
|
-
candleColorEpsilon: -1,
|
|
192
|
-
chartType: "candles",
|
|
193
|
-
lineColor: "#2962ff",
|
|
194
|
-
lineWidth: 2,
|
|
195
|
-
areaFillOpacity: 0.12,
|
|
196
|
-
baselinePrice: null,
|
|
197
|
-
// 0 = deterministic scale (lightweight-charts behavior): the y-range is a
|
|
198
|
-
// pure function of the visible data, so it can never drift or "breathe".
|
|
199
|
-
// Values > 0 re-enable eased contraction for hosts that prefer it.
|
|
200
|
-
autoScaleSmoothing: 0,
|
|
201
|
-
autoScaleIgnoreLatestCandle: true,
|
|
202
|
-
kineticScroll: { touch: true, mouse: false },
|
|
203
|
-
timeStepMs: 0,
|
|
204
|
-
clockOffsetMs: 0,
|
|
205
|
-
pinOutOfRangeLines: false,
|
|
206
|
-
doubleClickEnabled: true,
|
|
207
|
-
doubleClickAction: "reset",
|
|
208
|
-
crosshair: DEFAULT_CROSSHAIR_OPTIONS,
|
|
209
|
-
grid: DEFAULT_GRID_OPTIONS,
|
|
210
|
-
watermark: DEFAULT_WATERMARK_OPTIONS,
|
|
211
|
-
priceLines: [],
|
|
212
|
-
orderLines: [],
|
|
213
|
-
tickerLine: {
|
|
214
|
-
visible: true,
|
|
215
|
-
style: "dotted",
|
|
216
|
-
thickness: 1,
|
|
217
|
-
labelTextColor: "#0b1220",
|
|
218
|
-
labelSubtext: "",
|
|
219
|
-
labelSubtexts: [],
|
|
220
|
-
labelSubtextColor: "#0b1220",
|
|
221
|
-
labelSubtextFontSize: 0,
|
|
222
|
-
showCountdownInLabel: false,
|
|
223
|
-
labelBorderRadius: 3
|
|
224
|
-
},
|
|
225
|
-
labels: DEFAULT_LABELS_OPTIONS,
|
|
226
|
-
dashPatterns: DEFAULT_DASH_PATTERNS,
|
|
227
|
-
indicators: [],
|
|
228
|
-
drawings: []
|
|
229
|
-
};
|
|
230
|
-
var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
231
|
-
...baseOptions,
|
|
232
|
-
...options,
|
|
233
|
-
axis: {
|
|
234
|
-
...baseOptions.axis,
|
|
235
|
-
...options.axis ?? {},
|
|
236
|
-
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {}
|
|
237
|
-
},
|
|
238
|
-
xAxis: {
|
|
239
|
-
...baseOptions.xAxis,
|
|
240
|
-
...options.axis ?? {},
|
|
241
|
-
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
|
|
242
|
-
...options.xAxis ?? {}
|
|
243
|
-
},
|
|
244
|
-
yAxis: {
|
|
245
|
-
...baseOptions.yAxis,
|
|
246
|
-
...options.axis ?? {},
|
|
247
|
-
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
|
|
248
|
-
...options.yAxis ?? {}
|
|
249
|
-
},
|
|
250
|
-
crosshair: {
|
|
251
|
-
...baseOptions.crosshair,
|
|
252
|
-
...options.crosshair ?? {}
|
|
253
|
-
},
|
|
254
|
-
grid: {
|
|
255
|
-
...baseOptions.grid,
|
|
256
|
-
...options.grid ?? {}
|
|
257
|
-
},
|
|
258
|
-
watermark: {
|
|
259
|
-
...baseOptions.watermark,
|
|
260
|
-
...options.watermark ?? {}
|
|
261
|
-
},
|
|
262
|
-
tickerLine: {
|
|
263
|
-
...baseOptions.tickerLine,
|
|
264
|
-
...options.tickerLine ?? {}
|
|
265
|
-
},
|
|
266
|
-
labels: {
|
|
267
|
-
...baseOptions.labels,
|
|
268
|
-
...options.labels ?? {}
|
|
269
|
-
},
|
|
270
|
-
dashPatterns: {
|
|
271
|
-
...baseOptions.dashPatterns,
|
|
272
|
-
...options.dashPatterns ?? {}
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
var getIndicatorSourceValue = (point, source) => {
|
|
276
|
-
if (source === "open") return point.o;
|
|
277
|
-
if (source === "high") return point.h;
|
|
278
|
-
if (source === "low") return point.l;
|
|
279
|
-
if (source === "hl2") return (point.h + point.l) / 2;
|
|
280
|
-
return point.c;
|
|
281
|
-
};
|
|
282
|
-
var clampIndicatorLength = (value, fallback) => {
|
|
283
|
-
return Math.max(1, Math.round(Number(value) || fallback));
|
|
284
|
-
};
|
|
285
|
-
var parseColorToRgb = (value) => {
|
|
286
|
-
if (!value) return null;
|
|
287
|
-
const normalized = value.trim().toLowerCase();
|
|
288
|
-
if (normalized === "white") return { r: 255, g: 255, b: 255 };
|
|
289
|
-
if (normalized === "black") return { r: 0, g: 0, b: 0 };
|
|
290
|
-
const hex3 = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/.exec(normalized);
|
|
291
|
-
if (hex3) {
|
|
292
|
-
return {
|
|
293
|
-
r: parseInt(hex3[1] + hex3[1], 16),
|
|
294
|
-
g: parseInt(hex3[2] + hex3[2], 16),
|
|
295
|
-
b: parseInt(hex3[3] + hex3[3], 16)
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
const hex = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})(?:[0-9a-f]{2})?$/.exec(normalized);
|
|
299
|
-
if (hex) {
|
|
300
|
-
return {
|
|
301
|
-
r: parseInt(hex[1], 16),
|
|
302
|
-
g: parseInt(hex[2], 16),
|
|
303
|
-
b: parseInt(hex[3], 16)
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
const rgb = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*[\d.]+\s*)?\)$/.exec(normalized);
|
|
307
|
-
if (rgb) {
|
|
308
|
-
return { r: Number(rgb[1]), g: Number(rgb[2]), b: Number(rgb[3]) };
|
|
309
|
-
}
|
|
310
|
-
return null;
|
|
311
|
-
};
|
|
312
|
-
var labelTextColorOn = (background, textColor) => {
|
|
313
|
-
const rgb = parseColorToRgb(background);
|
|
314
|
-
if (!rgb) return textColor;
|
|
315
|
-
const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
|
|
316
|
-
return luminance >= 0.62 ? "#000000" : textColor;
|
|
317
|
-
};
|
|
318
|
-
var computeSmaSeries = (data, length, source) => {
|
|
319
|
-
const result = new Array(data.length).fill(null);
|
|
320
|
-
let sum = 0;
|
|
321
|
-
for (let i = 0; i < data.length; i += 1) {
|
|
322
|
-
const point = data[i];
|
|
323
|
-
if (!point) continue;
|
|
324
|
-
const value = getIndicatorSourceValue(point, source);
|
|
325
|
-
sum += value;
|
|
326
|
-
if (i >= length) {
|
|
327
|
-
const oldPoint = data[i - length];
|
|
328
|
-
if (oldPoint) {
|
|
329
|
-
sum -= getIndicatorSourceValue(oldPoint, source);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
if (i >= length - 1) {
|
|
333
|
-
result[i] = sum / length;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return result;
|
|
337
|
-
};
|
|
338
|
-
var computeEmaSeries = (data, length, source) => {
|
|
339
|
-
const result = new Array(data.length).fill(null);
|
|
340
|
-
const alpha = 2 / (length + 1);
|
|
341
|
-
let prev = null;
|
|
342
|
-
for (let i = 0; i < data.length; i += 1) {
|
|
343
|
-
const point = data[i];
|
|
344
|
-
if (!point) continue;
|
|
345
|
-
const value = getIndicatorSourceValue(point, source);
|
|
346
|
-
prev = prev === null ? value : alpha * value + (1 - alpha) * prev;
|
|
347
|
-
if (i >= length - 1) {
|
|
348
|
-
result[i] = prev;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
return result;
|
|
352
|
-
};
|
|
353
|
-
var computeRmaSeries = (data, length, source) => {
|
|
354
|
-
const result = new Array(data.length).fill(null);
|
|
355
|
-
let prev = null;
|
|
356
|
-
let seedSum = 0;
|
|
357
|
-
for (let i = 0; i < data.length; i += 1) {
|
|
358
|
-
const point = data[i];
|
|
359
|
-
if (!point) continue;
|
|
360
|
-
const value = getIndicatorSourceValue(point, source);
|
|
361
|
-
if (i < length) {
|
|
362
|
-
seedSum += value;
|
|
363
|
-
if (i === length - 1) {
|
|
364
|
-
prev = seedSum / length;
|
|
365
|
-
result[i] = prev;
|
|
366
|
-
}
|
|
367
|
-
continue;
|
|
368
|
-
}
|
|
369
|
-
prev = prev === null ? value : (prev * (length - 1) + value) / length;
|
|
370
|
-
result[i] = prev;
|
|
371
|
-
}
|
|
372
|
-
return result;
|
|
373
|
-
};
|
|
374
|
-
var computeWmaSeriesFromValues = (values, length) => {
|
|
375
|
-
const result = new Array(values.length).fill(null);
|
|
376
|
-
const denominator = length * (length + 1) / 2;
|
|
377
|
-
for (let i = 0; i < values.length; i += 1) {
|
|
378
|
-
if (i < length - 1) continue;
|
|
379
|
-
let weighted = 0;
|
|
380
|
-
let valid = true;
|
|
381
|
-
for (let j = 0; j < length; j += 1) {
|
|
382
|
-
const value = values[i - length + 1 + j];
|
|
383
|
-
if (value == null) {
|
|
384
|
-
valid = false;
|
|
385
|
-
break;
|
|
386
|
-
}
|
|
387
|
-
weighted += value * (j + 1);
|
|
388
|
-
}
|
|
389
|
-
if (valid) {
|
|
390
|
-
result[i] = weighted / denominator;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return result;
|
|
114
|
+
var computeWmaSeriesFromValues = (values, length) => {
|
|
115
|
+
const result = new Array(values.length).fill(null);
|
|
116
|
+
const denominator = length * (length + 1) / 2;
|
|
117
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
118
|
+
if (i < length - 1) continue;
|
|
119
|
+
let weighted = 0;
|
|
120
|
+
let valid = true;
|
|
121
|
+
for (let j = 0; j < length; j += 1) {
|
|
122
|
+
const value = values[i - length + 1 + j];
|
|
123
|
+
if (value == null) {
|
|
124
|
+
valid = false;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
weighted += value * (j + 1);
|
|
128
|
+
}
|
|
129
|
+
if (valid) {
|
|
130
|
+
result[i] = weighted / denominator;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
394
134
|
};
|
|
395
135
|
var computeWmaSeries = (data, length, source) => {
|
|
396
136
|
const sourceValues = data.map((point) => getIndicatorSourceValue(point, source));
|
|
@@ -1477,1060 +1217,571 @@ var drawSeparateMultiSeries = (ctx, renderContext, seriesList, options = {}) =>
|
|
|
1477
1217
|
}
|
|
1478
1218
|
return paneInfo;
|
|
1479
1219
|
};
|
|
1480
|
-
var
|
|
1481
|
-
const
|
|
1482
|
-
const
|
|
1483
|
-
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
}
|
|
1487
|
-
const
|
|
1488
|
-
|
|
1220
|
+
var computeMacd = (data, fast, slow, signalLength) => {
|
|
1221
|
+
const fastEma = computeEmaSeries(data, fast, "close");
|
|
1222
|
+
const slowEma = computeEmaSeries(data, slow, "close");
|
|
1223
|
+
const macd = fastEma.map((value, idx) => {
|
|
1224
|
+
const slowValue = slowEma[idx];
|
|
1225
|
+
return value == null || slowValue == null ? null : value - slowValue;
|
|
1226
|
+
});
|
|
1227
|
+
const signal = emaFromValues(macd, signalLength);
|
|
1228
|
+
const hist = macd.map((value, idx) => {
|
|
1229
|
+
const signalValue = signal[idx];
|
|
1230
|
+
return value == null || signalValue == null ? null : value - signalValue;
|
|
1231
|
+
});
|
|
1232
|
+
return { macd, signal, hist };
|
|
1489
1233
|
};
|
|
1490
|
-
var
|
|
1491
|
-
const
|
|
1492
|
-
const
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
const
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1234
|
+
var computeStochastic = (data, kLength, kSmoothing, dLength) => {
|
|
1235
|
+
const highestHigh = rollingExtremeSeries(data.map((point) => point.h), kLength, "max");
|
|
1236
|
+
const lowestLow = rollingExtremeSeries(data.map((point) => point.l), kLength, "min");
|
|
1237
|
+
const rawK = data.map((point, idx) => {
|
|
1238
|
+
const hh = highestHigh[idx];
|
|
1239
|
+
const ll = lowestLow[idx];
|
|
1240
|
+
if (hh == null || ll == null) return null;
|
|
1241
|
+
const range = hh - ll;
|
|
1242
|
+
return range === 0 ? 50 : (point.c - ll) / range * 100;
|
|
1243
|
+
});
|
|
1244
|
+
const k = kSmoothing > 1 ? smaFromValues(rawK, kSmoothing) : rawK;
|
|
1245
|
+
const d = smaFromValues(k, dLength);
|
|
1246
|
+
return { k, d };
|
|
1247
|
+
};
|
|
1248
|
+
var computeStochRsi = (data, rsiLength, stochLength, kSmoothing, dSmoothing) => {
|
|
1249
|
+
const rsi = computeRsiSeries(data, rsiLength);
|
|
1250
|
+
const finiteRsi = rsi.map((value) => value == null ? Number.NaN : value);
|
|
1251
|
+
const highest = rollingExtremeSeries(finiteRsi, stochLength, "max");
|
|
1252
|
+
const lowest = rollingExtremeSeries(finiteRsi, stochLength, "min");
|
|
1253
|
+
const rawK = rsi.map((value, idx) => {
|
|
1254
|
+
const hh = highest[idx];
|
|
1255
|
+
const ll = lowest[idx];
|
|
1256
|
+
if (value == null || hh == null || ll == null || !Number.isFinite(hh) || !Number.isFinite(ll)) return null;
|
|
1257
|
+
const range = hh - ll;
|
|
1258
|
+
return range === 0 ? 50 : (value - ll) / range * 100;
|
|
1259
|
+
});
|
|
1260
|
+
const k = smaFromValues(rawK, Math.max(1, kSmoothing));
|
|
1261
|
+
const d = smaFromValues(k, Math.max(1, dSmoothing));
|
|
1262
|
+
return { k, d };
|
|
1263
|
+
};
|
|
1264
|
+
var computeDmi = (data, length) => {
|
|
1265
|
+
const count = data.length;
|
|
1266
|
+
const plusDm = new Array(count).fill(null);
|
|
1267
|
+
const minusDm = new Array(count).fill(null);
|
|
1268
|
+
const trueRange = new Array(count).fill(null);
|
|
1269
|
+
for (let i = 1; i < count; i += 1) {
|
|
1270
|
+
const point = data[i];
|
|
1271
|
+
const prev = data[i - 1];
|
|
1272
|
+
const upMove = point.h - prev.h;
|
|
1273
|
+
const downMove = prev.l - point.l;
|
|
1274
|
+
plusDm[i] = upMove > downMove && upMove > 0 ? upMove : 0;
|
|
1275
|
+
minusDm[i] = downMove > upMove && downMove > 0 ? downMove : 0;
|
|
1276
|
+
trueRange[i] = Math.max(point.h - point.l, Math.abs(point.h - prev.c), Math.abs(point.l - prev.c));
|
|
1277
|
+
}
|
|
1278
|
+
const smoothPlus = rmaFromValues(plusDm, length);
|
|
1279
|
+
const smoothMinus = rmaFromValues(minusDm, length);
|
|
1280
|
+
const smoothTr = rmaFromValues(trueRange, length);
|
|
1281
|
+
const plusDi = smoothPlus.map((value, idx) => {
|
|
1282
|
+
const tr = smoothTr[idx];
|
|
1283
|
+
return value == null || tr == null || tr === 0 ? null : 100 * value / tr;
|
|
1284
|
+
});
|
|
1285
|
+
const minusDi = smoothMinus.map((value, idx) => {
|
|
1286
|
+
const tr = smoothTr[idx];
|
|
1287
|
+
return value == null || tr == null || tr === 0 ? null : 100 * value / tr;
|
|
1288
|
+
});
|
|
1289
|
+
const dx = plusDi.map((value, idx) => {
|
|
1290
|
+
const minus = minusDi[idx];
|
|
1291
|
+
if (value == null || minus == null) return null;
|
|
1292
|
+
const sum = value + minus;
|
|
1293
|
+
return sum === 0 ? 0 : 100 * Math.abs(value - minus) / sum;
|
|
1294
|
+
});
|
|
1295
|
+
const adx = rmaFromValues(dx, length);
|
|
1296
|
+
return { adx, plusDi, minusDi };
|
|
1297
|
+
};
|
|
1298
|
+
var computeObvSeries = (data) => {
|
|
1299
|
+
const result = new Array(data.length).fill(null);
|
|
1300
|
+
let obv = 0;
|
|
1301
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
1302
|
+
const point = data[i];
|
|
1303
|
+
if (i > 0) {
|
|
1304
|
+
const prev = data[i - 1];
|
|
1305
|
+
const volume = Math.max(0, point.v ?? 0);
|
|
1306
|
+
if (point.c > prev.c) obv += volume;
|
|
1307
|
+
else if (point.c < prev.c) obv -= volume;
|
|
1516
1308
|
}
|
|
1309
|
+
result[i] = obv;
|
|
1517
1310
|
}
|
|
1518
1311
|
return result;
|
|
1519
1312
|
};
|
|
1520
|
-
var
|
|
1521
|
-
const
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1313
|
+
var computeMfiSeries = (data, length) => {
|
|
1314
|
+
const result = new Array(data.length).fill(null);
|
|
1315
|
+
const positiveFlow = new Array(data.length).fill(0);
|
|
1316
|
+
const negativeFlow = new Array(data.length).fill(0);
|
|
1317
|
+
let prevTypical = null;
|
|
1318
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
1319
|
+
const point = data[i];
|
|
1320
|
+
const typical = (point.h + point.l + point.c) / 3;
|
|
1321
|
+
const flow = typical * Math.max(0, point.v ?? 0);
|
|
1322
|
+
if (prevTypical !== null) {
|
|
1323
|
+
if (typical > prevTypical) positiveFlow[i] = flow;
|
|
1324
|
+
else if (typical < prevTypical) negativeFlow[i] = flow;
|
|
1527
1325
|
}
|
|
1528
|
-
|
|
1529
|
-
tr[i] = Math.max(bar.h - bar.l, Math.abs(bar.h - prevClose), Math.abs(bar.l - prevClose));
|
|
1326
|
+
prevTypical = typical;
|
|
1530
1327
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
stdev: scriptStdevSeries,
|
|
1544
|
-
atr: scriptAtrSeries
|
|
1545
|
-
});
|
|
1546
|
-
var SCRIPT_PLOT_PALETTE = ["#2962ff", "#ff6d00", "#26a69a", "#ab47bc", "#ef5350", "#fdd835"];
|
|
1547
|
-
var hashScriptSource = (source) => {
|
|
1548
|
-
let hash = 5381;
|
|
1549
|
-
for (let i = 0; i < source.length; i += 1) {
|
|
1550
|
-
hash = (hash << 5) + hash + source.charCodeAt(i) | 0;
|
|
1328
|
+
let posSum = 0;
|
|
1329
|
+
let negSum = 0;
|
|
1330
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
1331
|
+
posSum += positiveFlow[i];
|
|
1332
|
+
negSum += negativeFlow[i];
|
|
1333
|
+
if (i >= length) {
|
|
1334
|
+
posSum -= positiveFlow[i - length];
|
|
1335
|
+
negSum -= negativeFlow[i - length];
|
|
1336
|
+
}
|
|
1337
|
+
if (i >= length) {
|
|
1338
|
+
result[i] = negSum === 0 ? 100 : 100 - 100 / (1 + posSum / negSum);
|
|
1339
|
+
}
|
|
1551
1340
|
}
|
|
1552
|
-
return
|
|
1341
|
+
return result;
|
|
1553
1342
|
};
|
|
1554
|
-
var
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1343
|
+
var computeCciSeries = (data, length) => {
|
|
1344
|
+
const result = new Array(data.length).fill(null);
|
|
1345
|
+
const typical = data.map((point) => (point.h + point.l + point.c) / 3);
|
|
1346
|
+
const smaTypical = smaFromValues(typical, length);
|
|
1347
|
+
for (let i = length - 1; i < data.length; i += 1) {
|
|
1348
|
+
const mean = smaTypical[i];
|
|
1349
|
+
if (mean == null) continue;
|
|
1350
|
+
let meanDeviation = 0;
|
|
1351
|
+
for (let j = 0; j < length; j += 1) {
|
|
1352
|
+
meanDeviation += Math.abs(typical[i - j] - mean);
|
|
1353
|
+
}
|
|
1354
|
+
meanDeviation /= length;
|
|
1355
|
+
result[i] = meanDeviation === 0 ? 0 : (typical[i] - mean) / (0.015 * meanDeviation);
|
|
1561
1356
|
}
|
|
1357
|
+
return result;
|
|
1562
1358
|
};
|
|
1563
|
-
var
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
return
|
|
1359
|
+
var computeWilliamsRSeries = (data, length) => {
|
|
1360
|
+
const highest = rollingExtremeSeries(data.map((point) => point.h), length, "max");
|
|
1361
|
+
const lowest = rollingExtremeSeries(data.map((point) => point.l), length, "min");
|
|
1362
|
+
return data.map((point, idx) => {
|
|
1363
|
+
const hh = highest[idx];
|
|
1364
|
+
const ll = lowest[idx];
|
|
1365
|
+
if (hh == null || ll == null) return null;
|
|
1366
|
+
const range = hh - ll;
|
|
1367
|
+
return range === 0 ? -50 : (hh - point.c) / range * -100;
|
|
1572
1368
|
});
|
|
1573
|
-
guard.reset = () => {
|
|
1574
|
-
count = 0;
|
|
1575
|
-
deadline = Date.now() + SCRIPT_GUARD_BUDGET_MS;
|
|
1576
|
-
};
|
|
1577
|
-
return guard;
|
|
1578
1369
|
};
|
|
1579
|
-
var
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1370
|
+
var computeRocSeries = (data, length) => {
|
|
1371
|
+
return data.map((point, idx) => {
|
|
1372
|
+
if (idx < length) return null;
|
|
1373
|
+
const past = data[idx - length].c;
|
|
1374
|
+
return past === 0 ? null : 100 * (point.c - past) / past;
|
|
1375
|
+
});
|
|
1376
|
+
};
|
|
1377
|
+
var computeMomentumSeries = (data, length) => {
|
|
1378
|
+
return data.map((point, idx) => idx < length ? null : point.c - data[idx - length].c);
|
|
1379
|
+
};
|
|
1380
|
+
var computePsarSeries = (data, start, increment, maximum) => {
|
|
1381
|
+
const count = data.length;
|
|
1382
|
+
const result = new Array(count).fill(null);
|
|
1383
|
+
if (count < 2) return result;
|
|
1384
|
+
let isUp = data[1].c >= data[0].c;
|
|
1385
|
+
let sar = isUp ? data[0].l : data[0].h;
|
|
1386
|
+
let extremePoint = isUp ? data[0].h : data[0].l;
|
|
1387
|
+
let accelerationFactor = start;
|
|
1388
|
+
for (let i = 1; i < count; i += 1) {
|
|
1389
|
+
const point = data[i];
|
|
1390
|
+
sar += accelerationFactor * (extremePoint - sar);
|
|
1391
|
+
if (isUp) {
|
|
1392
|
+
sar = Math.min(sar, data[i - 1].l, data[Math.max(0, i - 2)].l);
|
|
1393
|
+
if (point.l < sar) {
|
|
1394
|
+
isUp = false;
|
|
1395
|
+
sar = extremePoint;
|
|
1396
|
+
extremePoint = point.l;
|
|
1397
|
+
accelerationFactor = start;
|
|
1398
|
+
} else if (point.h > extremePoint) {
|
|
1399
|
+
extremePoint = point.h;
|
|
1400
|
+
accelerationFactor = Math.min(maximum, accelerationFactor + increment);
|
|
1590
1401
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1402
|
+
} else {
|
|
1403
|
+
sar = Math.max(sar, data[i - 1].h, data[Math.max(0, i - 2)].h);
|
|
1404
|
+
if (point.h > sar) {
|
|
1405
|
+
isUp = true;
|
|
1406
|
+
sar = extremePoint;
|
|
1407
|
+
extremePoint = point.h;
|
|
1408
|
+
accelerationFactor = start;
|
|
1409
|
+
} else if (point.l < extremePoint) {
|
|
1410
|
+
extremePoint = point.l;
|
|
1411
|
+
accelerationFactor = Math.min(maximum, accelerationFactor + increment);
|
|
1593
1412
|
}
|
|
1594
|
-
if (quote === "`" && cj === "$" && source[j + 1] === "{") {
|
|
1595
|
-
let depth = 1;
|
|
1596
|
-
j += 2;
|
|
1597
|
-
while (j < n && depth > 0) {
|
|
1598
|
-
const ce = source[j];
|
|
1599
|
-
if (ce === "\\") {
|
|
1600
|
-
j += 2;
|
|
1601
|
-
continue;
|
|
1602
|
-
}
|
|
1603
|
-
if (ce === '"' || ce === "'" || ce === "`") {
|
|
1604
|
-
j = scanString(j);
|
|
1605
|
-
continue;
|
|
1606
|
-
}
|
|
1607
|
-
if (ce === "{") depth += 1;
|
|
1608
|
-
else if (ce === "}") depth -= 1;
|
|
1609
|
-
j += 1;
|
|
1610
|
-
}
|
|
1611
|
-
continue;
|
|
1612
|
-
}
|
|
1613
|
-
j += 1;
|
|
1614
|
-
}
|
|
1615
|
-
return n;
|
|
1616
|
-
};
|
|
1617
|
-
const scanRegex = (start) => {
|
|
1618
|
-
let j = start + 1;
|
|
1619
|
-
let inClass = false;
|
|
1620
|
-
while (j < n) {
|
|
1621
|
-
const cj = source[j];
|
|
1622
|
-
if (cj === "\\") {
|
|
1623
|
-
j += 2;
|
|
1624
|
-
continue;
|
|
1625
|
-
}
|
|
1626
|
-
if (cj === "\n") return j;
|
|
1627
|
-
if (cj === "[") inClass = true;
|
|
1628
|
-
else if (cj === "]") inClass = false;
|
|
1629
|
-
else if (cj === "/" && !inClass) return j + 1;
|
|
1630
|
-
j += 1;
|
|
1631
1413
|
}
|
|
1632
|
-
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
}
|
|
1652
|
-
return j;
|
|
1653
|
-
}
|
|
1654
|
-
return n;
|
|
1655
|
-
};
|
|
1656
|
-
const matchParen = (openIdx) => {
|
|
1657
|
-
let depth = 0;
|
|
1658
|
-
let j = openIdx;
|
|
1659
|
-
while (j < n) {
|
|
1660
|
-
const cj = source[j];
|
|
1661
|
-
if (cj === '"' || cj === "'" || cj === "`") {
|
|
1662
|
-
j = scanString(j);
|
|
1663
|
-
continue;
|
|
1664
|
-
}
|
|
1665
|
-
if (cj === "/" && source[j + 1] === "/") {
|
|
1666
|
-
const nl = source.indexOf("\n", j);
|
|
1667
|
-
j = nl === -1 ? n : nl + 1;
|
|
1668
|
-
continue;
|
|
1669
|
-
}
|
|
1670
|
-
if (cj === "/" && source[j + 1] === "*") {
|
|
1671
|
-
const end = source.indexOf("*/", j + 2);
|
|
1672
|
-
j = end === -1 ? n : end + 2;
|
|
1673
|
-
continue;
|
|
1674
|
-
}
|
|
1675
|
-
if (cj === "(") depth += 1;
|
|
1676
|
-
else if (cj === ")") {
|
|
1677
|
-
depth -= 1;
|
|
1678
|
-
if (depth === 0) return j;
|
|
1679
|
-
}
|
|
1680
|
-
j += 1;
|
|
1414
|
+
result[i] = sar;
|
|
1415
|
+
}
|
|
1416
|
+
return result;
|
|
1417
|
+
};
|
|
1418
|
+
var computeSuperTrend = (data, atrLength, multiplier) => {
|
|
1419
|
+
const count = data.length;
|
|
1420
|
+
const up = new Array(count).fill(null);
|
|
1421
|
+
const down = new Array(count).fill(null);
|
|
1422
|
+
const atr = computeAtrSeries(data, atrLength);
|
|
1423
|
+
let prevUpper = Number.NaN;
|
|
1424
|
+
let prevLower = Number.NaN;
|
|
1425
|
+
let trend = 1;
|
|
1426
|
+
let prevClose = 0;
|
|
1427
|
+
for (let i = 0; i < count; i += 1) {
|
|
1428
|
+
const point = data[i];
|
|
1429
|
+
const atrValue = atr[i];
|
|
1430
|
+
if (atrValue == null) {
|
|
1431
|
+
prevClose = point.c;
|
|
1432
|
+
continue;
|
|
1681
1433
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
const
|
|
1692
|
-
if (
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
if (inner[j] === "\\") {
|
|
1697
|
-
j += 2;
|
|
1698
|
-
continue;
|
|
1699
|
-
}
|
|
1700
|
-
if (inner[j] === quote) {
|
|
1701
|
-
j += 1;
|
|
1702
|
-
break;
|
|
1703
|
-
}
|
|
1704
|
-
j += 1;
|
|
1705
|
-
}
|
|
1706
|
-
continue;
|
|
1707
|
-
}
|
|
1708
|
-
if (cj === "(" || cj === "[" || cj === "{") depth += 1;
|
|
1709
|
-
else if (cj === ")" || cj === "]" || cj === "}") depth -= 1;
|
|
1710
|
-
else if (cj === ";" && depth === 0) {
|
|
1711
|
-
parts.push(inner.slice(last, j));
|
|
1712
|
-
last = j + 1;
|
|
1434
|
+
const mid = (point.h + point.l) / 2;
|
|
1435
|
+
const basicUpper = mid + multiplier * atrValue;
|
|
1436
|
+
const basicLower = mid - multiplier * atrValue;
|
|
1437
|
+
if (!Number.isFinite(prevUpper)) {
|
|
1438
|
+
prevUpper = basicUpper;
|
|
1439
|
+
prevLower = basicLower;
|
|
1440
|
+
trend = point.c >= mid ? 1 : -1;
|
|
1441
|
+
} else {
|
|
1442
|
+
const finalUpper = basicUpper < prevUpper || prevClose > prevUpper ? basicUpper : prevUpper;
|
|
1443
|
+
const finalLower = basicLower > prevLower || prevClose < prevLower ? basicLower : prevLower;
|
|
1444
|
+
if (trend === 1) {
|
|
1445
|
+
trend = point.c < finalLower ? -1 : 1;
|
|
1446
|
+
} else {
|
|
1447
|
+
trend = point.c > finalUpper ? 1 : -1;
|
|
1713
1448
|
}
|
|
1714
|
-
|
|
1449
|
+
prevUpper = finalUpper;
|
|
1450
|
+
prevLower = finalLower;
|
|
1715
1451
|
}
|
|
1716
|
-
|
|
1717
|
-
|
|
1452
|
+
if (trend === 1) up[i] = prevLower;
|
|
1453
|
+
else down[i] = prevUpper;
|
|
1454
|
+
prevClose = point.c;
|
|
1455
|
+
}
|
|
1456
|
+
return { up, down };
|
|
1457
|
+
};
|
|
1458
|
+
var computeIchimoku = (data, conversionLength, baseLength, spanBLength, displacement) => {
|
|
1459
|
+
const count = data.length;
|
|
1460
|
+
const highs = data.map((point) => point.h);
|
|
1461
|
+
const lows = data.map((point) => point.l);
|
|
1462
|
+
const midlineOf = (length) => {
|
|
1463
|
+
const highest = rollingExtremeSeries(highs, length, "max");
|
|
1464
|
+
const lowest = rollingExtremeSeries(lows, length, "min");
|
|
1465
|
+
return highest.map((value, idx) => {
|
|
1466
|
+
const low = lowest[idx];
|
|
1467
|
+
return value == null || low == null ? null : (value + low) / 2;
|
|
1468
|
+
});
|
|
1718
1469
|
};
|
|
1719
|
-
const
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
let out = "";
|
|
1735
|
-
let i = 0;
|
|
1736
|
-
let lastSig = "";
|
|
1737
|
-
let lastWord = "";
|
|
1738
|
-
while (i < n) {
|
|
1739
|
-
const ch = source[i];
|
|
1740
|
-
if (ch === "/" && source[i + 1] === "/") {
|
|
1741
|
-
const nl = source.indexOf("\n", i);
|
|
1742
|
-
const end = nl === -1 ? n : nl;
|
|
1743
|
-
out += source.slice(i, end);
|
|
1744
|
-
i = end;
|
|
1745
|
-
continue;
|
|
1746
|
-
}
|
|
1747
|
-
if (ch === "/" && source[i + 1] === "*") {
|
|
1748
|
-
const close = source.indexOf("*/", i + 2);
|
|
1749
|
-
const end = close === -1 ? n : close + 2;
|
|
1750
|
-
out += source.slice(i, end);
|
|
1751
|
-
i = end;
|
|
1752
|
-
continue;
|
|
1753
|
-
}
|
|
1754
|
-
if (ch === '"' || ch === "'" || ch === "`") {
|
|
1755
|
-
const end = scanString(i);
|
|
1756
|
-
out += source.slice(i, end);
|
|
1757
|
-
lastSig = ch;
|
|
1758
|
-
lastWord = "";
|
|
1759
|
-
i = end;
|
|
1760
|
-
continue;
|
|
1470
|
+
const tenkan = midlineOf(conversionLength);
|
|
1471
|
+
const kijun = midlineOf(baseLength);
|
|
1472
|
+
const spanARaw = tenkan.map((value, idx) => {
|
|
1473
|
+
const base = kijun[idx];
|
|
1474
|
+
return value == null || base == null ? null : (value + base) / 2;
|
|
1475
|
+
});
|
|
1476
|
+
const spanBRaw = midlineOf(spanBLength);
|
|
1477
|
+
const spanA = new Array(count).fill(null);
|
|
1478
|
+
const spanB = new Array(count).fill(null);
|
|
1479
|
+
const chikou = new Array(count).fill(null);
|
|
1480
|
+
for (let i = 0; i < count; i += 1) {
|
|
1481
|
+
const shifted = i - displacement;
|
|
1482
|
+
if (shifted >= 0) {
|
|
1483
|
+
spanA[i] = spanARaw[shifted] ?? null;
|
|
1484
|
+
spanB[i] = spanBRaw[shifted] ?? null;
|
|
1761
1485
|
}
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
const end = scanRegex(i);
|
|
1766
|
-
out += source.slice(i, end);
|
|
1767
|
-
lastSig = "/";
|
|
1768
|
-
lastWord = "";
|
|
1769
|
-
i = end;
|
|
1770
|
-
continue;
|
|
1771
|
-
}
|
|
1772
|
-
out += ch;
|
|
1773
|
-
lastSig = ch;
|
|
1774
|
-
lastWord = "";
|
|
1775
|
-
i += 1;
|
|
1776
|
-
continue;
|
|
1486
|
+
const forward = i + displacement;
|
|
1487
|
+
if (forward < count) {
|
|
1488
|
+
chikou[i] = data[forward].c;
|
|
1777
1489
|
}
|
|
1778
|
-
if (isIdChar(ch)) {
|
|
1779
|
-
let j = i;
|
|
1780
|
-
while (j < n && isIdChar(source[j])) j += 1;
|
|
1781
|
-
const word = source.slice(i, j);
|
|
1782
|
-
if ((word === "while" || word === "for") && lastSig !== ".") {
|
|
1783
|
-
const k = skipWsAndComments(j);
|
|
1784
|
-
if (source[k] === "(") {
|
|
1785
|
-
const close = matchParen(k);
|
|
1786
|
-
if (close !== -1) {
|
|
1787
|
-
const inner = source.slice(k + 1, close);
|
|
1788
|
-
if (word === "while") {
|
|
1789
|
-
out += `while (${guardName}() && (${inner}))`;
|
|
1790
|
-
lastSig = ")";
|
|
1791
|
-
lastWord = "";
|
|
1792
|
-
i = close + 1;
|
|
1793
|
-
continue;
|
|
1794
|
-
}
|
|
1795
|
-
const parts = splitTopLevelSemicolons(inner);
|
|
1796
|
-
if (parts.length === 3) {
|
|
1797
|
-
const cond = parts[1].trim();
|
|
1798
|
-
const guardedCond = cond ? `${guardName}() && (${cond})` : `${guardName}()`;
|
|
1799
|
-
out += `for (${parts[0]}; ${guardedCond}; ${parts[2]})`;
|
|
1800
|
-
lastSig = ")";
|
|
1801
|
-
lastWord = "";
|
|
1802
|
-
i = close + 1;
|
|
1803
|
-
continue;
|
|
1804
|
-
}
|
|
1805
|
-
out += source.slice(i, close + 1);
|
|
1806
|
-
lastSig = ")";
|
|
1807
|
-
lastWord = "";
|
|
1808
|
-
i = close + 1;
|
|
1809
|
-
continue;
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
out += word;
|
|
1814
|
-
lastSig = word[word.length - 1];
|
|
1815
|
-
lastWord = word;
|
|
1816
|
-
i = j;
|
|
1817
|
-
continue;
|
|
1818
|
-
}
|
|
1819
|
-
out += ch;
|
|
1820
|
-
if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") {
|
|
1821
|
-
lastSig = ch;
|
|
1822
|
-
lastWord = "";
|
|
1823
|
-
}
|
|
1824
|
-
i += 1;
|
|
1825
1490
|
}
|
|
1826
|
-
return
|
|
1491
|
+
return { tenkan, kijun, spanA, spanB, chikou };
|
|
1827
1492
|
};
|
|
1828
|
-
var
|
|
1829
|
-
const
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
)
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
factory = makeFactory(source);
|
|
1841
|
-
}
|
|
1842
|
-
guard.reset();
|
|
1843
|
-
return factory(guard);
|
|
1493
|
+
var computeKeltner = (data, emaLength, atrLength, multiplier) => {
|
|
1494
|
+
const basis = computeEmaSeries(data, emaLength, "close");
|
|
1495
|
+
const atr = computeAtrSeries(data, atrLength);
|
|
1496
|
+
const upper = basis.map((value, idx) => {
|
|
1497
|
+
const atrValue = atr[idx];
|
|
1498
|
+
return value == null || atrValue == null ? null : value + multiplier * atrValue;
|
|
1499
|
+
});
|
|
1500
|
+
const lower = basis.map((value, idx) => {
|
|
1501
|
+
const atrValue = atr[idx];
|
|
1502
|
+
return value == null || atrValue == null ? null : value - multiplier * atrValue;
|
|
1503
|
+
});
|
|
1504
|
+
return { basis, upper, lower };
|
|
1844
1505
|
};
|
|
1845
|
-
var
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
return
|
|
1851
|
-
}
|
|
1506
|
+
var computeDonchian = (data, length) => {
|
|
1507
|
+
const upper = rollingExtremeSeries(data.map((point) => point.h), length, "max");
|
|
1508
|
+
const lower = rollingExtremeSeries(data.map((point) => point.l), length, "min");
|
|
1509
|
+
const basis = upper.map((value, idx) => {
|
|
1510
|
+
const low = lower[idx];
|
|
1511
|
+
return value == null || low == null ? null : (value + low) / 2;
|
|
1512
|
+
});
|
|
1513
|
+
return { upper, lower, basis };
|
|
1852
1514
|
};
|
|
1853
|
-
var
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1515
|
+
var BUILTIN_MACD_INDICATOR = {
|
|
1516
|
+
id: "macd",
|
|
1517
|
+
name: "MACD",
|
|
1518
|
+
pane: "separate",
|
|
1519
|
+
paneHeightRatio: 0.2,
|
|
1520
|
+
defaultInputs: {
|
|
1521
|
+
fast: 12,
|
|
1522
|
+
slow: 26,
|
|
1523
|
+
signal: 9,
|
|
1524
|
+
macdColor: "#2962ff",
|
|
1525
|
+
signalColor: "#ff6d00",
|
|
1526
|
+
histUpColor: "#26a69a",
|
|
1527
|
+
histDownColor: "#ef5350",
|
|
1528
|
+
showValueLine: true
|
|
1529
|
+
},
|
|
1530
|
+
draw: (ctx, renderContext, inputs) => {
|
|
1531
|
+
const fast = clampIndicatorLength(inputs.fast, 12);
|
|
1532
|
+
const slow = clampIndicatorLength(inputs.slow, 26);
|
|
1533
|
+
const signalLength = clampIndicatorLength(inputs.signal, 9);
|
|
1534
|
+
const { macd, signal, hist } = withCachedComputation(
|
|
1535
|
+
`macd|${fast}|${slow}|${signalLength}`,
|
|
1536
|
+
renderContext.data,
|
|
1537
|
+
() => computeMacd(renderContext.data, fast, slow, signalLength)
|
|
1538
|
+
);
|
|
1539
|
+
return drawSeparateMultiSeries(
|
|
1540
|
+
ctx,
|
|
1541
|
+
renderContext,
|
|
1542
|
+
[
|
|
1543
|
+
{
|
|
1544
|
+
values: hist,
|
|
1545
|
+
color: inputs.histUpColor ?? "#26a69a",
|
|
1546
|
+
negativeColor: inputs.histDownColor ?? "#ef5350",
|
|
1547
|
+
histogram: true
|
|
1548
|
+
},
|
|
1549
|
+
{ values: macd, color: inputs.macdColor ?? "#2962ff", label: "MACD" },
|
|
1550
|
+
{ values: signal, color: inputs.signalColor ?? "#ff6d00", label: "Signal" }
|
|
1551
|
+
],
|
|
1552
|
+
{
|
|
1553
|
+
title: `MACD ${fast} ${slow} ${signalLength}`,
|
|
1554
|
+
includeZero: true,
|
|
1555
|
+
guideLines: [0],
|
|
1556
|
+
decimals: 2,
|
|
1557
|
+
valueLabelSeriesIndex: 1,
|
|
1558
|
+
valueLines: inputs.showValueLine !== false
|
|
1559
|
+
}
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1862
1562
|
};
|
|
1863
|
-
var
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1563
|
+
var BUILTIN_STOCHASTIC_INDICATOR = {
|
|
1564
|
+
id: "stochastic",
|
|
1565
|
+
name: "Stoch",
|
|
1566
|
+
pane: "separate",
|
|
1567
|
+
paneHeightRatio: 0.18,
|
|
1568
|
+
defaultInputs: { kLength: 14, kSmoothing: 1, dLength: 3, kColor: "#2962ff", dColor: "#ff6d00", showValueLine: true },
|
|
1569
|
+
draw: (ctx, renderContext, inputs) => {
|
|
1570
|
+
const kLength = clampIndicatorLength(inputs.kLength, 14);
|
|
1571
|
+
const kSmoothing = clampIndicatorLength(inputs.kSmoothing, 1);
|
|
1572
|
+
const dLength = clampIndicatorLength(inputs.dLength, 3);
|
|
1573
|
+
const { k, d } = withCachedComputation(
|
|
1574
|
+
`stochastic|${kLength}|${kSmoothing}|${dLength}`,
|
|
1575
|
+
renderContext.data,
|
|
1576
|
+
() => computeStochastic(renderContext.data, kLength, kSmoothing, dLength)
|
|
1577
|
+
);
|
|
1578
|
+
return drawSeparateMultiSeries(
|
|
1579
|
+
ctx,
|
|
1580
|
+
renderContext,
|
|
1581
|
+
[
|
|
1582
|
+
{ values: k, color: inputs.kColor ?? "#2962ff", label: "%K" },
|
|
1583
|
+
{ values: d, color: inputs.dColor ?? "#ff6d00", label: "%D" }
|
|
1584
|
+
],
|
|
1585
|
+
{
|
|
1586
|
+
title: `Stoch ${kLength} ${kSmoothing} ${dLength}`,
|
|
1587
|
+
minOverride: 0,
|
|
1588
|
+
maxOverride: 100,
|
|
1589
|
+
guideLines: [20, 80],
|
|
1590
|
+
axisTicks: [0, 20, 50, 80, 100],
|
|
1591
|
+
decimals: 2,
|
|
1592
|
+
valueLines: inputs.showValueLine !== false
|
|
1593
|
+
}
|
|
1594
|
+
);
|
|
1868
1595
|
}
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1596
|
+
};
|
|
1597
|
+
var BUILTIN_STOCHRSI_INDICATOR = {
|
|
1598
|
+
id: "stochrsi",
|
|
1599
|
+
name: "Stoch RSI",
|
|
1600
|
+
pane: "separate",
|
|
1601
|
+
paneHeightRatio: 0.18,
|
|
1602
|
+
defaultInputs: {
|
|
1603
|
+
rsiLength: 14,
|
|
1604
|
+
stochLength: 14,
|
|
1605
|
+
kSmoothing: 3,
|
|
1606
|
+
dSmoothing: 3,
|
|
1607
|
+
kColor: "#2962ff",
|
|
1608
|
+
dColor: "#ff6d00",
|
|
1609
|
+
showValueLine: true
|
|
1610
|
+
},
|
|
1611
|
+
draw: (ctx, renderContext, inputs) => {
|
|
1612
|
+
const rsiLength = clampIndicatorLength(inputs.rsiLength, 14);
|
|
1613
|
+
const stochLength = clampIndicatorLength(inputs.stochLength, 14);
|
|
1614
|
+
const kSmoothing = clampIndicatorLength(inputs.kSmoothing, 3);
|
|
1615
|
+
const dSmoothing = clampIndicatorLength(inputs.dSmoothing, 3);
|
|
1616
|
+
const { k, d } = withCachedComputation(
|
|
1617
|
+
`stochrsi|${rsiLength}|${stochLength}|${kSmoothing}|${dSmoothing}`,
|
|
1618
|
+
renderContext.data,
|
|
1619
|
+
() => computeStochRsi(renderContext.data, rsiLength, stochLength, kSmoothing, dSmoothing)
|
|
1620
|
+
);
|
|
1621
|
+
return drawSeparateMultiSeries(
|
|
1622
|
+
ctx,
|
|
1623
|
+
renderContext,
|
|
1624
|
+
[
|
|
1625
|
+
{ values: k, color: inputs.kColor ?? "#2962ff", label: "%K" },
|
|
1626
|
+
{ values: d, color: inputs.dColor ?? "#ff6d00", label: "%D" }
|
|
1627
|
+
],
|
|
1628
|
+
{
|
|
1629
|
+
title: `Stoch RSI ${rsiLength} ${stochLength}`,
|
|
1630
|
+
minOverride: 0,
|
|
1631
|
+
maxOverride: 100,
|
|
1632
|
+
guideLines: [20, 80],
|
|
1633
|
+
axisTicks: [0, 20, 50, 80, 100],
|
|
1634
|
+
decimals: 2,
|
|
1635
|
+
valueLines: inputs.showValueLine !== false
|
|
1636
|
+
}
|
|
1637
|
+
);
|
|
1876
1638
|
}
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1639
|
+
};
|
|
1640
|
+
var BUILTIN_ADX_INDICATOR = {
|
|
1641
|
+
id: "adx",
|
|
1642
|
+
name: "ADX/DMI",
|
|
1643
|
+
pane: "separate",
|
|
1644
|
+
paneHeightRatio: 0.18,
|
|
1645
|
+
defaultInputs: {
|
|
1646
|
+
length: 14,
|
|
1647
|
+
adxColor: "#ff6d00",
|
|
1648
|
+
plusDiColor: "#26a69a",
|
|
1649
|
+
minusDiColor: "#ef5350",
|
|
1650
|
+
showDi: true,
|
|
1651
|
+
showValueLine: true
|
|
1652
|
+
},
|
|
1653
|
+
draw: (ctx, renderContext, inputs) => {
|
|
1654
|
+
const length = clampIndicatorLength(inputs.length, 14);
|
|
1655
|
+
const { adx, plusDi, minusDi } = withCachedComputation(
|
|
1656
|
+
`adx|${length}`,
|
|
1657
|
+
renderContext.data,
|
|
1658
|
+
() => computeDmi(renderContext.data, length)
|
|
1659
|
+
);
|
|
1660
|
+
const seriesList = [
|
|
1661
|
+
{ values: adx, color: inputs.adxColor ?? "#ff6d00", label: "ADX" }
|
|
1662
|
+
];
|
|
1663
|
+
if (inputs.showDi !== false) {
|
|
1664
|
+
seriesList.push(
|
|
1665
|
+
{ values: plusDi, color: inputs.plusDiColor ?? "#26a69a", label: "+DI", width: 1 },
|
|
1666
|
+
{ values: minusDi, color: inputs.minusDiColor ?? "#ef5350", label: "-DI", width: 1 }
|
|
1667
|
+
);
|
|
1882
1668
|
}
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
return { error: "compute() must return { plots: [...] }" };
|
|
1891
|
-
}
|
|
1892
|
-
for (const plot of result.plots) {
|
|
1893
|
-
if (!plot || !Array.isArray(plot.values)) {
|
|
1894
|
-
return { error: "Every plot needs a `values` array" };
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
return { result };
|
|
1898
|
-
} catch (error) {
|
|
1899
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1900
|
-
if (error instanceof ScriptBudgetError) {
|
|
1901
|
-
trippedError = message;
|
|
1902
|
-
}
|
|
1903
|
-
return { error: message };
|
|
1904
|
-
}
|
|
1905
|
-
};
|
|
1906
|
-
const cachedCompute = (data, inputs) => withCachedComputation(
|
|
1907
|
-
`script|${definition.id}|${sourceKey}|${JSON.stringify(inputs)}`,
|
|
1908
|
-
data,
|
|
1909
|
-
() => runCompute(data, inputs)
|
|
1910
|
-
);
|
|
1911
|
-
const plugin = {
|
|
1912
|
-
id: definition.id,
|
|
1913
|
-
name: definition.name,
|
|
1914
|
-
pane,
|
|
1915
|
-
defaultInputs,
|
|
1916
|
-
draw: (ctx, renderContext, inputs) => {
|
|
1917
|
-
const outcome = cachedCompute(renderContext.data, inputs);
|
|
1918
|
-
if (!outcome.result) {
|
|
1919
|
-
drawScriptError(ctx, renderContext, definition.name, outcome.error ?? "Unknown script error");
|
|
1920
|
-
return pane === "separate" ? { title: `${definition.name} (script error)` } : void 0;
|
|
1921
|
-
}
|
|
1922
|
-
const { plots, range, guides, decimals } = outcome.result;
|
|
1923
|
-
if (pane === "separate") {
|
|
1924
|
-
const seriesList = plots.map((plot, index) => ({
|
|
1925
|
-
values: plot.values,
|
|
1926
|
-
color: plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
|
|
1927
|
-
...plot.width !== void 0 ? { width: plot.width } : {},
|
|
1928
|
-
...plot.title ? { label: plot.title } : {},
|
|
1929
|
-
...plot.style === "histogram" ? { histogram: true } : {},
|
|
1930
|
-
...plot.negativeColor ? { negativeColor: plot.negativeColor } : {}
|
|
1931
|
-
}));
|
|
1932
|
-
return drawSeparateMultiSeries(ctx, renderContext, seriesList, {
|
|
1933
|
-
title: definition.name,
|
|
1934
|
-
...range?.min !== void 0 ? { minOverride: range.min } : {},
|
|
1935
|
-
...range?.max !== void 0 ? { maxOverride: range.max } : {},
|
|
1936
|
-
...guides && guides.length > 0 ? { guideLines: guides } : {},
|
|
1937
|
-
...decimals !== void 0 ? { decimals } : {},
|
|
1938
|
-
valueLines: inputs.showValueLine !== false
|
|
1939
|
-
});
|
|
1940
|
-
}
|
|
1941
|
-
for (let index = 0; index < plots.length; index += 1) {
|
|
1942
|
-
const plot = plots[index];
|
|
1943
|
-
if (plot.style === "histogram") continue;
|
|
1944
|
-
drawOverlaySeries(
|
|
1945
|
-
ctx,
|
|
1946
|
-
renderContext,
|
|
1947
|
-
plot.values,
|
|
1948
|
-
plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
|
|
1949
|
-
plot.width ?? 2
|
|
1950
|
-
);
|
|
1951
|
-
}
|
|
1952
|
-
return void 0;
|
|
1953
|
-
},
|
|
1954
|
-
...pane === "overlay" ? {
|
|
1955
|
-
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
1956
|
-
const outcome = cachedCompute(data, inputs);
|
|
1957
|
-
if (!outcome.result) return null;
|
|
1958
|
-
const lineSeries = outcome.result.plots.filter((plot) => plot.style !== "histogram").map((plot) => plot.values);
|
|
1959
|
-
if (lineSeries.length === 0) return null;
|
|
1960
|
-
return rangeOfSeries(lineSeries, startIndex, endIndex, skipIndex);
|
|
1961
|
-
}
|
|
1962
|
-
} : {}
|
|
1963
|
-
};
|
|
1964
|
-
if (definition.paneHeightRatio !== void 0) {
|
|
1965
|
-
plugin.paneHeightRatio = definition.paneHeightRatio;
|
|
1669
|
+
return drawSeparateMultiSeries(ctx, renderContext, seriesList, {
|
|
1670
|
+
title: `ADX ${length}`,
|
|
1671
|
+
minOverride: 0,
|
|
1672
|
+
guideLines: [20],
|
|
1673
|
+
decimals: 2,
|
|
1674
|
+
valueLines: inputs.showValueLine !== false
|
|
1675
|
+
});
|
|
1966
1676
|
}
|
|
1967
|
-
return plugin;
|
|
1968
|
-
};
|
|
1969
|
-
var computeMacd = (data, fast, slow, signalLength) => {
|
|
1970
|
-
const fastEma = computeEmaSeries(data, fast, "close");
|
|
1971
|
-
const slowEma = computeEmaSeries(data, slow, "close");
|
|
1972
|
-
const macd = fastEma.map((value, idx) => {
|
|
1973
|
-
const slowValue = slowEma[idx];
|
|
1974
|
-
return value == null || slowValue == null ? null : value - slowValue;
|
|
1975
|
-
});
|
|
1976
|
-
const signal = emaFromValues(macd, signalLength);
|
|
1977
|
-
const hist = macd.map((value, idx) => {
|
|
1978
|
-
const signalValue = signal[idx];
|
|
1979
|
-
return value == null || signalValue == null ? null : value - signalValue;
|
|
1980
|
-
});
|
|
1981
|
-
return { macd, signal, hist };
|
|
1982
|
-
};
|
|
1983
|
-
var computeStochastic = (data, kLength, kSmoothing, dLength) => {
|
|
1984
|
-
const highestHigh = rollingExtremeSeries(data.map((point) => point.h), kLength, "max");
|
|
1985
|
-
const lowestLow = rollingExtremeSeries(data.map((point) => point.l), kLength, "min");
|
|
1986
|
-
const rawK = data.map((point, idx) => {
|
|
1987
|
-
const hh = highestHigh[idx];
|
|
1988
|
-
const ll = lowestLow[idx];
|
|
1989
|
-
if (hh == null || ll == null) return null;
|
|
1990
|
-
const range = hh - ll;
|
|
1991
|
-
return range === 0 ? 50 : (point.c - ll) / range * 100;
|
|
1992
|
-
});
|
|
1993
|
-
const k = kSmoothing > 1 ? smaFromValues(rawK, kSmoothing) : rawK;
|
|
1994
|
-
const d = smaFromValues(k, dLength);
|
|
1995
|
-
return { k, d };
|
|
1996
|
-
};
|
|
1997
|
-
var computeStochRsi = (data, rsiLength, stochLength, kSmoothing, dSmoothing) => {
|
|
1998
|
-
const rsi = computeRsiSeries(data, rsiLength);
|
|
1999
|
-
const finiteRsi = rsi.map((value) => value == null ? Number.NaN : value);
|
|
2000
|
-
const highest = rollingExtremeSeries(finiteRsi, stochLength, "max");
|
|
2001
|
-
const lowest = rollingExtremeSeries(finiteRsi, stochLength, "min");
|
|
2002
|
-
const rawK = rsi.map((value, idx) => {
|
|
2003
|
-
const hh = highest[idx];
|
|
2004
|
-
const ll = lowest[idx];
|
|
2005
|
-
if (value == null || hh == null || ll == null || !Number.isFinite(hh) || !Number.isFinite(ll)) return null;
|
|
2006
|
-
const range = hh - ll;
|
|
2007
|
-
return range === 0 ? 50 : (value - ll) / range * 100;
|
|
2008
|
-
});
|
|
2009
|
-
const k = smaFromValues(rawK, Math.max(1, kSmoothing));
|
|
2010
|
-
const d = smaFromValues(k, Math.max(1, dSmoothing));
|
|
2011
|
-
return { k, d };
|
|
2012
1677
|
};
|
|
2013
|
-
var
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
const
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
1678
|
+
var BUILTIN_OBV_INDICATOR = {
|
|
1679
|
+
id: "obv",
|
|
1680
|
+
name: "OBV",
|
|
1681
|
+
pane: "separate",
|
|
1682
|
+
paneHeightRatio: 0.16,
|
|
1683
|
+
defaultInputs: { color: "#2962ff", width: 2, showValueLine: true },
|
|
1684
|
+
draw: (ctx, renderContext, inputs) => {
|
|
1685
|
+
const values = withCachedSeries("obv", renderContext.data, () => computeObvSeries(renderContext.data));
|
|
1686
|
+
return drawSeparateMultiSeries(
|
|
1687
|
+
ctx,
|
|
1688
|
+
renderContext,
|
|
1689
|
+
[{ values, color: inputs.color ?? "#2962ff", width: Number(inputs.width) || 2, label: "OBV" }],
|
|
1690
|
+
{ title: "OBV", format: formatCompactNumber, valueLines: inputs.showValueLine !== false }
|
|
1691
|
+
);
|
|
2026
1692
|
}
|
|
2027
|
-
const smoothPlus = rmaFromValues(plusDm, length);
|
|
2028
|
-
const smoothMinus = rmaFromValues(minusDm, length);
|
|
2029
|
-
const smoothTr = rmaFromValues(trueRange, length);
|
|
2030
|
-
const plusDi = smoothPlus.map((value, idx) => {
|
|
2031
|
-
const tr = smoothTr[idx];
|
|
2032
|
-
return value == null || tr == null || tr === 0 ? null : 100 * value / tr;
|
|
2033
|
-
});
|
|
2034
|
-
const minusDi = smoothMinus.map((value, idx) => {
|
|
2035
|
-
const tr = smoothTr[idx];
|
|
2036
|
-
return value == null || tr == null || tr === 0 ? null : 100 * value / tr;
|
|
2037
|
-
});
|
|
2038
|
-
const dx = plusDi.map((value, idx) => {
|
|
2039
|
-
const minus = minusDi[idx];
|
|
2040
|
-
if (value == null || minus == null) return null;
|
|
2041
|
-
const sum = value + minus;
|
|
2042
|
-
return sum === 0 ? 0 : 100 * Math.abs(value - minus) / sum;
|
|
2043
|
-
});
|
|
2044
|
-
const adx = rmaFromValues(dx, length);
|
|
2045
|
-
return { adx, plusDi, minusDi };
|
|
2046
1693
|
};
|
|
2047
|
-
var
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
1694
|
+
var BUILTIN_MFI_INDICATOR = {
|
|
1695
|
+
id: "mfi",
|
|
1696
|
+
name: "MFI",
|
|
1697
|
+
pane: "separate",
|
|
1698
|
+
paneHeightRatio: 0.16,
|
|
1699
|
+
defaultInputs: { length: 14, color: "#7e57c2", width: 2, showValueLine: true },
|
|
1700
|
+
draw: (ctx, renderContext, inputs) => {
|
|
1701
|
+
const length = clampIndicatorLength(inputs.length, 14);
|
|
1702
|
+
const values = withCachedSeries(
|
|
1703
|
+
`mfi|${length}`,
|
|
1704
|
+
renderContext.data,
|
|
1705
|
+
() => computeMfiSeries(renderContext.data, length)
|
|
1706
|
+
);
|
|
1707
|
+
return drawSeparateMultiSeries(
|
|
1708
|
+
ctx,
|
|
1709
|
+
renderContext,
|
|
1710
|
+
[{ values, color: inputs.color ?? "#7e57c2", width: Number(inputs.width) || 2 }],
|
|
1711
|
+
{
|
|
1712
|
+
title: `MFI ${length}`,
|
|
1713
|
+
minOverride: 0,
|
|
1714
|
+
maxOverride: 100,
|
|
1715
|
+
guideLines: [20, 80],
|
|
1716
|
+
axisTicks: [0, 20, 50, 80, 100],
|
|
1717
|
+
decimals: 2,
|
|
1718
|
+
valueLines: inputs.showValueLine !== false
|
|
1719
|
+
}
|
|
1720
|
+
);
|
|
2059
1721
|
}
|
|
2060
|
-
return result;
|
|
2061
1722
|
};
|
|
2062
|
-
var
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
const
|
|
2070
|
-
const
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
1723
|
+
var BUILTIN_CCI_INDICATOR = {
|
|
1724
|
+
id: "cci",
|
|
1725
|
+
name: "CCI",
|
|
1726
|
+
pane: "separate",
|
|
1727
|
+
paneHeightRatio: 0.16,
|
|
1728
|
+
defaultInputs: { length: 20, color: "#2962ff", width: 2, showValueLine: true },
|
|
1729
|
+
draw: (ctx, renderContext, inputs) => {
|
|
1730
|
+
const length = clampIndicatorLength(inputs.length, 20);
|
|
1731
|
+
const values = withCachedSeries(
|
|
1732
|
+
`cci|${length}`,
|
|
1733
|
+
renderContext.data,
|
|
1734
|
+
() => computeCciSeries(renderContext.data, length)
|
|
1735
|
+
);
|
|
1736
|
+
return drawSeparateMultiSeries(
|
|
1737
|
+
ctx,
|
|
1738
|
+
renderContext,
|
|
1739
|
+
[{ values, color: inputs.color ?? "#2962ff", width: Number(inputs.width) || 2 }],
|
|
1740
|
+
{ title: `CCI ${length}`, guideLines: [-100, 0, 100], decimals: 2, valueLines: inputs.showValueLine !== false }
|
|
1741
|
+
);
|
|
2076
1742
|
}
|
|
2077
|
-
let posSum = 0;
|
|
2078
|
-
let negSum = 0;
|
|
2079
|
-
for (let i = 0; i < data.length; i += 1) {
|
|
2080
|
-
posSum += positiveFlow[i];
|
|
2081
|
-
negSum += negativeFlow[i];
|
|
2082
|
-
if (i >= length) {
|
|
2083
|
-
posSum -= positiveFlow[i - length];
|
|
2084
|
-
negSum -= negativeFlow[i - length];
|
|
2085
|
-
}
|
|
2086
|
-
if (i >= length) {
|
|
2087
|
-
result[i] = negSum === 0 ? 100 : 100 - 100 / (1 + posSum / negSum);
|
|
2088
|
-
}
|
|
2089
|
-
}
|
|
2090
|
-
return result;
|
|
2091
|
-
};
|
|
2092
|
-
var computeCciSeries = (data, length) => {
|
|
2093
|
-
const result = new Array(data.length).fill(null);
|
|
2094
|
-
const typical = data.map((point) => (point.h + point.l + point.c) / 3);
|
|
2095
|
-
const smaTypical = smaFromValues(typical, length);
|
|
2096
|
-
for (let i = length - 1; i < data.length; i += 1) {
|
|
2097
|
-
const mean = smaTypical[i];
|
|
2098
|
-
if (mean == null) continue;
|
|
2099
|
-
let meanDeviation = 0;
|
|
2100
|
-
for (let j = 0; j < length; j += 1) {
|
|
2101
|
-
meanDeviation += Math.abs(typical[i - j] - mean);
|
|
2102
|
-
}
|
|
2103
|
-
meanDeviation /= length;
|
|
2104
|
-
result[i] = meanDeviation === 0 ? 0 : (typical[i] - mean) / (0.015 * meanDeviation);
|
|
2105
|
-
}
|
|
2106
|
-
return result;
|
|
2107
|
-
};
|
|
2108
|
-
var computeWilliamsRSeries = (data, length) => {
|
|
2109
|
-
const highest = rollingExtremeSeries(data.map((point) => point.h), length, "max");
|
|
2110
|
-
const lowest = rollingExtremeSeries(data.map((point) => point.l), length, "min");
|
|
2111
|
-
return data.map((point, idx) => {
|
|
2112
|
-
const hh = highest[idx];
|
|
2113
|
-
const ll = lowest[idx];
|
|
2114
|
-
if (hh == null || ll == null) return null;
|
|
2115
|
-
const range = hh - ll;
|
|
2116
|
-
return range === 0 ? -50 : (hh - point.c) / range * -100;
|
|
2117
|
-
});
|
|
2118
|
-
};
|
|
2119
|
-
var computeRocSeries = (data, length) => {
|
|
2120
|
-
return data.map((point, idx) => {
|
|
2121
|
-
if (idx < length) return null;
|
|
2122
|
-
const past = data[idx - length].c;
|
|
2123
|
-
return past === 0 ? null : 100 * (point.c - past) / past;
|
|
2124
|
-
});
|
|
2125
|
-
};
|
|
2126
|
-
var computeMomentumSeries = (data, length) => {
|
|
2127
|
-
return data.map((point, idx) => idx < length ? null : point.c - data[idx - length].c);
|
|
2128
|
-
};
|
|
2129
|
-
var computePsarSeries = (data, start, increment, maximum) => {
|
|
2130
|
-
const count = data.length;
|
|
2131
|
-
const result = new Array(count).fill(null);
|
|
2132
|
-
if (count < 2) return result;
|
|
2133
|
-
let isUp = data[1].c >= data[0].c;
|
|
2134
|
-
let sar = isUp ? data[0].l : data[0].h;
|
|
2135
|
-
let extremePoint = isUp ? data[0].h : data[0].l;
|
|
2136
|
-
let accelerationFactor = start;
|
|
2137
|
-
for (let i = 1; i < count; i += 1) {
|
|
2138
|
-
const point = data[i];
|
|
2139
|
-
sar += accelerationFactor * (extremePoint - sar);
|
|
2140
|
-
if (isUp) {
|
|
2141
|
-
sar = Math.min(sar, data[i - 1].l, data[Math.max(0, i - 2)].l);
|
|
2142
|
-
if (point.l < sar) {
|
|
2143
|
-
isUp = false;
|
|
2144
|
-
sar = extremePoint;
|
|
2145
|
-
extremePoint = point.l;
|
|
2146
|
-
accelerationFactor = start;
|
|
2147
|
-
} else if (point.h > extremePoint) {
|
|
2148
|
-
extremePoint = point.h;
|
|
2149
|
-
accelerationFactor = Math.min(maximum, accelerationFactor + increment);
|
|
2150
|
-
}
|
|
2151
|
-
} else {
|
|
2152
|
-
sar = Math.max(sar, data[i - 1].h, data[Math.max(0, i - 2)].h);
|
|
2153
|
-
if (point.h > sar) {
|
|
2154
|
-
isUp = true;
|
|
2155
|
-
sar = extremePoint;
|
|
2156
|
-
extremePoint = point.h;
|
|
2157
|
-
accelerationFactor = start;
|
|
2158
|
-
} else if (point.l < extremePoint) {
|
|
2159
|
-
extremePoint = point.l;
|
|
2160
|
-
accelerationFactor = Math.min(maximum, accelerationFactor + increment);
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
result[i] = sar;
|
|
2164
|
-
}
|
|
2165
|
-
return result;
|
|
2166
|
-
};
|
|
2167
|
-
var computeSuperTrend = (data, atrLength, multiplier) => {
|
|
2168
|
-
const count = data.length;
|
|
2169
|
-
const up = new Array(count).fill(null);
|
|
2170
|
-
const down = new Array(count).fill(null);
|
|
2171
|
-
const atr = computeAtrSeries(data, atrLength);
|
|
2172
|
-
let prevUpper = Number.NaN;
|
|
2173
|
-
let prevLower = Number.NaN;
|
|
2174
|
-
let trend = 1;
|
|
2175
|
-
let prevClose = 0;
|
|
2176
|
-
for (let i = 0; i < count; i += 1) {
|
|
2177
|
-
const point = data[i];
|
|
2178
|
-
const atrValue = atr[i];
|
|
2179
|
-
if (atrValue == null) {
|
|
2180
|
-
prevClose = point.c;
|
|
2181
|
-
continue;
|
|
2182
|
-
}
|
|
2183
|
-
const mid = (point.h + point.l) / 2;
|
|
2184
|
-
const basicUpper = mid + multiplier * atrValue;
|
|
2185
|
-
const basicLower = mid - multiplier * atrValue;
|
|
2186
|
-
if (!Number.isFinite(prevUpper)) {
|
|
2187
|
-
prevUpper = basicUpper;
|
|
2188
|
-
prevLower = basicLower;
|
|
2189
|
-
trend = point.c >= mid ? 1 : -1;
|
|
2190
|
-
} else {
|
|
2191
|
-
const finalUpper = basicUpper < prevUpper || prevClose > prevUpper ? basicUpper : prevUpper;
|
|
2192
|
-
const finalLower = basicLower > prevLower || prevClose < prevLower ? basicLower : prevLower;
|
|
2193
|
-
if (trend === 1) {
|
|
2194
|
-
trend = point.c < finalLower ? -1 : 1;
|
|
2195
|
-
} else {
|
|
2196
|
-
trend = point.c > finalUpper ? 1 : -1;
|
|
2197
|
-
}
|
|
2198
|
-
prevUpper = finalUpper;
|
|
2199
|
-
prevLower = finalLower;
|
|
2200
|
-
}
|
|
2201
|
-
if (trend === 1) up[i] = prevLower;
|
|
2202
|
-
else down[i] = prevUpper;
|
|
2203
|
-
prevClose = point.c;
|
|
2204
|
-
}
|
|
2205
|
-
return { up, down };
|
|
2206
|
-
};
|
|
2207
|
-
var computeIchimoku = (data, conversionLength, baseLength, spanBLength, displacement) => {
|
|
2208
|
-
const count = data.length;
|
|
2209
|
-
const highs = data.map((point) => point.h);
|
|
2210
|
-
const lows = data.map((point) => point.l);
|
|
2211
|
-
const midlineOf = (length) => {
|
|
2212
|
-
const highest = rollingExtremeSeries(highs, length, "max");
|
|
2213
|
-
const lowest = rollingExtremeSeries(lows, length, "min");
|
|
2214
|
-
return highest.map((value, idx) => {
|
|
2215
|
-
const low = lowest[idx];
|
|
2216
|
-
return value == null || low == null ? null : (value + low) / 2;
|
|
2217
|
-
});
|
|
2218
|
-
};
|
|
2219
|
-
const tenkan = midlineOf(conversionLength);
|
|
2220
|
-
const kijun = midlineOf(baseLength);
|
|
2221
|
-
const spanARaw = tenkan.map((value, idx) => {
|
|
2222
|
-
const base = kijun[idx];
|
|
2223
|
-
return value == null || base == null ? null : (value + base) / 2;
|
|
2224
|
-
});
|
|
2225
|
-
const spanBRaw = midlineOf(spanBLength);
|
|
2226
|
-
const spanA = new Array(count).fill(null);
|
|
2227
|
-
const spanB = new Array(count).fill(null);
|
|
2228
|
-
const chikou = new Array(count).fill(null);
|
|
2229
|
-
for (let i = 0; i < count; i += 1) {
|
|
2230
|
-
const shifted = i - displacement;
|
|
2231
|
-
if (shifted >= 0) {
|
|
2232
|
-
spanA[i] = spanARaw[shifted] ?? null;
|
|
2233
|
-
spanB[i] = spanBRaw[shifted] ?? null;
|
|
2234
|
-
}
|
|
2235
|
-
const forward = i + displacement;
|
|
2236
|
-
if (forward < count) {
|
|
2237
|
-
chikou[i] = data[forward].c;
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2240
|
-
return { tenkan, kijun, spanA, spanB, chikou };
|
|
2241
1743
|
};
|
|
2242
|
-
var
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
const upper = basis.map((value, idx) => {
|
|
2246
|
-
const atrValue = atr[idx];
|
|
2247
|
-
return value == null || atrValue == null ? null : value + multiplier * atrValue;
|
|
2248
|
-
});
|
|
2249
|
-
const lower = basis.map((value, idx) => {
|
|
2250
|
-
const atrValue = atr[idx];
|
|
2251
|
-
return value == null || atrValue == null ? null : value - multiplier * atrValue;
|
|
2252
|
-
});
|
|
2253
|
-
return { basis, upper, lower };
|
|
2254
|
-
};
|
|
2255
|
-
var computeDonchian = (data, length) => {
|
|
2256
|
-
const upper = rollingExtremeSeries(data.map((point) => point.h), length, "max");
|
|
2257
|
-
const lower = rollingExtremeSeries(data.map((point) => point.l), length, "min");
|
|
2258
|
-
const basis = upper.map((value, idx) => {
|
|
2259
|
-
const low = lower[idx];
|
|
2260
|
-
return value == null || low == null ? null : (value + low) / 2;
|
|
2261
|
-
});
|
|
2262
|
-
return { upper, lower, basis };
|
|
2263
|
-
};
|
|
2264
|
-
var BUILTIN_MACD_INDICATOR = {
|
|
2265
|
-
id: "macd",
|
|
2266
|
-
name: "MACD",
|
|
1744
|
+
var BUILTIN_WILLIAMSR_INDICATOR = {
|
|
1745
|
+
id: "williamsr",
|
|
1746
|
+
name: "Williams %R",
|
|
2267
1747
|
pane: "separate",
|
|
2268
|
-
paneHeightRatio: 0.
|
|
2269
|
-
defaultInputs: {
|
|
2270
|
-
fast: 12,
|
|
2271
|
-
slow: 26,
|
|
2272
|
-
signal: 9,
|
|
2273
|
-
macdColor: "#2962ff",
|
|
2274
|
-
signalColor: "#ff6d00",
|
|
2275
|
-
histUpColor: "#26a69a",
|
|
2276
|
-
histDownColor: "#ef5350",
|
|
2277
|
-
showValueLine: true
|
|
2278
|
-
},
|
|
1748
|
+
paneHeightRatio: 0.16,
|
|
1749
|
+
defaultInputs: { length: 14, color: "#7e57c2", width: 2, showValueLine: true },
|
|
2279
1750
|
draw: (ctx, renderContext, inputs) => {
|
|
2280
|
-
const
|
|
2281
|
-
const
|
|
2282
|
-
|
|
2283
|
-
const { macd, signal, hist } = withCachedComputation(
|
|
2284
|
-
`macd|${fast}|${slow}|${signalLength}`,
|
|
1751
|
+
const length = clampIndicatorLength(inputs.length, 14);
|
|
1752
|
+
const values = withCachedSeries(
|
|
1753
|
+
`williamsr|${length}`,
|
|
2285
1754
|
renderContext.data,
|
|
2286
|
-
() =>
|
|
1755
|
+
() => computeWilliamsRSeries(renderContext.data, length)
|
|
2287
1756
|
);
|
|
2288
1757
|
return drawSeparateMultiSeries(
|
|
2289
1758
|
ctx,
|
|
2290
1759
|
renderContext,
|
|
2291
|
-
[
|
|
2292
|
-
{
|
|
2293
|
-
values: hist,
|
|
2294
|
-
color: inputs.histUpColor ?? "#26a69a",
|
|
2295
|
-
negativeColor: inputs.histDownColor ?? "#ef5350",
|
|
2296
|
-
histogram: true
|
|
2297
|
-
},
|
|
2298
|
-
{ values: macd, color: inputs.macdColor ?? "#2962ff", label: "MACD" },
|
|
2299
|
-
{ values: signal, color: inputs.signalColor ?? "#ff6d00", label: "Signal" }
|
|
2300
|
-
],
|
|
1760
|
+
[{ values, color: inputs.color ?? "#7e57c2", width: Number(inputs.width) || 2 }],
|
|
2301
1761
|
{
|
|
2302
|
-
title:
|
|
2303
|
-
|
|
2304
|
-
|
|
1762
|
+
title: `%R ${length}`,
|
|
1763
|
+
minOverride: -100,
|
|
1764
|
+
maxOverride: 0,
|
|
1765
|
+
guideLines: [-80, -20],
|
|
1766
|
+
axisTicks: [-100, -80, -50, -20, 0],
|
|
2305
1767
|
decimals: 2,
|
|
2306
|
-
valueLabelSeriesIndex: 1,
|
|
2307
1768
|
valueLines: inputs.showValueLine !== false
|
|
2308
1769
|
}
|
|
2309
1770
|
);
|
|
2310
1771
|
}
|
|
2311
1772
|
};
|
|
2312
|
-
var
|
|
2313
|
-
id: "
|
|
2314
|
-
name: "
|
|
1773
|
+
var BUILTIN_ROC_INDICATOR = {
|
|
1774
|
+
id: "roc",
|
|
1775
|
+
name: "ROC",
|
|
2315
1776
|
pane: "separate",
|
|
2316
|
-
paneHeightRatio: 0.
|
|
2317
|
-
defaultInputs: {
|
|
1777
|
+
paneHeightRatio: 0.16,
|
|
1778
|
+
defaultInputs: { length: 9, color: "#2962ff", width: 2, showValueLine: true },
|
|
2318
1779
|
draw: (ctx, renderContext, inputs) => {
|
|
2319
|
-
const
|
|
2320
|
-
const
|
|
2321
|
-
|
|
2322
|
-
const { k, d } = withCachedComputation(
|
|
2323
|
-
`stochastic|${kLength}|${kSmoothing}|${dLength}`,
|
|
1780
|
+
const length = clampIndicatorLength(inputs.length, 9);
|
|
1781
|
+
const values = withCachedSeries(
|
|
1782
|
+
`roc|${length}`,
|
|
2324
1783
|
renderContext.data,
|
|
2325
|
-
() =>
|
|
2326
|
-
);
|
|
2327
|
-
return drawSeparateMultiSeries(
|
|
2328
|
-
ctx,
|
|
2329
|
-
renderContext,
|
|
2330
|
-
[
|
|
2331
|
-
{ values: k, color: inputs.kColor ?? "#2962ff", label: "%K" },
|
|
2332
|
-
{ values: d, color: inputs.dColor ?? "#ff6d00", label: "%D" }
|
|
2333
|
-
],
|
|
2334
|
-
{
|
|
2335
|
-
title: `Stoch ${kLength} ${kSmoothing} ${dLength}`,
|
|
2336
|
-
minOverride: 0,
|
|
2337
|
-
maxOverride: 100,
|
|
2338
|
-
guideLines: [20, 80],
|
|
2339
|
-
axisTicks: [0, 20, 50, 80, 100],
|
|
2340
|
-
decimals: 2,
|
|
2341
|
-
valueLines: inputs.showValueLine !== false
|
|
2342
|
-
}
|
|
2343
|
-
);
|
|
2344
|
-
}
|
|
2345
|
-
};
|
|
2346
|
-
var BUILTIN_STOCHRSI_INDICATOR = {
|
|
2347
|
-
id: "stochrsi",
|
|
2348
|
-
name: "Stoch RSI",
|
|
2349
|
-
pane: "separate",
|
|
2350
|
-
paneHeightRatio: 0.18,
|
|
2351
|
-
defaultInputs: {
|
|
2352
|
-
rsiLength: 14,
|
|
2353
|
-
stochLength: 14,
|
|
2354
|
-
kSmoothing: 3,
|
|
2355
|
-
dSmoothing: 3,
|
|
2356
|
-
kColor: "#2962ff",
|
|
2357
|
-
dColor: "#ff6d00",
|
|
2358
|
-
showValueLine: true
|
|
2359
|
-
},
|
|
2360
|
-
draw: (ctx, renderContext, inputs) => {
|
|
2361
|
-
const rsiLength = clampIndicatorLength(inputs.rsiLength, 14);
|
|
2362
|
-
const stochLength = clampIndicatorLength(inputs.stochLength, 14);
|
|
2363
|
-
const kSmoothing = clampIndicatorLength(inputs.kSmoothing, 3);
|
|
2364
|
-
const dSmoothing = clampIndicatorLength(inputs.dSmoothing, 3);
|
|
2365
|
-
const { k, d } = withCachedComputation(
|
|
2366
|
-
`stochrsi|${rsiLength}|${stochLength}|${kSmoothing}|${dSmoothing}`,
|
|
2367
|
-
renderContext.data,
|
|
2368
|
-
() => computeStochRsi(renderContext.data, rsiLength, stochLength, kSmoothing, dSmoothing)
|
|
2369
|
-
);
|
|
2370
|
-
return drawSeparateMultiSeries(
|
|
2371
|
-
ctx,
|
|
2372
|
-
renderContext,
|
|
2373
|
-
[
|
|
2374
|
-
{ values: k, color: inputs.kColor ?? "#2962ff", label: "%K" },
|
|
2375
|
-
{ values: d, color: inputs.dColor ?? "#ff6d00", label: "%D" }
|
|
2376
|
-
],
|
|
2377
|
-
{
|
|
2378
|
-
title: `Stoch RSI ${rsiLength} ${stochLength}`,
|
|
2379
|
-
minOverride: 0,
|
|
2380
|
-
maxOverride: 100,
|
|
2381
|
-
guideLines: [20, 80],
|
|
2382
|
-
axisTicks: [0, 20, 50, 80, 100],
|
|
2383
|
-
decimals: 2,
|
|
2384
|
-
valueLines: inputs.showValueLine !== false
|
|
2385
|
-
}
|
|
2386
|
-
);
|
|
2387
|
-
}
|
|
2388
|
-
};
|
|
2389
|
-
var BUILTIN_ADX_INDICATOR = {
|
|
2390
|
-
id: "adx",
|
|
2391
|
-
name: "ADX/DMI",
|
|
2392
|
-
pane: "separate",
|
|
2393
|
-
paneHeightRatio: 0.18,
|
|
2394
|
-
defaultInputs: {
|
|
2395
|
-
length: 14,
|
|
2396
|
-
adxColor: "#ff6d00",
|
|
2397
|
-
plusDiColor: "#26a69a",
|
|
2398
|
-
minusDiColor: "#ef5350",
|
|
2399
|
-
showDi: true,
|
|
2400
|
-
showValueLine: true
|
|
2401
|
-
},
|
|
2402
|
-
draw: (ctx, renderContext, inputs) => {
|
|
2403
|
-
const length = clampIndicatorLength(inputs.length, 14);
|
|
2404
|
-
const { adx, plusDi, minusDi } = withCachedComputation(
|
|
2405
|
-
`adx|${length}`,
|
|
2406
|
-
renderContext.data,
|
|
2407
|
-
() => computeDmi(renderContext.data, length)
|
|
2408
|
-
);
|
|
2409
|
-
const seriesList = [
|
|
2410
|
-
{ values: adx, color: inputs.adxColor ?? "#ff6d00", label: "ADX" }
|
|
2411
|
-
];
|
|
2412
|
-
if (inputs.showDi !== false) {
|
|
2413
|
-
seriesList.push(
|
|
2414
|
-
{ values: plusDi, color: inputs.plusDiColor ?? "#26a69a", label: "+DI", width: 1 },
|
|
2415
|
-
{ values: minusDi, color: inputs.minusDiColor ?? "#ef5350", label: "-DI", width: 1 }
|
|
2416
|
-
);
|
|
2417
|
-
}
|
|
2418
|
-
return drawSeparateMultiSeries(ctx, renderContext, seriesList, {
|
|
2419
|
-
title: `ADX ${length}`,
|
|
2420
|
-
minOverride: 0,
|
|
2421
|
-
guideLines: [20],
|
|
2422
|
-
decimals: 2,
|
|
2423
|
-
valueLines: inputs.showValueLine !== false
|
|
2424
|
-
});
|
|
2425
|
-
}
|
|
2426
|
-
};
|
|
2427
|
-
var BUILTIN_OBV_INDICATOR = {
|
|
2428
|
-
id: "obv",
|
|
2429
|
-
name: "OBV",
|
|
2430
|
-
pane: "separate",
|
|
2431
|
-
paneHeightRatio: 0.16,
|
|
2432
|
-
defaultInputs: { color: "#2962ff", width: 2, showValueLine: true },
|
|
2433
|
-
draw: (ctx, renderContext, inputs) => {
|
|
2434
|
-
const values = withCachedSeries("obv", renderContext.data, () => computeObvSeries(renderContext.data));
|
|
2435
|
-
return drawSeparateMultiSeries(
|
|
2436
|
-
ctx,
|
|
2437
|
-
renderContext,
|
|
2438
|
-
[{ values, color: inputs.color ?? "#2962ff", width: Number(inputs.width) || 2, label: "OBV" }],
|
|
2439
|
-
{ title: "OBV", format: formatCompactNumber, valueLines: inputs.showValueLine !== false }
|
|
2440
|
-
);
|
|
2441
|
-
}
|
|
2442
|
-
};
|
|
2443
|
-
var BUILTIN_MFI_INDICATOR = {
|
|
2444
|
-
id: "mfi",
|
|
2445
|
-
name: "MFI",
|
|
2446
|
-
pane: "separate",
|
|
2447
|
-
paneHeightRatio: 0.16,
|
|
2448
|
-
defaultInputs: { length: 14, color: "#7e57c2", width: 2, showValueLine: true },
|
|
2449
|
-
draw: (ctx, renderContext, inputs) => {
|
|
2450
|
-
const length = clampIndicatorLength(inputs.length, 14);
|
|
2451
|
-
const values = withCachedSeries(
|
|
2452
|
-
`mfi|${length}`,
|
|
2453
|
-
renderContext.data,
|
|
2454
|
-
() => computeMfiSeries(renderContext.data, length)
|
|
2455
|
-
);
|
|
2456
|
-
return drawSeparateMultiSeries(
|
|
2457
|
-
ctx,
|
|
2458
|
-
renderContext,
|
|
2459
|
-
[{ values, color: inputs.color ?? "#7e57c2", width: Number(inputs.width) || 2 }],
|
|
2460
|
-
{
|
|
2461
|
-
title: `MFI ${length}`,
|
|
2462
|
-
minOverride: 0,
|
|
2463
|
-
maxOverride: 100,
|
|
2464
|
-
guideLines: [20, 80],
|
|
2465
|
-
axisTicks: [0, 20, 50, 80, 100],
|
|
2466
|
-
decimals: 2,
|
|
2467
|
-
valueLines: inputs.showValueLine !== false
|
|
2468
|
-
}
|
|
2469
|
-
);
|
|
2470
|
-
}
|
|
2471
|
-
};
|
|
2472
|
-
var BUILTIN_CCI_INDICATOR = {
|
|
2473
|
-
id: "cci",
|
|
2474
|
-
name: "CCI",
|
|
2475
|
-
pane: "separate",
|
|
2476
|
-
paneHeightRatio: 0.16,
|
|
2477
|
-
defaultInputs: { length: 20, color: "#2962ff", width: 2, showValueLine: true },
|
|
2478
|
-
draw: (ctx, renderContext, inputs) => {
|
|
2479
|
-
const length = clampIndicatorLength(inputs.length, 20);
|
|
2480
|
-
const values = withCachedSeries(
|
|
2481
|
-
`cci|${length}`,
|
|
2482
|
-
renderContext.data,
|
|
2483
|
-
() => computeCciSeries(renderContext.data, length)
|
|
2484
|
-
);
|
|
2485
|
-
return drawSeparateMultiSeries(
|
|
2486
|
-
ctx,
|
|
2487
|
-
renderContext,
|
|
2488
|
-
[{ values, color: inputs.color ?? "#2962ff", width: Number(inputs.width) || 2 }],
|
|
2489
|
-
{ title: `CCI ${length}`, guideLines: [-100, 0, 100], decimals: 2, valueLines: inputs.showValueLine !== false }
|
|
2490
|
-
);
|
|
2491
|
-
}
|
|
2492
|
-
};
|
|
2493
|
-
var BUILTIN_WILLIAMSR_INDICATOR = {
|
|
2494
|
-
id: "williamsr",
|
|
2495
|
-
name: "Williams %R",
|
|
2496
|
-
pane: "separate",
|
|
2497
|
-
paneHeightRatio: 0.16,
|
|
2498
|
-
defaultInputs: { length: 14, color: "#7e57c2", width: 2, showValueLine: true },
|
|
2499
|
-
draw: (ctx, renderContext, inputs) => {
|
|
2500
|
-
const length = clampIndicatorLength(inputs.length, 14);
|
|
2501
|
-
const values = withCachedSeries(
|
|
2502
|
-
`williamsr|${length}`,
|
|
2503
|
-
renderContext.data,
|
|
2504
|
-
() => computeWilliamsRSeries(renderContext.data, length)
|
|
2505
|
-
);
|
|
2506
|
-
return drawSeparateMultiSeries(
|
|
2507
|
-
ctx,
|
|
2508
|
-
renderContext,
|
|
2509
|
-
[{ values, color: inputs.color ?? "#7e57c2", width: Number(inputs.width) || 2 }],
|
|
2510
|
-
{
|
|
2511
|
-
title: `%R ${length}`,
|
|
2512
|
-
minOverride: -100,
|
|
2513
|
-
maxOverride: 0,
|
|
2514
|
-
guideLines: [-80, -20],
|
|
2515
|
-
axisTicks: [-100, -80, -50, -20, 0],
|
|
2516
|
-
decimals: 2,
|
|
2517
|
-
valueLines: inputs.showValueLine !== false
|
|
2518
|
-
}
|
|
2519
|
-
);
|
|
2520
|
-
}
|
|
2521
|
-
};
|
|
2522
|
-
var BUILTIN_ROC_INDICATOR = {
|
|
2523
|
-
id: "roc",
|
|
2524
|
-
name: "ROC",
|
|
2525
|
-
pane: "separate",
|
|
2526
|
-
paneHeightRatio: 0.16,
|
|
2527
|
-
defaultInputs: { length: 9, color: "#2962ff", width: 2, showValueLine: true },
|
|
2528
|
-
draw: (ctx, renderContext, inputs) => {
|
|
2529
|
-
const length = clampIndicatorLength(inputs.length, 9);
|
|
2530
|
-
const values = withCachedSeries(
|
|
2531
|
-
`roc|${length}`,
|
|
2532
|
-
renderContext.data,
|
|
2533
|
-
() => computeRocSeries(renderContext.data, length)
|
|
1784
|
+
() => computeRocSeries(renderContext.data, length)
|
|
2534
1785
|
);
|
|
2535
1786
|
return drawSeparateMultiSeries(
|
|
2536
1787
|
ctx,
|
|
@@ -2686,114 +1937,871 @@ var BUILTIN_ICHIMOKU_INDICATOR = {
|
|
|
2686
1937
|
drawOverlaySeries(ctx, renderContext, chikou, inputs.chikouColor ?? "#43a047", 1);
|
|
2687
1938
|
}
|
|
2688
1939
|
},
|
|
2689
|
-
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
2690
|
-
const conversionLength = clampIndicatorLength(inputs.conversionLength, 9);
|
|
2691
|
-
const baseLength = clampIndicatorLength(inputs.baseLength, 26);
|
|
2692
|
-
const spanBLength = clampIndicatorLength(inputs.spanBLength, 52);
|
|
2693
|
-
const displacement = clampIndicatorLength(inputs.displacement, 26);
|
|
2694
|
-
const { tenkan, kijun, spanA, spanB } = withCachedComputation(
|
|
2695
|
-
`ichimoku|${conversionLength}|${baseLength}|${spanBLength}|${displacement}`,
|
|
2696
|
-
data,
|
|
2697
|
-
() => computeIchimoku(data, conversionLength, baseLength, spanBLength, displacement)
|
|
2698
|
-
);
|
|
2699
|
-
return rangeOfSeries([tenkan, kijun, spanA, spanB], startIndex, endIndex, skipIndex);
|
|
2700
|
-
}
|
|
1940
|
+
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
1941
|
+
const conversionLength = clampIndicatorLength(inputs.conversionLength, 9);
|
|
1942
|
+
const baseLength = clampIndicatorLength(inputs.baseLength, 26);
|
|
1943
|
+
const spanBLength = clampIndicatorLength(inputs.spanBLength, 52);
|
|
1944
|
+
const displacement = clampIndicatorLength(inputs.displacement, 26);
|
|
1945
|
+
const { tenkan, kijun, spanA, spanB } = withCachedComputation(
|
|
1946
|
+
`ichimoku|${conversionLength}|${baseLength}|${spanBLength}|${displacement}`,
|
|
1947
|
+
data,
|
|
1948
|
+
() => computeIchimoku(data, conversionLength, baseLength, spanBLength, displacement)
|
|
1949
|
+
);
|
|
1950
|
+
return rangeOfSeries([tenkan, kijun, spanA, spanB], startIndex, endIndex, skipIndex);
|
|
1951
|
+
}
|
|
1952
|
+
};
|
|
1953
|
+
var BUILTIN_KELTNER_INDICATOR = {
|
|
1954
|
+
id: "keltner",
|
|
1955
|
+
name: "KC",
|
|
1956
|
+
pane: "overlay",
|
|
1957
|
+
defaultInputs: {
|
|
1958
|
+
emaLength: 20,
|
|
1959
|
+
atrLength: 10,
|
|
1960
|
+
multiplier: 2,
|
|
1961
|
+
basisColor: "#2962ff",
|
|
1962
|
+
bandColor: "#2962ff",
|
|
1963
|
+
width: 1.5,
|
|
1964
|
+
fillOpacity: 0.04
|
|
1965
|
+
},
|
|
1966
|
+
draw: (ctx, renderContext, inputs) => {
|
|
1967
|
+
const emaLength = clampIndicatorLength(inputs.emaLength, 20);
|
|
1968
|
+
const atrLength = clampIndicatorLength(inputs.atrLength, 10);
|
|
1969
|
+
const multiplier = Math.max(0.1, Number(inputs.multiplier) || 2);
|
|
1970
|
+
const { basis, upper, lower } = withCachedComputation(
|
|
1971
|
+
`keltner|${emaLength}|${atrLength}|${multiplier}`,
|
|
1972
|
+
renderContext.data,
|
|
1973
|
+
() => computeKeltner(renderContext.data, emaLength, atrLength, multiplier)
|
|
1974
|
+
);
|
|
1975
|
+
const bandColor = inputs.bandColor ?? "#2962ff";
|
|
1976
|
+
const width = Number(inputs.width) || 1.5;
|
|
1977
|
+
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.fillOpacity) || 0.04);
|
|
1978
|
+
drawOverlaySeries(ctx, renderContext, upper, bandColor, width);
|
|
1979
|
+
drawOverlaySeries(ctx, renderContext, lower, bandColor, width);
|
|
1980
|
+
drawOverlaySeries(ctx, renderContext, basis, inputs.basisColor ?? "#2962ff", width);
|
|
1981
|
+
},
|
|
1982
|
+
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
1983
|
+
const emaLength = clampIndicatorLength(inputs.emaLength, 20);
|
|
1984
|
+
const atrLength = clampIndicatorLength(inputs.atrLength, 10);
|
|
1985
|
+
const multiplier = Math.max(0.1, Number(inputs.multiplier) || 2);
|
|
1986
|
+
const { upper, lower } = withCachedComputation(
|
|
1987
|
+
`keltner|${emaLength}|${atrLength}|${multiplier}`,
|
|
1988
|
+
data,
|
|
1989
|
+
() => computeKeltner(data, emaLength, atrLength, multiplier)
|
|
1990
|
+
);
|
|
1991
|
+
return rangeOfSeries([upper, lower], startIndex, endIndex, skipIndex);
|
|
1992
|
+
}
|
|
1993
|
+
};
|
|
1994
|
+
var BUILTIN_DONCHIAN_INDICATOR = {
|
|
1995
|
+
id: "donchian",
|
|
1996
|
+
name: "DC",
|
|
1997
|
+
pane: "overlay",
|
|
1998
|
+
defaultInputs: { length: 20, bandColor: "#2962ff", basisColor: "#ff6d00", width: 1.5, fillOpacity: 0.04 },
|
|
1999
|
+
draw: (ctx, renderContext, inputs) => {
|
|
2000
|
+
const length = clampIndicatorLength(inputs.length, 20);
|
|
2001
|
+
const { upper, lower, basis } = withCachedComputation(
|
|
2002
|
+
`donchian|${length}`,
|
|
2003
|
+
renderContext.data,
|
|
2004
|
+
() => computeDonchian(renderContext.data, length)
|
|
2005
|
+
);
|
|
2006
|
+
const bandColor = inputs.bandColor ?? "#2962ff";
|
|
2007
|
+
const width = Number(inputs.width) || 1.5;
|
|
2008
|
+
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.fillOpacity) || 0.04);
|
|
2009
|
+
drawOverlaySeries(ctx, renderContext, upper, bandColor, width);
|
|
2010
|
+
drawOverlaySeries(ctx, renderContext, lower, bandColor, width);
|
|
2011
|
+
drawOverlaySeries(ctx, renderContext, basis, inputs.basisColor ?? "#ff6d00", width);
|
|
2012
|
+
},
|
|
2013
|
+
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
2014
|
+
const length = clampIndicatorLength(inputs.length, 20);
|
|
2015
|
+
const { upper, lower } = withCachedComputation(`donchian|${length}`, data, () => computeDonchian(data, length));
|
|
2016
|
+
return rangeOfSeries([upper, lower], startIndex, endIndex, skipIndex);
|
|
2017
|
+
}
|
|
2018
|
+
};
|
|
2019
|
+
var BUILTIN_INDICATORS = [
|
|
2020
|
+
BUILTIN_VOLUME_INDICATOR,
|
|
2021
|
+
BUILTIN_SMA_INDICATOR,
|
|
2022
|
+
BUILTIN_EMA_INDICATOR,
|
|
2023
|
+
BUILTIN_RSI_INDICATOR,
|
|
2024
|
+
BUILTIN_WMA_INDICATOR,
|
|
2025
|
+
BUILTIN_VWMA_INDICATOR,
|
|
2026
|
+
BUILTIN_RMA_INDICATOR,
|
|
2027
|
+
BUILTIN_HMA_INDICATOR,
|
|
2028
|
+
BUILTIN_VWAP_INDICATOR,
|
|
2029
|
+
BUILTIN_BOLLINGER_INDICATOR,
|
|
2030
|
+
BUILTIN_STDDEV_INDICATOR,
|
|
2031
|
+
BUILTIN_ATR_INDICATOR,
|
|
2032
|
+
BUILTIN_MACD_INDICATOR,
|
|
2033
|
+
BUILTIN_STOCHASTIC_INDICATOR,
|
|
2034
|
+
BUILTIN_STOCHRSI_INDICATOR,
|
|
2035
|
+
BUILTIN_ADX_INDICATOR,
|
|
2036
|
+
BUILTIN_OBV_INDICATOR,
|
|
2037
|
+
BUILTIN_MFI_INDICATOR,
|
|
2038
|
+
BUILTIN_CCI_INDICATOR,
|
|
2039
|
+
BUILTIN_WILLIAMSR_INDICATOR,
|
|
2040
|
+
BUILTIN_ROC_INDICATOR,
|
|
2041
|
+
BUILTIN_MOMENTUM_INDICATOR,
|
|
2042
|
+
BUILTIN_PSAR_INDICATOR,
|
|
2043
|
+
BUILTIN_SUPERTREND_INDICATOR,
|
|
2044
|
+
BUILTIN_ICHIMOKU_INDICATOR,
|
|
2045
|
+
BUILTIN_KELTNER_INDICATOR,
|
|
2046
|
+
BUILTIN_DONCHIAN_INDICATOR
|
|
2047
|
+
];
|
|
2048
|
+
|
|
2049
|
+
// src/scripts.ts
|
|
2050
|
+
var scriptRollingExtreme = (values, length, mode) => {
|
|
2051
|
+
const fallback = mode === "max" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
|
|
2052
|
+
const filled = new Array(values.length);
|
|
2053
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
2054
|
+
const value = values[i];
|
|
2055
|
+
filled[i] = value != null && Number.isFinite(value) ? value : fallback;
|
|
2056
|
+
}
|
|
2057
|
+
const raw = rollingExtremeSeries(filled, Math.max(1, Math.floor(length)), mode);
|
|
2058
|
+
return raw.map((value) => value != null && Number.isFinite(value) ? value : null);
|
|
2059
|
+
};
|
|
2060
|
+
var scriptStdevSeries = (values, length) => {
|
|
2061
|
+
const window2 = Math.max(1, Math.floor(length));
|
|
2062
|
+
const result = new Array(values.length).fill(null);
|
|
2063
|
+
let sum = 0;
|
|
2064
|
+
let sumSq = 0;
|
|
2065
|
+
let valid = 0;
|
|
2066
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
2067
|
+
const value = values[i];
|
|
2068
|
+
if (value == null || !Number.isFinite(value)) {
|
|
2069
|
+
sum = 0;
|
|
2070
|
+
sumSq = 0;
|
|
2071
|
+
valid = 0;
|
|
2072
|
+
continue;
|
|
2073
|
+
}
|
|
2074
|
+
sum += value;
|
|
2075
|
+
sumSq += value * value;
|
|
2076
|
+
valid += 1;
|
|
2077
|
+
if (valid > window2) {
|
|
2078
|
+
const old = values[i - window2];
|
|
2079
|
+
sum -= old;
|
|
2080
|
+
sumSq -= old * old;
|
|
2081
|
+
valid -= 1;
|
|
2082
|
+
}
|
|
2083
|
+
if (valid === window2) {
|
|
2084
|
+
const mean = sum / window2;
|
|
2085
|
+
result[i] = Math.sqrt(Math.max(0, sumSq / window2 - mean * mean));
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
return result;
|
|
2089
|
+
};
|
|
2090
|
+
var scriptAtrSeries = (bars, length) => {
|
|
2091
|
+
const tr = new Array(bars.length).fill(null);
|
|
2092
|
+
for (let i = 0; i < bars.length; i += 1) {
|
|
2093
|
+
const bar = bars[i];
|
|
2094
|
+
if (i === 0) {
|
|
2095
|
+
tr[i] = bar.h - bar.l;
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2098
|
+
const prevClose = bars[i - 1].c;
|
|
2099
|
+
tr[i] = Math.max(bar.h - bar.l, Math.abs(bar.h - prevClose), Math.abs(bar.l - prevClose));
|
|
2100
|
+
}
|
|
2101
|
+
return rmaFromValues(tr, Math.max(1, Math.floor(length)));
|
|
2102
|
+
};
|
|
2103
|
+
var SCRIPT_HELPERS = Object.freeze({
|
|
2104
|
+
sma: (values, length) => smaFromValues(values, Math.max(1, Math.floor(length))),
|
|
2105
|
+
ema: (values, length) => emaFromValues(values, Math.max(1, Math.floor(length))),
|
|
2106
|
+
rma: (values, length) => rmaFromValues(values, Math.max(1, Math.floor(length))),
|
|
2107
|
+
highest: (values, length) => scriptRollingExtreme(values, length, "max"),
|
|
2108
|
+
lowest: (values, length) => scriptRollingExtreme(values, length, "min"),
|
|
2109
|
+
change: (values, length = 1) => values.map((value, index) => {
|
|
2110
|
+
const prev = values[index - Math.max(1, Math.floor(length))];
|
|
2111
|
+
return value != null && prev != null ? value - prev : null;
|
|
2112
|
+
}),
|
|
2113
|
+
stdev: scriptStdevSeries,
|
|
2114
|
+
atr: scriptAtrSeries
|
|
2115
|
+
});
|
|
2116
|
+
var SCRIPT_PLOT_PALETTE = ["#2962ff", "#ff6d00", "#26a69a", "#ab47bc", "#ef5350", "#fdd835"];
|
|
2117
|
+
var hashScriptSource = (source) => {
|
|
2118
|
+
let hash = 5381;
|
|
2119
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
2120
|
+
hash = (hash << 5) + hash + source.charCodeAt(i) | 0;
|
|
2121
|
+
}
|
|
2122
|
+
return (hash >>> 0).toString(36);
|
|
2123
|
+
};
|
|
2124
|
+
var SCRIPT_GUARD_BUDGET_MS = 1e3;
|
|
2125
|
+
var ScriptBudgetError = class extends Error {
|
|
2126
|
+
constructor() {
|
|
2127
|
+
super(
|
|
2128
|
+
`Script exceeded the ${SCRIPT_GUARD_BUDGET_MS}ms execution budget (possible infinite loop) \u2014 edit the script to retry`
|
|
2129
|
+
);
|
|
2130
|
+
this.name = "ScriptBudgetError";
|
|
2131
|
+
}
|
|
2132
|
+
};
|
|
2133
|
+
var createScriptGuard = () => {
|
|
2134
|
+
let deadline = Date.now() + SCRIPT_GUARD_BUDGET_MS;
|
|
2135
|
+
let count = 0;
|
|
2136
|
+
const guard = (() => {
|
|
2137
|
+
count += 1;
|
|
2138
|
+
if ((count & 1023) === 0 && Date.now() > deadline) {
|
|
2139
|
+
throw new ScriptBudgetError();
|
|
2140
|
+
}
|
|
2141
|
+
return true;
|
|
2142
|
+
});
|
|
2143
|
+
guard.reset = () => {
|
|
2144
|
+
count = 0;
|
|
2145
|
+
deadline = Date.now() + SCRIPT_GUARD_BUDGET_MS;
|
|
2146
|
+
};
|
|
2147
|
+
return guard;
|
|
2148
|
+
};
|
|
2149
|
+
var injectLoopGuards = (source, guardName) => {
|
|
2150
|
+
const n = source.length;
|
|
2151
|
+
const isIdChar = (ch) => ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_" || ch === "$";
|
|
2152
|
+
const scanString = (start) => {
|
|
2153
|
+
const quote = source[start];
|
|
2154
|
+
let j = start + 1;
|
|
2155
|
+
while (j < n) {
|
|
2156
|
+
const cj = source[j];
|
|
2157
|
+
if (cj === "\\") {
|
|
2158
|
+
j += 2;
|
|
2159
|
+
continue;
|
|
2160
|
+
}
|
|
2161
|
+
if (cj === quote) {
|
|
2162
|
+
return j + 1;
|
|
2163
|
+
}
|
|
2164
|
+
if (quote === "`" && cj === "$" && source[j + 1] === "{") {
|
|
2165
|
+
let depth = 1;
|
|
2166
|
+
j += 2;
|
|
2167
|
+
while (j < n && depth > 0) {
|
|
2168
|
+
const ce = source[j];
|
|
2169
|
+
if (ce === "\\") {
|
|
2170
|
+
j += 2;
|
|
2171
|
+
continue;
|
|
2172
|
+
}
|
|
2173
|
+
if (ce === '"' || ce === "'" || ce === "`") {
|
|
2174
|
+
j = scanString(j);
|
|
2175
|
+
continue;
|
|
2176
|
+
}
|
|
2177
|
+
if (ce === "{") depth += 1;
|
|
2178
|
+
else if (ce === "}") depth -= 1;
|
|
2179
|
+
j += 1;
|
|
2180
|
+
}
|
|
2181
|
+
continue;
|
|
2182
|
+
}
|
|
2183
|
+
j += 1;
|
|
2184
|
+
}
|
|
2185
|
+
return n;
|
|
2186
|
+
};
|
|
2187
|
+
const scanRegex = (start) => {
|
|
2188
|
+
let j = start + 1;
|
|
2189
|
+
let inClass = false;
|
|
2190
|
+
while (j < n) {
|
|
2191
|
+
const cj = source[j];
|
|
2192
|
+
if (cj === "\\") {
|
|
2193
|
+
j += 2;
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
if (cj === "\n") return j;
|
|
2197
|
+
if (cj === "[") inClass = true;
|
|
2198
|
+
else if (cj === "]") inClass = false;
|
|
2199
|
+
else if (cj === "/" && !inClass) return j + 1;
|
|
2200
|
+
j += 1;
|
|
2201
|
+
}
|
|
2202
|
+
return n;
|
|
2203
|
+
};
|
|
2204
|
+
const skipWsAndComments = (k) => {
|
|
2205
|
+
let j = k;
|
|
2206
|
+
while (j < n) {
|
|
2207
|
+
const cj = source[j];
|
|
2208
|
+
if (cj === " " || cj === " " || cj === "\n" || cj === "\r") {
|
|
2209
|
+
j += 1;
|
|
2210
|
+
continue;
|
|
2211
|
+
}
|
|
2212
|
+
if (cj === "/" && source[j + 1] === "/") {
|
|
2213
|
+
const nl = source.indexOf("\n", j);
|
|
2214
|
+
j = nl === -1 ? n : nl + 1;
|
|
2215
|
+
continue;
|
|
2216
|
+
}
|
|
2217
|
+
if (cj === "/" && source[j + 1] === "*") {
|
|
2218
|
+
const end = source.indexOf("*/", j + 2);
|
|
2219
|
+
j = end === -1 ? n : end + 2;
|
|
2220
|
+
continue;
|
|
2221
|
+
}
|
|
2222
|
+
return j;
|
|
2223
|
+
}
|
|
2224
|
+
return n;
|
|
2225
|
+
};
|
|
2226
|
+
const matchParen = (openIdx) => {
|
|
2227
|
+
let depth = 0;
|
|
2228
|
+
let j = openIdx;
|
|
2229
|
+
while (j < n) {
|
|
2230
|
+
const cj = source[j];
|
|
2231
|
+
if (cj === '"' || cj === "'" || cj === "`") {
|
|
2232
|
+
j = scanString(j);
|
|
2233
|
+
continue;
|
|
2234
|
+
}
|
|
2235
|
+
if (cj === "/" && source[j + 1] === "/") {
|
|
2236
|
+
const nl = source.indexOf("\n", j);
|
|
2237
|
+
j = nl === -1 ? n : nl + 1;
|
|
2238
|
+
continue;
|
|
2239
|
+
}
|
|
2240
|
+
if (cj === "/" && source[j + 1] === "*") {
|
|
2241
|
+
const end = source.indexOf("*/", j + 2);
|
|
2242
|
+
j = end === -1 ? n : end + 2;
|
|
2243
|
+
continue;
|
|
2244
|
+
}
|
|
2245
|
+
if (cj === "(") depth += 1;
|
|
2246
|
+
else if (cj === ")") {
|
|
2247
|
+
depth -= 1;
|
|
2248
|
+
if (depth === 0) return j;
|
|
2249
|
+
}
|
|
2250
|
+
j += 1;
|
|
2251
|
+
}
|
|
2252
|
+
return -1;
|
|
2253
|
+
};
|
|
2254
|
+
const splitTopLevelSemicolons = (inner) => {
|
|
2255
|
+
const parts = [];
|
|
2256
|
+
let depth = 0;
|
|
2257
|
+
let j = 0;
|
|
2258
|
+
let last = 0;
|
|
2259
|
+
const m = inner.length;
|
|
2260
|
+
while (j < m) {
|
|
2261
|
+
const cj = inner[j];
|
|
2262
|
+
if (cj === '"' || cj === "'" || cj === "`") {
|
|
2263
|
+
const quote = cj;
|
|
2264
|
+
j += 1;
|
|
2265
|
+
while (j < m) {
|
|
2266
|
+
if (inner[j] === "\\") {
|
|
2267
|
+
j += 2;
|
|
2268
|
+
continue;
|
|
2269
|
+
}
|
|
2270
|
+
if (inner[j] === quote) {
|
|
2271
|
+
j += 1;
|
|
2272
|
+
break;
|
|
2273
|
+
}
|
|
2274
|
+
j += 1;
|
|
2275
|
+
}
|
|
2276
|
+
continue;
|
|
2277
|
+
}
|
|
2278
|
+
if (cj === "(" || cj === "[" || cj === "{") depth += 1;
|
|
2279
|
+
else if (cj === ")" || cj === "]" || cj === "}") depth -= 1;
|
|
2280
|
+
else if (cj === ";" && depth === 0) {
|
|
2281
|
+
parts.push(inner.slice(last, j));
|
|
2282
|
+
last = j + 1;
|
|
2283
|
+
}
|
|
2284
|
+
j += 1;
|
|
2285
|
+
}
|
|
2286
|
+
parts.push(inner.slice(last));
|
|
2287
|
+
return parts;
|
|
2288
|
+
};
|
|
2289
|
+
const REGEX_PRECEDING_WORDS = /* @__PURE__ */ new Set([
|
|
2290
|
+
"return",
|
|
2291
|
+
"typeof",
|
|
2292
|
+
"case",
|
|
2293
|
+
"do",
|
|
2294
|
+
"else",
|
|
2295
|
+
"in",
|
|
2296
|
+
"of",
|
|
2297
|
+
"new",
|
|
2298
|
+
"delete",
|
|
2299
|
+
"void",
|
|
2300
|
+
"instanceof",
|
|
2301
|
+
"yield",
|
|
2302
|
+
"await"
|
|
2303
|
+
]);
|
|
2304
|
+
let out = "";
|
|
2305
|
+
let i = 0;
|
|
2306
|
+
let lastSig = "";
|
|
2307
|
+
let lastWord = "";
|
|
2308
|
+
while (i < n) {
|
|
2309
|
+
const ch = source[i];
|
|
2310
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
2311
|
+
const nl = source.indexOf("\n", i);
|
|
2312
|
+
const end = nl === -1 ? n : nl;
|
|
2313
|
+
out += source.slice(i, end);
|
|
2314
|
+
i = end;
|
|
2315
|
+
continue;
|
|
2316
|
+
}
|
|
2317
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
2318
|
+
const close = source.indexOf("*/", i + 2);
|
|
2319
|
+
const end = close === -1 ? n : close + 2;
|
|
2320
|
+
out += source.slice(i, end);
|
|
2321
|
+
i = end;
|
|
2322
|
+
continue;
|
|
2323
|
+
}
|
|
2324
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
2325
|
+
const end = scanString(i);
|
|
2326
|
+
out += source.slice(i, end);
|
|
2327
|
+
lastSig = ch;
|
|
2328
|
+
lastWord = "";
|
|
2329
|
+
i = end;
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2332
|
+
if (ch === "/") {
|
|
2333
|
+
const regexLikely = lastSig === "" || "(,=:;!&|?{}[+-*%~^<>".includes(lastSig) || REGEX_PRECEDING_WORDS.has(lastWord);
|
|
2334
|
+
if (regexLikely) {
|
|
2335
|
+
const end = scanRegex(i);
|
|
2336
|
+
out += source.slice(i, end);
|
|
2337
|
+
lastSig = "/";
|
|
2338
|
+
lastWord = "";
|
|
2339
|
+
i = end;
|
|
2340
|
+
continue;
|
|
2341
|
+
}
|
|
2342
|
+
out += ch;
|
|
2343
|
+
lastSig = ch;
|
|
2344
|
+
lastWord = "";
|
|
2345
|
+
i += 1;
|
|
2346
|
+
continue;
|
|
2347
|
+
}
|
|
2348
|
+
if (isIdChar(ch)) {
|
|
2349
|
+
let j = i;
|
|
2350
|
+
while (j < n && isIdChar(source[j])) j += 1;
|
|
2351
|
+
const word = source.slice(i, j);
|
|
2352
|
+
if ((word === "while" || word === "for") && lastSig !== ".") {
|
|
2353
|
+
const k = skipWsAndComments(j);
|
|
2354
|
+
if (source[k] === "(") {
|
|
2355
|
+
const close = matchParen(k);
|
|
2356
|
+
if (close !== -1) {
|
|
2357
|
+
const inner = source.slice(k + 1, close);
|
|
2358
|
+
if (word === "while") {
|
|
2359
|
+
out += `while (${guardName}() && (${inner}))`;
|
|
2360
|
+
lastSig = ")";
|
|
2361
|
+
lastWord = "";
|
|
2362
|
+
i = close + 1;
|
|
2363
|
+
continue;
|
|
2364
|
+
}
|
|
2365
|
+
const parts = splitTopLevelSemicolons(inner);
|
|
2366
|
+
if (parts.length === 3) {
|
|
2367
|
+
const cond = parts[1].trim();
|
|
2368
|
+
const guardedCond = cond ? `${guardName}() && (${cond})` : `${guardName}()`;
|
|
2369
|
+
out += `for (${parts[0]}; ${guardedCond}; ${parts[2]})`;
|
|
2370
|
+
lastSig = ")";
|
|
2371
|
+
lastWord = "";
|
|
2372
|
+
i = close + 1;
|
|
2373
|
+
continue;
|
|
2374
|
+
}
|
|
2375
|
+
out += source.slice(i, close + 1);
|
|
2376
|
+
lastSig = ")";
|
|
2377
|
+
lastWord = "";
|
|
2378
|
+
i = close + 1;
|
|
2379
|
+
continue;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
out += word;
|
|
2384
|
+
lastSig = word[word.length - 1];
|
|
2385
|
+
lastWord = word;
|
|
2386
|
+
i = j;
|
|
2387
|
+
continue;
|
|
2388
|
+
}
|
|
2389
|
+
out += ch;
|
|
2390
|
+
if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") {
|
|
2391
|
+
lastSig = ch;
|
|
2392
|
+
lastWord = "";
|
|
2393
|
+
}
|
|
2394
|
+
i += 1;
|
|
2395
|
+
}
|
|
2396
|
+
return out;
|
|
2397
|
+
};
|
|
2398
|
+
var compileScriptCompute = (source, guard) => {
|
|
2399
|
+
const makeFactory = (body) => new Function(
|
|
2400
|
+
"__hpGuard",
|
|
2401
|
+
`"use strict";
|
|
2402
|
+
${body}
|
|
2403
|
+
;if (typeof compute !== "function") { throw new Error("Script must define function compute(bars, inputs, hp)"); }
|
|
2404
|
+
return compute;`
|
|
2405
|
+
);
|
|
2406
|
+
let factory;
|
|
2407
|
+
try {
|
|
2408
|
+
factory = makeFactory(injectLoopGuards(source, "__hpGuard"));
|
|
2409
|
+
} catch {
|
|
2410
|
+
factory = makeFactory(source);
|
|
2411
|
+
}
|
|
2412
|
+
guard.reset();
|
|
2413
|
+
return factory(guard);
|
|
2414
|
+
};
|
|
2415
|
+
var validateScriptSource = (source) => {
|
|
2416
|
+
try {
|
|
2417
|
+
compileScriptCompute(source, createScriptGuard());
|
|
2418
|
+
return null;
|
|
2419
|
+
} catch (error) {
|
|
2420
|
+
return error instanceof Error ? error.message : String(error);
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2423
|
+
var drawScriptError = (ctx, renderContext, name, message) => {
|
|
2424
|
+
ctx.save();
|
|
2425
|
+
ctx.font = "11px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
|
2426
|
+
ctx.textAlign = "left";
|
|
2427
|
+
ctx.textBaseline = "top";
|
|
2428
|
+
ctx.fillStyle = "#ef5350";
|
|
2429
|
+
const text = `${name}: ${message}`.slice(0, 160);
|
|
2430
|
+
ctx.fillText(text, renderContext.chartLeft + 10, renderContext.chartTop + 24);
|
|
2431
|
+
ctx.restore();
|
|
2432
|
+
};
|
|
2433
|
+
var compileScriptIndicator = (definition) => {
|
|
2434
|
+
const pane = definition.pane ?? "separate";
|
|
2435
|
+
const defaultInputs = { showValueLine: true };
|
|
2436
|
+
for (const input of definition.inputs ?? []) {
|
|
2437
|
+
defaultInputs[input.key] = input.default;
|
|
2438
|
+
}
|
|
2439
|
+
let compiled = null;
|
|
2440
|
+
let compileError = null;
|
|
2441
|
+
const guard = createScriptGuard();
|
|
2442
|
+
try {
|
|
2443
|
+
compiled = compileScriptCompute(definition.source, guard);
|
|
2444
|
+
} catch (error) {
|
|
2445
|
+
compileError = error instanceof Error ? error.message : String(error);
|
|
2446
|
+
}
|
|
2447
|
+
const sourceKey = hashScriptSource(definition.source);
|
|
2448
|
+
let trippedError = null;
|
|
2449
|
+
const runCompute = (data, inputs) => {
|
|
2450
|
+
if (!compiled) {
|
|
2451
|
+
return { error: compileError ?? "Script failed to compile" };
|
|
2452
|
+
}
|
|
2453
|
+
if (trippedError) {
|
|
2454
|
+
return { error: trippedError };
|
|
2455
|
+
}
|
|
2456
|
+
try {
|
|
2457
|
+
guard.reset();
|
|
2458
|
+
const result = compiled(data, inputs, SCRIPT_HELPERS);
|
|
2459
|
+
if (!result || !Array.isArray(result.plots)) {
|
|
2460
|
+
return { error: "compute() must return { plots: [...] }" };
|
|
2461
|
+
}
|
|
2462
|
+
for (const plot of result.plots) {
|
|
2463
|
+
if (!plot || !Array.isArray(plot.values)) {
|
|
2464
|
+
return { error: "Every plot needs a `values` array" };
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
return { result };
|
|
2468
|
+
} catch (error) {
|
|
2469
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2470
|
+
if (error instanceof ScriptBudgetError) {
|
|
2471
|
+
trippedError = message;
|
|
2472
|
+
}
|
|
2473
|
+
return { error: message };
|
|
2474
|
+
}
|
|
2475
|
+
};
|
|
2476
|
+
const cachedCompute = (data, inputs) => withCachedComputation(
|
|
2477
|
+
`script|${definition.id}|${sourceKey}|${JSON.stringify(inputs)}`,
|
|
2478
|
+
data,
|
|
2479
|
+
() => runCompute(data, inputs)
|
|
2480
|
+
);
|
|
2481
|
+
const plugin = {
|
|
2482
|
+
id: definition.id,
|
|
2483
|
+
name: definition.name,
|
|
2484
|
+
pane,
|
|
2485
|
+
defaultInputs,
|
|
2486
|
+
draw: (ctx, renderContext, inputs) => {
|
|
2487
|
+
const outcome = cachedCompute(renderContext.data, inputs);
|
|
2488
|
+
if (!outcome.result) {
|
|
2489
|
+
drawScriptError(ctx, renderContext, definition.name, outcome.error ?? "Unknown script error");
|
|
2490
|
+
return pane === "separate" ? { title: `${definition.name} (script error)` } : void 0;
|
|
2491
|
+
}
|
|
2492
|
+
const { plots, range, guides, decimals } = outcome.result;
|
|
2493
|
+
if (pane === "separate") {
|
|
2494
|
+
const seriesList = plots.map((plot, index) => ({
|
|
2495
|
+
values: plot.values,
|
|
2496
|
+
color: plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
|
|
2497
|
+
...plot.width !== void 0 ? { width: plot.width } : {},
|
|
2498
|
+
...plot.title ? { label: plot.title } : {},
|
|
2499
|
+
...plot.style === "histogram" ? { histogram: true } : {},
|
|
2500
|
+
...plot.negativeColor ? { negativeColor: plot.negativeColor } : {}
|
|
2501
|
+
}));
|
|
2502
|
+
return drawSeparateMultiSeries(ctx, renderContext, seriesList, {
|
|
2503
|
+
title: definition.name,
|
|
2504
|
+
...range?.min !== void 0 ? { minOverride: range.min } : {},
|
|
2505
|
+
...range?.max !== void 0 ? { maxOverride: range.max } : {},
|
|
2506
|
+
...guides && guides.length > 0 ? { guideLines: guides } : {},
|
|
2507
|
+
...decimals !== void 0 ? { decimals } : {},
|
|
2508
|
+
valueLines: inputs.showValueLine !== false
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2511
|
+
for (let index = 0; index < plots.length; index += 1) {
|
|
2512
|
+
const plot = plots[index];
|
|
2513
|
+
if (plot.style === "histogram") continue;
|
|
2514
|
+
drawOverlaySeries(
|
|
2515
|
+
ctx,
|
|
2516
|
+
renderContext,
|
|
2517
|
+
plot.values,
|
|
2518
|
+
plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
|
|
2519
|
+
plot.width ?? 2
|
|
2520
|
+
);
|
|
2521
|
+
}
|
|
2522
|
+
return void 0;
|
|
2523
|
+
},
|
|
2524
|
+
...pane === "overlay" ? {
|
|
2525
|
+
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
2526
|
+
const outcome = cachedCompute(data, inputs);
|
|
2527
|
+
if (!outcome.result) return null;
|
|
2528
|
+
const lineSeries = outcome.result.plots.filter((plot) => plot.style !== "histogram").map((plot) => plot.values);
|
|
2529
|
+
if (lineSeries.length === 0) return null;
|
|
2530
|
+
return rangeOfSeries(lineSeries, startIndex, endIndex, skipIndex);
|
|
2531
|
+
}
|
|
2532
|
+
} : {}
|
|
2533
|
+
};
|
|
2534
|
+
if (definition.paneHeightRatio !== void 0) {
|
|
2535
|
+
plugin.paneHeightRatio = definition.paneHeightRatio;
|
|
2536
|
+
}
|
|
2537
|
+
return plugin;
|
|
2538
|
+
};
|
|
2539
|
+
|
|
2540
|
+
// src/options.ts
|
|
2541
|
+
var DEFAULT_GRID_OPTIONS = {
|
|
2542
|
+
color: "#2b2f38",
|
|
2543
|
+
opacity: 0.38,
|
|
2544
|
+
horizontalLines: true,
|
|
2545
|
+
verticalLines: true,
|
|
2546
|
+
xTickCount: 8,
|
|
2547
|
+
yTickCount: 6,
|
|
2548
|
+
horizontalTickCount: 6,
|
|
2549
|
+
sessionSeparators: false,
|
|
2550
|
+
sessionSeparatorColor: "#3b4150",
|
|
2551
|
+
sessionSeparatorOpacity: 0.55
|
|
2552
|
+
};
|
|
2553
|
+
var DEFAULT_AXIS_OPTIONS = {
|
|
2554
|
+
lineColor: "#3b3f47",
|
|
2555
|
+
textColor: "#a9adb6",
|
|
2556
|
+
fontSize: 12,
|
|
2557
|
+
lineWidth: 1
|
|
2558
|
+
};
|
|
2559
|
+
var DEFAULT_CROSSHAIR_OPTIONS = {
|
|
2560
|
+
visible: true,
|
|
2561
|
+
color: "#94a3b8",
|
|
2562
|
+
width: 1,
|
|
2563
|
+
style: "dotted",
|
|
2564
|
+
mode: "cross",
|
|
2565
|
+
dotRadius: 3,
|
|
2566
|
+
showHorizontal: true,
|
|
2567
|
+
showVertical: true,
|
|
2568
|
+
showPriceLabel: true,
|
|
2569
|
+
showTimeLabel: true,
|
|
2570
|
+
timeLabelFormat: "auto",
|
|
2571
|
+
labelBackgroundColor: "#0b1220",
|
|
2572
|
+
labelTextColor: "#cbd5e1",
|
|
2573
|
+
labelBorderRadius: 3,
|
|
2574
|
+
labelBorderColor: "#94a3b8",
|
|
2575
|
+
labelBorderWidth: 1,
|
|
2576
|
+
labelBorderStyle: "solid",
|
|
2577
|
+
showPriceActionButton: false,
|
|
2578
|
+
priceActionButtonIcon: "plusThin",
|
|
2579
|
+
priceActionButtonText: "+",
|
|
2580
|
+
priceActionButtonSize: 16,
|
|
2581
|
+
priceActionButtonGap: 4,
|
|
2582
|
+
priceActionButtonRounded: true,
|
|
2583
|
+
priceActionButtonBorderRadius: 8,
|
|
2584
|
+
sideHintLeft: "",
|
|
2585
|
+
sideHintRight: "",
|
|
2586
|
+
sideHintLeftColor: "#26a69a",
|
|
2587
|
+
sideHintRightColor: "#ef5350"
|
|
2588
|
+
};
|
|
2589
|
+
var DEFAULT_WATERMARK_OPTIONS = {
|
|
2590
|
+
visible: false,
|
|
2591
|
+
text: "",
|
|
2592
|
+
color: "#81858d",
|
|
2593
|
+
opacity: 0.14,
|
|
2594
|
+
fontSize: 92,
|
|
2595
|
+
fontWeight: 700,
|
|
2596
|
+
thickness: 0,
|
|
2597
|
+
imageSrc: "",
|
|
2598
|
+
imageScale: 1,
|
|
2599
|
+
imageMaxWidthRatio: 0.42,
|
|
2600
|
+
imageMaxHeightRatio: 0.3,
|
|
2601
|
+
imageTintColor: "",
|
|
2602
|
+
imageTintOpacity: 1
|
|
2603
|
+
};
|
|
2604
|
+
var DEFAULT_DASH_PATTERNS = {
|
|
2605
|
+
dotted: [2, 2],
|
|
2606
|
+
dashed: [8, 6],
|
|
2607
|
+
connectorDotted: [2, 3],
|
|
2608
|
+
connectorDashed: [6, 5],
|
|
2609
|
+
borderDotted: [2, 2],
|
|
2610
|
+
borderDashed: [6, 4]
|
|
2611
|
+
};
|
|
2612
|
+
var DEFAULT_LABELS_OPTIONS = {
|
|
2613
|
+
visible: true,
|
|
2614
|
+
symbolName: "",
|
|
2615
|
+
showSymbolName: false,
|
|
2616
|
+
showLastPrice: true,
|
|
2617
|
+
showPreviousClose: false,
|
|
2618
|
+
previousClosePrice: Number.NaN,
|
|
2619
|
+
showHighLow: false,
|
|
2620
|
+
showBidAsk: false,
|
|
2621
|
+
bidPrice: Number.NaN,
|
|
2622
|
+
askPrice: Number.NaN,
|
|
2623
|
+
showIndicatorNames: false,
|
|
2624
|
+
showIndicatorValues: false,
|
|
2625
|
+
showIndicatorValueLabels: true,
|
|
2626
|
+
indicatorLegendPosition: "top-left",
|
|
2627
|
+
indicatorLegendOffsetX: 10,
|
|
2628
|
+
indicatorLegendOffsetY: 10,
|
|
2629
|
+
showCountdownToBarClose: false,
|
|
2630
|
+
noOverlapping: true,
|
|
2631
|
+
backgroundColor: "#0b1220",
|
|
2632
|
+
textColor: "#cbd5e1",
|
|
2633
|
+
mutedTextColor: "#94a3b8",
|
|
2634
|
+
symbolNameBackgroundColor: "#1f2937",
|
|
2635
|
+
symbolNameTextColor: "#e5e7eb",
|
|
2636
|
+
previousCloseColor: "#94a3b8",
|
|
2637
|
+
highLowColor: "#a78bfa",
|
|
2638
|
+
bidColor: "#ef4444",
|
|
2639
|
+
askColor: "#22c55e",
|
|
2640
|
+
indicatorTextColor: "#cbd5e1",
|
|
2641
|
+
borderRadius: 3,
|
|
2642
|
+
labelHeight: 20,
|
|
2643
|
+
labelPaddingX: 8
|
|
2644
|
+
};
|
|
2645
|
+
var DEFAULT_PRICE_LINE_OPTIONS = {
|
|
2646
|
+
visible: true,
|
|
2647
|
+
style: "solid",
|
|
2648
|
+
thickness: 1,
|
|
2649
|
+
color: "#38bdf8",
|
|
2650
|
+
labelBackgroundColor: "#0b1220",
|
|
2651
|
+
labelTextColor: "#60a5fa",
|
|
2652
|
+
labelBorderRadius: 3,
|
|
2653
|
+
showLabel: true,
|
|
2654
|
+
pinOutOfRange: false
|
|
2655
|
+
};
|
|
2656
|
+
var DEFAULT_ORDER_LINE_OPTIONS = {
|
|
2657
|
+
visible: true,
|
|
2658
|
+
behavior: "static",
|
|
2659
|
+
style: "solid",
|
|
2660
|
+
thickness: 1,
|
|
2661
|
+
color: "rgba(59,130,246,0.8)",
|
|
2662
|
+
labelBackgroundColor: "#0b1220",
|
|
2663
|
+
labelTextColor: "#60a5fa",
|
|
2664
|
+
labelBorderRadius: 3,
|
|
2665
|
+
showCloseButton: true,
|
|
2666
|
+
widgetPosition: "left",
|
|
2667
|
+
widgetPaddingRight: 10,
|
|
2668
|
+
draggable: false,
|
|
2669
|
+
actionButtonAction: "execute",
|
|
2670
|
+
actionButtonTextColor: "#dbeafe",
|
|
2671
|
+
actionButtonBackgroundColor: "#2563eb",
|
|
2672
|
+
actionButtonBorderRadius: 2,
|
|
2673
|
+
actionButtonMinWidth: 34,
|
|
2674
|
+
actionButtonPaddingX: 0,
|
|
2675
|
+
actionButtonFullHeight: true,
|
|
2676
|
+
actionButtonFontWeight: 500,
|
|
2677
|
+
actionButtonBorderColor: "#2563eb",
|
|
2678
|
+
actionButtonBorderStyle: "solid",
|
|
2679
|
+
actionButtonsInnerGap: 6,
|
|
2680
|
+
actionButtonsGroupGap: 8,
|
|
2681
|
+
actionButtons: [],
|
|
2682
|
+
connectorToPrice: Number.NaN,
|
|
2683
|
+
connectorColor: "#2563eb",
|
|
2684
|
+
connectorStyle: "dotted",
|
|
2685
|
+
connectorThickness: 1,
|
|
2686
|
+
connectorAnchorPaddingRight: 10,
|
|
2687
|
+
fillToPrice: Number.NaN,
|
|
2688
|
+
fillColor: "rgba(37,99,235,0.18)",
|
|
2689
|
+
pinOutOfRange: false
|
|
2690
|
+
};
|
|
2691
|
+
var DEFAULT_OPTIONS = {
|
|
2692
|
+
width: 720,
|
|
2693
|
+
height: 360,
|
|
2694
|
+
backgroundColor: "#101114",
|
|
2695
|
+
axisColor: "#7f8289",
|
|
2696
|
+
axis: DEFAULT_AXIS_OPTIONS,
|
|
2697
|
+
xAxis: DEFAULT_AXIS_OPTIONS,
|
|
2698
|
+
yAxis: DEFAULT_AXIS_OPTIONS,
|
|
2699
|
+
priceDecimals: 2,
|
|
2700
|
+
stabilizePriceLabels: true,
|
|
2701
|
+
priceLabelMinIntegerDigits: 3,
|
|
2702
|
+
priceLabelWidthTemplate: "",
|
|
2703
|
+
initialViewport: "latest",
|
|
2704
|
+
initialVisibleBars: 60,
|
|
2705
|
+
minVisibleBars: 5,
|
|
2706
|
+
maxVisibleBars: 2e4,
|
|
2707
|
+
maxPanBars: 1e6,
|
|
2708
|
+
rightEdgePaddingBars: 2,
|
|
2709
|
+
preserveViewportOnDataUpdate: true,
|
|
2710
|
+
upColor: "#2fb171",
|
|
2711
|
+
downColor: "#d35a5a",
|
|
2712
|
+
gridColor: "#252932",
|
|
2713
|
+
fontFamily: "Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif",
|
|
2714
|
+
candleBodyWidthRatio: 0.7,
|
|
2715
|
+
candleMinWidth: 0.5,
|
|
2716
|
+
candleWickWidth: 1,
|
|
2717
|
+
tickSize: 0,
|
|
2718
|
+
candleColorMode: "openClose",
|
|
2719
|
+
candleColorEpsilon: -1,
|
|
2720
|
+
chartType: "candles",
|
|
2721
|
+
lineColor: "#2962ff",
|
|
2722
|
+
lineWidth: 2,
|
|
2723
|
+
areaFillOpacity: 0.12,
|
|
2724
|
+
baselinePrice: null,
|
|
2725
|
+
// 0 = deterministic scale (lightweight-charts behavior): the y-range is a
|
|
2726
|
+
// pure function of the visible data, so it can never drift or "breathe".
|
|
2727
|
+
// Values > 0 re-enable eased contraction for hosts that prefer it.
|
|
2728
|
+
autoScaleSmoothing: 0,
|
|
2729
|
+
autoScaleIgnoreLatestCandle: true,
|
|
2730
|
+
kineticScroll: { touch: true, mouse: false },
|
|
2731
|
+
timeStepMs: 0,
|
|
2732
|
+
clockOffsetMs: 0,
|
|
2733
|
+
pinOutOfRangeLines: false,
|
|
2734
|
+
doubleClickEnabled: true,
|
|
2735
|
+
doubleClickAction: "reset",
|
|
2736
|
+
crosshair: DEFAULT_CROSSHAIR_OPTIONS,
|
|
2737
|
+
grid: DEFAULT_GRID_OPTIONS,
|
|
2738
|
+
watermark: DEFAULT_WATERMARK_OPTIONS,
|
|
2739
|
+
priceLines: [],
|
|
2740
|
+
orderLines: [],
|
|
2741
|
+
tickerLine: {
|
|
2742
|
+
visible: true,
|
|
2743
|
+
style: "dotted",
|
|
2744
|
+
thickness: 1,
|
|
2745
|
+
labelTextColor: "#0b1220",
|
|
2746
|
+
labelSubtext: "",
|
|
2747
|
+
labelSubtexts: [],
|
|
2748
|
+
labelSubtextColor: "#0b1220",
|
|
2749
|
+
labelSubtextFontSize: 0,
|
|
2750
|
+
showCountdownInLabel: false,
|
|
2751
|
+
labelBorderRadius: 3
|
|
2752
|
+
},
|
|
2753
|
+
labels: DEFAULT_LABELS_OPTIONS,
|
|
2754
|
+
dashPatterns: DEFAULT_DASH_PATTERNS,
|
|
2755
|
+
indicators: [],
|
|
2756
|
+
drawings: []
|
|
2701
2757
|
};
|
|
2702
|
-
var
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
multiplier: 2,
|
|
2710
|
-
basisColor: "#2962ff",
|
|
2711
|
-
bandColor: "#2962ff",
|
|
2712
|
-
width: 1.5,
|
|
2713
|
-
fillOpacity: 0.04
|
|
2758
|
+
var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
2759
|
+
...baseOptions,
|
|
2760
|
+
...options,
|
|
2761
|
+
axis: {
|
|
2762
|
+
...baseOptions.axis,
|
|
2763
|
+
...options.axis ?? {},
|
|
2764
|
+
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {}
|
|
2714
2765
|
},
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
`keltner|${emaLength}|${atrLength}|${multiplier}`,
|
|
2721
|
-
renderContext.data,
|
|
2722
|
-
() => computeKeltner(renderContext.data, emaLength, atrLength, multiplier)
|
|
2723
|
-
);
|
|
2724
|
-
const bandColor = inputs.bandColor ?? "#2962ff";
|
|
2725
|
-
const width = Number(inputs.width) || 1.5;
|
|
2726
|
-
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.fillOpacity) || 0.04);
|
|
2727
|
-
drawOverlaySeries(ctx, renderContext, upper, bandColor, width);
|
|
2728
|
-
drawOverlaySeries(ctx, renderContext, lower, bandColor, width);
|
|
2729
|
-
drawOverlaySeries(ctx, renderContext, basis, inputs.basisColor ?? "#2962ff", width);
|
|
2766
|
+
xAxis: {
|
|
2767
|
+
...baseOptions.xAxis,
|
|
2768
|
+
...options.axis ?? {},
|
|
2769
|
+
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
|
|
2770
|
+
...options.xAxis ?? {}
|
|
2730
2771
|
},
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
`keltner|${emaLength}|${atrLength}|${multiplier}`,
|
|
2737
|
-
data,
|
|
2738
|
-
() => computeKeltner(data, emaLength, atrLength, multiplier)
|
|
2739
|
-
);
|
|
2740
|
-
return rangeOfSeries([upper, lower], startIndex, endIndex, skipIndex);
|
|
2741
|
-
}
|
|
2742
|
-
};
|
|
2743
|
-
var BUILTIN_DONCHIAN_INDICATOR = {
|
|
2744
|
-
id: "donchian",
|
|
2745
|
-
name: "DC",
|
|
2746
|
-
pane: "overlay",
|
|
2747
|
-
defaultInputs: { length: 20, bandColor: "#2962ff", basisColor: "#ff6d00", width: 1.5, fillOpacity: 0.04 },
|
|
2748
|
-
draw: (ctx, renderContext, inputs) => {
|
|
2749
|
-
const length = clampIndicatorLength(inputs.length, 20);
|
|
2750
|
-
const { upper, lower, basis } = withCachedComputation(
|
|
2751
|
-
`donchian|${length}`,
|
|
2752
|
-
renderContext.data,
|
|
2753
|
-
() => computeDonchian(renderContext.data, length)
|
|
2754
|
-
);
|
|
2755
|
-
const bandColor = inputs.bandColor ?? "#2962ff";
|
|
2756
|
-
const width = Number(inputs.width) || 1.5;
|
|
2757
|
-
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.fillOpacity) || 0.04);
|
|
2758
|
-
drawOverlaySeries(ctx, renderContext, upper, bandColor, width);
|
|
2759
|
-
drawOverlaySeries(ctx, renderContext, lower, bandColor, width);
|
|
2760
|
-
drawOverlaySeries(ctx, renderContext, basis, inputs.basisColor ?? "#ff6d00", width);
|
|
2772
|
+
yAxis: {
|
|
2773
|
+
...baseOptions.yAxis,
|
|
2774
|
+
...options.axis ?? {},
|
|
2775
|
+
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
|
|
2776
|
+
...options.yAxis ?? {}
|
|
2761
2777
|
},
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2778
|
+
crosshair: {
|
|
2779
|
+
...baseOptions.crosshair,
|
|
2780
|
+
...options.crosshair ?? {}
|
|
2781
|
+
},
|
|
2782
|
+
grid: {
|
|
2783
|
+
...baseOptions.grid,
|
|
2784
|
+
...options.grid ?? {}
|
|
2785
|
+
},
|
|
2786
|
+
watermark: {
|
|
2787
|
+
...baseOptions.watermark,
|
|
2788
|
+
...options.watermark ?? {}
|
|
2789
|
+
},
|
|
2790
|
+
tickerLine: {
|
|
2791
|
+
...baseOptions.tickerLine,
|
|
2792
|
+
...options.tickerLine ?? {}
|
|
2793
|
+
},
|
|
2794
|
+
labels: {
|
|
2795
|
+
...baseOptions.labels,
|
|
2796
|
+
...options.labels ?? {}
|
|
2797
|
+
},
|
|
2798
|
+
dashPatterns: {
|
|
2799
|
+
...baseOptions.dashPatterns,
|
|
2800
|
+
...options.dashPatterns ?? {}
|
|
2766
2801
|
}
|
|
2767
|
-
};
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
BUILTIN_SMA_INDICATOR,
|
|
2771
|
-
BUILTIN_EMA_INDICATOR,
|
|
2772
|
-
BUILTIN_RSI_INDICATOR,
|
|
2773
|
-
BUILTIN_WMA_INDICATOR,
|
|
2774
|
-
BUILTIN_VWMA_INDICATOR,
|
|
2775
|
-
BUILTIN_RMA_INDICATOR,
|
|
2776
|
-
BUILTIN_HMA_INDICATOR,
|
|
2777
|
-
BUILTIN_VWAP_INDICATOR,
|
|
2778
|
-
BUILTIN_BOLLINGER_INDICATOR,
|
|
2779
|
-
BUILTIN_STDDEV_INDICATOR,
|
|
2780
|
-
BUILTIN_ATR_INDICATOR,
|
|
2781
|
-
BUILTIN_MACD_INDICATOR,
|
|
2782
|
-
BUILTIN_STOCHASTIC_INDICATOR,
|
|
2783
|
-
BUILTIN_STOCHRSI_INDICATOR,
|
|
2784
|
-
BUILTIN_ADX_INDICATOR,
|
|
2785
|
-
BUILTIN_OBV_INDICATOR,
|
|
2786
|
-
BUILTIN_MFI_INDICATOR,
|
|
2787
|
-
BUILTIN_CCI_INDICATOR,
|
|
2788
|
-
BUILTIN_WILLIAMSR_INDICATOR,
|
|
2789
|
-
BUILTIN_ROC_INDICATOR,
|
|
2790
|
-
BUILTIN_MOMENTUM_INDICATOR,
|
|
2791
|
-
BUILTIN_PSAR_INDICATOR,
|
|
2792
|
-
BUILTIN_SUPERTREND_INDICATOR,
|
|
2793
|
-
BUILTIN_ICHIMOKU_INDICATOR,
|
|
2794
|
-
BUILTIN_KELTNER_INDICATOR,
|
|
2795
|
-
BUILTIN_DONCHIAN_INDICATOR
|
|
2796
|
-
];
|
|
2802
|
+
});
|
|
2803
|
+
|
|
2804
|
+
// src/chart.ts
|
|
2797
2805
|
function createChart(element, options = {}) {
|
|
2798
2806
|
let mergedOptions = mergeChartOptions(DEFAULT_OPTIONS, options);
|
|
2799
2807
|
let resolvedAxis = { ...DEFAULT_AXIS_OPTIONS, ...mergedOptions.axis ?? {} };
|
|
@@ -2932,8 +2940,11 @@ function createChart(element, options = {}) {
|
|
|
2932
2940
|
);
|
|
2933
2941
|
let activeDrawingTool = null;
|
|
2934
2942
|
let draftDrawing = null;
|
|
2943
|
+
let measureState = null;
|
|
2935
2944
|
let drawingDragState = null;
|
|
2936
2945
|
let drawingsChangeHandler = null;
|
|
2946
|
+
let drawingToolChangeHandler = null;
|
|
2947
|
+
let lastNotifiedDrawingTool = null;
|
|
2937
2948
|
let drawingSelectHandler = null;
|
|
2938
2949
|
let drawingDoubleClickHandler = null;
|
|
2939
2950
|
let drawingEditTextHandler = null;
|
|
@@ -3526,6 +3537,13 @@ function createChart(element, options = {}) {
|
|
|
3526
3537
|
points: draftDrawing.points.map((point) => reanchorDrawingPoint(point))
|
|
3527
3538
|
};
|
|
3528
3539
|
}
|
|
3540
|
+
if (measureState) {
|
|
3541
|
+
measureState = {
|
|
3542
|
+
...measureState,
|
|
3543
|
+
start: reanchorDrawingPoint(measureState.start),
|
|
3544
|
+
end: reanchorDrawingPoint(measureState.end)
|
|
3545
|
+
};
|
|
3546
|
+
}
|
|
3529
3547
|
};
|
|
3530
3548
|
const getCandleDirectionByIndex = (index) => {
|
|
3531
3549
|
const point = data[index];
|
|
@@ -3627,6 +3645,22 @@ function createChart(element, options = {}) {
|
|
|
3627
3645
|
const pad = (value) => String(value).padStart(2, "0");
|
|
3628
3646
|
return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;
|
|
3629
3647
|
};
|
|
3648
|
+
const formatTimeSpan = (ms) => {
|
|
3649
|
+
const totalMinutes = Math.max(0, Math.round(ms / 6e4));
|
|
3650
|
+
if (totalMinutes < 1) {
|
|
3651
|
+
return `${Math.max(1, Math.round(ms / 1e3))}s`;
|
|
3652
|
+
}
|
|
3653
|
+
const days = Math.floor(totalMinutes / 1440);
|
|
3654
|
+
const hours = Math.floor(totalMinutes % 1440 / 60);
|
|
3655
|
+
const minutes = totalMinutes % 60;
|
|
3656
|
+
if (days > 0) {
|
|
3657
|
+
return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
|
|
3658
|
+
}
|
|
3659
|
+
if (hours > 0) {
|
|
3660
|
+
return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
3661
|
+
}
|
|
3662
|
+
return `${minutes}m`;
|
|
3663
|
+
};
|
|
3630
3664
|
const getRightAxisLabelX = (chartRight) => chartRight + 1;
|
|
3631
3665
|
const getRightAxisLabelWidth = (chartRight, contentWidth) => {
|
|
3632
3666
|
const scaleWidth = Math.max(0, Math.floor(width - getRightAxisLabelX(chartRight)));
|
|
@@ -3995,6 +4029,10 @@ function createChart(element, options = {}) {
|
|
|
3995
4029
|
let pendingDrawUpdateAutoScale = false;
|
|
3996
4030
|
let pendingDrawOverlayOnly = true;
|
|
3997
4031
|
const scheduleDraw = (options2 = {}) => {
|
|
4032
|
+
if (activeDrawingTool !== lastNotifiedDrawingTool) {
|
|
4033
|
+
lastNotifiedDrawingTool = activeDrawingTool;
|
|
4034
|
+
drawingToolChangeHandler?.(activeDrawingTool);
|
|
4035
|
+
}
|
|
3998
4036
|
armCountdownHeartbeat();
|
|
3999
4037
|
const overlayOnly = options2.overlayOnly ?? false;
|
|
4000
4038
|
pendingDrawOverlayOnly = pendingDrawOverlayOnly && overlayOnly;
|
|
@@ -4807,7 +4845,7 @@ function createChart(element, options = {}) {
|
|
|
4807
4845
|
const t0 = getTimeForIndex(Math.round(p0.index));
|
|
4808
4846
|
const t1 = getTimeForIndex(Math.round(p1.index));
|
|
4809
4847
|
const spanMs = t0 && t1 ? Math.abs(t1.getTime() - t0.getTime()) : 0;
|
|
4810
|
-
labelLines.push(spanMs > 0 ? `${barSpan} bars, ${
|
|
4848
|
+
labelLines.push(spanMs > 0 ? `${barSpan} bars, ${formatTimeSpan(spanMs)}` : `${barSpan} bars`);
|
|
4811
4849
|
}
|
|
4812
4850
|
const pointValue = Number(drawing.pointValue);
|
|
4813
4851
|
if (Number.isFinite(pointValue) && pointValue > 0) {
|
|
@@ -5284,6 +5322,96 @@ function createChart(element, options = {}) {
|
|
|
5284
5322
|
if (draftDrawing) {
|
|
5285
5323
|
drawDrawing(draftDrawing, true);
|
|
5286
5324
|
}
|
|
5325
|
+
if (measureState) {
|
|
5326
|
+
const mStart = measureState.start;
|
|
5327
|
+
const mEnd = measureState.end;
|
|
5328
|
+
const mx0 = xFromDrawingPoint(mStart);
|
|
5329
|
+
const mx1 = xFromDrawingPoint(mEnd);
|
|
5330
|
+
const my0 = yFromPrice(mStart.price);
|
|
5331
|
+
const my1 = yFromPrice(mEnd.price);
|
|
5332
|
+
const mLeft = Math.min(mx0, mx1);
|
|
5333
|
+
const mRight = Math.max(mx0, mx1);
|
|
5334
|
+
const mTop = Math.min(my0, my1);
|
|
5335
|
+
const mBottom = Math.max(my0, my1);
|
|
5336
|
+
const mUp = mEnd.price >= mStart.price;
|
|
5337
|
+
const mColor = mUp ? "#2962ff" : "#f23645";
|
|
5338
|
+
ctx.save();
|
|
5339
|
+
ctx.fillStyle = hexToRgba(mColor, 0.12);
|
|
5340
|
+
ctx.fillRect(mLeft, mTop, Math.max(1, mRight - mLeft), Math.max(1, mBottom - mTop));
|
|
5341
|
+
ctx.strokeStyle = mColor;
|
|
5342
|
+
ctx.lineWidth = 1;
|
|
5343
|
+
ctx.setLineDash([]);
|
|
5344
|
+
const mArrowHead = 5;
|
|
5345
|
+
const mMidX = (mLeft + mRight) / 2;
|
|
5346
|
+
if (Math.abs(my1 - my0) > mArrowHead * 2) {
|
|
5347
|
+
ctx.beginPath();
|
|
5348
|
+
ctx.moveTo(crisp(mMidX), crisp(my0));
|
|
5349
|
+
ctx.lineTo(crisp(mMidX), crisp(my1));
|
|
5350
|
+
ctx.stroke();
|
|
5351
|
+
const dirY = my1 > my0 ? 1 : -1;
|
|
5352
|
+
ctx.beginPath();
|
|
5353
|
+
ctx.moveTo(mMidX, my1);
|
|
5354
|
+
ctx.lineTo(mMidX - mArrowHead, my1 - dirY * mArrowHead);
|
|
5355
|
+
ctx.moveTo(mMidX, my1);
|
|
5356
|
+
ctx.lineTo(mMidX + mArrowHead, my1 - dirY * mArrowHead);
|
|
5357
|
+
ctx.stroke();
|
|
5358
|
+
}
|
|
5359
|
+
const mMidY = (mTop + mBottom) / 2;
|
|
5360
|
+
if (Math.abs(mx1 - mx0) > mArrowHead * 2) {
|
|
5361
|
+
ctx.beginPath();
|
|
5362
|
+
ctx.moveTo(crisp(mx0), crisp(mMidY));
|
|
5363
|
+
ctx.lineTo(crisp(mx1), crisp(mMidY));
|
|
5364
|
+
ctx.stroke();
|
|
5365
|
+
const dirX = mx1 > mx0 ? 1 : -1;
|
|
5366
|
+
ctx.beginPath();
|
|
5367
|
+
ctx.moveTo(mx1, mMidY);
|
|
5368
|
+
ctx.lineTo(mx1 - dirX * mArrowHead, mMidY - mArrowHead);
|
|
5369
|
+
ctx.moveTo(mx1, mMidY);
|
|
5370
|
+
ctx.lineTo(mx1 - dirX * mArrowHead, mMidY + mArrowHead);
|
|
5371
|
+
ctx.stroke();
|
|
5372
|
+
}
|
|
5373
|
+
const mDiff = mEnd.price - mStart.price;
|
|
5374
|
+
const mBase = Math.abs(mStart.price) > 0 ? Math.abs(mStart.price) : 1;
|
|
5375
|
+
const mPct = mDiff / mBase * 100;
|
|
5376
|
+
const mTick = getConfiguredTickSize();
|
|
5377
|
+
const mTicks = mTick > 0 ? Math.round(mDiff / mTick) : 0;
|
|
5378
|
+
const mSigned = (value, text) => `${value < 0 ? "\u2212" : value > 0 ? "+" : ""}${text}`;
|
|
5379
|
+
const mLines = [
|
|
5380
|
+
mTick > 0 ? `${mSigned(mDiff, formatPrice(Math.abs(mDiff)))} (${mSigned(mPct, `${Math.abs(mPct).toFixed(2)}%`)}) ${mSigned(mTicks, String(Math.abs(mTicks)))}` : `${mSigned(mDiff, formatPrice(Math.abs(mDiff)))} (${mSigned(mPct, `${Math.abs(mPct).toFixed(2)}%`)})`
|
|
5381
|
+
];
|
|
5382
|
+
const mBars = Math.round(mEnd.index) - Math.round(mStart.index);
|
|
5383
|
+
const mT0 = getTimeForIndex(Math.round(mStart.index));
|
|
5384
|
+
const mT1 = getTimeForIndex(Math.round(mEnd.index));
|
|
5385
|
+
const mSpanMs = mT0 && mT1 ? Math.abs(mT1.getTime() - mT0.getTime()) : 0;
|
|
5386
|
+
mLines.push(
|
|
5387
|
+
mSpanMs > 0 ? `${mSigned(mBars, String(Math.abs(mBars)))} bars, ${formatTimeSpan(mSpanMs)}` : `${mSigned(mBars, String(Math.abs(mBars)))} bars`
|
|
5388
|
+
);
|
|
5389
|
+
const prevMeasureFont = ctx.font;
|
|
5390
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
5391
|
+
const mPad = 7;
|
|
5392
|
+
const mLineHeight = 15;
|
|
5393
|
+
const mTextW = Math.max(...mLines.map((line) => measureTextWidth(line)));
|
|
5394
|
+
const mPillW = mTextW + mPad * 2;
|
|
5395
|
+
const mPillH = mLines.length * mLineHeight + mPad;
|
|
5396
|
+
const mPillX = clamp(mMidX - mPillW / 2, chartLeft + 2, chartLeft + chartWidth - mPillW - 2);
|
|
5397
|
+
let mPillY = mUp ? mTop - 8 - mPillH : mBottom + 8;
|
|
5398
|
+
if (mPillY < chartTop + 2) {
|
|
5399
|
+
mPillY = mBottom + 8;
|
|
5400
|
+
}
|
|
5401
|
+
if (mPillY + mPillH > fullChartBottom - 2) {
|
|
5402
|
+
mPillY = Math.max(chartTop + 2, mTop - 8 - mPillH);
|
|
5403
|
+
}
|
|
5404
|
+
ctx.fillStyle = mColor;
|
|
5405
|
+
fillRoundedRect(mPillX, mPillY, mPillW, mPillH, 4);
|
|
5406
|
+
ctx.fillStyle = "#ffffff";
|
|
5407
|
+
ctx.textAlign = "center";
|
|
5408
|
+
ctx.textBaseline = "middle";
|
|
5409
|
+
mLines.forEach((line, lineIndex) => {
|
|
5410
|
+
ctx.fillText(line, mPillX + mPillW / 2, mPillY + mPad / 2 + lineIndex * mLineHeight + mLineHeight / 2);
|
|
5411
|
+
});
|
|
5412
|
+
ctx.font = prevMeasureFont;
|
|
5413
|
+
ctx.restore();
|
|
5414
|
+
}
|
|
5287
5415
|
if (tradeMarkers.length > 0 && data.length > 0) {
|
|
5288
5416
|
const visibleStart = Math.floor(xStart) - 1;
|
|
5289
5417
|
const visibleEnd = Math.ceil(xStart + xSpan) + 1;
|
|
@@ -7282,6 +7410,19 @@ function createChart(element, options = {}) {
|
|
|
7282
7410
|
return;
|
|
7283
7411
|
}
|
|
7284
7412
|
}
|
|
7413
|
+
if (measureState) {
|
|
7414
|
+
if (measureState.dragging) {
|
|
7415
|
+
measureState = { ...measureState, dragging: false };
|
|
7416
|
+
if (activeDrawingTool === "measure") {
|
|
7417
|
+
activeDrawingTool = null;
|
|
7418
|
+
canvas.style.cursor = "default";
|
|
7419
|
+
}
|
|
7420
|
+
scheduleDraw();
|
|
7421
|
+
return;
|
|
7422
|
+
}
|
|
7423
|
+
measureState = null;
|
|
7424
|
+
scheduleDraw();
|
|
7425
|
+
}
|
|
7285
7426
|
const drawingToolCapturesPointer = activeDrawingTool !== null || draftDrawing !== null;
|
|
7286
7427
|
if (!drawingToolCapturesPointer) {
|
|
7287
7428
|
const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
|
|
@@ -7356,6 +7497,18 @@ function createChart(element, options = {}) {
|
|
|
7356
7497
|
if (region === "outside") {
|
|
7357
7498
|
return;
|
|
7358
7499
|
}
|
|
7500
|
+
if (region === "plot" && (activeDrawingTool === "measure" || activeDrawingTool === null && draftDrawing === null && event.shiftKey && event.pointerType === "mouse")) {
|
|
7501
|
+
const measurePoint = drawingPointFromCanvas(point.x, point.y);
|
|
7502
|
+
if (measurePoint) {
|
|
7503
|
+
measureState = { start: measurePoint, end: measurePoint, dragging: true };
|
|
7504
|
+
activePointerId = event.pointerId;
|
|
7505
|
+
capturePointer(event.pointerId);
|
|
7506
|
+
setCrosshairPoint(null);
|
|
7507
|
+
canvas.style.cursor = "crosshair";
|
|
7508
|
+
scheduleDraw();
|
|
7509
|
+
return;
|
|
7510
|
+
}
|
|
7511
|
+
}
|
|
7359
7512
|
if (region === "plot" && !activeDrawingTool) {
|
|
7360
7513
|
const drawingHit = getDrawingHit(point.x, point.y);
|
|
7361
7514
|
if (drawingHit) {
|
|
@@ -7451,6 +7604,19 @@ function createChart(element, options = {}) {
|
|
|
7451
7604
|
}
|
|
7452
7605
|
return;
|
|
7453
7606
|
}
|
|
7607
|
+
if (measureState?.dragging) {
|
|
7608
|
+
if (activePointerId !== null && event.pointerId !== activePointerId) {
|
|
7609
|
+
return;
|
|
7610
|
+
}
|
|
7611
|
+
const nextMeasurePoint = drawingPointFromCanvas(point.x, point.y);
|
|
7612
|
+
if (nextMeasurePoint) {
|
|
7613
|
+
measureState = { ...measureState, end: nextMeasurePoint };
|
|
7614
|
+
setCrosshairPoint(null);
|
|
7615
|
+
canvas.style.cursor = "crosshair";
|
|
7616
|
+
scheduleDraw();
|
|
7617
|
+
}
|
|
7618
|
+
return;
|
|
7619
|
+
}
|
|
7454
7620
|
if (drawingDragState) {
|
|
7455
7621
|
if (activePointerId !== null && event.pointerId !== activePointerId) {
|
|
7456
7622
|
return;
|
|
@@ -7681,6 +7847,24 @@ function createChart(element, options = {}) {
|
|
|
7681
7847
|
if (event && activePointerId !== null && event.pointerId !== activePointerId) {
|
|
7682
7848
|
return;
|
|
7683
7849
|
}
|
|
7850
|
+
if (measureState?.dragging) {
|
|
7851
|
+
const movedMeasure = measureState.start.index !== measureState.end.index || measureState.start.price !== measureState.end.price;
|
|
7852
|
+
if (movedMeasure) {
|
|
7853
|
+
measureState = { ...measureState, dragging: false };
|
|
7854
|
+
if (activeDrawingTool === "measure") {
|
|
7855
|
+
activeDrawingTool = null;
|
|
7856
|
+
}
|
|
7857
|
+
canvas.style.cursor = "default";
|
|
7858
|
+
} else {
|
|
7859
|
+
canvas.style.cursor = "crosshair";
|
|
7860
|
+
}
|
|
7861
|
+
isDragging = false;
|
|
7862
|
+
dragMode = null;
|
|
7863
|
+
activePointerId = null;
|
|
7864
|
+
pointerDownInfo = null;
|
|
7865
|
+
scheduleDraw();
|
|
7866
|
+
return;
|
|
7867
|
+
}
|
|
7684
7868
|
if (orderDragState) {
|
|
7685
7869
|
const moved = orderDragState.lastPrice !== orderDragState.startPrice;
|
|
7686
7870
|
const finalLine = orderLines.find((line) => line.id === orderDragState?.orderId);
|
|
@@ -7861,6 +8045,15 @@ function createChart(element, options = {}) {
|
|
|
7861
8045
|
canvas.addEventListener("dblclick", onDoubleClick);
|
|
7862
8046
|
canvas.addEventListener("contextmenu", onContextMenu);
|
|
7863
8047
|
const onModifierKeyChange = (event) => {
|
|
8048
|
+
if (event.key === "Escape" && event.type === "keydown" && measureState) {
|
|
8049
|
+
measureState = null;
|
|
8050
|
+
if (activeDrawingTool === "measure") {
|
|
8051
|
+
activeDrawingTool = null;
|
|
8052
|
+
canvas.style.cursor = "default";
|
|
8053
|
+
}
|
|
8054
|
+
scheduleDraw();
|
|
8055
|
+
return;
|
|
8056
|
+
}
|
|
7864
8057
|
const active = event.metaKey || event.ctrlKey;
|
|
7865
8058
|
const shift = event.shiftKey;
|
|
7866
8059
|
const changed = active !== magnetModifierActive || shift !== shiftKeyActive;
|
|
@@ -8173,17 +8366,29 @@ function createChart(element, options = {}) {
|
|
|
8173
8366
|
const setActiveDrawingTool = (tool) => {
|
|
8174
8367
|
activeDrawingTool = tool;
|
|
8175
8368
|
draftDrawing = null;
|
|
8369
|
+
measureState = null;
|
|
8176
8370
|
canvas.style.cursor = tool ? "crosshair" : "default";
|
|
8177
8371
|
scheduleDraw();
|
|
8178
8372
|
};
|
|
8179
8373
|
const getActiveDrawingTool = () => activeDrawingTool;
|
|
8180
8374
|
const cancelDrawing = () => {
|
|
8375
|
+
let cancelled = false;
|
|
8181
8376
|
if (draftDrawing) {
|
|
8182
8377
|
draftDrawing = null;
|
|
8378
|
+
cancelled = true;
|
|
8379
|
+
}
|
|
8380
|
+
if (measureState) {
|
|
8381
|
+
measureState = null;
|
|
8382
|
+
if (activeDrawingTool === "measure") {
|
|
8383
|
+
activeDrawingTool = null;
|
|
8384
|
+
canvas.style.cursor = "default";
|
|
8385
|
+
}
|
|
8386
|
+
cancelled = true;
|
|
8387
|
+
}
|
|
8388
|
+
if (cancelled) {
|
|
8183
8389
|
scheduleDraw();
|
|
8184
|
-
return true;
|
|
8185
8390
|
}
|
|
8186
|
-
return
|
|
8391
|
+
return cancelled;
|
|
8187
8392
|
};
|
|
8188
8393
|
const setTradeMarkers = (markers) => {
|
|
8189
8394
|
tradeMarkers = Array.isArray(markers) ? markers.slice() : [];
|
|
@@ -8235,6 +8440,9 @@ function createChart(element, options = {}) {
|
|
|
8235
8440
|
const onDrawingsChange = (handler) => {
|
|
8236
8441
|
drawingsChangeHandler = handler;
|
|
8237
8442
|
};
|
|
8443
|
+
const onActiveDrawingToolChange = (handler) => {
|
|
8444
|
+
drawingToolChangeHandler = handler;
|
|
8445
|
+
};
|
|
8238
8446
|
const onDrawingSelect = (handler) => {
|
|
8239
8447
|
drawingSelectHandler = handler;
|
|
8240
8448
|
};
|
|
@@ -8321,6 +8529,7 @@ function createChart(element, options = {}) {
|
|
|
8321
8529
|
removeDrawing,
|
|
8322
8530
|
clearDrawings,
|
|
8323
8531
|
onDrawingsChange,
|
|
8532
|
+
onActiveDrawingToolChange,
|
|
8324
8533
|
onDrawingSelect,
|
|
8325
8534
|
onDrawingDoubleClick,
|
|
8326
8535
|
onDrawingEditText,
|