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