hyperprop-charting-library 0.1.132 → 0.1.134
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 +899 -578
- package/dist/hyperprop-charting-library.d.ts +66 -57
- package/dist/hyperprop-charting-library.js +1090 -772
- package/dist/index.cjs +899 -578
- package/dist/index.d.cts +66 -57
- package/dist/index.d.ts +66 -57
- package/dist/index.js +1090 -772
- package/docs/API.md +15 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/types.ts
|
|
2
2
|
var POSITION_DEFAULT_COLORS = ["#26a69a", "#ef5350", "#ffffff"];
|
|
3
3
|
var FIB_DEFAULT_PALETTE = [
|
|
4
4
|
"#787b86",
|
|
@@ -10,387 +10,127 @@ var FIB_DEFAULT_PALETTE = [
|
|
|
10
10
|
"#2962ff",
|
|
11
11
|
"#9c27b0"
|
|
12
12
|
];
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
sessionSeparators: false,
|
|
22
|
-
sessionSeparatorColor: "#3b4150",
|
|
23
|
-
sessionSeparatorOpacity: 0.55
|
|
13
|
+
|
|
14
|
+
// src/indicators.ts
|
|
15
|
+
var getIndicatorSourceValue = (point, source) => {
|
|
16
|
+
if (source === "open") return point.o;
|
|
17
|
+
if (source === "high") return point.h;
|
|
18
|
+
if (source === "low") return point.l;
|
|
19
|
+
if (source === "hl2") return (point.h + point.l) / 2;
|
|
20
|
+
return point.c;
|
|
24
21
|
};
|
|
25
|
-
var
|
|
26
|
-
|
|
27
|
-
textColor: "#a9adb6",
|
|
28
|
-
fontSize: 12,
|
|
29
|
-
lineWidth: 1
|
|
22
|
+
var clampIndicatorLength = (value, fallback) => {
|
|
23
|
+
return Math.max(1, Math.round(Number(value) || fallback));
|
|
30
24
|
};
|
|
31
|
-
var
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
sideHintRight: "",
|
|
58
|
-
sideHintLeftColor: "#26a69a",
|
|
59
|
-
sideHintRightColor: "#ef5350"
|
|
25
|
+
var parseColorToRgb = (value) => {
|
|
26
|
+
if (!value) return null;
|
|
27
|
+
const normalized = value.trim().toLowerCase();
|
|
28
|
+
if (normalized === "white") return { r: 255, g: 255, b: 255 };
|
|
29
|
+
if (normalized === "black") return { r: 0, g: 0, b: 0 };
|
|
30
|
+
const hex3 = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/.exec(normalized);
|
|
31
|
+
if (hex3) {
|
|
32
|
+
return {
|
|
33
|
+
r: parseInt(hex3[1] + hex3[1], 16),
|
|
34
|
+
g: parseInt(hex3[2] + hex3[2], 16),
|
|
35
|
+
b: parseInt(hex3[3] + hex3[3], 16)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const hex = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})(?:[0-9a-f]{2})?$/.exec(normalized);
|
|
39
|
+
if (hex) {
|
|
40
|
+
return {
|
|
41
|
+
r: parseInt(hex[1], 16),
|
|
42
|
+
g: parseInt(hex[2], 16),
|
|
43
|
+
b: parseInt(hex[3], 16)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const rgb = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*[\d.]+\s*)?\)$/.exec(normalized);
|
|
47
|
+
if (rgb) {
|
|
48
|
+
return { r: Number(rgb[1]), g: Number(rgb[2]), b: Number(rgb[3]) };
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
60
51
|
};
|
|
61
|
-
var
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
fontSize: 92,
|
|
67
|
-
fontWeight: 700,
|
|
68
|
-
thickness: 0,
|
|
69
|
-
imageSrc: "",
|
|
70
|
-
imageScale: 1,
|
|
71
|
-
imageMaxWidthRatio: 0.42,
|
|
72
|
-
imageMaxHeightRatio: 0.3,
|
|
73
|
-
imageTintColor: "",
|
|
74
|
-
imageTintOpacity: 1
|
|
52
|
+
var labelTextColorOn = (background, textColor) => {
|
|
53
|
+
const rgb = parseColorToRgb(background);
|
|
54
|
+
if (!rgb) return textColor;
|
|
55
|
+
const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
|
|
56
|
+
return luminance >= 0.62 ? "#000000" : textColor;
|
|
75
57
|
};
|
|
76
|
-
var
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
58
|
+
var computeSmaSeries = (data, length, source) => {
|
|
59
|
+
const result = new Array(data.length).fill(null);
|
|
60
|
+
let sum = 0;
|
|
61
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
62
|
+
const point = data[i];
|
|
63
|
+
if (!point) continue;
|
|
64
|
+
const value = getIndicatorSourceValue(point, source);
|
|
65
|
+
sum += value;
|
|
66
|
+
if (i >= length) {
|
|
67
|
+
const oldPoint = data[i - length];
|
|
68
|
+
if (oldPoint) {
|
|
69
|
+
sum -= getIndicatorSourceValue(oldPoint, source);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (i >= length - 1) {
|
|
73
|
+
result[i] = sum / length;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
83
77
|
};
|
|
84
|
-
var
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
indicatorLegendPosition: "top-left",
|
|
99
|
-
indicatorLegendOffsetX: 10,
|
|
100
|
-
indicatorLegendOffsetY: 10,
|
|
101
|
-
showCountdownToBarClose: false,
|
|
102
|
-
noOverlapping: true,
|
|
103
|
-
backgroundColor: "#0b1220",
|
|
104
|
-
textColor: "#cbd5e1",
|
|
105
|
-
mutedTextColor: "#94a3b8",
|
|
106
|
-
symbolNameBackgroundColor: "#1f2937",
|
|
107
|
-
symbolNameTextColor: "#e5e7eb",
|
|
108
|
-
previousCloseColor: "#94a3b8",
|
|
109
|
-
highLowColor: "#a78bfa",
|
|
110
|
-
bidColor: "#ef4444",
|
|
111
|
-
askColor: "#22c55e",
|
|
112
|
-
indicatorTextColor: "#cbd5e1",
|
|
113
|
-
borderRadius: 3,
|
|
114
|
-
labelHeight: 20,
|
|
115
|
-
labelPaddingX: 8
|
|
78
|
+
var computeEmaSeries = (data, length, source) => {
|
|
79
|
+
const result = new Array(data.length).fill(null);
|
|
80
|
+
const alpha = 2 / (length + 1);
|
|
81
|
+
let prev = null;
|
|
82
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
83
|
+
const point = data[i];
|
|
84
|
+
if (!point) continue;
|
|
85
|
+
const value = getIndicatorSourceValue(point, source);
|
|
86
|
+
prev = prev === null ? value : alpha * value + (1 - alpha) * prev;
|
|
87
|
+
if (i >= length - 1) {
|
|
88
|
+
result[i] = prev;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
116
92
|
};
|
|
117
|
-
var
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
93
|
+
var computeRmaSeries = (data, length, source) => {
|
|
94
|
+
const result = new Array(data.length).fill(null);
|
|
95
|
+
let prev = null;
|
|
96
|
+
let seedSum = 0;
|
|
97
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
98
|
+
const point = data[i];
|
|
99
|
+
if (!point) continue;
|
|
100
|
+
const value = getIndicatorSourceValue(point, source);
|
|
101
|
+
if (i < length) {
|
|
102
|
+
seedSum += value;
|
|
103
|
+
if (i === length - 1) {
|
|
104
|
+
prev = seedSum / length;
|
|
105
|
+
result[i] = prev;
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
prev = prev === null ? value : (prev * (length - 1) + value) / length;
|
|
110
|
+
result[i] = prev;
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
127
113
|
};
|
|
128
|
-
var
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
actionButtonFontWeight: 500,
|
|
149
|
-
actionButtonBorderColor: "#2563eb",
|
|
150
|
-
actionButtonBorderStyle: "solid",
|
|
151
|
-
actionButtonsInnerGap: 6,
|
|
152
|
-
actionButtonsGroupGap: 8,
|
|
153
|
-
actionButtons: [],
|
|
154
|
-
connectorToPrice: Number.NaN,
|
|
155
|
-
connectorColor: "#2563eb",
|
|
156
|
-
connectorStyle: "dotted",
|
|
157
|
-
connectorThickness: 1,
|
|
158
|
-
connectorAnchorPaddingRight: 10,
|
|
159
|
-
fillToPrice: Number.NaN,
|
|
160
|
-
fillColor: "rgba(37,99,235,0.18)",
|
|
161
|
-
pinOutOfRange: false
|
|
162
|
-
};
|
|
163
|
-
var DEFAULT_OPTIONS = {
|
|
164
|
-
width: 720,
|
|
165
|
-
height: 360,
|
|
166
|
-
backgroundColor: "#101114",
|
|
167
|
-
axisColor: "#7f8289",
|
|
168
|
-
axis: DEFAULT_AXIS_OPTIONS,
|
|
169
|
-
xAxis: DEFAULT_AXIS_OPTIONS,
|
|
170
|
-
yAxis: DEFAULT_AXIS_OPTIONS,
|
|
171
|
-
priceDecimals: 2,
|
|
172
|
-
stabilizePriceLabels: true,
|
|
173
|
-
priceLabelMinIntegerDigits: 3,
|
|
174
|
-
priceLabelWidthTemplate: "",
|
|
175
|
-
initialViewport: "latest",
|
|
176
|
-
initialVisibleBars: 60,
|
|
177
|
-
minVisibleBars: 5,
|
|
178
|
-
maxVisibleBars: 2e4,
|
|
179
|
-
maxPanBars: 1e6,
|
|
180
|
-
rightEdgePaddingBars: 2,
|
|
181
|
-
preserveViewportOnDataUpdate: true,
|
|
182
|
-
upColor: "#2fb171",
|
|
183
|
-
downColor: "#d35a5a",
|
|
184
|
-
gridColor: "#252932",
|
|
185
|
-
fontFamily: "Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif",
|
|
186
|
-
candleBodyWidthRatio: 0.7,
|
|
187
|
-
candleMinWidth: 0.5,
|
|
188
|
-
candleWickWidth: 1,
|
|
189
|
-
tickSize: 0,
|
|
190
|
-
candleColorMode: "openClose",
|
|
191
|
-
candleColorEpsilon: -1,
|
|
192
|
-
chartType: "candles",
|
|
193
|
-
lineColor: "#2962ff",
|
|
194
|
-
lineWidth: 2,
|
|
195
|
-
areaFillOpacity: 0.12,
|
|
196
|
-
baselinePrice: null,
|
|
197
|
-
// 0 = deterministic scale (lightweight-charts behavior): the y-range is a
|
|
198
|
-
// pure function of the visible data, so it can never drift or "breathe".
|
|
199
|
-
// Values > 0 re-enable eased contraction for hosts that prefer it.
|
|
200
|
-
autoScaleSmoothing: 0,
|
|
201
|
-
autoScaleIgnoreLatestCandle: true,
|
|
202
|
-
kineticScroll: { touch: true, mouse: false },
|
|
203
|
-
timeStepMs: 0,
|
|
204
|
-
clockOffsetMs: 0,
|
|
205
|
-
pinOutOfRangeLines: false,
|
|
206
|
-
doubleClickEnabled: true,
|
|
207
|
-
doubleClickAction: "reset",
|
|
208
|
-
crosshair: DEFAULT_CROSSHAIR_OPTIONS,
|
|
209
|
-
grid: DEFAULT_GRID_OPTIONS,
|
|
210
|
-
watermark: DEFAULT_WATERMARK_OPTIONS,
|
|
211
|
-
priceLines: [],
|
|
212
|
-
orderLines: [],
|
|
213
|
-
tickerLine: {
|
|
214
|
-
visible: true,
|
|
215
|
-
style: "dotted",
|
|
216
|
-
thickness: 1,
|
|
217
|
-
labelTextColor: "#0b1220",
|
|
218
|
-
labelSubtext: "",
|
|
219
|
-
labelSubtexts: [],
|
|
220
|
-
labelSubtextColor: "#0b1220",
|
|
221
|
-
labelSubtextFontSize: 0,
|
|
222
|
-
showCountdownInLabel: false,
|
|
223
|
-
labelBorderRadius: 3
|
|
224
|
-
},
|
|
225
|
-
labels: DEFAULT_LABELS_OPTIONS,
|
|
226
|
-
dashPatterns: DEFAULT_DASH_PATTERNS,
|
|
227
|
-
indicators: [],
|
|
228
|
-
drawings: []
|
|
229
|
-
};
|
|
230
|
-
var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
231
|
-
...baseOptions,
|
|
232
|
-
...options,
|
|
233
|
-
axis: {
|
|
234
|
-
...baseOptions.axis,
|
|
235
|
-
...options.axis ?? {},
|
|
236
|
-
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {}
|
|
237
|
-
},
|
|
238
|
-
xAxis: {
|
|
239
|
-
...baseOptions.xAxis,
|
|
240
|
-
...options.axis ?? {},
|
|
241
|
-
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
|
|
242
|
-
...options.xAxis ?? {}
|
|
243
|
-
},
|
|
244
|
-
yAxis: {
|
|
245
|
-
...baseOptions.yAxis,
|
|
246
|
-
...options.axis ?? {},
|
|
247
|
-
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
|
|
248
|
-
...options.yAxis ?? {}
|
|
249
|
-
},
|
|
250
|
-
crosshair: {
|
|
251
|
-
...baseOptions.crosshair,
|
|
252
|
-
...options.crosshair ?? {}
|
|
253
|
-
},
|
|
254
|
-
grid: {
|
|
255
|
-
...baseOptions.grid,
|
|
256
|
-
...options.grid ?? {}
|
|
257
|
-
},
|
|
258
|
-
watermark: {
|
|
259
|
-
...baseOptions.watermark,
|
|
260
|
-
...options.watermark ?? {}
|
|
261
|
-
},
|
|
262
|
-
tickerLine: {
|
|
263
|
-
...baseOptions.tickerLine,
|
|
264
|
-
...options.tickerLine ?? {}
|
|
265
|
-
},
|
|
266
|
-
labels: {
|
|
267
|
-
...baseOptions.labels,
|
|
268
|
-
...options.labels ?? {}
|
|
269
|
-
},
|
|
270
|
-
dashPatterns: {
|
|
271
|
-
...baseOptions.dashPatterns,
|
|
272
|
-
...options.dashPatterns ?? {}
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
var getIndicatorSourceValue = (point, source) => {
|
|
276
|
-
if (source === "open") return point.o;
|
|
277
|
-
if (source === "high") return point.h;
|
|
278
|
-
if (source === "low") return point.l;
|
|
279
|
-
if (source === "hl2") return (point.h + point.l) / 2;
|
|
280
|
-
return point.c;
|
|
281
|
-
};
|
|
282
|
-
var clampIndicatorLength = (value, fallback) => {
|
|
283
|
-
return Math.max(1, Math.round(Number(value) || fallback));
|
|
284
|
-
};
|
|
285
|
-
var parseColorToRgb = (value) => {
|
|
286
|
-
if (!value) return null;
|
|
287
|
-
const normalized = value.trim().toLowerCase();
|
|
288
|
-
if (normalized === "white") return { r: 255, g: 255, b: 255 };
|
|
289
|
-
if (normalized === "black") return { r: 0, g: 0, b: 0 };
|
|
290
|
-
const hex3 = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/.exec(normalized);
|
|
291
|
-
if (hex3) {
|
|
292
|
-
return {
|
|
293
|
-
r: parseInt(hex3[1] + hex3[1], 16),
|
|
294
|
-
g: parseInt(hex3[2] + hex3[2], 16),
|
|
295
|
-
b: parseInt(hex3[3] + hex3[3], 16)
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
const hex = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})(?:[0-9a-f]{2})?$/.exec(normalized);
|
|
299
|
-
if (hex) {
|
|
300
|
-
return {
|
|
301
|
-
r: parseInt(hex[1], 16),
|
|
302
|
-
g: parseInt(hex[2], 16),
|
|
303
|
-
b: parseInt(hex[3], 16)
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
const rgb = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*[\d.]+\s*)?\)$/.exec(normalized);
|
|
307
|
-
if (rgb) {
|
|
308
|
-
return { r: Number(rgb[1]), g: Number(rgb[2]), b: Number(rgb[3]) };
|
|
309
|
-
}
|
|
310
|
-
return null;
|
|
311
|
-
};
|
|
312
|
-
var labelTextColorOn = (background, textColor) => {
|
|
313
|
-
const rgb = parseColorToRgb(background);
|
|
314
|
-
if (!rgb) return textColor;
|
|
315
|
-
const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
|
|
316
|
-
return luminance >= 0.62 ? "#000000" : textColor;
|
|
317
|
-
};
|
|
318
|
-
var computeSmaSeries = (data, length, source) => {
|
|
319
|
-
const result = new Array(data.length).fill(null);
|
|
320
|
-
let sum = 0;
|
|
321
|
-
for (let i = 0; i < data.length; i += 1) {
|
|
322
|
-
const point = data[i];
|
|
323
|
-
if (!point) continue;
|
|
324
|
-
const value = getIndicatorSourceValue(point, source);
|
|
325
|
-
sum += value;
|
|
326
|
-
if (i >= length) {
|
|
327
|
-
const oldPoint = data[i - length];
|
|
328
|
-
if (oldPoint) {
|
|
329
|
-
sum -= getIndicatorSourceValue(oldPoint, source);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
if (i >= length - 1) {
|
|
333
|
-
result[i] = sum / length;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return result;
|
|
337
|
-
};
|
|
338
|
-
var computeEmaSeries = (data, length, source) => {
|
|
339
|
-
const result = new Array(data.length).fill(null);
|
|
340
|
-
const alpha = 2 / (length + 1);
|
|
341
|
-
let prev = null;
|
|
342
|
-
for (let i = 0; i < data.length; i += 1) {
|
|
343
|
-
const point = data[i];
|
|
344
|
-
if (!point) continue;
|
|
345
|
-
const value = getIndicatorSourceValue(point, source);
|
|
346
|
-
prev = prev === null ? value : alpha * value + (1 - alpha) * prev;
|
|
347
|
-
if (i >= length - 1) {
|
|
348
|
-
result[i] = prev;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
return result;
|
|
352
|
-
};
|
|
353
|
-
var computeRmaSeries = (data, length, source) => {
|
|
354
|
-
const result = new Array(data.length).fill(null);
|
|
355
|
-
let prev = null;
|
|
356
|
-
let seedSum = 0;
|
|
357
|
-
for (let i = 0; i < data.length; i += 1) {
|
|
358
|
-
const point = data[i];
|
|
359
|
-
if (!point) continue;
|
|
360
|
-
const value = getIndicatorSourceValue(point, source);
|
|
361
|
-
if (i < length) {
|
|
362
|
-
seedSum += value;
|
|
363
|
-
if (i === length - 1) {
|
|
364
|
-
prev = seedSum / length;
|
|
365
|
-
result[i] = prev;
|
|
366
|
-
}
|
|
367
|
-
continue;
|
|
368
|
-
}
|
|
369
|
-
prev = prev === null ? value : (prev * (length - 1) + value) / length;
|
|
370
|
-
result[i] = prev;
|
|
371
|
-
}
|
|
372
|
-
return result;
|
|
373
|
-
};
|
|
374
|
-
var computeWmaSeriesFromValues = (values, length) => {
|
|
375
|
-
const result = new Array(values.length).fill(null);
|
|
376
|
-
const denominator = length * (length + 1) / 2;
|
|
377
|
-
for (let i = 0; i < values.length; i += 1) {
|
|
378
|
-
if (i < length - 1) continue;
|
|
379
|
-
let weighted = 0;
|
|
380
|
-
let valid = true;
|
|
381
|
-
for (let j = 0; j < length; j += 1) {
|
|
382
|
-
const value = values[i - length + 1 + j];
|
|
383
|
-
if (value == null) {
|
|
384
|
-
valid = false;
|
|
385
|
-
break;
|
|
386
|
-
}
|
|
387
|
-
weighted += value * (j + 1);
|
|
388
|
-
}
|
|
389
|
-
if (valid) {
|
|
390
|
-
result[i] = weighted / denominator;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return result;
|
|
114
|
+
var computeWmaSeriesFromValues = (values, length) => {
|
|
115
|
+
const result = new Array(values.length).fill(null);
|
|
116
|
+
const denominator = length * (length + 1) / 2;
|
|
117
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
118
|
+
if (i < length - 1) continue;
|
|
119
|
+
let weighted = 0;
|
|
120
|
+
let valid = true;
|
|
121
|
+
for (let j = 0; j < length; j += 1) {
|
|
122
|
+
const value = values[i - length + 1 + j];
|
|
123
|
+
if (value == null) {
|
|
124
|
+
valid = false;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
weighted += value * (j + 1);
|
|
128
|
+
}
|
|
129
|
+
if (valid) {
|
|
130
|
+
result[i] = weighted / denominator;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
394
134
|
};
|
|
395
135
|
var computeWmaSeries = (data, length, source) => {
|
|
396
136
|
const sourceValues = data.map((point) => getIndicatorSourceValue(point, source));
|
|
@@ -603,14 +343,23 @@ var withCachedSeries = (key, data, compute) => {
|
|
|
603
343
|
return values;
|
|
604
344
|
};
|
|
605
345
|
var builtInComputationCache = /* @__PURE__ */ new Map();
|
|
346
|
+
var COMPUTATION_CACHE_MAX_ENTRIES = 128;
|
|
606
347
|
var withCachedComputation = (key, data, compute) => {
|
|
607
348
|
const fingerprint = getSeriesFingerprint(data);
|
|
608
349
|
const existing = builtInComputationCache.get(key);
|
|
609
350
|
if (existing && existing.fingerprint === fingerprint) {
|
|
351
|
+
builtInComputationCache.delete(key);
|
|
352
|
+
builtInComputationCache.set(key, existing);
|
|
610
353
|
return existing.value;
|
|
611
354
|
}
|
|
612
355
|
const value = compute();
|
|
356
|
+
builtInComputationCache.delete(key);
|
|
613
357
|
builtInComputationCache.set(key, { fingerprint, value });
|
|
358
|
+
while (builtInComputationCache.size > COMPUTATION_CACHE_MAX_ENTRIES) {
|
|
359
|
+
const oldest = builtInComputationCache.keys().next().value;
|
|
360
|
+
if (oldest === void 0) break;
|
|
361
|
+
builtInComputationCache.delete(oldest);
|
|
362
|
+
}
|
|
614
363
|
return value;
|
|
615
364
|
};
|
|
616
365
|
var rollingExtremeSeries = (values, length, mode) => {
|
|
@@ -1468,208 +1217,19 @@ var drawSeparateMultiSeries = (ctx, renderContext, seriesList, options = {}) =>
|
|
|
1468
1217
|
}
|
|
1469
1218
|
return paneInfo;
|
|
1470
1219
|
};
|
|
1471
|
-
var
|
|
1472
|
-
const
|
|
1473
|
-
const
|
|
1474
|
-
|
|
1475
|
-
const
|
|
1476
|
-
|
|
1477
|
-
}
|
|
1478
|
-
const
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
let sum = 0;
|
|
1485
|
-
let sumSq = 0;
|
|
1486
|
-
let valid = 0;
|
|
1487
|
-
for (let i = 0; i < values.length; i += 1) {
|
|
1488
|
-
const value = values[i];
|
|
1489
|
-
if (value == null || !Number.isFinite(value)) {
|
|
1490
|
-
sum = 0;
|
|
1491
|
-
sumSq = 0;
|
|
1492
|
-
valid = 0;
|
|
1493
|
-
continue;
|
|
1494
|
-
}
|
|
1495
|
-
sum += value;
|
|
1496
|
-
sumSq += value * value;
|
|
1497
|
-
valid += 1;
|
|
1498
|
-
if (valid > window2) {
|
|
1499
|
-
const old = values[i - window2];
|
|
1500
|
-
sum -= old;
|
|
1501
|
-
sumSq -= old * old;
|
|
1502
|
-
valid -= 1;
|
|
1503
|
-
}
|
|
1504
|
-
if (valid === window2) {
|
|
1505
|
-
const mean = sum / window2;
|
|
1506
|
-
result[i] = Math.sqrt(Math.max(0, sumSq / window2 - mean * mean));
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
return result;
|
|
1510
|
-
};
|
|
1511
|
-
var scriptAtrSeries = (bars, length) => {
|
|
1512
|
-
const tr = new Array(bars.length).fill(null);
|
|
1513
|
-
for (let i = 0; i < bars.length; i += 1) {
|
|
1514
|
-
const bar = bars[i];
|
|
1515
|
-
if (i === 0) {
|
|
1516
|
-
tr[i] = bar.h - bar.l;
|
|
1517
|
-
continue;
|
|
1518
|
-
}
|
|
1519
|
-
const prevClose = bars[i - 1].c;
|
|
1520
|
-
tr[i] = Math.max(bar.h - bar.l, Math.abs(bar.h - prevClose), Math.abs(bar.l - prevClose));
|
|
1521
|
-
}
|
|
1522
|
-
return rmaFromValues(tr, Math.max(1, Math.floor(length)));
|
|
1523
|
-
};
|
|
1524
|
-
var SCRIPT_HELPERS = Object.freeze({
|
|
1525
|
-
sma: (values, length) => smaFromValues(values, Math.max(1, Math.floor(length))),
|
|
1526
|
-
ema: (values, length) => emaFromValues(values, Math.max(1, Math.floor(length))),
|
|
1527
|
-
rma: (values, length) => rmaFromValues(values, Math.max(1, Math.floor(length))),
|
|
1528
|
-
highest: (values, length) => scriptRollingExtreme(values, length, "max"),
|
|
1529
|
-
lowest: (values, length) => scriptRollingExtreme(values, length, "min"),
|
|
1530
|
-
change: (values, length = 1) => values.map((value, index) => {
|
|
1531
|
-
const prev = values[index - Math.max(1, Math.floor(length))];
|
|
1532
|
-
return value != null && prev != null ? value - prev : null;
|
|
1533
|
-
}),
|
|
1534
|
-
stdev: scriptStdevSeries,
|
|
1535
|
-
atr: scriptAtrSeries
|
|
1536
|
-
});
|
|
1537
|
-
var SCRIPT_PLOT_PALETTE = ["#2962ff", "#ff6d00", "#26a69a", "#ab47bc", "#ef5350", "#fdd835"];
|
|
1538
|
-
var hashScriptSource = (source) => {
|
|
1539
|
-
let hash = 5381;
|
|
1540
|
-
for (let i = 0; i < source.length; i += 1) {
|
|
1541
|
-
hash = (hash << 5) + hash + source.charCodeAt(i) | 0;
|
|
1542
|
-
}
|
|
1543
|
-
return (hash >>> 0).toString(36);
|
|
1544
|
-
};
|
|
1545
|
-
var compileScriptCompute = (source) => {
|
|
1546
|
-
const factory = new Function(
|
|
1547
|
-
`"use strict";
|
|
1548
|
-
${source}
|
|
1549
|
-
;if (typeof compute !== "function") { throw new Error("Script must define function compute(bars, inputs, hp)"); }
|
|
1550
|
-
return compute;`
|
|
1551
|
-
);
|
|
1552
|
-
return factory();
|
|
1553
|
-
};
|
|
1554
|
-
var drawScriptError = (ctx, renderContext, name, message) => {
|
|
1555
|
-
ctx.save();
|
|
1556
|
-
ctx.font = "11px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
|
1557
|
-
ctx.textAlign = "left";
|
|
1558
|
-
ctx.textBaseline = "top";
|
|
1559
|
-
ctx.fillStyle = "#ef5350";
|
|
1560
|
-
const text = `${name}: ${message}`.slice(0, 160);
|
|
1561
|
-
ctx.fillText(text, renderContext.chartLeft + 10, renderContext.chartTop + 24);
|
|
1562
|
-
ctx.restore();
|
|
1563
|
-
};
|
|
1564
|
-
var compileScriptIndicator = (definition) => {
|
|
1565
|
-
const pane = definition.pane ?? "separate";
|
|
1566
|
-
const defaultInputs = { showValueLine: true };
|
|
1567
|
-
for (const input of definition.inputs ?? []) {
|
|
1568
|
-
defaultInputs[input.key] = input.default;
|
|
1569
|
-
}
|
|
1570
|
-
let compiled = null;
|
|
1571
|
-
let compileError = null;
|
|
1572
|
-
try {
|
|
1573
|
-
compiled = compileScriptCompute(definition.source);
|
|
1574
|
-
} catch (error) {
|
|
1575
|
-
compileError = error instanceof Error ? error.message : String(error);
|
|
1576
|
-
}
|
|
1577
|
-
const sourceKey = hashScriptSource(definition.source);
|
|
1578
|
-
const runCompute = (data, inputs) => {
|
|
1579
|
-
if (!compiled) {
|
|
1580
|
-
return { error: compileError ?? "Script failed to compile" };
|
|
1581
|
-
}
|
|
1582
|
-
try {
|
|
1583
|
-
const result = compiled(data, inputs, SCRIPT_HELPERS);
|
|
1584
|
-
if (!result || !Array.isArray(result.plots)) {
|
|
1585
|
-
return { error: "compute() must return { plots: [...] }" };
|
|
1586
|
-
}
|
|
1587
|
-
for (const plot of result.plots) {
|
|
1588
|
-
if (!plot || !Array.isArray(plot.values)) {
|
|
1589
|
-
return { error: "Every plot needs a `values` array" };
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
return { result };
|
|
1593
|
-
} catch (error) {
|
|
1594
|
-
return { error: error instanceof Error ? error.message : String(error) };
|
|
1595
|
-
}
|
|
1596
|
-
};
|
|
1597
|
-
const cachedCompute = (data, inputs) => withCachedComputation(
|
|
1598
|
-
`script|${definition.id}|${sourceKey}|${JSON.stringify(inputs)}`,
|
|
1599
|
-
data,
|
|
1600
|
-
() => runCompute(data, inputs)
|
|
1601
|
-
);
|
|
1602
|
-
const plugin = {
|
|
1603
|
-
id: definition.id,
|
|
1604
|
-
name: definition.name,
|
|
1605
|
-
pane,
|
|
1606
|
-
defaultInputs,
|
|
1607
|
-
draw: (ctx, renderContext, inputs) => {
|
|
1608
|
-
const outcome = cachedCompute(renderContext.data, inputs);
|
|
1609
|
-
if (!outcome.result) {
|
|
1610
|
-
drawScriptError(ctx, renderContext, definition.name, outcome.error ?? "Unknown script error");
|
|
1611
|
-
return pane === "separate" ? { title: `${definition.name} (script error)` } : void 0;
|
|
1612
|
-
}
|
|
1613
|
-
const { plots, range, guides, decimals } = outcome.result;
|
|
1614
|
-
if (pane === "separate") {
|
|
1615
|
-
const seriesList = plots.map((plot, index) => ({
|
|
1616
|
-
values: plot.values,
|
|
1617
|
-
color: plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
|
|
1618
|
-
...plot.width !== void 0 ? { width: plot.width } : {},
|
|
1619
|
-
...plot.title ? { label: plot.title } : {},
|
|
1620
|
-
...plot.style === "histogram" ? { histogram: true } : {},
|
|
1621
|
-
...plot.negativeColor ? { negativeColor: plot.negativeColor } : {}
|
|
1622
|
-
}));
|
|
1623
|
-
return drawSeparateMultiSeries(ctx, renderContext, seriesList, {
|
|
1624
|
-
title: definition.name,
|
|
1625
|
-
...range?.min !== void 0 ? { minOverride: range.min } : {},
|
|
1626
|
-
...range?.max !== void 0 ? { maxOverride: range.max } : {},
|
|
1627
|
-
...guides && guides.length > 0 ? { guideLines: guides } : {},
|
|
1628
|
-
...decimals !== void 0 ? { decimals } : {},
|
|
1629
|
-
valueLines: inputs.showValueLine !== false
|
|
1630
|
-
});
|
|
1631
|
-
}
|
|
1632
|
-
for (let index = 0; index < plots.length; index += 1) {
|
|
1633
|
-
const plot = plots[index];
|
|
1634
|
-
if (plot.style === "histogram") continue;
|
|
1635
|
-
drawOverlaySeries(
|
|
1636
|
-
ctx,
|
|
1637
|
-
renderContext,
|
|
1638
|
-
plot.values,
|
|
1639
|
-
plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
|
|
1640
|
-
plot.width ?? 2
|
|
1641
|
-
);
|
|
1642
|
-
}
|
|
1643
|
-
return void 0;
|
|
1644
|
-
},
|
|
1645
|
-
...pane === "overlay" ? {
|
|
1646
|
-
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
1647
|
-
const outcome = cachedCompute(data, inputs);
|
|
1648
|
-
if (!outcome.result) return null;
|
|
1649
|
-
const lineSeries = outcome.result.plots.filter((plot) => plot.style !== "histogram").map((plot) => plot.values);
|
|
1650
|
-
if (lineSeries.length === 0) return null;
|
|
1651
|
-
return rangeOfSeries(lineSeries, startIndex, endIndex, skipIndex);
|
|
1652
|
-
}
|
|
1653
|
-
} : {}
|
|
1654
|
-
};
|
|
1655
|
-
if (definition.paneHeightRatio !== void 0) {
|
|
1656
|
-
plugin.paneHeightRatio = definition.paneHeightRatio;
|
|
1657
|
-
}
|
|
1658
|
-
return plugin;
|
|
1659
|
-
};
|
|
1660
|
-
var computeMacd = (data, fast, slow, signalLength) => {
|
|
1661
|
-
const fastEma = computeEmaSeries(data, fast, "close");
|
|
1662
|
-
const slowEma = computeEmaSeries(data, slow, "close");
|
|
1663
|
-
const macd = fastEma.map((value, idx) => {
|
|
1664
|
-
const slowValue = slowEma[idx];
|
|
1665
|
-
return value == null || slowValue == null ? null : value - slowValue;
|
|
1666
|
-
});
|
|
1667
|
-
const signal = emaFromValues(macd, signalLength);
|
|
1668
|
-
const hist = macd.map((value, idx) => {
|
|
1669
|
-
const signalValue = signal[idx];
|
|
1670
|
-
return value == null || signalValue == null ? null : value - signalValue;
|
|
1671
|
-
});
|
|
1672
|
-
return { macd, signal, hist };
|
|
1220
|
+
var computeMacd = (data, fast, slow, signalLength) => {
|
|
1221
|
+
const fastEma = computeEmaSeries(data, fast, "close");
|
|
1222
|
+
const slowEma = computeEmaSeries(data, slow, "close");
|
|
1223
|
+
const macd = fastEma.map((value, idx) => {
|
|
1224
|
+
const slowValue = slowEma[idx];
|
|
1225
|
+
return value == null || slowValue == null ? null : value - slowValue;
|
|
1226
|
+
});
|
|
1227
|
+
const signal = emaFromValues(macd, signalLength);
|
|
1228
|
+
const hist = macd.map((value, idx) => {
|
|
1229
|
+
const signalValue = signal[idx];
|
|
1230
|
+
return value == null || signalValue == null ? null : value - signalValue;
|
|
1231
|
+
});
|
|
1232
|
+
return { macd, signal, hist };
|
|
1673
1233
|
};
|
|
1674
1234
|
var computeStochastic = (data, kLength, kSmoothing, dLength) => {
|
|
1675
1235
|
const highestHigh = rollingExtremeSeries(data.map((point) => point.h), kLength, "max");
|
|
@@ -2283,208 +1843,965 @@ var BUILTIN_PSAR_INDICATOR = {
|
|
|
2283
1843
|
ctx.fill(dots);
|
|
2284
1844
|
ctx.restore();
|
|
2285
1845
|
},
|
|
2286
|
-
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
2287
|
-
const start = Math.max(1e-3, Number(inputs.start) || 0.02);
|
|
2288
|
-
const increment = Math.max(1e-3, Number(inputs.increment) || 0.02);
|
|
2289
|
-
const maximum = Math.max(increment, Number(inputs.maximum) || 0.2);
|
|
2290
|
-
const values = withCachedSeries(
|
|
2291
|
-
`psar|${start}|${increment}|${maximum}`,
|
|
2292
|
-
data,
|
|
2293
|
-
() => computePsarSeries(data, start, increment, maximum)
|
|
2294
|
-
);
|
|
2295
|
-
return rangeOfSeries([values], startIndex, endIndex, skipIndex);
|
|
2296
|
-
}
|
|
1846
|
+
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
1847
|
+
const start = Math.max(1e-3, Number(inputs.start) || 0.02);
|
|
1848
|
+
const increment = Math.max(1e-3, Number(inputs.increment) || 0.02);
|
|
1849
|
+
const maximum = Math.max(increment, Number(inputs.maximum) || 0.2);
|
|
1850
|
+
const values = withCachedSeries(
|
|
1851
|
+
`psar|${start}|${increment}|${maximum}`,
|
|
1852
|
+
data,
|
|
1853
|
+
() => computePsarSeries(data, start, increment, maximum)
|
|
1854
|
+
);
|
|
1855
|
+
return rangeOfSeries([values], startIndex, endIndex, skipIndex);
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
var BUILTIN_SUPERTREND_INDICATOR = {
|
|
1859
|
+
id: "supertrend",
|
|
1860
|
+
name: "SuperTrend",
|
|
1861
|
+
pane: "overlay",
|
|
1862
|
+
defaultInputs: { atrLength: 10, multiplier: 3, upColor: "#26a69a", downColor: "#ef5350", width: 2 },
|
|
1863
|
+
draw: (ctx, renderContext, inputs) => {
|
|
1864
|
+
const atrLength = clampIndicatorLength(inputs.atrLength, 10);
|
|
1865
|
+
const multiplier = Math.max(0.1, Number(inputs.multiplier) || 3);
|
|
1866
|
+
const { up, down } = withCachedComputation(
|
|
1867
|
+
`supertrend|${atrLength}|${multiplier}`,
|
|
1868
|
+
renderContext.data,
|
|
1869
|
+
() => computeSuperTrend(renderContext.data, atrLength, multiplier)
|
|
1870
|
+
);
|
|
1871
|
+
const width = Number(inputs.width) || 2;
|
|
1872
|
+
drawOverlaySeries(ctx, renderContext, up, inputs.upColor ?? "#26a69a", width);
|
|
1873
|
+
drawOverlaySeries(ctx, renderContext, down, inputs.downColor ?? "#ef5350", width);
|
|
1874
|
+
},
|
|
1875
|
+
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
1876
|
+
const atrLength = clampIndicatorLength(inputs.atrLength, 10);
|
|
1877
|
+
const multiplier = Math.max(0.1, Number(inputs.multiplier) || 3);
|
|
1878
|
+
const { up, down } = withCachedComputation(
|
|
1879
|
+
`supertrend|${atrLength}|${multiplier}`,
|
|
1880
|
+
data,
|
|
1881
|
+
() => computeSuperTrend(data, atrLength, multiplier)
|
|
1882
|
+
);
|
|
1883
|
+
return rangeOfSeries([up, down], startIndex, endIndex, skipIndex);
|
|
1884
|
+
}
|
|
1885
|
+
};
|
|
1886
|
+
var BUILTIN_ICHIMOKU_INDICATOR = {
|
|
1887
|
+
id: "ichimoku",
|
|
1888
|
+
name: "Ichimoku",
|
|
1889
|
+
pane: "overlay",
|
|
1890
|
+
defaultInputs: {
|
|
1891
|
+
conversionLength: 9,
|
|
1892
|
+
baseLength: 26,
|
|
1893
|
+
spanBLength: 52,
|
|
1894
|
+
displacement: 26,
|
|
1895
|
+
tenkanColor: "#2962ff",
|
|
1896
|
+
kijunColor: "#b71c1c",
|
|
1897
|
+
spanAColor: "#26a69a",
|
|
1898
|
+
spanBColor: "#ef5350",
|
|
1899
|
+
chikouColor: "#43a047",
|
|
1900
|
+
cloudOpacity: 0.08,
|
|
1901
|
+
showChikou: true
|
|
1902
|
+
},
|
|
1903
|
+
draw: (ctx, renderContext, inputs) => {
|
|
1904
|
+
const conversionLength = clampIndicatorLength(inputs.conversionLength, 9);
|
|
1905
|
+
const baseLength = clampIndicatorLength(inputs.baseLength, 26);
|
|
1906
|
+
const spanBLength = clampIndicatorLength(inputs.spanBLength, 52);
|
|
1907
|
+
const displacement = clampIndicatorLength(inputs.displacement, 26);
|
|
1908
|
+
const { tenkan, kijun, spanA, spanB, chikou } = withCachedComputation(
|
|
1909
|
+
`ichimoku|${conversionLength}|${baseLength}|${spanBLength}|${displacement}`,
|
|
1910
|
+
renderContext.data,
|
|
1911
|
+
() => computeIchimoku(renderContext.data, conversionLength, baseLength, spanBLength, displacement)
|
|
1912
|
+
);
|
|
1913
|
+
const cloudOpacity = Math.min(0.5, Math.max(0, Number(inputs.cloudOpacity) || 0.08));
|
|
1914
|
+
const bullishA = spanA.map((value, idx) => {
|
|
1915
|
+
const other = spanB[idx];
|
|
1916
|
+
return value != null && other != null && value >= other ? value : null;
|
|
1917
|
+
});
|
|
1918
|
+
const bullishB = spanB.map((value, idx) => {
|
|
1919
|
+
const other = spanA[idx];
|
|
1920
|
+
return value != null && other != null && other >= value ? value : null;
|
|
1921
|
+
});
|
|
1922
|
+
const bearishA = spanA.map((value, idx) => {
|
|
1923
|
+
const other = spanB[idx];
|
|
1924
|
+
return value != null && other != null && value < other ? value : null;
|
|
1925
|
+
});
|
|
1926
|
+
const bearishB = spanB.map((value, idx) => {
|
|
1927
|
+
const other = spanA[idx];
|
|
1928
|
+
return value != null && other != null && other < value ? value : null;
|
|
1929
|
+
});
|
|
1930
|
+
fillBetweenSeries(ctx, renderContext, bullishA, bullishB, inputs.spanAColor ?? "#26a69a", cloudOpacity);
|
|
1931
|
+
fillBetweenSeries(ctx, renderContext, bearishB, bearishA, inputs.spanBColor ?? "#ef5350", cloudOpacity);
|
|
1932
|
+
drawOverlaySeries(ctx, renderContext, spanA, inputs.spanAColor ?? "#26a69a", 1);
|
|
1933
|
+
drawOverlaySeries(ctx, renderContext, spanB, inputs.spanBColor ?? "#ef5350", 1);
|
|
1934
|
+
drawOverlaySeries(ctx, renderContext, tenkan, inputs.tenkanColor ?? "#2962ff", 1.5);
|
|
1935
|
+
drawOverlaySeries(ctx, renderContext, kijun, inputs.kijunColor ?? "#b71c1c", 1.5);
|
|
1936
|
+
if (inputs.showChikou !== false) {
|
|
1937
|
+
drawOverlaySeries(ctx, renderContext, chikou, inputs.chikouColor ?? "#43a047", 1);
|
|
1938
|
+
}
|
|
1939
|
+
},
|
|
1940
|
+
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
1941
|
+
const conversionLength = clampIndicatorLength(inputs.conversionLength, 9);
|
|
1942
|
+
const baseLength = clampIndicatorLength(inputs.baseLength, 26);
|
|
1943
|
+
const spanBLength = clampIndicatorLength(inputs.spanBLength, 52);
|
|
1944
|
+
const displacement = clampIndicatorLength(inputs.displacement, 26);
|
|
1945
|
+
const { tenkan, kijun, spanA, spanB } = withCachedComputation(
|
|
1946
|
+
`ichimoku|${conversionLength}|${baseLength}|${spanBLength}|${displacement}`,
|
|
1947
|
+
data,
|
|
1948
|
+
() => computeIchimoku(data, conversionLength, baseLength, spanBLength, displacement)
|
|
1949
|
+
);
|
|
1950
|
+
return rangeOfSeries([tenkan, kijun, spanA, spanB], startIndex, endIndex, skipIndex);
|
|
1951
|
+
}
|
|
1952
|
+
};
|
|
1953
|
+
var BUILTIN_KELTNER_INDICATOR = {
|
|
1954
|
+
id: "keltner",
|
|
1955
|
+
name: "KC",
|
|
1956
|
+
pane: "overlay",
|
|
1957
|
+
defaultInputs: {
|
|
1958
|
+
emaLength: 20,
|
|
1959
|
+
atrLength: 10,
|
|
1960
|
+
multiplier: 2,
|
|
1961
|
+
basisColor: "#2962ff",
|
|
1962
|
+
bandColor: "#2962ff",
|
|
1963
|
+
width: 1.5,
|
|
1964
|
+
fillOpacity: 0.04
|
|
1965
|
+
},
|
|
1966
|
+
draw: (ctx, renderContext, inputs) => {
|
|
1967
|
+
const emaLength = clampIndicatorLength(inputs.emaLength, 20);
|
|
1968
|
+
const atrLength = clampIndicatorLength(inputs.atrLength, 10);
|
|
1969
|
+
const multiplier = Math.max(0.1, Number(inputs.multiplier) || 2);
|
|
1970
|
+
const { basis, upper, lower } = withCachedComputation(
|
|
1971
|
+
`keltner|${emaLength}|${atrLength}|${multiplier}`,
|
|
1972
|
+
renderContext.data,
|
|
1973
|
+
() => computeKeltner(renderContext.data, emaLength, atrLength, multiplier)
|
|
1974
|
+
);
|
|
1975
|
+
const bandColor = inputs.bandColor ?? "#2962ff";
|
|
1976
|
+
const width = Number(inputs.width) || 1.5;
|
|
1977
|
+
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.fillOpacity) || 0.04);
|
|
1978
|
+
drawOverlaySeries(ctx, renderContext, upper, bandColor, width);
|
|
1979
|
+
drawOverlaySeries(ctx, renderContext, lower, bandColor, width);
|
|
1980
|
+
drawOverlaySeries(ctx, renderContext, basis, inputs.basisColor ?? "#2962ff", width);
|
|
1981
|
+
},
|
|
1982
|
+
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
1983
|
+
const emaLength = clampIndicatorLength(inputs.emaLength, 20);
|
|
1984
|
+
const atrLength = clampIndicatorLength(inputs.atrLength, 10);
|
|
1985
|
+
const multiplier = Math.max(0.1, Number(inputs.multiplier) || 2);
|
|
1986
|
+
const { upper, lower } = withCachedComputation(
|
|
1987
|
+
`keltner|${emaLength}|${atrLength}|${multiplier}`,
|
|
1988
|
+
data,
|
|
1989
|
+
() => computeKeltner(data, emaLength, atrLength, multiplier)
|
|
1990
|
+
);
|
|
1991
|
+
return rangeOfSeries([upper, lower], startIndex, endIndex, skipIndex);
|
|
1992
|
+
}
|
|
1993
|
+
};
|
|
1994
|
+
var BUILTIN_DONCHIAN_INDICATOR = {
|
|
1995
|
+
id: "donchian",
|
|
1996
|
+
name: "DC",
|
|
1997
|
+
pane: "overlay",
|
|
1998
|
+
defaultInputs: { length: 20, bandColor: "#2962ff", basisColor: "#ff6d00", width: 1.5, fillOpacity: 0.04 },
|
|
1999
|
+
draw: (ctx, renderContext, inputs) => {
|
|
2000
|
+
const length = clampIndicatorLength(inputs.length, 20);
|
|
2001
|
+
const { upper, lower, basis } = withCachedComputation(
|
|
2002
|
+
`donchian|${length}`,
|
|
2003
|
+
renderContext.data,
|
|
2004
|
+
() => computeDonchian(renderContext.data, length)
|
|
2005
|
+
);
|
|
2006
|
+
const bandColor = inputs.bandColor ?? "#2962ff";
|
|
2007
|
+
const width = Number(inputs.width) || 1.5;
|
|
2008
|
+
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.fillOpacity) || 0.04);
|
|
2009
|
+
drawOverlaySeries(ctx, renderContext, upper, bandColor, width);
|
|
2010
|
+
drawOverlaySeries(ctx, renderContext, lower, bandColor, width);
|
|
2011
|
+
drawOverlaySeries(ctx, renderContext, basis, inputs.basisColor ?? "#ff6d00", width);
|
|
2012
|
+
},
|
|
2013
|
+
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
2014
|
+
const length = clampIndicatorLength(inputs.length, 20);
|
|
2015
|
+
const { upper, lower } = withCachedComputation(`donchian|${length}`, data, () => computeDonchian(data, length));
|
|
2016
|
+
return rangeOfSeries([upper, lower], startIndex, endIndex, skipIndex);
|
|
2017
|
+
}
|
|
2018
|
+
};
|
|
2019
|
+
var BUILTIN_INDICATORS = [
|
|
2020
|
+
BUILTIN_VOLUME_INDICATOR,
|
|
2021
|
+
BUILTIN_SMA_INDICATOR,
|
|
2022
|
+
BUILTIN_EMA_INDICATOR,
|
|
2023
|
+
BUILTIN_RSI_INDICATOR,
|
|
2024
|
+
BUILTIN_WMA_INDICATOR,
|
|
2025
|
+
BUILTIN_VWMA_INDICATOR,
|
|
2026
|
+
BUILTIN_RMA_INDICATOR,
|
|
2027
|
+
BUILTIN_HMA_INDICATOR,
|
|
2028
|
+
BUILTIN_VWAP_INDICATOR,
|
|
2029
|
+
BUILTIN_BOLLINGER_INDICATOR,
|
|
2030
|
+
BUILTIN_STDDEV_INDICATOR,
|
|
2031
|
+
BUILTIN_ATR_INDICATOR,
|
|
2032
|
+
BUILTIN_MACD_INDICATOR,
|
|
2033
|
+
BUILTIN_STOCHASTIC_INDICATOR,
|
|
2034
|
+
BUILTIN_STOCHRSI_INDICATOR,
|
|
2035
|
+
BUILTIN_ADX_INDICATOR,
|
|
2036
|
+
BUILTIN_OBV_INDICATOR,
|
|
2037
|
+
BUILTIN_MFI_INDICATOR,
|
|
2038
|
+
BUILTIN_CCI_INDICATOR,
|
|
2039
|
+
BUILTIN_WILLIAMSR_INDICATOR,
|
|
2040
|
+
BUILTIN_ROC_INDICATOR,
|
|
2041
|
+
BUILTIN_MOMENTUM_INDICATOR,
|
|
2042
|
+
BUILTIN_PSAR_INDICATOR,
|
|
2043
|
+
BUILTIN_SUPERTREND_INDICATOR,
|
|
2044
|
+
BUILTIN_ICHIMOKU_INDICATOR,
|
|
2045
|
+
BUILTIN_KELTNER_INDICATOR,
|
|
2046
|
+
BUILTIN_DONCHIAN_INDICATOR
|
|
2047
|
+
];
|
|
2048
|
+
|
|
2049
|
+
// src/scripts.ts
|
|
2050
|
+
var scriptRollingExtreme = (values, length, mode) => {
|
|
2051
|
+
const fallback = mode === "max" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
|
|
2052
|
+
const filled = new Array(values.length);
|
|
2053
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
2054
|
+
const value = values[i];
|
|
2055
|
+
filled[i] = value != null && Number.isFinite(value) ? value : fallback;
|
|
2056
|
+
}
|
|
2057
|
+
const raw = rollingExtremeSeries(filled, Math.max(1, Math.floor(length)), mode);
|
|
2058
|
+
return raw.map((value) => value != null && Number.isFinite(value) ? value : null);
|
|
2059
|
+
};
|
|
2060
|
+
var scriptStdevSeries = (values, length) => {
|
|
2061
|
+
const window2 = Math.max(1, Math.floor(length));
|
|
2062
|
+
const result = new Array(values.length).fill(null);
|
|
2063
|
+
let sum = 0;
|
|
2064
|
+
let sumSq = 0;
|
|
2065
|
+
let valid = 0;
|
|
2066
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
2067
|
+
const value = values[i];
|
|
2068
|
+
if (value == null || !Number.isFinite(value)) {
|
|
2069
|
+
sum = 0;
|
|
2070
|
+
sumSq = 0;
|
|
2071
|
+
valid = 0;
|
|
2072
|
+
continue;
|
|
2073
|
+
}
|
|
2074
|
+
sum += value;
|
|
2075
|
+
sumSq += value * value;
|
|
2076
|
+
valid += 1;
|
|
2077
|
+
if (valid > window2) {
|
|
2078
|
+
const old = values[i - window2];
|
|
2079
|
+
sum -= old;
|
|
2080
|
+
sumSq -= old * old;
|
|
2081
|
+
valid -= 1;
|
|
2082
|
+
}
|
|
2083
|
+
if (valid === window2) {
|
|
2084
|
+
const mean = sum / window2;
|
|
2085
|
+
result[i] = Math.sqrt(Math.max(0, sumSq / window2 - mean * mean));
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
return result;
|
|
2089
|
+
};
|
|
2090
|
+
var scriptAtrSeries = (bars, length) => {
|
|
2091
|
+
const tr = new Array(bars.length).fill(null);
|
|
2092
|
+
for (let i = 0; i < bars.length; i += 1) {
|
|
2093
|
+
const bar = bars[i];
|
|
2094
|
+
if (i === 0) {
|
|
2095
|
+
tr[i] = bar.h - bar.l;
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2098
|
+
const prevClose = bars[i - 1].c;
|
|
2099
|
+
tr[i] = Math.max(bar.h - bar.l, Math.abs(bar.h - prevClose), Math.abs(bar.l - prevClose));
|
|
2100
|
+
}
|
|
2101
|
+
return rmaFromValues(tr, Math.max(1, Math.floor(length)));
|
|
2102
|
+
};
|
|
2103
|
+
var SCRIPT_HELPERS = Object.freeze({
|
|
2104
|
+
sma: (values, length) => smaFromValues(values, Math.max(1, Math.floor(length))),
|
|
2105
|
+
ema: (values, length) => emaFromValues(values, Math.max(1, Math.floor(length))),
|
|
2106
|
+
rma: (values, length) => rmaFromValues(values, Math.max(1, Math.floor(length))),
|
|
2107
|
+
highest: (values, length) => scriptRollingExtreme(values, length, "max"),
|
|
2108
|
+
lowest: (values, length) => scriptRollingExtreme(values, length, "min"),
|
|
2109
|
+
change: (values, length = 1) => values.map((value, index) => {
|
|
2110
|
+
const prev = values[index - Math.max(1, Math.floor(length))];
|
|
2111
|
+
return value != null && prev != null ? value - prev : null;
|
|
2112
|
+
}),
|
|
2113
|
+
stdev: scriptStdevSeries,
|
|
2114
|
+
atr: scriptAtrSeries
|
|
2115
|
+
});
|
|
2116
|
+
var SCRIPT_PLOT_PALETTE = ["#2962ff", "#ff6d00", "#26a69a", "#ab47bc", "#ef5350", "#fdd835"];
|
|
2117
|
+
var hashScriptSource = (source) => {
|
|
2118
|
+
let hash = 5381;
|
|
2119
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
2120
|
+
hash = (hash << 5) + hash + source.charCodeAt(i) | 0;
|
|
2121
|
+
}
|
|
2122
|
+
return (hash >>> 0).toString(36);
|
|
2123
|
+
};
|
|
2124
|
+
var SCRIPT_GUARD_BUDGET_MS = 1e3;
|
|
2125
|
+
var ScriptBudgetError = class extends Error {
|
|
2126
|
+
constructor() {
|
|
2127
|
+
super(
|
|
2128
|
+
`Script exceeded the ${SCRIPT_GUARD_BUDGET_MS}ms execution budget (possible infinite loop) \u2014 edit the script to retry`
|
|
2129
|
+
);
|
|
2130
|
+
this.name = "ScriptBudgetError";
|
|
2131
|
+
}
|
|
2132
|
+
};
|
|
2133
|
+
var createScriptGuard = () => {
|
|
2134
|
+
let deadline = Date.now() + SCRIPT_GUARD_BUDGET_MS;
|
|
2135
|
+
let count = 0;
|
|
2136
|
+
const guard = (() => {
|
|
2137
|
+
count += 1;
|
|
2138
|
+
if ((count & 1023) === 0 && Date.now() > deadline) {
|
|
2139
|
+
throw new ScriptBudgetError();
|
|
2140
|
+
}
|
|
2141
|
+
return true;
|
|
2142
|
+
});
|
|
2143
|
+
guard.reset = () => {
|
|
2144
|
+
count = 0;
|
|
2145
|
+
deadline = Date.now() + SCRIPT_GUARD_BUDGET_MS;
|
|
2146
|
+
};
|
|
2147
|
+
return guard;
|
|
2148
|
+
};
|
|
2149
|
+
var injectLoopGuards = (source, guardName) => {
|
|
2150
|
+
const n = source.length;
|
|
2151
|
+
const isIdChar = (ch) => ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_" || ch === "$";
|
|
2152
|
+
const scanString = (start) => {
|
|
2153
|
+
const quote = source[start];
|
|
2154
|
+
let j = start + 1;
|
|
2155
|
+
while (j < n) {
|
|
2156
|
+
const cj = source[j];
|
|
2157
|
+
if (cj === "\\") {
|
|
2158
|
+
j += 2;
|
|
2159
|
+
continue;
|
|
2160
|
+
}
|
|
2161
|
+
if (cj === quote) {
|
|
2162
|
+
return j + 1;
|
|
2163
|
+
}
|
|
2164
|
+
if (quote === "`" && cj === "$" && source[j + 1] === "{") {
|
|
2165
|
+
let depth = 1;
|
|
2166
|
+
j += 2;
|
|
2167
|
+
while (j < n && depth > 0) {
|
|
2168
|
+
const ce = source[j];
|
|
2169
|
+
if (ce === "\\") {
|
|
2170
|
+
j += 2;
|
|
2171
|
+
continue;
|
|
2172
|
+
}
|
|
2173
|
+
if (ce === '"' || ce === "'" || ce === "`") {
|
|
2174
|
+
j = scanString(j);
|
|
2175
|
+
continue;
|
|
2176
|
+
}
|
|
2177
|
+
if (ce === "{") depth += 1;
|
|
2178
|
+
else if (ce === "}") depth -= 1;
|
|
2179
|
+
j += 1;
|
|
2180
|
+
}
|
|
2181
|
+
continue;
|
|
2182
|
+
}
|
|
2183
|
+
j += 1;
|
|
2184
|
+
}
|
|
2185
|
+
return n;
|
|
2186
|
+
};
|
|
2187
|
+
const scanRegex = (start) => {
|
|
2188
|
+
let j = start + 1;
|
|
2189
|
+
let inClass = false;
|
|
2190
|
+
while (j < n) {
|
|
2191
|
+
const cj = source[j];
|
|
2192
|
+
if (cj === "\\") {
|
|
2193
|
+
j += 2;
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
if (cj === "\n") return j;
|
|
2197
|
+
if (cj === "[") inClass = true;
|
|
2198
|
+
else if (cj === "]") inClass = false;
|
|
2199
|
+
else if (cj === "/" && !inClass) return j + 1;
|
|
2200
|
+
j += 1;
|
|
2201
|
+
}
|
|
2202
|
+
return n;
|
|
2203
|
+
};
|
|
2204
|
+
const skipWsAndComments = (k) => {
|
|
2205
|
+
let j = k;
|
|
2206
|
+
while (j < n) {
|
|
2207
|
+
const cj = source[j];
|
|
2208
|
+
if (cj === " " || cj === " " || cj === "\n" || cj === "\r") {
|
|
2209
|
+
j += 1;
|
|
2210
|
+
continue;
|
|
2211
|
+
}
|
|
2212
|
+
if (cj === "/" && source[j + 1] === "/") {
|
|
2213
|
+
const nl = source.indexOf("\n", j);
|
|
2214
|
+
j = nl === -1 ? n : nl + 1;
|
|
2215
|
+
continue;
|
|
2216
|
+
}
|
|
2217
|
+
if (cj === "/" && source[j + 1] === "*") {
|
|
2218
|
+
const end = source.indexOf("*/", j + 2);
|
|
2219
|
+
j = end === -1 ? n : end + 2;
|
|
2220
|
+
continue;
|
|
2221
|
+
}
|
|
2222
|
+
return j;
|
|
2223
|
+
}
|
|
2224
|
+
return n;
|
|
2225
|
+
};
|
|
2226
|
+
const matchParen = (openIdx) => {
|
|
2227
|
+
let depth = 0;
|
|
2228
|
+
let j = openIdx;
|
|
2229
|
+
while (j < n) {
|
|
2230
|
+
const cj = source[j];
|
|
2231
|
+
if (cj === '"' || cj === "'" || cj === "`") {
|
|
2232
|
+
j = scanString(j);
|
|
2233
|
+
continue;
|
|
2234
|
+
}
|
|
2235
|
+
if (cj === "/" && source[j + 1] === "/") {
|
|
2236
|
+
const nl = source.indexOf("\n", j);
|
|
2237
|
+
j = nl === -1 ? n : nl + 1;
|
|
2238
|
+
continue;
|
|
2239
|
+
}
|
|
2240
|
+
if (cj === "/" && source[j + 1] === "*") {
|
|
2241
|
+
const end = source.indexOf("*/", j + 2);
|
|
2242
|
+
j = end === -1 ? n : end + 2;
|
|
2243
|
+
continue;
|
|
2244
|
+
}
|
|
2245
|
+
if (cj === "(") depth += 1;
|
|
2246
|
+
else if (cj === ")") {
|
|
2247
|
+
depth -= 1;
|
|
2248
|
+
if (depth === 0) return j;
|
|
2249
|
+
}
|
|
2250
|
+
j += 1;
|
|
2251
|
+
}
|
|
2252
|
+
return -1;
|
|
2253
|
+
};
|
|
2254
|
+
const splitTopLevelSemicolons = (inner) => {
|
|
2255
|
+
const parts = [];
|
|
2256
|
+
let depth = 0;
|
|
2257
|
+
let j = 0;
|
|
2258
|
+
let last = 0;
|
|
2259
|
+
const m = inner.length;
|
|
2260
|
+
while (j < m) {
|
|
2261
|
+
const cj = inner[j];
|
|
2262
|
+
if (cj === '"' || cj === "'" || cj === "`") {
|
|
2263
|
+
const quote = cj;
|
|
2264
|
+
j += 1;
|
|
2265
|
+
while (j < m) {
|
|
2266
|
+
if (inner[j] === "\\") {
|
|
2267
|
+
j += 2;
|
|
2268
|
+
continue;
|
|
2269
|
+
}
|
|
2270
|
+
if (inner[j] === quote) {
|
|
2271
|
+
j += 1;
|
|
2272
|
+
break;
|
|
2273
|
+
}
|
|
2274
|
+
j += 1;
|
|
2275
|
+
}
|
|
2276
|
+
continue;
|
|
2277
|
+
}
|
|
2278
|
+
if (cj === "(" || cj === "[" || cj === "{") depth += 1;
|
|
2279
|
+
else if (cj === ")" || cj === "]" || cj === "}") depth -= 1;
|
|
2280
|
+
else if (cj === ";" && depth === 0) {
|
|
2281
|
+
parts.push(inner.slice(last, j));
|
|
2282
|
+
last = j + 1;
|
|
2283
|
+
}
|
|
2284
|
+
j += 1;
|
|
2285
|
+
}
|
|
2286
|
+
parts.push(inner.slice(last));
|
|
2287
|
+
return parts;
|
|
2288
|
+
};
|
|
2289
|
+
const REGEX_PRECEDING_WORDS = /* @__PURE__ */ new Set([
|
|
2290
|
+
"return",
|
|
2291
|
+
"typeof",
|
|
2292
|
+
"case",
|
|
2293
|
+
"do",
|
|
2294
|
+
"else",
|
|
2295
|
+
"in",
|
|
2296
|
+
"of",
|
|
2297
|
+
"new",
|
|
2298
|
+
"delete",
|
|
2299
|
+
"void",
|
|
2300
|
+
"instanceof",
|
|
2301
|
+
"yield",
|
|
2302
|
+
"await"
|
|
2303
|
+
]);
|
|
2304
|
+
let out = "";
|
|
2305
|
+
let i = 0;
|
|
2306
|
+
let lastSig = "";
|
|
2307
|
+
let lastWord = "";
|
|
2308
|
+
while (i < n) {
|
|
2309
|
+
const ch = source[i];
|
|
2310
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
2311
|
+
const nl = source.indexOf("\n", i);
|
|
2312
|
+
const end = nl === -1 ? n : nl;
|
|
2313
|
+
out += source.slice(i, end);
|
|
2314
|
+
i = end;
|
|
2315
|
+
continue;
|
|
2316
|
+
}
|
|
2317
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
2318
|
+
const close = source.indexOf("*/", i + 2);
|
|
2319
|
+
const end = close === -1 ? n : close + 2;
|
|
2320
|
+
out += source.slice(i, end);
|
|
2321
|
+
i = end;
|
|
2322
|
+
continue;
|
|
2323
|
+
}
|
|
2324
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
2325
|
+
const end = scanString(i);
|
|
2326
|
+
out += source.slice(i, end);
|
|
2327
|
+
lastSig = ch;
|
|
2328
|
+
lastWord = "";
|
|
2329
|
+
i = end;
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2332
|
+
if (ch === "/") {
|
|
2333
|
+
const regexLikely = lastSig === "" || "(,=:;!&|?{}[+-*%~^<>".includes(lastSig) || REGEX_PRECEDING_WORDS.has(lastWord);
|
|
2334
|
+
if (regexLikely) {
|
|
2335
|
+
const end = scanRegex(i);
|
|
2336
|
+
out += source.slice(i, end);
|
|
2337
|
+
lastSig = "/";
|
|
2338
|
+
lastWord = "";
|
|
2339
|
+
i = end;
|
|
2340
|
+
continue;
|
|
2341
|
+
}
|
|
2342
|
+
out += ch;
|
|
2343
|
+
lastSig = ch;
|
|
2344
|
+
lastWord = "";
|
|
2345
|
+
i += 1;
|
|
2346
|
+
continue;
|
|
2347
|
+
}
|
|
2348
|
+
if (isIdChar(ch)) {
|
|
2349
|
+
let j = i;
|
|
2350
|
+
while (j < n && isIdChar(source[j])) j += 1;
|
|
2351
|
+
const word = source.slice(i, j);
|
|
2352
|
+
if ((word === "while" || word === "for") && lastSig !== ".") {
|
|
2353
|
+
const k = skipWsAndComments(j);
|
|
2354
|
+
if (source[k] === "(") {
|
|
2355
|
+
const close = matchParen(k);
|
|
2356
|
+
if (close !== -1) {
|
|
2357
|
+
const inner = source.slice(k + 1, close);
|
|
2358
|
+
if (word === "while") {
|
|
2359
|
+
out += `while (${guardName}() && (${inner}))`;
|
|
2360
|
+
lastSig = ")";
|
|
2361
|
+
lastWord = "";
|
|
2362
|
+
i = close + 1;
|
|
2363
|
+
continue;
|
|
2364
|
+
}
|
|
2365
|
+
const parts = splitTopLevelSemicolons(inner);
|
|
2366
|
+
if (parts.length === 3) {
|
|
2367
|
+
const cond = parts[1].trim();
|
|
2368
|
+
const guardedCond = cond ? `${guardName}() && (${cond})` : `${guardName}()`;
|
|
2369
|
+
out += `for (${parts[0]}; ${guardedCond}; ${parts[2]})`;
|
|
2370
|
+
lastSig = ")";
|
|
2371
|
+
lastWord = "";
|
|
2372
|
+
i = close + 1;
|
|
2373
|
+
continue;
|
|
2374
|
+
}
|
|
2375
|
+
out += source.slice(i, close + 1);
|
|
2376
|
+
lastSig = ")";
|
|
2377
|
+
lastWord = "";
|
|
2378
|
+
i = close + 1;
|
|
2379
|
+
continue;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
out += word;
|
|
2384
|
+
lastSig = word[word.length - 1];
|
|
2385
|
+
lastWord = word;
|
|
2386
|
+
i = j;
|
|
2387
|
+
continue;
|
|
2388
|
+
}
|
|
2389
|
+
out += ch;
|
|
2390
|
+
if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") {
|
|
2391
|
+
lastSig = ch;
|
|
2392
|
+
lastWord = "";
|
|
2393
|
+
}
|
|
2394
|
+
i += 1;
|
|
2395
|
+
}
|
|
2396
|
+
return out;
|
|
2397
|
+
};
|
|
2398
|
+
var compileScriptCompute = (source, guard) => {
|
|
2399
|
+
const makeFactory = (body) => new Function(
|
|
2400
|
+
"__hpGuard",
|
|
2401
|
+
`"use strict";
|
|
2402
|
+
${body}
|
|
2403
|
+
;if (typeof compute !== "function") { throw new Error("Script must define function compute(bars, inputs, hp)"); }
|
|
2404
|
+
return compute;`
|
|
2405
|
+
);
|
|
2406
|
+
let factory;
|
|
2407
|
+
try {
|
|
2408
|
+
factory = makeFactory(injectLoopGuards(source, "__hpGuard"));
|
|
2409
|
+
} catch {
|
|
2410
|
+
factory = makeFactory(source);
|
|
2411
|
+
}
|
|
2412
|
+
guard.reset();
|
|
2413
|
+
return factory(guard);
|
|
2414
|
+
};
|
|
2415
|
+
var validateScriptSource = (source) => {
|
|
2416
|
+
try {
|
|
2417
|
+
compileScriptCompute(source, createScriptGuard());
|
|
2418
|
+
return null;
|
|
2419
|
+
} catch (error) {
|
|
2420
|
+
return error instanceof Error ? error.message : String(error);
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2423
|
+
var drawScriptError = (ctx, renderContext, name, message) => {
|
|
2424
|
+
ctx.save();
|
|
2425
|
+
ctx.font = "11px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
|
2426
|
+
ctx.textAlign = "left";
|
|
2427
|
+
ctx.textBaseline = "top";
|
|
2428
|
+
ctx.fillStyle = "#ef5350";
|
|
2429
|
+
const text = `${name}: ${message}`.slice(0, 160);
|
|
2430
|
+
ctx.fillText(text, renderContext.chartLeft + 10, renderContext.chartTop + 24);
|
|
2431
|
+
ctx.restore();
|
|
2432
|
+
};
|
|
2433
|
+
var compileScriptIndicator = (definition) => {
|
|
2434
|
+
const pane = definition.pane ?? "separate";
|
|
2435
|
+
const defaultInputs = { showValueLine: true };
|
|
2436
|
+
for (const input of definition.inputs ?? []) {
|
|
2437
|
+
defaultInputs[input.key] = input.default;
|
|
2438
|
+
}
|
|
2439
|
+
let compiled = null;
|
|
2440
|
+
let compileError = null;
|
|
2441
|
+
const guard = createScriptGuard();
|
|
2442
|
+
try {
|
|
2443
|
+
compiled = compileScriptCompute(definition.source, guard);
|
|
2444
|
+
} catch (error) {
|
|
2445
|
+
compileError = error instanceof Error ? error.message : String(error);
|
|
2446
|
+
}
|
|
2447
|
+
const sourceKey = hashScriptSource(definition.source);
|
|
2448
|
+
let trippedError = null;
|
|
2449
|
+
const runCompute = (data, inputs) => {
|
|
2450
|
+
if (!compiled) {
|
|
2451
|
+
return { error: compileError ?? "Script failed to compile" };
|
|
2452
|
+
}
|
|
2453
|
+
if (trippedError) {
|
|
2454
|
+
return { error: trippedError };
|
|
2455
|
+
}
|
|
2456
|
+
try {
|
|
2457
|
+
guard.reset();
|
|
2458
|
+
const result = compiled(data, inputs, SCRIPT_HELPERS);
|
|
2459
|
+
if (!result || !Array.isArray(result.plots)) {
|
|
2460
|
+
return { error: "compute() must return { plots: [...] }" };
|
|
2461
|
+
}
|
|
2462
|
+
for (const plot of result.plots) {
|
|
2463
|
+
if (!plot || !Array.isArray(plot.values)) {
|
|
2464
|
+
return { error: "Every plot needs a `values` array" };
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
return { result };
|
|
2468
|
+
} catch (error) {
|
|
2469
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2470
|
+
if (error instanceof ScriptBudgetError) {
|
|
2471
|
+
trippedError = message;
|
|
2472
|
+
}
|
|
2473
|
+
return { error: message };
|
|
2474
|
+
}
|
|
2475
|
+
};
|
|
2476
|
+
const cachedCompute = (data, inputs) => withCachedComputation(
|
|
2477
|
+
`script|${definition.id}|${sourceKey}|${JSON.stringify(inputs)}`,
|
|
2478
|
+
data,
|
|
2479
|
+
() => runCompute(data, inputs)
|
|
2480
|
+
);
|
|
2481
|
+
const plugin = {
|
|
2482
|
+
id: definition.id,
|
|
2483
|
+
name: definition.name,
|
|
2484
|
+
pane,
|
|
2485
|
+
defaultInputs,
|
|
2486
|
+
draw: (ctx, renderContext, inputs) => {
|
|
2487
|
+
const outcome = cachedCompute(renderContext.data, inputs);
|
|
2488
|
+
if (!outcome.result) {
|
|
2489
|
+
drawScriptError(ctx, renderContext, definition.name, outcome.error ?? "Unknown script error");
|
|
2490
|
+
return pane === "separate" ? { title: `${definition.name} (script error)` } : void 0;
|
|
2491
|
+
}
|
|
2492
|
+
const { plots, range, guides, decimals } = outcome.result;
|
|
2493
|
+
if (pane === "separate") {
|
|
2494
|
+
const seriesList = plots.map((plot, index) => ({
|
|
2495
|
+
values: plot.values,
|
|
2496
|
+
color: plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
|
|
2497
|
+
...plot.width !== void 0 ? { width: plot.width } : {},
|
|
2498
|
+
...plot.title ? { label: plot.title } : {},
|
|
2499
|
+
...plot.style === "histogram" ? { histogram: true } : {},
|
|
2500
|
+
...plot.negativeColor ? { negativeColor: plot.negativeColor } : {}
|
|
2501
|
+
}));
|
|
2502
|
+
return drawSeparateMultiSeries(ctx, renderContext, seriesList, {
|
|
2503
|
+
title: definition.name,
|
|
2504
|
+
...range?.min !== void 0 ? { minOverride: range.min } : {},
|
|
2505
|
+
...range?.max !== void 0 ? { maxOverride: range.max } : {},
|
|
2506
|
+
...guides && guides.length > 0 ? { guideLines: guides } : {},
|
|
2507
|
+
...decimals !== void 0 ? { decimals } : {},
|
|
2508
|
+
valueLines: inputs.showValueLine !== false
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2511
|
+
for (let index = 0; index < plots.length; index += 1) {
|
|
2512
|
+
const plot = plots[index];
|
|
2513
|
+
if (plot.style === "histogram") continue;
|
|
2514
|
+
drawOverlaySeries(
|
|
2515
|
+
ctx,
|
|
2516
|
+
renderContext,
|
|
2517
|
+
plot.values,
|
|
2518
|
+
plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
|
|
2519
|
+
plot.width ?? 2
|
|
2520
|
+
);
|
|
2521
|
+
}
|
|
2522
|
+
return void 0;
|
|
2523
|
+
},
|
|
2524
|
+
...pane === "overlay" ? {
|
|
2525
|
+
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
2526
|
+
const outcome = cachedCompute(data, inputs);
|
|
2527
|
+
if (!outcome.result) return null;
|
|
2528
|
+
const lineSeries = outcome.result.plots.filter((plot) => plot.style !== "histogram").map((plot) => plot.values);
|
|
2529
|
+
if (lineSeries.length === 0) return null;
|
|
2530
|
+
return rangeOfSeries(lineSeries, startIndex, endIndex, skipIndex);
|
|
2531
|
+
}
|
|
2532
|
+
} : {}
|
|
2533
|
+
};
|
|
2534
|
+
if (definition.paneHeightRatio !== void 0) {
|
|
2535
|
+
plugin.paneHeightRatio = definition.paneHeightRatio;
|
|
2536
|
+
}
|
|
2537
|
+
return plugin;
|
|
2538
|
+
};
|
|
2539
|
+
|
|
2540
|
+
// src/options.ts
|
|
2541
|
+
var DEFAULT_GRID_OPTIONS = {
|
|
2542
|
+
color: "#2b2f38",
|
|
2543
|
+
opacity: 0.38,
|
|
2544
|
+
horizontalLines: true,
|
|
2545
|
+
verticalLines: true,
|
|
2546
|
+
xTickCount: 8,
|
|
2547
|
+
yTickCount: 6,
|
|
2548
|
+
horizontalTickCount: 6,
|
|
2549
|
+
sessionSeparators: false,
|
|
2550
|
+
sessionSeparatorColor: "#3b4150",
|
|
2551
|
+
sessionSeparatorOpacity: 0.55
|
|
2552
|
+
};
|
|
2553
|
+
var DEFAULT_AXIS_OPTIONS = {
|
|
2554
|
+
lineColor: "#3b3f47",
|
|
2555
|
+
textColor: "#a9adb6",
|
|
2556
|
+
fontSize: 12,
|
|
2557
|
+
lineWidth: 1
|
|
2558
|
+
};
|
|
2559
|
+
var DEFAULT_CROSSHAIR_OPTIONS = {
|
|
2560
|
+
visible: true,
|
|
2561
|
+
color: "#94a3b8",
|
|
2562
|
+
width: 1,
|
|
2563
|
+
style: "dotted",
|
|
2564
|
+
mode: "cross",
|
|
2565
|
+
dotRadius: 3,
|
|
2566
|
+
showHorizontal: true,
|
|
2567
|
+
showVertical: true,
|
|
2568
|
+
showPriceLabel: true,
|
|
2569
|
+
showTimeLabel: true,
|
|
2570
|
+
timeLabelFormat: "auto",
|
|
2571
|
+
labelBackgroundColor: "#0b1220",
|
|
2572
|
+
labelTextColor: "#cbd5e1",
|
|
2573
|
+
labelBorderRadius: 3,
|
|
2574
|
+
labelBorderColor: "#94a3b8",
|
|
2575
|
+
labelBorderWidth: 1,
|
|
2576
|
+
labelBorderStyle: "solid",
|
|
2577
|
+
showPriceActionButton: false,
|
|
2578
|
+
priceActionButtonIcon: "plusThin",
|
|
2579
|
+
priceActionButtonText: "+",
|
|
2580
|
+
priceActionButtonSize: 16,
|
|
2581
|
+
priceActionButtonGap: 4,
|
|
2582
|
+
priceActionButtonRounded: true,
|
|
2583
|
+
priceActionButtonBorderRadius: 8,
|
|
2584
|
+
sideHintLeft: "",
|
|
2585
|
+
sideHintRight: "",
|
|
2586
|
+
sideHintLeftColor: "#26a69a",
|
|
2587
|
+
sideHintRightColor: "#ef5350"
|
|
2588
|
+
};
|
|
2589
|
+
var DEFAULT_WATERMARK_OPTIONS = {
|
|
2590
|
+
visible: false,
|
|
2591
|
+
text: "",
|
|
2592
|
+
color: "#81858d",
|
|
2593
|
+
opacity: 0.14,
|
|
2594
|
+
fontSize: 92,
|
|
2595
|
+
fontWeight: 700,
|
|
2596
|
+
thickness: 0,
|
|
2597
|
+
imageSrc: "",
|
|
2598
|
+
imageScale: 1,
|
|
2599
|
+
imageMaxWidthRatio: 0.42,
|
|
2600
|
+
imageMaxHeightRatio: 0.3,
|
|
2601
|
+
imageTintColor: "",
|
|
2602
|
+
imageTintOpacity: 1
|
|
2603
|
+
};
|
|
2604
|
+
var DEFAULT_DASH_PATTERNS = {
|
|
2605
|
+
dotted: [2, 2],
|
|
2606
|
+
dashed: [8, 6],
|
|
2607
|
+
connectorDotted: [2, 3],
|
|
2608
|
+
connectorDashed: [6, 5],
|
|
2609
|
+
borderDotted: [2, 2],
|
|
2610
|
+
borderDashed: [6, 4]
|
|
2611
|
+
};
|
|
2612
|
+
var DEFAULT_LABELS_OPTIONS = {
|
|
2613
|
+
visible: true,
|
|
2614
|
+
symbolName: "",
|
|
2615
|
+
showSymbolName: false,
|
|
2616
|
+
showLastPrice: true,
|
|
2617
|
+
showPreviousClose: false,
|
|
2618
|
+
previousClosePrice: Number.NaN,
|
|
2619
|
+
showHighLow: false,
|
|
2620
|
+
showBidAsk: false,
|
|
2621
|
+
bidPrice: Number.NaN,
|
|
2622
|
+
askPrice: Number.NaN,
|
|
2623
|
+
showIndicatorNames: false,
|
|
2624
|
+
showIndicatorValues: false,
|
|
2625
|
+
showIndicatorValueLabels: true,
|
|
2626
|
+
indicatorLegendPosition: "top-left",
|
|
2627
|
+
indicatorLegendOffsetX: 10,
|
|
2628
|
+
indicatorLegendOffsetY: 10,
|
|
2629
|
+
showCountdownToBarClose: false,
|
|
2630
|
+
noOverlapping: true,
|
|
2631
|
+
backgroundColor: "#0b1220",
|
|
2632
|
+
textColor: "#cbd5e1",
|
|
2633
|
+
mutedTextColor: "#94a3b8",
|
|
2634
|
+
symbolNameBackgroundColor: "#1f2937",
|
|
2635
|
+
symbolNameTextColor: "#e5e7eb",
|
|
2636
|
+
previousCloseColor: "#94a3b8",
|
|
2637
|
+
highLowColor: "#a78bfa",
|
|
2638
|
+
bidColor: "#ef4444",
|
|
2639
|
+
askColor: "#22c55e",
|
|
2640
|
+
indicatorTextColor: "#cbd5e1",
|
|
2641
|
+
borderRadius: 3,
|
|
2642
|
+
labelHeight: 20,
|
|
2643
|
+
labelPaddingX: 8
|
|
2644
|
+
};
|
|
2645
|
+
var DEFAULT_PRICE_LINE_OPTIONS = {
|
|
2646
|
+
visible: true,
|
|
2647
|
+
style: "solid",
|
|
2648
|
+
thickness: 1,
|
|
2649
|
+
color: "#38bdf8",
|
|
2650
|
+
labelBackgroundColor: "#0b1220",
|
|
2651
|
+
labelTextColor: "#60a5fa",
|
|
2652
|
+
labelBorderRadius: 3,
|
|
2653
|
+
showLabel: true,
|
|
2654
|
+
pinOutOfRange: false
|
|
2655
|
+
};
|
|
2656
|
+
var DEFAULT_ORDER_LINE_OPTIONS = {
|
|
2657
|
+
visible: true,
|
|
2658
|
+
behavior: "static",
|
|
2659
|
+
style: "solid",
|
|
2660
|
+
thickness: 1,
|
|
2661
|
+
color: "rgba(59,130,246,0.8)",
|
|
2662
|
+
labelBackgroundColor: "#0b1220",
|
|
2663
|
+
labelTextColor: "#60a5fa",
|
|
2664
|
+
labelBorderRadius: 3,
|
|
2665
|
+
showCloseButton: true,
|
|
2666
|
+
widgetPosition: "left",
|
|
2667
|
+
widgetPaddingRight: 10,
|
|
2668
|
+
draggable: false,
|
|
2669
|
+
actionButtonAction: "execute",
|
|
2670
|
+
actionButtonTextColor: "#dbeafe",
|
|
2671
|
+
actionButtonBackgroundColor: "#2563eb",
|
|
2672
|
+
actionButtonBorderRadius: 2,
|
|
2673
|
+
actionButtonMinWidth: 34,
|
|
2674
|
+
actionButtonPaddingX: 0,
|
|
2675
|
+
actionButtonFullHeight: true,
|
|
2676
|
+
actionButtonFontWeight: 500,
|
|
2677
|
+
actionButtonBorderColor: "#2563eb",
|
|
2678
|
+
actionButtonBorderStyle: "solid",
|
|
2679
|
+
actionButtonsInnerGap: 6,
|
|
2680
|
+
actionButtonsGroupGap: 8,
|
|
2681
|
+
actionButtons: [],
|
|
2682
|
+
connectorToPrice: Number.NaN,
|
|
2683
|
+
connectorColor: "#2563eb",
|
|
2684
|
+
connectorStyle: "dotted",
|
|
2685
|
+
connectorThickness: 1,
|
|
2686
|
+
connectorAnchorPaddingRight: 10,
|
|
2687
|
+
fillToPrice: Number.NaN,
|
|
2688
|
+
fillColor: "rgba(37,99,235,0.18)",
|
|
2689
|
+
pinOutOfRange: false
|
|
2690
|
+
};
|
|
2691
|
+
var DEFAULT_OPTIONS = {
|
|
2692
|
+
width: 720,
|
|
2693
|
+
height: 360,
|
|
2694
|
+
backgroundColor: "#101114",
|
|
2695
|
+
axisColor: "#7f8289",
|
|
2696
|
+
axis: DEFAULT_AXIS_OPTIONS,
|
|
2697
|
+
xAxis: DEFAULT_AXIS_OPTIONS,
|
|
2698
|
+
yAxis: DEFAULT_AXIS_OPTIONS,
|
|
2699
|
+
priceDecimals: 2,
|
|
2700
|
+
stabilizePriceLabels: true,
|
|
2701
|
+
priceLabelMinIntegerDigits: 3,
|
|
2702
|
+
priceLabelWidthTemplate: "",
|
|
2703
|
+
initialViewport: "latest",
|
|
2704
|
+
initialVisibleBars: 60,
|
|
2705
|
+
minVisibleBars: 5,
|
|
2706
|
+
maxVisibleBars: 2e4,
|
|
2707
|
+
maxPanBars: 1e6,
|
|
2708
|
+
rightEdgePaddingBars: 2,
|
|
2709
|
+
preserveViewportOnDataUpdate: true,
|
|
2710
|
+
upColor: "#2fb171",
|
|
2711
|
+
downColor: "#d35a5a",
|
|
2712
|
+
gridColor: "#252932",
|
|
2713
|
+
fontFamily: "Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif",
|
|
2714
|
+
candleBodyWidthRatio: 0.7,
|
|
2715
|
+
candleMinWidth: 0.5,
|
|
2716
|
+
candleWickWidth: 1,
|
|
2717
|
+
tickSize: 0,
|
|
2718
|
+
candleColorMode: "openClose",
|
|
2719
|
+
candleColorEpsilon: -1,
|
|
2720
|
+
chartType: "candles",
|
|
2721
|
+
lineColor: "#2962ff",
|
|
2722
|
+
lineWidth: 2,
|
|
2723
|
+
areaFillOpacity: 0.12,
|
|
2724
|
+
baselinePrice: null,
|
|
2725
|
+
// 0 = deterministic scale (lightweight-charts behavior): the y-range is a
|
|
2726
|
+
// pure function of the visible data, so it can never drift or "breathe".
|
|
2727
|
+
// Values > 0 re-enable eased contraction for hosts that prefer it.
|
|
2728
|
+
autoScaleSmoothing: 0,
|
|
2729
|
+
autoScaleIgnoreLatestCandle: true,
|
|
2730
|
+
kineticScroll: { touch: true, mouse: false },
|
|
2731
|
+
timeStepMs: 0,
|
|
2732
|
+
clockOffsetMs: 0,
|
|
2733
|
+
pinOutOfRangeLines: false,
|
|
2734
|
+
doubleClickEnabled: true,
|
|
2735
|
+
doubleClickAction: "reset",
|
|
2736
|
+
crosshair: DEFAULT_CROSSHAIR_OPTIONS,
|
|
2737
|
+
grid: DEFAULT_GRID_OPTIONS,
|
|
2738
|
+
watermark: DEFAULT_WATERMARK_OPTIONS,
|
|
2739
|
+
priceLines: [],
|
|
2740
|
+
orderLines: [],
|
|
2741
|
+
tickerLine: {
|
|
2742
|
+
visible: true,
|
|
2743
|
+
style: "dotted",
|
|
2744
|
+
thickness: 1,
|
|
2745
|
+
labelTextColor: "#0b1220",
|
|
2746
|
+
labelSubtext: "",
|
|
2747
|
+
labelSubtexts: [],
|
|
2748
|
+
labelSubtextColor: "#0b1220",
|
|
2749
|
+
labelSubtextFontSize: 0,
|
|
2750
|
+
showCountdownInLabel: false,
|
|
2751
|
+
labelBorderRadius: 3
|
|
2752
|
+
},
|
|
2753
|
+
labels: DEFAULT_LABELS_OPTIONS,
|
|
2754
|
+
dashPatterns: DEFAULT_DASH_PATTERNS,
|
|
2755
|
+
indicators: [],
|
|
2756
|
+
drawings: []
|
|
2297
2757
|
};
|
|
2298
|
-
var
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
const multiplier = Math.max(0.1, Number(inputs.multiplier) || 3);
|
|
2306
|
-
const { up, down } = withCachedComputation(
|
|
2307
|
-
`supertrend|${atrLength}|${multiplier}`,
|
|
2308
|
-
renderContext.data,
|
|
2309
|
-
() => computeSuperTrend(renderContext.data, atrLength, multiplier)
|
|
2310
|
-
);
|
|
2311
|
-
const width = Number(inputs.width) || 2;
|
|
2312
|
-
drawOverlaySeries(ctx, renderContext, up, inputs.upColor ?? "#26a69a", width);
|
|
2313
|
-
drawOverlaySeries(ctx, renderContext, down, inputs.downColor ?? "#ef5350", width);
|
|
2758
|
+
var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
2759
|
+
...baseOptions,
|
|
2760
|
+
...options,
|
|
2761
|
+
axis: {
|
|
2762
|
+
...baseOptions.axis,
|
|
2763
|
+
...options.axis ?? {},
|
|
2764
|
+
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {}
|
|
2314
2765
|
},
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
data,
|
|
2321
|
-
() => computeSuperTrend(data, atrLength, multiplier)
|
|
2322
|
-
);
|
|
2323
|
-
return rangeOfSeries([up, down], startIndex, endIndex, skipIndex);
|
|
2324
|
-
}
|
|
2325
|
-
};
|
|
2326
|
-
var BUILTIN_ICHIMOKU_INDICATOR = {
|
|
2327
|
-
id: "ichimoku",
|
|
2328
|
-
name: "Ichimoku",
|
|
2329
|
-
pane: "overlay",
|
|
2330
|
-
defaultInputs: {
|
|
2331
|
-
conversionLength: 9,
|
|
2332
|
-
baseLength: 26,
|
|
2333
|
-
spanBLength: 52,
|
|
2334
|
-
displacement: 26,
|
|
2335
|
-
tenkanColor: "#2962ff",
|
|
2336
|
-
kijunColor: "#b71c1c",
|
|
2337
|
-
spanAColor: "#26a69a",
|
|
2338
|
-
spanBColor: "#ef5350",
|
|
2339
|
-
chikouColor: "#43a047",
|
|
2340
|
-
cloudOpacity: 0.08,
|
|
2341
|
-
showChikou: true
|
|
2766
|
+
xAxis: {
|
|
2767
|
+
...baseOptions.xAxis,
|
|
2768
|
+
...options.axis ?? {},
|
|
2769
|
+
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
|
|
2770
|
+
...options.xAxis ?? {}
|
|
2342
2771
|
},
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
const { tenkan, kijun, spanA, spanB, chikou } = withCachedComputation(
|
|
2349
|
-
`ichimoku|${conversionLength}|${baseLength}|${spanBLength}|${displacement}`,
|
|
2350
|
-
renderContext.data,
|
|
2351
|
-
() => computeIchimoku(renderContext.data, conversionLength, baseLength, spanBLength, displacement)
|
|
2352
|
-
);
|
|
2353
|
-
const cloudOpacity = Math.min(0.5, Math.max(0, Number(inputs.cloudOpacity) || 0.08));
|
|
2354
|
-
const bullishA = spanA.map((value, idx) => {
|
|
2355
|
-
const other = spanB[idx];
|
|
2356
|
-
return value != null && other != null && value >= other ? value : null;
|
|
2357
|
-
});
|
|
2358
|
-
const bullishB = spanB.map((value, idx) => {
|
|
2359
|
-
const other = spanA[idx];
|
|
2360
|
-
return value != null && other != null && other >= value ? value : null;
|
|
2361
|
-
});
|
|
2362
|
-
const bearishA = spanA.map((value, idx) => {
|
|
2363
|
-
const other = spanB[idx];
|
|
2364
|
-
return value != null && other != null && value < other ? value : null;
|
|
2365
|
-
});
|
|
2366
|
-
const bearishB = spanB.map((value, idx) => {
|
|
2367
|
-
const other = spanA[idx];
|
|
2368
|
-
return value != null && other != null && other < value ? value : null;
|
|
2369
|
-
});
|
|
2370
|
-
fillBetweenSeries(ctx, renderContext, bullishA, bullishB, inputs.spanAColor ?? "#26a69a", cloudOpacity);
|
|
2371
|
-
fillBetweenSeries(ctx, renderContext, bearishB, bearishA, inputs.spanBColor ?? "#ef5350", cloudOpacity);
|
|
2372
|
-
drawOverlaySeries(ctx, renderContext, spanA, inputs.spanAColor ?? "#26a69a", 1);
|
|
2373
|
-
drawOverlaySeries(ctx, renderContext, spanB, inputs.spanBColor ?? "#ef5350", 1);
|
|
2374
|
-
drawOverlaySeries(ctx, renderContext, tenkan, inputs.tenkanColor ?? "#2962ff", 1.5);
|
|
2375
|
-
drawOverlaySeries(ctx, renderContext, kijun, inputs.kijunColor ?? "#b71c1c", 1.5);
|
|
2376
|
-
if (inputs.showChikou !== false) {
|
|
2377
|
-
drawOverlaySeries(ctx, renderContext, chikou, inputs.chikouColor ?? "#43a047", 1);
|
|
2378
|
-
}
|
|
2772
|
+
yAxis: {
|
|
2773
|
+
...baseOptions.yAxis,
|
|
2774
|
+
...options.axis ?? {},
|
|
2775
|
+
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
|
|
2776
|
+
...options.yAxis ?? {}
|
|
2379
2777
|
},
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
const spanBLength = clampIndicatorLength(inputs.spanBLength, 52);
|
|
2384
|
-
const displacement = clampIndicatorLength(inputs.displacement, 26);
|
|
2385
|
-
const { tenkan, kijun, spanA, spanB } = withCachedComputation(
|
|
2386
|
-
`ichimoku|${conversionLength}|${baseLength}|${spanBLength}|${displacement}`,
|
|
2387
|
-
data,
|
|
2388
|
-
() => computeIchimoku(data, conversionLength, baseLength, spanBLength, displacement)
|
|
2389
|
-
);
|
|
2390
|
-
return rangeOfSeries([tenkan, kijun, spanA, spanB], startIndex, endIndex, skipIndex);
|
|
2391
|
-
}
|
|
2392
|
-
};
|
|
2393
|
-
var BUILTIN_KELTNER_INDICATOR = {
|
|
2394
|
-
id: "keltner",
|
|
2395
|
-
name: "KC",
|
|
2396
|
-
pane: "overlay",
|
|
2397
|
-
defaultInputs: {
|
|
2398
|
-
emaLength: 20,
|
|
2399
|
-
atrLength: 10,
|
|
2400
|
-
multiplier: 2,
|
|
2401
|
-
basisColor: "#2962ff",
|
|
2402
|
-
bandColor: "#2962ff",
|
|
2403
|
-
width: 1.5,
|
|
2404
|
-
fillOpacity: 0.04
|
|
2778
|
+
crosshair: {
|
|
2779
|
+
...baseOptions.crosshair,
|
|
2780
|
+
...options.crosshair ?? {}
|
|
2405
2781
|
},
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
const multiplier = Math.max(0.1, Number(inputs.multiplier) || 2);
|
|
2410
|
-
const { basis, upper, lower } = withCachedComputation(
|
|
2411
|
-
`keltner|${emaLength}|${atrLength}|${multiplier}`,
|
|
2412
|
-
renderContext.data,
|
|
2413
|
-
() => computeKeltner(renderContext.data, emaLength, atrLength, multiplier)
|
|
2414
|
-
);
|
|
2415
|
-
const bandColor = inputs.bandColor ?? "#2962ff";
|
|
2416
|
-
const width = Number(inputs.width) || 1.5;
|
|
2417
|
-
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.fillOpacity) || 0.04);
|
|
2418
|
-
drawOverlaySeries(ctx, renderContext, upper, bandColor, width);
|
|
2419
|
-
drawOverlaySeries(ctx, renderContext, lower, bandColor, width);
|
|
2420
|
-
drawOverlaySeries(ctx, renderContext, basis, inputs.basisColor ?? "#2962ff", width);
|
|
2782
|
+
grid: {
|
|
2783
|
+
...baseOptions.grid,
|
|
2784
|
+
...options.grid ?? {}
|
|
2421
2785
|
},
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
const multiplier = Math.max(0.1, Number(inputs.multiplier) || 2);
|
|
2426
|
-
const { upper, lower } = withCachedComputation(
|
|
2427
|
-
`keltner|${emaLength}|${atrLength}|${multiplier}`,
|
|
2428
|
-
data,
|
|
2429
|
-
() => computeKeltner(data, emaLength, atrLength, multiplier)
|
|
2430
|
-
);
|
|
2431
|
-
return rangeOfSeries([upper, lower], startIndex, endIndex, skipIndex);
|
|
2432
|
-
}
|
|
2433
|
-
};
|
|
2434
|
-
var BUILTIN_DONCHIAN_INDICATOR = {
|
|
2435
|
-
id: "donchian",
|
|
2436
|
-
name: "DC",
|
|
2437
|
-
pane: "overlay",
|
|
2438
|
-
defaultInputs: { length: 20, bandColor: "#2962ff", basisColor: "#ff6d00", width: 1.5, fillOpacity: 0.04 },
|
|
2439
|
-
draw: (ctx, renderContext, inputs) => {
|
|
2440
|
-
const length = clampIndicatorLength(inputs.length, 20);
|
|
2441
|
-
const { upper, lower, basis } = withCachedComputation(
|
|
2442
|
-
`donchian|${length}`,
|
|
2443
|
-
renderContext.data,
|
|
2444
|
-
() => computeDonchian(renderContext.data, length)
|
|
2445
|
-
);
|
|
2446
|
-
const bandColor = inputs.bandColor ?? "#2962ff";
|
|
2447
|
-
const width = Number(inputs.width) || 1.5;
|
|
2448
|
-
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.fillOpacity) || 0.04);
|
|
2449
|
-
drawOverlaySeries(ctx, renderContext, upper, bandColor, width);
|
|
2450
|
-
drawOverlaySeries(ctx, renderContext, lower, bandColor, width);
|
|
2451
|
-
drawOverlaySeries(ctx, renderContext, basis, inputs.basisColor ?? "#ff6d00", width);
|
|
2786
|
+
watermark: {
|
|
2787
|
+
...baseOptions.watermark,
|
|
2788
|
+
...options.watermark ?? {}
|
|
2452
2789
|
},
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2790
|
+
tickerLine: {
|
|
2791
|
+
...baseOptions.tickerLine,
|
|
2792
|
+
...options.tickerLine ?? {}
|
|
2793
|
+
},
|
|
2794
|
+
labels: {
|
|
2795
|
+
...baseOptions.labels,
|
|
2796
|
+
...options.labels ?? {}
|
|
2797
|
+
},
|
|
2798
|
+
dashPatterns: {
|
|
2799
|
+
...baseOptions.dashPatterns,
|
|
2800
|
+
...options.dashPatterns ?? {}
|
|
2457
2801
|
}
|
|
2458
|
-
};
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
BUILTIN_SMA_INDICATOR,
|
|
2462
|
-
BUILTIN_EMA_INDICATOR,
|
|
2463
|
-
BUILTIN_RSI_INDICATOR,
|
|
2464
|
-
BUILTIN_WMA_INDICATOR,
|
|
2465
|
-
BUILTIN_VWMA_INDICATOR,
|
|
2466
|
-
BUILTIN_RMA_INDICATOR,
|
|
2467
|
-
BUILTIN_HMA_INDICATOR,
|
|
2468
|
-
BUILTIN_VWAP_INDICATOR,
|
|
2469
|
-
BUILTIN_BOLLINGER_INDICATOR,
|
|
2470
|
-
BUILTIN_STDDEV_INDICATOR,
|
|
2471
|
-
BUILTIN_ATR_INDICATOR,
|
|
2472
|
-
BUILTIN_MACD_INDICATOR,
|
|
2473
|
-
BUILTIN_STOCHASTIC_INDICATOR,
|
|
2474
|
-
BUILTIN_STOCHRSI_INDICATOR,
|
|
2475
|
-
BUILTIN_ADX_INDICATOR,
|
|
2476
|
-
BUILTIN_OBV_INDICATOR,
|
|
2477
|
-
BUILTIN_MFI_INDICATOR,
|
|
2478
|
-
BUILTIN_CCI_INDICATOR,
|
|
2479
|
-
BUILTIN_WILLIAMSR_INDICATOR,
|
|
2480
|
-
BUILTIN_ROC_INDICATOR,
|
|
2481
|
-
BUILTIN_MOMENTUM_INDICATOR,
|
|
2482
|
-
BUILTIN_PSAR_INDICATOR,
|
|
2483
|
-
BUILTIN_SUPERTREND_INDICATOR,
|
|
2484
|
-
BUILTIN_ICHIMOKU_INDICATOR,
|
|
2485
|
-
BUILTIN_KELTNER_INDICATOR,
|
|
2486
|
-
BUILTIN_DONCHIAN_INDICATOR
|
|
2487
|
-
];
|
|
2802
|
+
});
|
|
2803
|
+
|
|
2804
|
+
// src/chart.ts
|
|
2488
2805
|
function createChart(element, options = {}) {
|
|
2489
2806
|
let mergedOptions = mergeChartOptions(DEFAULT_OPTIONS, options);
|
|
2490
2807
|
let resolvedAxis = { ...DEFAULT_AXIS_OPTIONS, ...mergedOptions.axis ?? {} };
|
|
@@ -8040,5 +8357,6 @@ export {
|
|
|
8040
8357
|
FIB_DEFAULT_PALETTE,
|
|
8041
8358
|
POSITION_DEFAULT_COLORS,
|
|
8042
8359
|
compileScriptIndicator,
|
|
8043
|
-
createChart
|
|
8360
|
+
createChart,
|
|
8361
|
+
validateScriptSource
|
|
8044
8362
|
};
|