@backtest-kit/signals 0.0.1
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/README.md +302 -0
- package/build/index.cjs +3004 -0
- package/build/index.mjs +2991 -0
- package/package.json +81 -0
- package/types.d.ts +294 -0
package/build/index.cjs
ADDED
|
@@ -0,0 +1,3004 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var tradingSignals = require('trading-signals');
|
|
4
|
+
var backtestKit = require('backtest-kit');
|
|
5
|
+
var diKit = require('di-kit');
|
|
6
|
+
var functoolsKit = require('functools-kit');
|
|
7
|
+
|
|
8
|
+
const { provide, inject, init, override } = diKit.createActivator("signal");
|
|
9
|
+
|
|
10
|
+
const commonServices$1 = {
|
|
11
|
+
loggerService: Symbol("loggerService"),
|
|
12
|
+
};
|
|
13
|
+
const mathServices$1 = {
|
|
14
|
+
longTermMathService: Symbol('longTermMathService'),
|
|
15
|
+
swingTermMathService: Symbol('swingTermMathService'),
|
|
16
|
+
shortTermMathService: Symbol('shortTermMathService'),
|
|
17
|
+
microTermMathService: Symbol('microTermMathService'),
|
|
18
|
+
bookDataMathService: Symbol('bookDataMathService'),
|
|
19
|
+
};
|
|
20
|
+
const historyServices$1 = {
|
|
21
|
+
fifteenMinuteCandleHistoryService: Symbol('fifteenMinuteCandleHistoryService'),
|
|
22
|
+
hourCandleHistoryService: Symbol('hourCandleHistoryService'),
|
|
23
|
+
oneMinuteCandleHistoryService: Symbol('oneMinuteCandleHistoryService'),
|
|
24
|
+
thirtyMinuteCandleHistoryService: Symbol('thirtyMinuteCandleHistoryService'),
|
|
25
|
+
};
|
|
26
|
+
const TYPES = {
|
|
27
|
+
...commonServices$1,
|
|
28
|
+
...mathServices$1,
|
|
29
|
+
...historyServices$1,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const TABLE_ROWS_LIMIT$3 = 48;
|
|
33
|
+
const WARMUP_PERIOD$3 = 50;
|
|
34
|
+
const columns$3 = [
|
|
35
|
+
{
|
|
36
|
+
key: "rsi14",
|
|
37
|
+
label: "RSI(14)",
|
|
38
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: "stochasticRSI14",
|
|
42
|
+
label: "Stochastic RSI(14)",
|
|
43
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
key: "macd12_26_9",
|
|
47
|
+
label: "MACD(12,26,9)",
|
|
48
|
+
format: (v) => (v !== null ? Number(v).toFixed(4) : "N/A"),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
key: "signal9",
|
|
52
|
+
label: "Signal(9)",
|
|
53
|
+
format: (v) => (v !== null ? Number(v).toFixed(4) : "N/A"),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
key: "adx14",
|
|
57
|
+
label: "ADX(14)",
|
|
58
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
key: "pdi14",
|
|
62
|
+
label: "+DI(14)",
|
|
63
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: "ndi14",
|
|
67
|
+
label: "-DI(14)",
|
|
68
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
key: "atr14",
|
|
72
|
+
label: "ATR(14)",
|
|
73
|
+
format: async (v, symbol) => v !== null
|
|
74
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
75
|
+
: "N/A",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
key: "atr14_raw",
|
|
79
|
+
label: "ATR(14) Raw",
|
|
80
|
+
format: async (v, symbol) => v !== null
|
|
81
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
82
|
+
: "N/A",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: "atr20",
|
|
86
|
+
label: "ATR(20)",
|
|
87
|
+
format: async (v, symbol) => v !== null
|
|
88
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
89
|
+
: "N/A",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
key: "cci20",
|
|
93
|
+
label: "CCI(20)",
|
|
94
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
key: "stochastic14_3_3_K",
|
|
98
|
+
label: "Stochastic K(14,3,3)",
|
|
99
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
key: "stochastic14_3_3_D",
|
|
103
|
+
label: "Stochastic D(14,3,3)",
|
|
104
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
key: "momentum10",
|
|
108
|
+
label: "Momentum(10)",
|
|
109
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
key: "dema21",
|
|
113
|
+
label: "DEMA(21)",
|
|
114
|
+
format: async (v, symbol) => v !== null
|
|
115
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
116
|
+
: "N/A",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
key: "wma20",
|
|
120
|
+
label: "WMA(20)",
|
|
121
|
+
format: async (v, symbol) => v !== null
|
|
122
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
123
|
+
: "N/A",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
key: "sma50",
|
|
127
|
+
label: "SMA(50)",
|
|
128
|
+
format: async (v, symbol) => v !== null
|
|
129
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
130
|
+
: "N/A",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
key: "ema20",
|
|
134
|
+
label: "EMA(20)",
|
|
135
|
+
format: async (v, symbol) => v !== null
|
|
136
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
137
|
+
: "N/A",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
key: "ema34",
|
|
141
|
+
label: "EMA(34)",
|
|
142
|
+
format: async (v, symbol) => v !== null
|
|
143
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
144
|
+
: "N/A",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
key: "currentPrice",
|
|
148
|
+
label: "Current Price",
|
|
149
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
key: "support",
|
|
153
|
+
label: "Support Level",
|
|
154
|
+
format: async (v, symbol) => v !== null
|
|
155
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
156
|
+
: "N/A",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
key: "resistance",
|
|
160
|
+
label: "Resistance Level",
|
|
161
|
+
format: async (v, symbol) => v !== null
|
|
162
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
163
|
+
: "N/A",
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
key: "bollinger20_2_upper",
|
|
167
|
+
label: "Bollinger Upper(20,2.0)",
|
|
168
|
+
format: async (v, symbol) => v !== null
|
|
169
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
170
|
+
: "N/A",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
key: "bollinger20_2_middle",
|
|
174
|
+
label: "Bollinger Middle(20,2.0)",
|
|
175
|
+
format: async (v, symbol) => v !== null
|
|
176
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
177
|
+
: "N/A",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
key: "bollinger20_2_lower",
|
|
181
|
+
label: "Bollinger Lower(20,2.0)",
|
|
182
|
+
format: async (v, symbol) => v !== null
|
|
183
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
184
|
+
: "N/A",
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
key: "fibonacciNearestLevel",
|
|
188
|
+
label: "Fibonacci Nearest Level",
|
|
189
|
+
format: (v) => String(v),
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
key: "fibonacciNearestPrice",
|
|
193
|
+
label: "Fibonacci Nearest Price",
|
|
194
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
key: "fibonacciDistance",
|
|
198
|
+
label: "Fibonacci Distance",
|
|
199
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
key: "bodySize",
|
|
203
|
+
label: "Body Size",
|
|
204
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
key: "closePrice",
|
|
208
|
+
label: "Close Price",
|
|
209
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
key: "date",
|
|
213
|
+
label: "Timestamp",
|
|
214
|
+
format: (v) => new Date(v).toISOString(),
|
|
215
|
+
},
|
|
216
|
+
];
|
|
217
|
+
function isUnsafe$4(value) {
|
|
218
|
+
if (typeof value !== "number") {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
if (isNaN(value)) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
if (!isFinite(value)) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
function calculateFibonacciLevels$2(candles, endIndex) {
|
|
230
|
+
const lookbackPeriod = Math.min(24, endIndex + 1);
|
|
231
|
+
const startIndex = endIndex + 1 - lookbackPeriod;
|
|
232
|
+
const recentCandles = candles.slice(startIndex, endIndex + 1);
|
|
233
|
+
const high = Math.max(...recentCandles.map((c) => Number(c.high)));
|
|
234
|
+
const low = Math.min(...recentCandles.map((c) => Number(c.low)));
|
|
235
|
+
const range = high - low;
|
|
236
|
+
const levels = {
|
|
237
|
+
"0.0%": high,
|
|
238
|
+
"23.6%": high - range * 0.236,
|
|
239
|
+
"38.2%": high - range * 0.382,
|
|
240
|
+
"50.0%": high - range * 0.5,
|
|
241
|
+
"61.8%": high - range * 0.618,
|
|
242
|
+
"78.6%": high - range * 0.786,
|
|
243
|
+
"100.0%": low,
|
|
244
|
+
"127.2%": high - range * 1.272,
|
|
245
|
+
"161.8%": high - range * 1.618,
|
|
246
|
+
};
|
|
247
|
+
const currentPrice = Number(candles[endIndex].close);
|
|
248
|
+
let nearestLevel = {
|
|
249
|
+
level: "50.0%",
|
|
250
|
+
price: levels["50.0%"],
|
|
251
|
+
distance: Math.abs(currentPrice - levels["50.0%"]),
|
|
252
|
+
};
|
|
253
|
+
Object.entries(levels).forEach(([level, price]) => {
|
|
254
|
+
const distance = Math.abs(currentPrice - price);
|
|
255
|
+
if (distance < nearestLevel.distance) {
|
|
256
|
+
nearestLevel = { level, price, distance };
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
return nearestLevel;
|
|
260
|
+
}
|
|
261
|
+
function generateAnalysis$3(symbol, candles) {
|
|
262
|
+
const closes = candles.map((candle) => Number(candle.close));
|
|
263
|
+
const highs = candles.map((candle) => Number(candle.high));
|
|
264
|
+
const lows = candles.map((candle) => Number(candle.low));
|
|
265
|
+
const volumes = candles.map((candle) => Number(candle.volume));
|
|
266
|
+
const opens = candles.map((candle) => Number(candle.open));
|
|
267
|
+
const rsi = new tradingSignals.FasterRSI(14);
|
|
268
|
+
const stochasticRSI = new tradingSignals.FasterStochasticRSI(14);
|
|
269
|
+
const macdShortEMA = new tradingSignals.FasterEMA(12);
|
|
270
|
+
const macdLongEMA = new tradingSignals.FasterEMA(26);
|
|
271
|
+
const macdSignalEMA = new tradingSignals.FasterEMA(9);
|
|
272
|
+
const macd = new tradingSignals.FasterMACD(macdShortEMA, macdLongEMA, macdSignalEMA);
|
|
273
|
+
const bollinger = new tradingSignals.FasterBollingerBands(20, 2.0);
|
|
274
|
+
const atr14 = new tradingSignals.FasterATR(14);
|
|
275
|
+
const atr20 = new tradingSignals.FasterATR(20);
|
|
276
|
+
const ema20 = new tradingSignals.FasterEMA(20);
|
|
277
|
+
const ema34 = new tradingSignals.FasterEMA(34);
|
|
278
|
+
const dema = new tradingSignals.FasterDEMA(21);
|
|
279
|
+
const wma = new tradingSignals.FasterWMA(20);
|
|
280
|
+
const momentum = new tradingSignals.FasterMOM(10);
|
|
281
|
+
const stochastic = new tradingSignals.FasterStochasticOscillator(14, 3, 3);
|
|
282
|
+
const adx = new tradingSignals.FasterADX(14);
|
|
283
|
+
const cci = new tradingSignals.FasterCCI(20);
|
|
284
|
+
const sma50 = new tradingSignals.FasterSMA(50);
|
|
285
|
+
const results = [];
|
|
286
|
+
candles.forEach((_candle, i) => {
|
|
287
|
+
const high = highs[i];
|
|
288
|
+
const low = lows[i];
|
|
289
|
+
const close = closes[i];
|
|
290
|
+
const open = opens[i];
|
|
291
|
+
const currentPrice = close;
|
|
292
|
+
// Update all indicators
|
|
293
|
+
rsi.update(close, false);
|
|
294
|
+
stochasticRSI.update(close, false);
|
|
295
|
+
macd.update(close, false);
|
|
296
|
+
bollinger.update(close, false);
|
|
297
|
+
atr14.update({ high, low, close }, false);
|
|
298
|
+
atr20.update({ high, low, close }, false);
|
|
299
|
+
ema20.update(close, false);
|
|
300
|
+
ema34.update(close, false);
|
|
301
|
+
dema.update(close, false);
|
|
302
|
+
wma.update(close, false);
|
|
303
|
+
momentum.update(close, false);
|
|
304
|
+
stochastic.update({ high, low, close }, false);
|
|
305
|
+
adx.update({ high, low, close }, false);
|
|
306
|
+
cci.update({ high, low, close }, false);
|
|
307
|
+
sma50.update(close, false);
|
|
308
|
+
// Determine minimum warm-up period needed (largest indicator period)
|
|
309
|
+
// SMA(50) is the largest period
|
|
310
|
+
// Skip rows until all indicators are warmed up
|
|
311
|
+
if (i < WARMUP_PERIOD$3) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
// Volume trend calculation
|
|
315
|
+
const volumeSma6 = new tradingSignals.FasterSMA(6);
|
|
316
|
+
const volumeSma6Prev = new tradingSignals.FasterSMA(6);
|
|
317
|
+
const volumeStart = Math.max(0, i + 1 - 12);
|
|
318
|
+
const prevVolumeData = volumes.slice(volumeStart, Math.min(volumeStart + 6, i + 1));
|
|
319
|
+
const recentVolumeData = volumes.slice(Math.max(0, i + 1 - 6), i + 1);
|
|
320
|
+
if (prevVolumeData.length > 0) {
|
|
321
|
+
prevVolumeData.forEach((vol) => volumeSma6Prev.update(vol, false));
|
|
322
|
+
}
|
|
323
|
+
if (recentVolumeData.length > 0) {
|
|
324
|
+
recentVolumeData.forEach((vol) => volumeSma6.update(vol, false));
|
|
325
|
+
}
|
|
326
|
+
const recentVolumeRaw = volumeSma6.getResult();
|
|
327
|
+
const prevVolumeRaw = volumeSma6Prev.getResult();
|
|
328
|
+
const recentVolume = !isUnsafe$4(recentVolumeRaw)
|
|
329
|
+
? recentVolumeRaw
|
|
330
|
+
: volumes[i];
|
|
331
|
+
const prevVolume = !isUnsafe$4(prevVolumeRaw)
|
|
332
|
+
? prevVolumeRaw
|
|
333
|
+
: volumes[Math.max(0, i - 6)];
|
|
334
|
+
const volumeTrend = !isUnsafe$4(recentVolume) && !isUnsafe$4(prevVolume)
|
|
335
|
+
? recentVolume > prevVolume * 1.1
|
|
336
|
+
? "increasing"
|
|
337
|
+
: recentVolume < prevVolume * 0.9
|
|
338
|
+
? "decreasing"
|
|
339
|
+
: "stable"
|
|
340
|
+
: "stable";
|
|
341
|
+
// Support/Resistance calculation
|
|
342
|
+
const pivotPeriod = Math.min(4, i + 1);
|
|
343
|
+
const startIdx = i + 1 - pivotPeriod;
|
|
344
|
+
const recentHighs = highs
|
|
345
|
+
.slice(startIdx, i + 1)
|
|
346
|
+
.filter((h) => !isUnsafe$4(h));
|
|
347
|
+
const recentLows = lows.slice(startIdx, i + 1).filter((l) => !isUnsafe$4(l));
|
|
348
|
+
const support = recentLows.length > 0 ? Math.min(...recentLows) : currentPrice;
|
|
349
|
+
const resistance = recentHighs.length > 0 ? Math.max(...recentHighs) : currentPrice;
|
|
350
|
+
// Fibonacci calculation
|
|
351
|
+
const fibonacciNearest = calculateFibonacciLevels$2(candles, i);
|
|
352
|
+
// Get results
|
|
353
|
+
const rsiValue = rsi.getResult() ?? null;
|
|
354
|
+
const stochasticRSIResult = stochasticRSI.getResult();
|
|
355
|
+
const stochasticRSIValue = !isUnsafe$4(stochasticRSIResult)
|
|
356
|
+
? stochasticRSIResult * 100
|
|
357
|
+
: null;
|
|
358
|
+
const macdResult = macd.getResult();
|
|
359
|
+
const bollingerResult = bollinger.getResult();
|
|
360
|
+
const stochasticResult = stochastic.getResult();
|
|
361
|
+
const adxValue = adx.getResult() ?? null;
|
|
362
|
+
const pdiValue = typeof adx.pdi === "number" ? adx.pdi * 100 : null;
|
|
363
|
+
const ndiValue = typeof adx.mdi === "number" ? adx.mdi * 100 : null;
|
|
364
|
+
const bodySize = Math.abs(close - open);
|
|
365
|
+
results.push({
|
|
366
|
+
symbol,
|
|
367
|
+
rsi14: rsiValue != null && !isUnsafe$4(rsiValue) ? rsiValue : null,
|
|
368
|
+
stochasticRSI14: stochasticRSIValue,
|
|
369
|
+
macd12_26_9: macdResult && !isUnsafe$4(macdResult.macd) ? macdResult.macd : null,
|
|
370
|
+
signal9: macdResult && !isUnsafe$4(macdResult.signal) ? macdResult.signal : null,
|
|
371
|
+
bollinger20_2_upper: bollingerResult && !isUnsafe$4(bollingerResult.upper)
|
|
372
|
+
? bollingerResult.upper
|
|
373
|
+
: null,
|
|
374
|
+
bollinger20_2_middle: bollingerResult && !isUnsafe$4(bollingerResult.middle)
|
|
375
|
+
? bollingerResult.middle
|
|
376
|
+
: null,
|
|
377
|
+
bollinger20_2_lower: bollingerResult && !isUnsafe$4(bollingerResult.lower)
|
|
378
|
+
? bollingerResult.lower
|
|
379
|
+
: null,
|
|
380
|
+
atr14: atr14.getResult() != null && !isUnsafe$4(atr14.getResult())
|
|
381
|
+
? atr14.getResult()
|
|
382
|
+
: null,
|
|
383
|
+
atr14_raw: atr14.getResult() != null && !isUnsafe$4(atr14.getResult())
|
|
384
|
+
? atr14.getResult()
|
|
385
|
+
: null,
|
|
386
|
+
atr20: atr20.getResult() != null && !isUnsafe$4(atr20.getResult())
|
|
387
|
+
? atr20.getResult()
|
|
388
|
+
: null,
|
|
389
|
+
sma50: sma50.getResult() != null && !isUnsafe$4(sma50.getResult())
|
|
390
|
+
? sma50.getResult()
|
|
391
|
+
: null,
|
|
392
|
+
ema20: ema20.getResult() != null && !isUnsafe$4(ema20.getResult())
|
|
393
|
+
? ema20.getResult()
|
|
394
|
+
: null,
|
|
395
|
+
ema34: ema34.getResult() != null && !isUnsafe$4(ema34.getResult())
|
|
396
|
+
? ema34.getResult()
|
|
397
|
+
: null,
|
|
398
|
+
dema21: dema.getResult() != null && !isUnsafe$4(dema.getResult())
|
|
399
|
+
? dema.getResult()
|
|
400
|
+
: null,
|
|
401
|
+
wma20: wma.getResult() != null && !isUnsafe$4(wma.getResult())
|
|
402
|
+
? wma.getResult()
|
|
403
|
+
: null,
|
|
404
|
+
momentum10: momentum.getResult() != null && !isUnsafe$4(momentum.getResult())
|
|
405
|
+
? momentum.getResult()
|
|
406
|
+
: null,
|
|
407
|
+
stochastic14_3_3_K: stochasticResult && !isUnsafe$4(stochasticResult.stochK)
|
|
408
|
+
? stochasticResult.stochK
|
|
409
|
+
: null,
|
|
410
|
+
stochastic14_3_3_D: stochasticResult && !isUnsafe$4(stochasticResult.stochD)
|
|
411
|
+
? stochasticResult.stochD
|
|
412
|
+
: null,
|
|
413
|
+
adx14: adxValue != null && !isUnsafe$4(adxValue) ? adxValue : null,
|
|
414
|
+
pdi14: pdiValue != null && !isUnsafe$4(pdiValue) ? pdiValue : null,
|
|
415
|
+
ndi14: ndiValue != null && !isUnsafe$4(ndiValue) ? ndiValue : null,
|
|
416
|
+
cci20: cci.getResult() != null && !isUnsafe$4(cci.getResult())
|
|
417
|
+
? cci.getResult()
|
|
418
|
+
: null,
|
|
419
|
+
volumeTrend,
|
|
420
|
+
support: support != null && !isUnsafe$4(support)
|
|
421
|
+
? support
|
|
422
|
+
: !isUnsafe$4(currentPrice)
|
|
423
|
+
? currentPrice
|
|
424
|
+
: null,
|
|
425
|
+
resistance: resistance != null && !isUnsafe$4(resistance)
|
|
426
|
+
? resistance
|
|
427
|
+
: !isUnsafe$4(currentPrice)
|
|
428
|
+
? currentPrice
|
|
429
|
+
: null,
|
|
430
|
+
currentPrice: currentPrice != null && !isUnsafe$4(currentPrice) ? currentPrice : null,
|
|
431
|
+
fibonacciNearestLevel: fibonacciNearest.level,
|
|
432
|
+
fibonacciNearestPrice: fibonacciNearest.price,
|
|
433
|
+
fibonacciDistance: fibonacciNearest.distance,
|
|
434
|
+
bodySize,
|
|
435
|
+
closePrice: close,
|
|
436
|
+
date: new Date(),
|
|
437
|
+
lookbackPeriod: "48 candles (48 hours) with SMA(50) from 100 hours",
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
return results;
|
|
441
|
+
}
|
|
442
|
+
async function generateHistoryTable$3(indicators, symbol) {
|
|
443
|
+
let markdown = "";
|
|
444
|
+
const currentData = await backtestKit.getDate();
|
|
445
|
+
markdown += `# 1-Hour Candles Trading Analysis for ${symbol} (Historical Data)\n`;
|
|
446
|
+
markdown += `> Current time: ${currentData.toISOString()}\n\n`;
|
|
447
|
+
const header = `| ${columns$3.map((col) => col.label).join(" | ")} |\n`;
|
|
448
|
+
const separator = `| ${columns$3.map(() => "---").join(" | ")} |\n`;
|
|
449
|
+
const tableRows = await Promise.all(indicators.map(async (ind) => {
|
|
450
|
+
const cells = await Promise.all(columns$3.map(async (col) => await col.format(ind[col.key], symbol)));
|
|
451
|
+
return `| ${cells.join(" | ")} |`;
|
|
452
|
+
}));
|
|
453
|
+
markdown += header;
|
|
454
|
+
markdown += separator;
|
|
455
|
+
markdown += tableRows.join("\n");
|
|
456
|
+
markdown += "\n\n";
|
|
457
|
+
markdown += "## Data Sources\n";
|
|
458
|
+
markdown += "- **Timeframe**: 1-hour candles\n";
|
|
459
|
+
markdown += "- **Lookback Period**: 48 candles (48 hours)\n";
|
|
460
|
+
markdown +=
|
|
461
|
+
"- **RSI(14)**: over previous 14 candles (14 hours on 1h timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
462
|
+
markdown +=
|
|
463
|
+
"- **Stochastic RSI(14)**: over previous 14 candles (14 hours on 1h timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
464
|
+
markdown +=
|
|
465
|
+
"- **MACD(12,26,9)**: fast 12 and slow 26 periods on 1h timeframe before row timestamp (Min: -∞, Max: +∞)\n";
|
|
466
|
+
markdown +=
|
|
467
|
+
"- **Signal(9)**: over previous 9 candles (9 hours on 1h timeframe) before row timestamp (Min: -∞, Max: +∞)\n";
|
|
468
|
+
markdown +=
|
|
469
|
+
"- **ADX(14)**: over previous 14 candles (14 hours on 1h timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
470
|
+
markdown +=
|
|
471
|
+
"- **+DI(14)**: over previous 14 candles (14 hours on 1h timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
472
|
+
markdown +=
|
|
473
|
+
"- **-DI(14)**: over previous 14 candles (14 hours on 1h timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
474
|
+
markdown +=
|
|
475
|
+
"- **ATR(14)**: over previous 14 candles (14 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
476
|
+
markdown +=
|
|
477
|
+
"- **ATR(14) Raw**: raw value over previous 14 candles before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
478
|
+
markdown +=
|
|
479
|
+
"- **ATR(20) Raw**: raw value over previous 20 candles (20 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
480
|
+
markdown +=
|
|
481
|
+
"- **CCI(20)**: over previous 20 candles (20 hours on 1h timeframe) before row timestamp (Min: -∞, Max: +∞)\n";
|
|
482
|
+
markdown +=
|
|
483
|
+
"- **Bollinger Upper(20,2.0)**: over previous 20 candles (20 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
484
|
+
markdown +=
|
|
485
|
+
"- **Bollinger Middle(20,2.0)**: over previous 20 candles (20 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
486
|
+
markdown +=
|
|
487
|
+
"- **Bollinger Lower(20,2.0)**: over previous 20 candles (20 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
488
|
+
markdown +=
|
|
489
|
+
"- **Stochastic K(14,3,3)**: over previous 14 candles (14 hours on 1h timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
490
|
+
markdown +=
|
|
491
|
+
"- **Stochastic D(14,3,3)**: over previous 14 candles (14 hours on 1h timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
492
|
+
markdown +=
|
|
493
|
+
"- **DEMA(21)**: over previous 21 candles (21 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
494
|
+
markdown +=
|
|
495
|
+
"- **WMA(20)**: over previous 20 candles (20 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
496
|
+
markdown +=
|
|
497
|
+
"- **SMA(50)**: over previous 50 candles (50 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
498
|
+
markdown +=
|
|
499
|
+
"- **EMA(20)**: over previous 20 candles (20 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
500
|
+
markdown +=
|
|
501
|
+
"- **EMA(34)**: over previous 34 candles (34 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
502
|
+
markdown +=
|
|
503
|
+
"- **Momentum(10)**: over previous 10 candles (10 hours on 1h timeframe) before row timestamp (Min: -∞ USD, Max: +∞ USD)\n";
|
|
504
|
+
markdown +=
|
|
505
|
+
"- **Support**: over previous 4 candles (4 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
506
|
+
markdown +=
|
|
507
|
+
"- **Resistance**: over previous 4 candles (4 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
508
|
+
markdown +=
|
|
509
|
+
"- **Fibonacci Nearest Level**: nearest level name before row timestamp\n";
|
|
510
|
+
markdown +=
|
|
511
|
+
"- **Fibonacci Nearest Price**: nearest price level before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
512
|
+
markdown +=
|
|
513
|
+
"- **Fibonacci Distance**: distance to nearest level before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
514
|
+
markdown +=
|
|
515
|
+
"- **Current Price**: close price at row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
516
|
+
markdown +=
|
|
517
|
+
"- **Body Size**: candle body size at row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
518
|
+
markdown +=
|
|
519
|
+
"- **Close Price**: close price at row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
520
|
+
return markdown;
|
|
521
|
+
}
|
|
522
|
+
class LongTermHistoryService {
|
|
523
|
+
constructor() {
|
|
524
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
525
|
+
this.getData = async (symbol, candles) => {
|
|
526
|
+
this.loggerService.log("longTermHistoryService getData", {
|
|
527
|
+
symbol,
|
|
528
|
+
candles: candles.length,
|
|
529
|
+
});
|
|
530
|
+
return generateAnalysis$3(symbol, candles);
|
|
531
|
+
};
|
|
532
|
+
this.getReport = async (symbol) => {
|
|
533
|
+
this.loggerService.log("longTermHistoryService getReport", { symbol });
|
|
534
|
+
const fullCandles = await backtestKit.getCandles(symbol, "1h", 100);
|
|
535
|
+
// Use all candles for indicator warm-up, then filter to last TABLE_ROWS_LIMIT rows
|
|
536
|
+
const allRows = await this.getData(symbol, fullCandles);
|
|
537
|
+
const rows = allRows.slice(-TABLE_ROWS_LIMIT$3);
|
|
538
|
+
return generateHistoryTable$3(rows, symbol);
|
|
539
|
+
};
|
|
540
|
+
this.generateHistoryTable = async (symbol, rows) => {
|
|
541
|
+
this.loggerService.log("longTermHistoryService generateHistoryTable", {
|
|
542
|
+
symbol,
|
|
543
|
+
rowCount: rows.length,
|
|
544
|
+
});
|
|
545
|
+
return generateHistoryTable$3(rows, symbol);
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const WARMUP_PERIOD$2 = 21;
|
|
551
|
+
const TABLE_ROWS_LIMIT$2 = 40;
|
|
552
|
+
const columns$2 = [
|
|
553
|
+
{
|
|
554
|
+
key: "rsi9",
|
|
555
|
+
label: "RSI(9)",
|
|
556
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
key: "rsi14",
|
|
560
|
+
label: "RSI(14)",
|
|
561
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
key: "stochasticRSI9",
|
|
565
|
+
label: "Stochastic RSI(9)",
|
|
566
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
key: "stochasticRSI14",
|
|
570
|
+
label: "Stochastic RSI(14)",
|
|
571
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
key: "macd8_21_5",
|
|
575
|
+
label: "MACD(8,21,5)",
|
|
576
|
+
format: (v) => (v !== null ? Number(v).toFixed(4) : "N/A"),
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
key: "signal5",
|
|
580
|
+
label: "Signal(5)",
|
|
581
|
+
format: (v) => (v !== null ? Number(v).toFixed(4) : "N/A"),
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
key: "macdHistogram",
|
|
585
|
+
label: "MACD Histogram",
|
|
586
|
+
format: (v) => (v !== null ? Number(v).toFixed(4) : "N/A"),
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
key: "adx9",
|
|
590
|
+
label: "ADX(9)",
|
|
591
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
key: "plusDI9",
|
|
595
|
+
label: "+DI(9)",
|
|
596
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
key: "minusDI9",
|
|
600
|
+
label: "-DI(9)",
|
|
601
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
key: "atr5",
|
|
605
|
+
label: "ATR(5)",
|
|
606
|
+
format: async (v, symbol) => v !== null
|
|
607
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
608
|
+
: "N/A",
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
key: "atr9",
|
|
612
|
+
label: "ATR(9)",
|
|
613
|
+
format: async (v, symbol) => v !== null
|
|
614
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
615
|
+
: "N/A",
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
key: "cci9",
|
|
619
|
+
label: "CCI(9)",
|
|
620
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
key: "stochasticK3_3_3",
|
|
624
|
+
label: "Stochastic K(3,3,3)",
|
|
625
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
key: "stochasticD3_3_3",
|
|
629
|
+
label: "Stochastic D(3,3,3)",
|
|
630
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
key: "stochasticK5_3_3",
|
|
634
|
+
label: "Stochastic K(5,3,3)",
|
|
635
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
key: "stochasticD5_3_3",
|
|
639
|
+
label: "Stochastic D(5,3,3)",
|
|
640
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
key: "momentum5",
|
|
644
|
+
label: "Momentum(5)",
|
|
645
|
+
format: async (v, symbol) => v !== null
|
|
646
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
647
|
+
: "N/A",
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
key: "momentum10",
|
|
651
|
+
label: "Momentum(10)",
|
|
652
|
+
format: async (v, symbol) => v !== null
|
|
653
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
654
|
+
: "N/A",
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
key: "roc1",
|
|
658
|
+
label: "ROC(1)",
|
|
659
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(3)}%` : "N/A"),
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
key: "roc3",
|
|
663
|
+
label: "ROC(3)",
|
|
664
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(3)}%` : "N/A"),
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
key: "roc5",
|
|
668
|
+
label: "ROC(5)",
|
|
669
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(3)}%` : "N/A"),
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
key: "ema3",
|
|
673
|
+
label: "EMA(3)",
|
|
674
|
+
format: async (v, symbol) => v !== null
|
|
675
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
676
|
+
: "N/A",
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
key: "ema8",
|
|
680
|
+
label: "EMA(8)",
|
|
681
|
+
format: async (v, symbol) => v !== null
|
|
682
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
683
|
+
: "N/A",
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
key: "ema13",
|
|
687
|
+
label: "EMA(13)",
|
|
688
|
+
format: async (v, symbol) => v !== null
|
|
689
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
690
|
+
: "N/A",
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
key: "ema21",
|
|
694
|
+
label: "EMA(21)",
|
|
695
|
+
format: async (v, symbol) => v !== null
|
|
696
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
697
|
+
: "N/A",
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
key: "sma8",
|
|
701
|
+
label: "SMA(8)",
|
|
702
|
+
format: async (v, symbol) => v !== null
|
|
703
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
704
|
+
: "N/A",
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
key: "dema8",
|
|
708
|
+
label: "DEMA(8)",
|
|
709
|
+
format: async (v, symbol) => v !== null
|
|
710
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
711
|
+
: "N/A",
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
key: "wma5",
|
|
715
|
+
label: "WMA(5)",
|
|
716
|
+
format: async (v, symbol) => v !== null
|
|
717
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
718
|
+
: "N/A",
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
key: "currentPrice",
|
|
722
|
+
label: "Current Price",
|
|
723
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
key: "priceChange1m",
|
|
727
|
+
label: "1m Change",
|
|
728
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(3)}%` : "N/A"),
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
key: "priceChange3m",
|
|
732
|
+
label: "3m Change",
|
|
733
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(3)}%` : "N/A"),
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
key: "priceChange5m",
|
|
737
|
+
label: "5m Change",
|
|
738
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(3)}%` : "N/A"),
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
key: "support",
|
|
742
|
+
label: "Support Level",
|
|
743
|
+
format: async (v, symbol) => v !== null
|
|
744
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
745
|
+
: "N/A",
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
key: "resistance",
|
|
749
|
+
label: "Resistance Level",
|
|
750
|
+
format: async (v, symbol) => v !== null
|
|
751
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
752
|
+
: "N/A",
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
key: "bollingerUpper8_2",
|
|
756
|
+
label: "Bollinger Upper(8,2.0)",
|
|
757
|
+
format: async (v, symbol) => v !== null
|
|
758
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
759
|
+
: "N/A",
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
key: "bollingerMiddle8_2",
|
|
763
|
+
label: "Bollinger Middle(8,2.0)",
|
|
764
|
+
format: async (v, symbol) => v !== null
|
|
765
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
766
|
+
: "N/A",
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
key: "bollingerLower8_2",
|
|
770
|
+
label: "Bollinger Lower(8,2.0)",
|
|
771
|
+
format: async (v, symbol) => v !== null
|
|
772
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
773
|
+
: "N/A",
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
key: "bollingerWidth8_2",
|
|
777
|
+
label: "Bollinger Width(8,2.0)",
|
|
778
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(3)}%` : "N/A"),
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
key: "bollingerPosition",
|
|
782
|
+
label: "Bollinger Position",
|
|
783
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(1)}%` : "N/A"),
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
key: "volumeSma5",
|
|
787
|
+
label: "Volume SMA(5)",
|
|
788
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
key: "volumeRatio",
|
|
792
|
+
label: "Volume Ratio",
|
|
793
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(2)}x` : "N/A"),
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
key: "volatility5",
|
|
797
|
+
label: "Volatility(5)",
|
|
798
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(3)}%` : "N/A"),
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
key: "trueRange",
|
|
802
|
+
label: "True Range",
|
|
803
|
+
format: async (v, symbol) => v !== null
|
|
804
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
805
|
+
: "N/A",
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
key: "squeezeMomentum",
|
|
809
|
+
label: "Squeeze Momentum",
|
|
810
|
+
format: (v) => (v !== null ? Number(v).toFixed(3) : "N/A"),
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
key: "pressureIndex",
|
|
814
|
+
label: "Pressure Index",
|
|
815
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(1)}%` : "N/A"),
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
key: "closePrice",
|
|
819
|
+
label: "Close Price",
|
|
820
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
key: "date",
|
|
824
|
+
label: "Timestamp",
|
|
825
|
+
format: (v) => new Date(v).toISOString(),
|
|
826
|
+
},
|
|
827
|
+
];
|
|
828
|
+
function isUnsafe$3(value) {
|
|
829
|
+
if (typeof value !== "number") {
|
|
830
|
+
return true;
|
|
831
|
+
}
|
|
832
|
+
if (isNaN(value)) {
|
|
833
|
+
return true;
|
|
834
|
+
}
|
|
835
|
+
if (!isFinite(value)) {
|
|
836
|
+
return true;
|
|
837
|
+
}
|
|
838
|
+
return false;
|
|
839
|
+
}
|
|
840
|
+
function calculateVolumeMetrics(candles, endIndex) {
|
|
841
|
+
const volumes = candles.slice(0, endIndex + 1).map((c) => Number(c.volume));
|
|
842
|
+
if (volumes.length < 5) {
|
|
843
|
+
return { volumeSma5: null, volumeRatio: null, volumeTrend: "stable" };
|
|
844
|
+
}
|
|
845
|
+
const volumeSma5 = new tradingSignals.FasterSMA(5);
|
|
846
|
+
volumes.forEach((vol) => volumeSma5.update(vol, false));
|
|
847
|
+
const avgVolumeRaw = volumeSma5.getResult();
|
|
848
|
+
const avgVolume = !isUnsafe$3(avgVolumeRaw) ? avgVolumeRaw : 0;
|
|
849
|
+
const currentVolume = volumes[volumes.length - 1];
|
|
850
|
+
const volumeRatio = avgVolume > 0 && !isUnsafe$3(currentVolume) ? currentVolume / avgVolume : 1;
|
|
851
|
+
let volumeTrend = "stable";
|
|
852
|
+
if (volumes.length >= 6) {
|
|
853
|
+
const recent3 = volumes.slice(-3);
|
|
854
|
+
const prev3 = volumes.slice(-6, -3);
|
|
855
|
+
if (prev3.length >= 3) {
|
|
856
|
+
const recentAvg = recent3.reduce((a, b) => a + b, 0) / 3;
|
|
857
|
+
const prevAvg = prev3.reduce((a, b) => a + b, 0) / 3;
|
|
858
|
+
if (recentAvg > prevAvg * 1.2)
|
|
859
|
+
volumeTrend = "increasing";
|
|
860
|
+
else if (recentAvg < prevAvg * 0.8)
|
|
861
|
+
volumeTrend = "decreasing";
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
return { volumeSma5: avgVolume, volumeRatio, volumeTrend };
|
|
865
|
+
}
|
|
866
|
+
function calculatePriceChanges(candles, endIndex) {
|
|
867
|
+
const closes = candles.slice(0, endIndex + 1).map((c) => Number(c.close));
|
|
868
|
+
if (closes.length < 2) {
|
|
869
|
+
return { priceChange1m: null, priceChange3m: null, priceChange5m: null };
|
|
870
|
+
}
|
|
871
|
+
const current = closes[closes.length - 1];
|
|
872
|
+
const priceChange1m = closes.length >= 2
|
|
873
|
+
? ((current - closes[closes.length - 2]) / closes[closes.length - 2]) *
|
|
874
|
+
100
|
|
875
|
+
: null;
|
|
876
|
+
const priceChange3m = closes.length >= 4
|
|
877
|
+
? ((current - closes[closes.length - 4]) / closes[closes.length - 4]) *
|
|
878
|
+
100
|
|
879
|
+
: null;
|
|
880
|
+
const priceChange5m = closes.length >= 6
|
|
881
|
+
? ((current - closes[closes.length - 6]) / closes[closes.length - 6]) *
|
|
882
|
+
100
|
|
883
|
+
: null;
|
|
884
|
+
return { priceChange1m, priceChange3m, priceChange5m };
|
|
885
|
+
}
|
|
886
|
+
function calculateSupportResistance$1(candles, endIndex, currentPrice) {
|
|
887
|
+
const recentPeriod = Math.min(30, endIndex + 1);
|
|
888
|
+
const startIdx = endIndex + 1 - recentPeriod;
|
|
889
|
+
const recentCandles = candles.slice(startIdx, endIndex + 1);
|
|
890
|
+
if (recentCandles.length < 10) {
|
|
891
|
+
return { support: currentPrice, resistance: currentPrice };
|
|
892
|
+
}
|
|
893
|
+
const highs = recentCandles.map((c) => Number(c.high));
|
|
894
|
+
const lows = recentCandles.map((c) => Number(c.low));
|
|
895
|
+
const minDistance = currentPrice * 0.003;
|
|
896
|
+
const significantHighs = [...highs]
|
|
897
|
+
.filter((h) => !isUnsafe$3(h) && h > currentPrice + minDistance)
|
|
898
|
+
.sort((a, b) => a - b);
|
|
899
|
+
const significantLows = [...lows]
|
|
900
|
+
.filter((l) => !isUnsafe$3(l) && l < currentPrice - minDistance)
|
|
901
|
+
.sort((a, b) => b - a);
|
|
902
|
+
const safeHighs = highs.filter((h) => !isUnsafe$3(h));
|
|
903
|
+
const safeLows = lows.filter((l) => !isUnsafe$3(l));
|
|
904
|
+
const resistance = significantHighs.length > 0
|
|
905
|
+
? significantHighs[0]
|
|
906
|
+
: safeHighs.length > 0
|
|
907
|
+
? Math.max(...safeHighs)
|
|
908
|
+
: currentPrice;
|
|
909
|
+
const support = significantLows.length > 0
|
|
910
|
+
? significantLows[0]
|
|
911
|
+
: safeLows.length > 0
|
|
912
|
+
? Math.min(...safeLows)
|
|
913
|
+
: currentPrice;
|
|
914
|
+
return { support, resistance };
|
|
915
|
+
}
|
|
916
|
+
function generateAnalysis$2(symbol, candles) {
|
|
917
|
+
const closes = candles.map((candle) => Number(candle.close));
|
|
918
|
+
const highs = candles.map((candle) => Number(candle.high));
|
|
919
|
+
const lows = candles.map((candle) => Number(candle.low));
|
|
920
|
+
const opens = candles.map((candle) => Number(candle.open));
|
|
921
|
+
const rsi9 = new tradingSignals.FasterRSI(9);
|
|
922
|
+
const rsi14 = new tradingSignals.FasterRSI(14);
|
|
923
|
+
const stochasticRSI9 = new tradingSignals.FasterStochasticRSI(9);
|
|
924
|
+
const stochasticRSI14 = new tradingSignals.FasterStochasticRSI(14);
|
|
925
|
+
const macdShortEMA = new tradingSignals.FasterEMA(8);
|
|
926
|
+
const macdLongEMA = new tradingSignals.FasterEMA(21);
|
|
927
|
+
const macdSignalEMA = new tradingSignals.FasterEMA(5);
|
|
928
|
+
const macd = new tradingSignals.FasterMACD(macdShortEMA, macdLongEMA, macdSignalEMA);
|
|
929
|
+
const bollinger8 = new tradingSignals.FasterBollingerBands(8, 2.0);
|
|
930
|
+
const stochastic3 = new tradingSignals.FasterStochasticOscillator(3, 3, 3);
|
|
931
|
+
const stochastic5 = new tradingSignals.FasterStochasticOscillator(5, 3, 3);
|
|
932
|
+
const adx9 = new tradingSignals.FasterADX(9);
|
|
933
|
+
const atr5 = new tradingSignals.FasterATR(5);
|
|
934
|
+
const atr9 = new tradingSignals.FasterATR(9);
|
|
935
|
+
const cci9 = new tradingSignals.FasterCCI(9);
|
|
936
|
+
const momentum5 = new tradingSignals.FasterMOM(5);
|
|
937
|
+
const momentum10 = new tradingSignals.FasterMOM(10);
|
|
938
|
+
const roc1 = new tradingSignals.FasterROC(1);
|
|
939
|
+
const roc3 = new tradingSignals.FasterROC(3);
|
|
940
|
+
const roc5 = new tradingSignals.FasterROC(5);
|
|
941
|
+
const ema3 = new tradingSignals.FasterEMA(3);
|
|
942
|
+
const ema8 = new tradingSignals.FasterEMA(8);
|
|
943
|
+
const ema13 = new tradingSignals.FasterEMA(13);
|
|
944
|
+
const ema21 = new tradingSignals.FasterEMA(21);
|
|
945
|
+
const sma8 = new tradingSignals.FasterSMA(8);
|
|
946
|
+
const dema8 = new tradingSignals.FasterDEMA(8);
|
|
947
|
+
const wma5 = new tradingSignals.FasterWMA(5);
|
|
948
|
+
const results = [];
|
|
949
|
+
candles.forEach((_candle, i) => {
|
|
950
|
+
const high = highs[i];
|
|
951
|
+
const low = lows[i];
|
|
952
|
+
const close = closes[i];
|
|
953
|
+
opens[i];
|
|
954
|
+
const currentPrice = close;
|
|
955
|
+
// Update all indicators
|
|
956
|
+
rsi9.update(close, false);
|
|
957
|
+
rsi14.update(close, false);
|
|
958
|
+
stochasticRSI9.update(close, false);
|
|
959
|
+
stochasticRSI14.update(close, false);
|
|
960
|
+
macd.update(close, false);
|
|
961
|
+
bollinger8.update(close, false);
|
|
962
|
+
stochastic3.update({ high, low, close }, false);
|
|
963
|
+
stochastic5.update({ high, low, close }, false);
|
|
964
|
+
adx9.update({ high, low, close }, false);
|
|
965
|
+
atr5.update({ high, low, close }, false);
|
|
966
|
+
atr9.update({ high, low, close }, false);
|
|
967
|
+
cci9.update({ high, low, close }, false);
|
|
968
|
+
momentum5.update(close, false);
|
|
969
|
+
momentum10.update(close, false);
|
|
970
|
+
roc1.update(close, false);
|
|
971
|
+
roc3.update(close, false);
|
|
972
|
+
roc5.update(close, false);
|
|
973
|
+
ema3.update(close, false);
|
|
974
|
+
ema8.update(close, false);
|
|
975
|
+
ema13.update(close, false);
|
|
976
|
+
ema21.update(close, false);
|
|
977
|
+
sma8.update(close, false);
|
|
978
|
+
dema8.update(close, false);
|
|
979
|
+
wma5.update(close, false);
|
|
980
|
+
// Determine minimum warm-up period needed (largest indicator period)
|
|
981
|
+
// EMA(21) is the largest period
|
|
982
|
+
// Skip rows until all indicators are warmed up
|
|
983
|
+
if (i < WARMUP_PERIOD$2) {
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
const volumeMetrics = calculateVolumeMetrics(candles, i);
|
|
987
|
+
const priceChanges = calculatePriceChanges(candles, i);
|
|
988
|
+
const { support, resistance } = calculateSupportResistance$1(candles, i, currentPrice);
|
|
989
|
+
const volatility5 = i >= 4
|
|
990
|
+
? Math.sqrt(candles.slice(i - 4, i + 1).reduce((sum, c, idx, arr) => {
|
|
991
|
+
if (idx === 0)
|
|
992
|
+
return 0;
|
|
993
|
+
const prevClose = Number(arr[idx - 1].close);
|
|
994
|
+
const currentClose = Number(c.close);
|
|
995
|
+
const return_ = Math.log(currentClose / prevClose);
|
|
996
|
+
return sum + return_ * return_;
|
|
997
|
+
}, 0) / 4) * 100
|
|
998
|
+
: null;
|
|
999
|
+
const trueRange = i >= 1
|
|
1000
|
+
? Math.max(high - low, Math.abs(high - closes[i - 1]), Math.abs(low - closes[i - 1]))
|
|
1001
|
+
: null;
|
|
1002
|
+
const rsi9Value = rsi9.getResult() ?? null;
|
|
1003
|
+
const rsi14Value = rsi14.getResult() ?? null;
|
|
1004
|
+
const stochasticRSI9Result = stochasticRSI9.getResult();
|
|
1005
|
+
const stochasticRSI9Value = !isUnsafe$3(stochasticRSI9Result)
|
|
1006
|
+
? stochasticRSI9Result * 100
|
|
1007
|
+
: null;
|
|
1008
|
+
const stochasticRSI14Result = stochasticRSI14.getResult();
|
|
1009
|
+
const stochasticRSI14Value = !isUnsafe$3(stochasticRSI14Result)
|
|
1010
|
+
? stochasticRSI14Result * 100
|
|
1011
|
+
: null;
|
|
1012
|
+
const macdResult = macd.getResult();
|
|
1013
|
+
const bollingerResult = bollinger8.getResult();
|
|
1014
|
+
const stochastic3Result = stochastic3.getResult();
|
|
1015
|
+
const stochastic5Result = stochastic5.getResult();
|
|
1016
|
+
const adx9Value = adx9.getResult() ?? null;
|
|
1017
|
+
const plusDI9 = typeof adx9.pdi === "number" ? adx9.pdi * 100 : null;
|
|
1018
|
+
const minusDI9 = typeof adx9.mdi === "number" ? adx9.mdi * 100 : null;
|
|
1019
|
+
const bollingerUpper8_2 = bollingerResult && !isUnsafe$3(bollingerResult.upper)
|
|
1020
|
+
? bollingerResult.upper
|
|
1021
|
+
: null;
|
|
1022
|
+
const bollingerMiddle8_2 = bollingerResult && !isUnsafe$3(bollingerResult.middle)
|
|
1023
|
+
? bollingerResult.middle
|
|
1024
|
+
: null;
|
|
1025
|
+
const bollingerLower8_2 = bollingerResult && !isUnsafe$3(bollingerResult.lower)
|
|
1026
|
+
? bollingerResult.lower
|
|
1027
|
+
: null;
|
|
1028
|
+
let bollingerPosition = null;
|
|
1029
|
+
if (!isUnsafe$3(bollingerUpper8_2) &&
|
|
1030
|
+
!isUnsafe$3(bollingerLower8_2) &&
|
|
1031
|
+
!isUnsafe$3(currentPrice)) {
|
|
1032
|
+
const range = bollingerUpper8_2 - bollingerLower8_2;
|
|
1033
|
+
if (range > 0 && !isUnsafe$3(range)) {
|
|
1034
|
+
bollingerPosition = ((currentPrice - bollingerLower8_2) / range) * 100;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
const bollingerWidth8_2 = !isUnsafe$3(bollingerUpper8_2) &&
|
|
1038
|
+
!isUnsafe$3(bollingerLower8_2) &&
|
|
1039
|
+
!isUnsafe$3(bollingerMiddle8_2) &&
|
|
1040
|
+
bollingerMiddle8_2 !== 0
|
|
1041
|
+
? ((bollingerUpper8_2 - bollingerLower8_2) / bollingerMiddle8_2) * 100
|
|
1042
|
+
: null;
|
|
1043
|
+
const atr9Value = atr9.getResult() ?? null;
|
|
1044
|
+
const squeezeMomentum = !isUnsafe$3(bollingerWidth8_2) &&
|
|
1045
|
+
!isUnsafe$3(atr9Value) &&
|
|
1046
|
+
!isUnsafe$3(currentPrice) &&
|
|
1047
|
+
currentPrice !== 0
|
|
1048
|
+
? bollingerWidth8_2 / ((atr9Value / currentPrice) * 100)
|
|
1049
|
+
: null;
|
|
1050
|
+
let pressureIndex = null;
|
|
1051
|
+
if (i >= 1) {
|
|
1052
|
+
const range = high - low;
|
|
1053
|
+
if (!isUnsafe$3(range) && range > 0) {
|
|
1054
|
+
pressureIndex = ((close - low - (high - close)) / range) * 100;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
results.push({
|
|
1058
|
+
symbol,
|
|
1059
|
+
rsi9: rsi9Value != null && !isUnsafe$3(rsi9Value) ? rsi9Value : null,
|
|
1060
|
+
rsi14: rsi14Value != null && !isUnsafe$3(rsi14Value) ? rsi14Value : null,
|
|
1061
|
+
stochasticRSI9: stochasticRSI9Value,
|
|
1062
|
+
stochasticRSI14: stochasticRSI14Value,
|
|
1063
|
+
macd8_21_5: macdResult && !isUnsafe$3(macdResult.macd) ? macdResult.macd : null,
|
|
1064
|
+
signal5: macdResult && !isUnsafe$3(macdResult.signal) ? macdResult.signal : null,
|
|
1065
|
+
macdHistogram: macdResult && !isUnsafe$3(macdResult.histogram)
|
|
1066
|
+
? macdResult.histogram
|
|
1067
|
+
: null,
|
|
1068
|
+
bollingerUpper8_2,
|
|
1069
|
+
bollingerMiddle8_2,
|
|
1070
|
+
bollingerLower8_2,
|
|
1071
|
+
bollingerWidth8_2,
|
|
1072
|
+
bollingerPosition,
|
|
1073
|
+
stochasticK3_3_3: stochastic3Result && !isUnsafe$3(stochastic3Result.stochK)
|
|
1074
|
+
? stochastic3Result.stochK
|
|
1075
|
+
: null,
|
|
1076
|
+
stochasticD3_3_3: stochastic3Result && !isUnsafe$3(stochastic3Result.stochD)
|
|
1077
|
+
? stochastic3Result.stochD
|
|
1078
|
+
: null,
|
|
1079
|
+
stochasticK5_3_3: stochastic5Result && !isUnsafe$3(stochastic5Result.stochK)
|
|
1080
|
+
? stochastic5Result.stochK
|
|
1081
|
+
: null,
|
|
1082
|
+
stochasticD5_3_3: stochastic5Result && !isUnsafe$3(stochastic5Result.stochD)
|
|
1083
|
+
? stochastic5Result.stochD
|
|
1084
|
+
: null,
|
|
1085
|
+
adx9: adx9Value != null && !isUnsafe$3(adx9Value) ? adx9Value : null,
|
|
1086
|
+
plusDI9: plusDI9 != null && !isUnsafe$3(plusDI9) ? plusDI9 : null,
|
|
1087
|
+
minusDI9: minusDI9 != null && !isUnsafe$3(minusDI9) ? minusDI9 : null,
|
|
1088
|
+
atr5: atr5.getResult() != null && !isUnsafe$3(atr5.getResult())
|
|
1089
|
+
? atr5.getResult()
|
|
1090
|
+
: null,
|
|
1091
|
+
atr9: atr9Value != null && !isUnsafe$3(atr9Value) ? atr9Value : null,
|
|
1092
|
+
cci9: cci9.getResult() != null && !isUnsafe$3(cci9.getResult())
|
|
1093
|
+
? cci9.getResult()
|
|
1094
|
+
: null,
|
|
1095
|
+
momentum5: momentum5.getResult() != null && !isUnsafe$3(momentum5.getResult())
|
|
1096
|
+
? momentum5.getResult()
|
|
1097
|
+
: null,
|
|
1098
|
+
momentum10: momentum10.getResult() != null && !isUnsafe$3(momentum10.getResult())
|
|
1099
|
+
? momentum10.getResult()
|
|
1100
|
+
: null,
|
|
1101
|
+
roc1: roc1.getResult() != null && !isUnsafe$3(roc1.getResult())
|
|
1102
|
+
? roc1.getResult()
|
|
1103
|
+
: null,
|
|
1104
|
+
roc3: roc3.getResult() != null && !isUnsafe$3(roc3.getResult())
|
|
1105
|
+
? roc3.getResult()
|
|
1106
|
+
: null,
|
|
1107
|
+
roc5: roc5.getResult() != null && !isUnsafe$3(roc5.getResult())
|
|
1108
|
+
? roc5.getResult()
|
|
1109
|
+
: null,
|
|
1110
|
+
ema3: ema3.getResult() != null && !isUnsafe$3(ema3.getResult())
|
|
1111
|
+
? ema3.getResult()
|
|
1112
|
+
: null,
|
|
1113
|
+
ema8: ema8.getResult() != null && !isUnsafe$3(ema8.getResult())
|
|
1114
|
+
? ema8.getResult()
|
|
1115
|
+
: null,
|
|
1116
|
+
ema13: ema13.getResult() != null && !isUnsafe$3(ema13.getResult())
|
|
1117
|
+
? ema13.getResult()
|
|
1118
|
+
: null,
|
|
1119
|
+
ema21: ema21.getResult() != null && !isUnsafe$3(ema21.getResult())
|
|
1120
|
+
? ema21.getResult()
|
|
1121
|
+
: null,
|
|
1122
|
+
sma8: sma8.getResult() != null && !isUnsafe$3(sma8.getResult())
|
|
1123
|
+
? sma8.getResult()
|
|
1124
|
+
: null,
|
|
1125
|
+
dema8: dema8.getResult() != null && !isUnsafe$3(dema8.getResult())
|
|
1126
|
+
? dema8.getResult()
|
|
1127
|
+
: null,
|
|
1128
|
+
wma5: wma5.getResult() != null && !isUnsafe$3(wma5.getResult())
|
|
1129
|
+
? wma5.getResult()
|
|
1130
|
+
: null,
|
|
1131
|
+
volumeSma5: volumeMetrics.volumeSma5,
|
|
1132
|
+
volumeRatio: volumeMetrics.volumeRatio,
|
|
1133
|
+
volumeTrend: volumeMetrics.volumeTrend,
|
|
1134
|
+
currentPrice: currentPrice != null && !isUnsafe$3(currentPrice) ? currentPrice : null,
|
|
1135
|
+
priceChange1m: priceChanges.priceChange1m,
|
|
1136
|
+
priceChange3m: priceChanges.priceChange3m,
|
|
1137
|
+
priceChange5m: priceChanges.priceChange5m,
|
|
1138
|
+
volatility5,
|
|
1139
|
+
trueRange,
|
|
1140
|
+
support: support != null && !isUnsafe$3(support)
|
|
1141
|
+
? support
|
|
1142
|
+
: !isUnsafe$3(currentPrice)
|
|
1143
|
+
? currentPrice
|
|
1144
|
+
: null,
|
|
1145
|
+
resistance: resistance != null && !isUnsafe$3(resistance)
|
|
1146
|
+
? resistance
|
|
1147
|
+
: !isUnsafe$3(currentPrice)
|
|
1148
|
+
? currentPrice
|
|
1149
|
+
: null,
|
|
1150
|
+
squeezeMomentum,
|
|
1151
|
+
pressureIndex,
|
|
1152
|
+
closePrice: close,
|
|
1153
|
+
date: new Date(),
|
|
1154
|
+
lookbackPeriod: "60 candles (60 minutes)",
|
|
1155
|
+
});
|
|
1156
|
+
});
|
|
1157
|
+
// Return only the last TABLE_ROWS_LIMIT rows
|
|
1158
|
+
return results.slice(-TABLE_ROWS_LIMIT$2);
|
|
1159
|
+
}
|
|
1160
|
+
async function generateHistoryTable$2(indicators, symbol) {
|
|
1161
|
+
let markdown = "";
|
|
1162
|
+
const currentData = await backtestKit.getDate();
|
|
1163
|
+
markdown += `# 1-Minute Candles Trading Analysis for ${symbol} (Historical Data)\n`;
|
|
1164
|
+
markdown += `> Current time: ${currentData.toISOString()}\n\n`;
|
|
1165
|
+
const header = `| ${columns$2.map((col) => col.label).join(" | ")} |\n`;
|
|
1166
|
+
const separator = `| ${columns$2.map(() => "---").join(" | ")} |\n`;
|
|
1167
|
+
const tableRows = await Promise.all(indicators.map(async (ind) => {
|
|
1168
|
+
const cells = await Promise.all(columns$2.map(async (col) => await col.format(ind[col.key], symbol)));
|
|
1169
|
+
return `| ${cells.join(" | ")} |`;
|
|
1170
|
+
}));
|
|
1171
|
+
markdown += header;
|
|
1172
|
+
markdown += separator;
|
|
1173
|
+
markdown += tableRows.join("\n");
|
|
1174
|
+
markdown += "\n\n";
|
|
1175
|
+
markdown += "## Data Sources\n";
|
|
1176
|
+
markdown += "- **Timeframe**: 1-minute candles\n";
|
|
1177
|
+
markdown += "- **Lookback Period**: 60 candles (60 minutes)\n";
|
|
1178
|
+
markdown +=
|
|
1179
|
+
"- **RSI(9)**: over previous 9 candles (9 minutes on 1m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1180
|
+
markdown +=
|
|
1181
|
+
"- **RSI(14)**: over previous 14 candles (14 minutes on 1m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1182
|
+
markdown +=
|
|
1183
|
+
"- **Stochastic RSI(9)**: over previous 9 candles (9 minutes on 1m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1184
|
+
markdown +=
|
|
1185
|
+
"- **Stochastic RSI(14)**: over previous 14 candles (14 minutes on 1m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1186
|
+
markdown +=
|
|
1187
|
+
"- **MACD(8,21,5)**: fast 8 and slow 21 periods on 1m timeframe before row timestamp (Min: -∞, Max: +∞)\n";
|
|
1188
|
+
markdown +=
|
|
1189
|
+
"- **Signal(5)**: over previous 5 candles (5 minutes on 1m timeframe) before row timestamp (Min: -∞, Max: +∞)\n";
|
|
1190
|
+
markdown +=
|
|
1191
|
+
"- **MACD Histogram**: histogram value before row timestamp (Min: -∞, Max: +∞)\n";
|
|
1192
|
+
markdown +=
|
|
1193
|
+
"- **ADX(9)**: over previous 9 candles (9 minutes on 1m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1194
|
+
markdown +=
|
|
1195
|
+
"- **+DI(9)**: over previous 9 candles (9 minutes on 1m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1196
|
+
markdown +=
|
|
1197
|
+
"- **-DI(9)**: over previous 9 candles (9 minutes on 1m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1198
|
+
markdown +=
|
|
1199
|
+
"- **ATR(5)**: over previous 5 candles (5 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
1200
|
+
markdown +=
|
|
1201
|
+
"- **ATR(9)**: over previous 9 candles (9 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
1202
|
+
markdown +=
|
|
1203
|
+
"- **CCI(9)**: over previous 9 candles (9 minutes on 1m timeframe) before row timestamp (Min: -∞, Max: +∞)\n";
|
|
1204
|
+
markdown +=
|
|
1205
|
+
"- **Bollinger Upper(8,2.0)**: over previous 8 candles (8 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
1206
|
+
markdown +=
|
|
1207
|
+
"- **Bollinger Middle(8,2.0)**: over previous 8 candles (8 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
1208
|
+
markdown +=
|
|
1209
|
+
"- **Bollinger Lower(8,2.0)**: over previous 8 candles (8 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
1210
|
+
markdown +=
|
|
1211
|
+
"- **Bollinger Width(8,2.0)**: width percentage before row timestamp (Min: 0%, Max: +∞)\n";
|
|
1212
|
+
markdown +=
|
|
1213
|
+
"- **Bollinger Position**: price position within bands before row timestamp (Min: 0%, Max: 100%)\n";
|
|
1214
|
+
markdown +=
|
|
1215
|
+
"- **Stochastic K(3,3,3)**: over previous 3 candles (3 minutes on 1m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1216
|
+
markdown +=
|
|
1217
|
+
"- **Stochastic D(3,3,3)**: over previous 3 candles (3 minutes on 1m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1218
|
+
markdown +=
|
|
1219
|
+
"- **Stochastic K(5,3,3)**: over previous 5 candles (5 minutes on 1m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1220
|
+
markdown +=
|
|
1221
|
+
"- **Stochastic D(5,3,3)**: over previous 5 candles (5 minutes on 1m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1222
|
+
markdown +=
|
|
1223
|
+
"- **Momentum(5)**: over previous 5 candles (5 minutes on 1m timeframe) before row timestamp (Min: -∞ USD, Max: +∞ USD)\n";
|
|
1224
|
+
markdown +=
|
|
1225
|
+
"- **Momentum(10)**: over previous 10 candles (10 minutes on 1m timeframe) before row timestamp (Min: -∞ USD, Max: +∞ USD)\n";
|
|
1226
|
+
markdown +=
|
|
1227
|
+
"- **ROC(1)**: over previous 1 candle (1 minute on 1m timeframe) before row timestamp (Min: -∞%, Max: +∞%)\n";
|
|
1228
|
+
markdown +=
|
|
1229
|
+
"- **ROC(3)**: over previous 3 candles (3 minutes on 1m timeframe) before row timestamp (Min: -∞%, Max: +∞%)\n";
|
|
1230
|
+
markdown +=
|
|
1231
|
+
"- **ROC(5)**: over previous 5 candles (5 minutes on 1m timeframe) before row timestamp (Min: -∞%, Max: +∞%)\n";
|
|
1232
|
+
markdown +=
|
|
1233
|
+
"- **EMA(3)**: over previous 3 candles (3 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1234
|
+
markdown +=
|
|
1235
|
+
"- **EMA(8)**: over previous 8 candles (8 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1236
|
+
markdown +=
|
|
1237
|
+
"- **EMA(13)**: over previous 13 candles (13 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1238
|
+
markdown +=
|
|
1239
|
+
"- **EMA(21)**: over previous 21 candles (21 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1240
|
+
markdown +=
|
|
1241
|
+
"- **SMA(8)**: over previous 8 candles (8 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1242
|
+
markdown +=
|
|
1243
|
+
"- **DEMA(8)**: over previous 8 candles (8 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1244
|
+
markdown +=
|
|
1245
|
+
"- **WMA(5)**: over previous 5 candles (5 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1246
|
+
markdown +=
|
|
1247
|
+
"- **Volume SMA(5)**: over previous 5 candles (5 minutes on 1m timeframe) before row timestamp (Min: 0, Max: +∞)\n";
|
|
1248
|
+
markdown +=
|
|
1249
|
+
"- **Volume Ratio**: volume relative to average at row timestamp (Min: 0x, Max: +∞x)\n";
|
|
1250
|
+
markdown +=
|
|
1251
|
+
"- **Support**: over previous 30 candles (30 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1252
|
+
markdown +=
|
|
1253
|
+
"- **Resistance**: over previous 30 candles (30 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1254
|
+
markdown +=
|
|
1255
|
+
"- **Current Price**: close price at row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1256
|
+
markdown +=
|
|
1257
|
+
"- **1m Change**: price change percentage over 1 minute at row timestamp (Min: -∞%, Max: +∞%)\n";
|
|
1258
|
+
markdown +=
|
|
1259
|
+
"- **3m Change**: price change percentage over 3 minutes at row timestamp (Min: -∞%, Max: +∞%)\n";
|
|
1260
|
+
markdown +=
|
|
1261
|
+
"- **5m Change**: price change percentage over 5 minutes at row timestamp (Min: -∞%, Max: +∞%)\n";
|
|
1262
|
+
markdown +=
|
|
1263
|
+
"- **Volatility(5)**: over previous 5 candles (5 minutes on 1m timeframe) before row timestamp (Min: 0%, Max: +∞)\n";
|
|
1264
|
+
markdown +=
|
|
1265
|
+
"- **True Range**: true range value at row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
1266
|
+
markdown +=
|
|
1267
|
+
"- **Squeeze Momentum**: squeeze momentum indicator at row timestamp (Min: 0, Max: +∞)\n";
|
|
1268
|
+
markdown +=
|
|
1269
|
+
"- **Pressure Index**: buying/selling pressure percentage at row timestamp (Min: -100%, Max: +100%)\n";
|
|
1270
|
+
markdown +=
|
|
1271
|
+
"- **Close Price**: close price at row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1272
|
+
return markdown;
|
|
1273
|
+
}
|
|
1274
|
+
class MicroTermHistoryService {
|
|
1275
|
+
constructor() {
|
|
1276
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
1277
|
+
this.getData = async (symbol, candles) => {
|
|
1278
|
+
this.loggerService.log("microTermHistoryService getData", {
|
|
1279
|
+
symbol,
|
|
1280
|
+
candles: candles.length,
|
|
1281
|
+
});
|
|
1282
|
+
return generateAnalysis$2(symbol, candles);
|
|
1283
|
+
};
|
|
1284
|
+
this.getReport = async (symbol) => {
|
|
1285
|
+
this.loggerService.log("microTermHistoryService getReport", { symbol });
|
|
1286
|
+
const candles = await backtestKit.getCandles(symbol, "1m", 60);
|
|
1287
|
+
const rows = await this.getData(symbol, candles);
|
|
1288
|
+
return generateHistoryTable$2(rows, symbol);
|
|
1289
|
+
};
|
|
1290
|
+
this.generateHistoryTable = async (symbol, rows) => {
|
|
1291
|
+
this.loggerService.log("microTermHistoryService generateHistoryTable", {
|
|
1292
|
+
symbol,
|
|
1293
|
+
rowCount: rows.length,
|
|
1294
|
+
});
|
|
1295
|
+
return generateHistoryTable$2(rows, symbol);
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
const TABLE_ROWS_LIMIT$1 = 48;
|
|
1301
|
+
const WARMUP_PERIOD$1 = 50;
|
|
1302
|
+
const columns$1 = [
|
|
1303
|
+
{
|
|
1304
|
+
key: "rsi9",
|
|
1305
|
+
label: "RSI(9)",
|
|
1306
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1307
|
+
},
|
|
1308
|
+
{
|
|
1309
|
+
key: "stochasticRSI9",
|
|
1310
|
+
label: "Stochastic RSI(9)",
|
|
1311
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
key: "macd8_21_5",
|
|
1315
|
+
label: "MACD(8,21,5)",
|
|
1316
|
+
format: (v) => (v !== null ? Number(v).toFixed(4) : "N/A"),
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
key: "signal5",
|
|
1320
|
+
label: "Signal(5)",
|
|
1321
|
+
format: (v) => (v !== null ? Number(v).toFixed(4) : "N/A"),
|
|
1322
|
+
},
|
|
1323
|
+
{
|
|
1324
|
+
key: "adx14",
|
|
1325
|
+
label: "ADX(14)",
|
|
1326
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1327
|
+
},
|
|
1328
|
+
{
|
|
1329
|
+
key: "plusDI14",
|
|
1330
|
+
label: "+DI(14)",
|
|
1331
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1332
|
+
},
|
|
1333
|
+
{
|
|
1334
|
+
key: "minusDI14",
|
|
1335
|
+
label: "-DI(14)",
|
|
1336
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1337
|
+
},
|
|
1338
|
+
{
|
|
1339
|
+
key: "atr9",
|
|
1340
|
+
label: "ATR(9)",
|
|
1341
|
+
format: async (v, symbol) => v !== null
|
|
1342
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1343
|
+
: "N/A",
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
key: "cci14",
|
|
1347
|
+
label: "CCI(14)",
|
|
1348
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1349
|
+
},
|
|
1350
|
+
{
|
|
1351
|
+
key: "stochasticK5_3_3",
|
|
1352
|
+
label: "Stochastic K(5,3,3)",
|
|
1353
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1354
|
+
},
|
|
1355
|
+
{
|
|
1356
|
+
key: "stochasticD5_3_3",
|
|
1357
|
+
label: "Stochastic D(5,3,3)",
|
|
1358
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1359
|
+
},
|
|
1360
|
+
{
|
|
1361
|
+
key: "momentum8",
|
|
1362
|
+
label: "Momentum(8)",
|
|
1363
|
+
format: async (v, symbol) => v !== null
|
|
1364
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1365
|
+
: "N/A",
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
key: "roc5",
|
|
1369
|
+
label: "ROC(5)",
|
|
1370
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(3)}%` : "N/A"),
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
key: "roc10",
|
|
1374
|
+
label: "ROC(10)",
|
|
1375
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(3)}%` : "N/A"),
|
|
1376
|
+
},
|
|
1377
|
+
{
|
|
1378
|
+
key: "sma50",
|
|
1379
|
+
label: "SMA(50)",
|
|
1380
|
+
format: async (v, symbol) => v !== null
|
|
1381
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1382
|
+
: "N/A",
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
key: "ema8",
|
|
1386
|
+
label: "EMA(8)",
|
|
1387
|
+
format: async (v, symbol) => v !== null
|
|
1388
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1389
|
+
: "N/A",
|
|
1390
|
+
},
|
|
1391
|
+
{
|
|
1392
|
+
key: "ema21",
|
|
1393
|
+
label: "EMA(21)",
|
|
1394
|
+
format: async (v, symbol) => v !== null
|
|
1395
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1396
|
+
: "N/A",
|
|
1397
|
+
},
|
|
1398
|
+
{
|
|
1399
|
+
key: "dema21",
|
|
1400
|
+
label: "DEMA(21)",
|
|
1401
|
+
format: async (v, symbol) => v !== null
|
|
1402
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1403
|
+
: "N/A",
|
|
1404
|
+
},
|
|
1405
|
+
{
|
|
1406
|
+
key: "wma20",
|
|
1407
|
+
label: "WMA(20)",
|
|
1408
|
+
format: async (v, symbol) => v !== null
|
|
1409
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1410
|
+
: "N/A",
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
key: "currentPrice",
|
|
1414
|
+
label: "Current Price",
|
|
1415
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
1416
|
+
},
|
|
1417
|
+
{
|
|
1418
|
+
key: "support",
|
|
1419
|
+
label: "Support Level",
|
|
1420
|
+
format: async (v, symbol) => v !== null
|
|
1421
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1422
|
+
: "N/A",
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
key: "resistance",
|
|
1426
|
+
label: "Resistance Level",
|
|
1427
|
+
format: async (v, symbol) => v !== null
|
|
1428
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1429
|
+
: "N/A",
|
|
1430
|
+
},
|
|
1431
|
+
{
|
|
1432
|
+
key: "bollingerUpper10_2",
|
|
1433
|
+
label: "Bollinger Upper(10,2.0)",
|
|
1434
|
+
format: async (v, symbol) => v !== null
|
|
1435
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1436
|
+
: "N/A",
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
key: "bollingerMiddle10_2",
|
|
1440
|
+
label: "Bollinger Middle(10,2.0)",
|
|
1441
|
+
format: async (v, symbol) => v !== null
|
|
1442
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1443
|
+
: "N/A",
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
key: "bollingerLower10_2",
|
|
1447
|
+
label: "Bollinger Lower(10,2.0)",
|
|
1448
|
+
format: async (v, symbol) => v !== null
|
|
1449
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1450
|
+
: "N/A",
|
|
1451
|
+
},
|
|
1452
|
+
{
|
|
1453
|
+
key: "bollingerWidth10_2",
|
|
1454
|
+
label: "Bollinger Width(10,2.0)",
|
|
1455
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(2)}%` : "N/A"),
|
|
1456
|
+
},
|
|
1457
|
+
{
|
|
1458
|
+
key: "fibonacciNearestLevel",
|
|
1459
|
+
label: "Fibonacci Nearest Level",
|
|
1460
|
+
format: (v) => String(v),
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
key: "fibonacciNearestPrice",
|
|
1464
|
+
label: "Fibonacci Nearest Price",
|
|
1465
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
1466
|
+
},
|
|
1467
|
+
{
|
|
1468
|
+
key: "fibonacciDistance",
|
|
1469
|
+
label: "Fibonacci Distance",
|
|
1470
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
key: "bodySize",
|
|
1474
|
+
label: "Body Size",
|
|
1475
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
key: "closePrice",
|
|
1479
|
+
label: "Close Price",
|
|
1480
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
1481
|
+
},
|
|
1482
|
+
{
|
|
1483
|
+
key: "date",
|
|
1484
|
+
label: "Timestamp",
|
|
1485
|
+
format: (v) => new Date(v).toISOString(),
|
|
1486
|
+
},
|
|
1487
|
+
];
|
|
1488
|
+
function isUnsafe$2(value) {
|
|
1489
|
+
if (typeof value !== "number") {
|
|
1490
|
+
return true;
|
|
1491
|
+
}
|
|
1492
|
+
if (isNaN(value)) {
|
|
1493
|
+
return true;
|
|
1494
|
+
}
|
|
1495
|
+
if (!isFinite(value)) {
|
|
1496
|
+
return true;
|
|
1497
|
+
}
|
|
1498
|
+
return false;
|
|
1499
|
+
}
|
|
1500
|
+
function calculateFibonacciLevels$1(candles, endIndex) {
|
|
1501
|
+
const lookbackPeriod = Math.min(288, endIndex + 1);
|
|
1502
|
+
const startIndex = endIndex + 1 - lookbackPeriod;
|
|
1503
|
+
const recentCandles = candles.slice(startIndex, endIndex + 1);
|
|
1504
|
+
const high = Math.max(...recentCandles.map((c) => Number(c.high)));
|
|
1505
|
+
const low = Math.min(...recentCandles.map((c) => Number(c.low)));
|
|
1506
|
+
const range = high - low;
|
|
1507
|
+
const levels = {
|
|
1508
|
+
"0.0%": high,
|
|
1509
|
+
"23.6%": high - range * 0.236,
|
|
1510
|
+
"38.2%": high - range * 0.382,
|
|
1511
|
+
"50.0%": high - range * 0.5,
|
|
1512
|
+
"61.8%": high - range * 0.618,
|
|
1513
|
+
"78.6%": high - range * 0.786,
|
|
1514
|
+
"100.0%": low,
|
|
1515
|
+
"127.2%": high - range * 1.272,
|
|
1516
|
+
"161.8%": high - range * 1.618,
|
|
1517
|
+
};
|
|
1518
|
+
const currentPrice = Number(candles[endIndex].close);
|
|
1519
|
+
let nearestLevel = {
|
|
1520
|
+
level: "50.0%",
|
|
1521
|
+
price: levels["50.0%"],
|
|
1522
|
+
distance: Math.abs(currentPrice - levels["50.0%"]),
|
|
1523
|
+
};
|
|
1524
|
+
Object.entries(levels).forEach(([level, price]) => {
|
|
1525
|
+
const distance = Math.abs(currentPrice - price);
|
|
1526
|
+
if (distance < nearestLevel.distance) {
|
|
1527
|
+
nearestLevel = { level, price, distance };
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1530
|
+
return nearestLevel;
|
|
1531
|
+
}
|
|
1532
|
+
function calculateVolumeTrend(candles, endIndex) {
|
|
1533
|
+
const volumes = candles.slice(0, endIndex + 1).map((c) => Number(c.volume));
|
|
1534
|
+
if (volumes.length < 16)
|
|
1535
|
+
return "stable";
|
|
1536
|
+
const recentVolumes = volumes.slice(-8);
|
|
1537
|
+
const olderVolumes = volumes.slice(-16, -8);
|
|
1538
|
+
if (recentVolumes.length < 4 || olderVolumes.length < 4)
|
|
1539
|
+
return "stable";
|
|
1540
|
+
const recentAvg = recentVolumes.reduce((sum, vol) => sum + vol, 0) / recentVolumes.length;
|
|
1541
|
+
const olderAvg = olderVolumes.reduce((sum, vol) => sum + vol, 0) / olderVolumes.length;
|
|
1542
|
+
if (recentAvg > olderAvg * 1.2)
|
|
1543
|
+
return "increasing";
|
|
1544
|
+
if (recentAvg < olderAvg * 0.8)
|
|
1545
|
+
return "decreasing";
|
|
1546
|
+
return "stable";
|
|
1547
|
+
}
|
|
1548
|
+
function generateAnalysis$1(symbol, candles) {
|
|
1549
|
+
const closes = candles.map((candle) => Number(candle.close));
|
|
1550
|
+
const highs = candles.map((candle) => Number(candle.high));
|
|
1551
|
+
const lows = candles.map((candle) => Number(candle.low));
|
|
1552
|
+
const opens = candles.map((candle) => Number(candle.open));
|
|
1553
|
+
const rsi = new tradingSignals.FasterRSI(9);
|
|
1554
|
+
const stochasticRSI = new tradingSignals.FasterStochasticRSI(9);
|
|
1555
|
+
const shortEMA = new tradingSignals.FasterEMA(8);
|
|
1556
|
+
const longEMA = new tradingSignals.FasterEMA(21);
|
|
1557
|
+
const signalEMA = new tradingSignals.FasterEMA(5);
|
|
1558
|
+
const macd = new tradingSignals.FasterMACD(shortEMA, longEMA, signalEMA);
|
|
1559
|
+
const bollinger = new tradingSignals.FasterBollingerBands(10, 2.0);
|
|
1560
|
+
const atr = new tradingSignals.FasterATR(9);
|
|
1561
|
+
const sma50 = new tradingSignals.FasterSMA(50);
|
|
1562
|
+
const ema8 = new tradingSignals.FasterEMA(8);
|
|
1563
|
+
const ema21 = new tradingSignals.FasterEMA(21);
|
|
1564
|
+
const dema21 = new tradingSignals.FasterDEMA(21);
|
|
1565
|
+
const wma20 = new tradingSignals.FasterWMA(20);
|
|
1566
|
+
const momentum = new tradingSignals.FasterMOM(8);
|
|
1567
|
+
const roc5 = new tradingSignals.FasterROC(5);
|
|
1568
|
+
const roc10 = new tradingSignals.FasterROC(10);
|
|
1569
|
+
const stochastic = new tradingSignals.FasterStochasticOscillator(5, 3, 3);
|
|
1570
|
+
const cci = new tradingSignals.FasterCCI(14);
|
|
1571
|
+
const adx = new tradingSignals.FasterADX(14);
|
|
1572
|
+
const results = [];
|
|
1573
|
+
candles.forEach((_candle, i) => {
|
|
1574
|
+
const high = highs[i];
|
|
1575
|
+
const low = lows[i];
|
|
1576
|
+
const close = closes[i];
|
|
1577
|
+
const open = opens[i];
|
|
1578
|
+
const currentPrice = close;
|
|
1579
|
+
// Update all indicators
|
|
1580
|
+
rsi.update(close, false);
|
|
1581
|
+
stochasticRSI.update(close, false);
|
|
1582
|
+
macd.update(close, false);
|
|
1583
|
+
bollinger.update(close, false);
|
|
1584
|
+
atr.update({ high, low, close }, false);
|
|
1585
|
+
sma50.update(close, false);
|
|
1586
|
+
ema8.update(close, false);
|
|
1587
|
+
ema21.update(close, false);
|
|
1588
|
+
dema21.update(close, false);
|
|
1589
|
+
wma20.update(close, false);
|
|
1590
|
+
momentum.update(close, false);
|
|
1591
|
+
roc5.update(close, false);
|
|
1592
|
+
roc10.update(close, false);
|
|
1593
|
+
stochastic.update({ high, low, close }, false);
|
|
1594
|
+
cci.update({ high, low, close }, false);
|
|
1595
|
+
adx.update({ high, low, close }, false);
|
|
1596
|
+
// Determine minimum warm-up period needed (largest indicator period)
|
|
1597
|
+
// SMA(50) is the largest period
|
|
1598
|
+
// Skip rows until all indicators are warmed up
|
|
1599
|
+
if (i < WARMUP_PERIOD$1) {
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
const volumeTrend = calculateVolumeTrend(candles, i);
|
|
1603
|
+
const pivotPeriod = Math.min(48, i + 1);
|
|
1604
|
+
const startIdx = i + 1 - pivotPeriod;
|
|
1605
|
+
const recentHighs = highs
|
|
1606
|
+
.slice(startIdx, i + 1)
|
|
1607
|
+
.filter((h) => !isUnsafe$2(h));
|
|
1608
|
+
const recentLows = lows.slice(startIdx, i + 1).filter((l) => !isUnsafe$2(l));
|
|
1609
|
+
const minDistance = currentPrice * 0.003;
|
|
1610
|
+
const significantHighs = [...recentHighs]
|
|
1611
|
+
.filter((h) => h > currentPrice + minDistance)
|
|
1612
|
+
.sort((a, b) => a - b);
|
|
1613
|
+
const significantLows = [...recentLows]
|
|
1614
|
+
.filter((l) => l < currentPrice - minDistance)
|
|
1615
|
+
.sort((a, b) => b - a);
|
|
1616
|
+
const resistance = significantHighs.length > 0
|
|
1617
|
+
? significantHighs[0]
|
|
1618
|
+
: recentHighs.length > 0
|
|
1619
|
+
? Math.max(...recentHighs)
|
|
1620
|
+
: currentPrice;
|
|
1621
|
+
const support = significantLows.length > 0
|
|
1622
|
+
? significantLows[0]
|
|
1623
|
+
: recentLows.length > 0
|
|
1624
|
+
? Math.min(...recentLows)
|
|
1625
|
+
: currentPrice;
|
|
1626
|
+
const fibonacciNearest = calculateFibonacciLevels$1(candles, i);
|
|
1627
|
+
const rsiValue = rsi.getResult() ?? null;
|
|
1628
|
+
const stochasticRSIResult = stochasticRSI.getResult();
|
|
1629
|
+
const stochasticRSIValue = !isUnsafe$2(stochasticRSIResult)
|
|
1630
|
+
? stochasticRSIResult * 100
|
|
1631
|
+
: null;
|
|
1632
|
+
const macdResult = macd.getResult();
|
|
1633
|
+
const bollingerResult = bollinger.getResult();
|
|
1634
|
+
const stochasticResult = stochastic.getResult();
|
|
1635
|
+
const adxValue = adx.getResult() ?? null;
|
|
1636
|
+
const plusDI14 = typeof adx.pdi === "number" ? adx.pdi * 100 : null;
|
|
1637
|
+
const minusDI14 = typeof adx.mdi === "number" ? adx.mdi * 100 : null;
|
|
1638
|
+
const bodySize = Math.abs(close - open);
|
|
1639
|
+
results.push({
|
|
1640
|
+
symbol,
|
|
1641
|
+
rsi9: rsiValue != null && !isUnsafe$2(rsiValue) ? rsiValue : null,
|
|
1642
|
+
stochasticRSI9: stochasticRSIValue,
|
|
1643
|
+
macd8_21_5: macdResult && !isUnsafe$2(macdResult.macd) ? macdResult.macd : null,
|
|
1644
|
+
signal5: macdResult && !isUnsafe$2(macdResult.signal) ? macdResult.signal : null,
|
|
1645
|
+
bollingerUpper10_2: bollingerResult && !isUnsafe$2(bollingerResult.upper)
|
|
1646
|
+
? bollingerResult.upper
|
|
1647
|
+
: null,
|
|
1648
|
+
bollingerMiddle10_2: bollingerResult && !isUnsafe$2(bollingerResult.middle)
|
|
1649
|
+
? bollingerResult.middle
|
|
1650
|
+
: null,
|
|
1651
|
+
bollingerLower10_2: bollingerResult && !isUnsafe$2(bollingerResult.lower)
|
|
1652
|
+
? bollingerResult.lower
|
|
1653
|
+
: null,
|
|
1654
|
+
bollingerWidth10_2: bollingerResult &&
|
|
1655
|
+
!isUnsafe$2(bollingerResult.upper) &&
|
|
1656
|
+
!isUnsafe$2(bollingerResult.lower) &&
|
|
1657
|
+
!isUnsafe$2(bollingerResult.middle)
|
|
1658
|
+
? ((bollingerResult.upper - bollingerResult.lower) /
|
|
1659
|
+
bollingerResult.middle) *
|
|
1660
|
+
100
|
|
1661
|
+
: null,
|
|
1662
|
+
stochasticK5_3_3: stochasticResult && !isUnsafe$2(stochasticResult.stochK)
|
|
1663
|
+
? stochasticResult.stochK
|
|
1664
|
+
: null,
|
|
1665
|
+
stochasticD5_3_3: stochasticResult && !isUnsafe$2(stochasticResult.stochD)
|
|
1666
|
+
? stochasticResult.stochD
|
|
1667
|
+
: null,
|
|
1668
|
+
adx14: adxValue != null && !isUnsafe$2(adxValue) ? adxValue : null,
|
|
1669
|
+
plusDI14: plusDI14 != null && !isUnsafe$2(plusDI14) ? plusDI14 : null,
|
|
1670
|
+
minusDI14: minusDI14 != null && !isUnsafe$2(minusDI14) ? minusDI14 : null,
|
|
1671
|
+
atr9: atr.getResult() != null && !isUnsafe$2(atr.getResult())
|
|
1672
|
+
? atr.getResult()
|
|
1673
|
+
: null,
|
|
1674
|
+
cci14: cci.getResult() != null && !isUnsafe$2(cci.getResult())
|
|
1675
|
+
? cci.getResult()
|
|
1676
|
+
: null,
|
|
1677
|
+
sma50: sma50.getResult() != null && !isUnsafe$2(sma50.getResult())
|
|
1678
|
+
? sma50.getResult()
|
|
1679
|
+
: null,
|
|
1680
|
+
ema8: ema8.getResult() != null && !isUnsafe$2(ema8.getResult())
|
|
1681
|
+
? ema8.getResult()
|
|
1682
|
+
: null,
|
|
1683
|
+
ema21: ema21.getResult() != null && !isUnsafe$2(ema21.getResult())
|
|
1684
|
+
? ema21.getResult()
|
|
1685
|
+
: null,
|
|
1686
|
+
dema21: dema21.getResult() != null && !isUnsafe$2(dema21.getResult())
|
|
1687
|
+
? dema21.getResult()
|
|
1688
|
+
: null,
|
|
1689
|
+
wma20: wma20.getResult() != null && !isUnsafe$2(wma20.getResult())
|
|
1690
|
+
? wma20.getResult()
|
|
1691
|
+
: null,
|
|
1692
|
+
momentum8: momentum.getResult() != null && !isUnsafe$2(momentum.getResult())
|
|
1693
|
+
? momentum.getResult()
|
|
1694
|
+
: null,
|
|
1695
|
+
roc5: roc5.getResult() != null && !isUnsafe$2(roc5.getResult())
|
|
1696
|
+
? roc5.getResult()
|
|
1697
|
+
: null,
|
|
1698
|
+
roc10: roc10.getResult() != null && !isUnsafe$2(roc10.getResult())
|
|
1699
|
+
? roc10.getResult()
|
|
1700
|
+
: null,
|
|
1701
|
+
volumeTrend,
|
|
1702
|
+
support: support != null && !isUnsafe$2(support)
|
|
1703
|
+
? support
|
|
1704
|
+
: !isUnsafe$2(currentPrice)
|
|
1705
|
+
? currentPrice
|
|
1706
|
+
: null,
|
|
1707
|
+
resistance: resistance != null && !isUnsafe$2(resistance)
|
|
1708
|
+
? resistance
|
|
1709
|
+
: !isUnsafe$2(currentPrice)
|
|
1710
|
+
? currentPrice
|
|
1711
|
+
: null,
|
|
1712
|
+
currentPrice: currentPrice != null && !isUnsafe$2(currentPrice) ? currentPrice : null,
|
|
1713
|
+
fibonacciNearestLevel: fibonacciNearest.level,
|
|
1714
|
+
fibonacciNearestPrice: fibonacciNearest.price,
|
|
1715
|
+
fibonacciDistance: fibonacciNearest.distance,
|
|
1716
|
+
bodySize,
|
|
1717
|
+
closePrice: close,
|
|
1718
|
+
date: new Date(),
|
|
1719
|
+
lookbackPeriod: "144 candles (36 hours)",
|
|
1720
|
+
});
|
|
1721
|
+
});
|
|
1722
|
+
// Return only the last TABLE_ROWS_LIMIT rows
|
|
1723
|
+
return results.slice(-TABLE_ROWS_LIMIT$1);
|
|
1724
|
+
}
|
|
1725
|
+
async function generateHistoryTable$1(indicators, symbol) {
|
|
1726
|
+
let markdown = "";
|
|
1727
|
+
const currentData = await backtestKit.getDate();
|
|
1728
|
+
markdown += `# 15-Minute Candles Trading Analysis for ${symbol} (Historical Data)\n`;
|
|
1729
|
+
markdown += `> Current time: ${currentData.toISOString()}\n\n`;
|
|
1730
|
+
const header = `| ${columns$1.map((col) => col.label).join(" | ")} |\n`;
|
|
1731
|
+
const separator = `| ${columns$1.map(() => "---").join(" | ")} |\n`;
|
|
1732
|
+
const tableRows = await Promise.all(indicators.map(async (ind) => {
|
|
1733
|
+
const cells = await Promise.all(columns$1.map(async (col) => await col.format(ind[col.key], symbol)));
|
|
1734
|
+
return `| ${cells.join(" | ")} |`;
|
|
1735
|
+
}));
|
|
1736
|
+
markdown += header;
|
|
1737
|
+
markdown += separator;
|
|
1738
|
+
markdown += tableRows.join("\n");
|
|
1739
|
+
markdown += "\n\n";
|
|
1740
|
+
markdown += "## Data Sources\n";
|
|
1741
|
+
markdown += "- **Timeframe**: 15-minute candles\n";
|
|
1742
|
+
markdown += "- **Lookback Period**: 144 candles (36 hours)\n";
|
|
1743
|
+
markdown +=
|
|
1744
|
+
"- **RSI(9)**: over previous 9 candles (135 minutes on 15m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1745
|
+
markdown +=
|
|
1746
|
+
"- **Stochastic RSI(9)**: over previous 9 candles (135 minutes on 15m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1747
|
+
markdown +=
|
|
1748
|
+
"- **MACD(8,21,5)**: fast 8 and slow 21 periods on 15m timeframe before row timestamp (Min: -∞, Max: +∞)\n";
|
|
1749
|
+
markdown +=
|
|
1750
|
+
"- **Signal(5)**: over previous 5 candles (75 minutes on 15m timeframe) before row timestamp (Min: -∞, Max: +∞)\n";
|
|
1751
|
+
markdown +=
|
|
1752
|
+
"- **ADX(14)**: over previous 14 candles (210 minutes on 15m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1753
|
+
markdown +=
|
|
1754
|
+
"- **+DI(14)**: over previous 14 candles (210 minutes on 15m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1755
|
+
markdown +=
|
|
1756
|
+
"- **-DI(14)**: over previous 14 candles (210 minutes on 15m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1757
|
+
markdown +=
|
|
1758
|
+
"- **ATR(9)**: over previous 9 candles (135 minutes on 15m timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
1759
|
+
markdown +=
|
|
1760
|
+
"- **CCI(14)**: over previous 14 candles (210 minutes on 15m timeframe) before row timestamp (Min: -∞, Max: +∞)\n";
|
|
1761
|
+
markdown +=
|
|
1762
|
+
"- **Bollinger Upper(10,2.0)**: over previous 10 candles (150 minutes on 15m timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
1763
|
+
markdown +=
|
|
1764
|
+
"- **Bollinger Middle(10,2.0)**: over previous 10 candles (150 minutes on 15m timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
1765
|
+
markdown +=
|
|
1766
|
+
"- **Bollinger Lower(10,2.0)**: over previous 10 candles (150 minutes on 15m timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
1767
|
+
markdown +=
|
|
1768
|
+
"- **Bollinger Width(10,2.0)**: width percentage before row timestamp (Min: 0%, Max: +∞)\n";
|
|
1769
|
+
markdown +=
|
|
1770
|
+
"- **Stochastic K(5,3,3)**: over previous 5 candles (75 minutes on 15m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1771
|
+
markdown +=
|
|
1772
|
+
"- **Stochastic D(5,3,3)**: over previous 5 candles (75 minutes on 15m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1773
|
+
markdown +=
|
|
1774
|
+
"- **Momentum(8)**: over previous 8 candles (120 minutes on 15m timeframe) before row timestamp (Min: -∞ USD, Max: +∞ USD)\n";
|
|
1775
|
+
markdown +=
|
|
1776
|
+
"- **ROC(5)**: over previous 5 candles (75 minutes on 15m timeframe) before row timestamp (Min: -∞%, Max: +∞%)\n";
|
|
1777
|
+
markdown +=
|
|
1778
|
+
"- **ROC(10)**: over previous 10 candles (150 minutes on 15m timeframe) before row timestamp (Min: -∞%, Max: +∞%)\n";
|
|
1779
|
+
markdown +=
|
|
1780
|
+
"- **SMA(50)**: over previous 50 candles (750 minutes on 15m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1781
|
+
markdown +=
|
|
1782
|
+
"- **EMA(8)**: over previous 8 candles (120 minutes on 15m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1783
|
+
markdown +=
|
|
1784
|
+
"- **EMA(21)**: over previous 21 candles (315 minutes on 15m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1785
|
+
markdown +=
|
|
1786
|
+
"- **DEMA(21)**: over previous 21 candles (315 minutes on 15m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1787
|
+
markdown +=
|
|
1788
|
+
"- **WMA(20)**: over previous 20 candles (300 minutes on 15m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1789
|
+
markdown +=
|
|
1790
|
+
"- **Support**: over previous 48 candles (12 hours on 15m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1791
|
+
markdown +=
|
|
1792
|
+
"- **Resistance**: over previous 48 candles (12 hours on 15m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1793
|
+
markdown +=
|
|
1794
|
+
"- **Fibonacci Nearest Level**: nearest level name before row timestamp\n";
|
|
1795
|
+
markdown +=
|
|
1796
|
+
"- **Fibonacci Nearest Price**: nearest price level over 288 candles (72h on 15m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1797
|
+
markdown +=
|
|
1798
|
+
"- **Fibonacci Distance**: distance to nearest level before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1799
|
+
markdown +=
|
|
1800
|
+
"- **Current Price**: close price at row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1801
|
+
markdown +=
|
|
1802
|
+
"- **Body Size**: candle body size at row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1803
|
+
markdown +=
|
|
1804
|
+
"- **Close Price**: close price at row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1805
|
+
return markdown;
|
|
1806
|
+
}
|
|
1807
|
+
class ShortTermHistoryService {
|
|
1808
|
+
constructor() {
|
|
1809
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
1810
|
+
this.getData = async (symbol, candles) => {
|
|
1811
|
+
this.loggerService.log("shortTermHistoryService getData", {
|
|
1812
|
+
symbol,
|
|
1813
|
+
candles: candles.length,
|
|
1814
|
+
});
|
|
1815
|
+
return generateAnalysis$1(symbol, candles);
|
|
1816
|
+
};
|
|
1817
|
+
this.getReport = async (symbol) => {
|
|
1818
|
+
this.loggerService.log("shortTermHistoryService getReport", { symbol });
|
|
1819
|
+
const candles = await backtestKit.getCandles(symbol, "15m", 144);
|
|
1820
|
+
const rows = await this.getData(symbol, candles);
|
|
1821
|
+
return generateHistoryTable$1(rows, symbol);
|
|
1822
|
+
};
|
|
1823
|
+
this.generateHistoryTable = async (symbol, rows) => {
|
|
1824
|
+
this.loggerService.log("shortTermHistoryService generateHistoryTable", {
|
|
1825
|
+
symbol,
|
|
1826
|
+
rowCount: rows.length,
|
|
1827
|
+
});
|
|
1828
|
+
return generateHistoryTable$1(rows, symbol);
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
const TABLE_ROWS_LIMIT = 30;
|
|
1834
|
+
const WARMUP_PERIOD = 34;
|
|
1835
|
+
const columns = [
|
|
1836
|
+
{
|
|
1837
|
+
key: "rsi14",
|
|
1838
|
+
label: "RSI(14)",
|
|
1839
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1840
|
+
},
|
|
1841
|
+
{
|
|
1842
|
+
key: "stochasticRSI14",
|
|
1843
|
+
label: "Stochastic RSI(14)",
|
|
1844
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1845
|
+
},
|
|
1846
|
+
{
|
|
1847
|
+
key: "macd12_26_9",
|
|
1848
|
+
label: "MACD(12,26,9)",
|
|
1849
|
+
format: (v) => (v !== null ? Number(v).toFixed(4) : "N/A"),
|
|
1850
|
+
},
|
|
1851
|
+
{
|
|
1852
|
+
key: "signal9",
|
|
1853
|
+
label: "Signal(9)",
|
|
1854
|
+
format: (v) => (v !== null ? Number(v).toFixed(4) : "N/A"),
|
|
1855
|
+
},
|
|
1856
|
+
{
|
|
1857
|
+
key: "adx14",
|
|
1858
|
+
label: "ADX(14)",
|
|
1859
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1860
|
+
},
|
|
1861
|
+
{
|
|
1862
|
+
key: "plusDI14",
|
|
1863
|
+
label: "+DI(14)",
|
|
1864
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1865
|
+
},
|
|
1866
|
+
{
|
|
1867
|
+
key: "minusDI14",
|
|
1868
|
+
label: "-DI(14)",
|
|
1869
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1870
|
+
},
|
|
1871
|
+
{
|
|
1872
|
+
key: "atr14",
|
|
1873
|
+
label: "ATR(14)",
|
|
1874
|
+
format: async (v, symbol) => v !== null
|
|
1875
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1876
|
+
: "N/A",
|
|
1877
|
+
},
|
|
1878
|
+
{
|
|
1879
|
+
key: "cci20",
|
|
1880
|
+
label: "CCI(20)",
|
|
1881
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1882
|
+
},
|
|
1883
|
+
{
|
|
1884
|
+
key: "stochasticK14_3_3",
|
|
1885
|
+
label: "Stochastic K(14,3,3)",
|
|
1886
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1887
|
+
},
|
|
1888
|
+
{
|
|
1889
|
+
key: "stochasticD14_3_3",
|
|
1890
|
+
label: "Stochastic D(14,3,3)",
|
|
1891
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1892
|
+
},
|
|
1893
|
+
{
|
|
1894
|
+
key: "momentum8",
|
|
1895
|
+
label: "Momentum(8)",
|
|
1896
|
+
format: async (v, symbol) => v !== null
|
|
1897
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1898
|
+
: "N/A",
|
|
1899
|
+
},
|
|
1900
|
+
{
|
|
1901
|
+
key: "dema21",
|
|
1902
|
+
label: "DEMA(21)",
|
|
1903
|
+
format: async (v, symbol) => v !== null
|
|
1904
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1905
|
+
: "N/A",
|
|
1906
|
+
},
|
|
1907
|
+
{
|
|
1908
|
+
key: "wma20",
|
|
1909
|
+
label: "WMA(20)",
|
|
1910
|
+
format: async (v, symbol) => v !== null
|
|
1911
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1912
|
+
: "N/A",
|
|
1913
|
+
},
|
|
1914
|
+
{
|
|
1915
|
+
key: "sma20",
|
|
1916
|
+
label: "SMA(20)",
|
|
1917
|
+
format: async (v, symbol) => v !== null
|
|
1918
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1919
|
+
: "N/A",
|
|
1920
|
+
},
|
|
1921
|
+
{
|
|
1922
|
+
key: "ema13",
|
|
1923
|
+
label: "EMA(13)",
|
|
1924
|
+
format: async (v, symbol) => v !== null
|
|
1925
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1926
|
+
: "N/A",
|
|
1927
|
+
},
|
|
1928
|
+
{
|
|
1929
|
+
key: "ema34",
|
|
1930
|
+
label: "EMA(34)",
|
|
1931
|
+
format: async (v, symbol) => v !== null
|
|
1932
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1933
|
+
: "N/A",
|
|
1934
|
+
},
|
|
1935
|
+
{
|
|
1936
|
+
key: "currentPrice",
|
|
1937
|
+
label: "Current Price",
|
|
1938
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
1939
|
+
},
|
|
1940
|
+
{
|
|
1941
|
+
key: "support",
|
|
1942
|
+
label: "Support Level",
|
|
1943
|
+
format: async (v, symbol) => v !== null
|
|
1944
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1945
|
+
: "N/A",
|
|
1946
|
+
},
|
|
1947
|
+
{
|
|
1948
|
+
key: "resistance",
|
|
1949
|
+
label: "Resistance Level",
|
|
1950
|
+
format: async (v, symbol) => v !== null
|
|
1951
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1952
|
+
: "N/A",
|
|
1953
|
+
},
|
|
1954
|
+
{
|
|
1955
|
+
key: "bollingerUpper20_2",
|
|
1956
|
+
label: "Bollinger Upper(20,2.0)",
|
|
1957
|
+
format: async (v, symbol) => v !== null
|
|
1958
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1959
|
+
: "N/A",
|
|
1960
|
+
},
|
|
1961
|
+
{
|
|
1962
|
+
key: "bollingerMiddle20_2",
|
|
1963
|
+
label: "Bollinger Middle(20,2.0)",
|
|
1964
|
+
format: async (v, symbol) => v !== null
|
|
1965
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1966
|
+
: "N/A",
|
|
1967
|
+
},
|
|
1968
|
+
{
|
|
1969
|
+
key: "bollingerLower20_2",
|
|
1970
|
+
label: "Bollinger Lower(20,2.0)",
|
|
1971
|
+
format: async (v, symbol) => v !== null
|
|
1972
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1973
|
+
: "N/A",
|
|
1974
|
+
},
|
|
1975
|
+
{
|
|
1976
|
+
key: "bollingerWidth20_2",
|
|
1977
|
+
label: "Bollinger Width(20,2.0)",
|
|
1978
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(2)}%` : "N/A"),
|
|
1979
|
+
},
|
|
1980
|
+
{
|
|
1981
|
+
key: "volume",
|
|
1982
|
+
label: "Volume",
|
|
1983
|
+
format: (v) => (v !== null ? Number(v).toFixed(2) : "N/A"),
|
|
1984
|
+
},
|
|
1985
|
+
{
|
|
1986
|
+
key: "volatility",
|
|
1987
|
+
label: "Basic Volatility",
|
|
1988
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(2)}%` : "N/A"),
|
|
1989
|
+
},
|
|
1990
|
+
{
|
|
1991
|
+
key: "priceMomentum6",
|
|
1992
|
+
label: "Price Momentum(6)",
|
|
1993
|
+
format: async (v, symbol) => v !== null
|
|
1994
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
1995
|
+
: "N/A",
|
|
1996
|
+
},
|
|
1997
|
+
{
|
|
1998
|
+
key: "fibonacciNearestSupport",
|
|
1999
|
+
label: "Fibonacci Nearest Support",
|
|
2000
|
+
format: async (v, symbol) => v !== null
|
|
2001
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
2002
|
+
: "N/A",
|
|
2003
|
+
},
|
|
2004
|
+
{
|
|
2005
|
+
key: "fibonacciNearestResistance",
|
|
2006
|
+
label: "Fibonacci Nearest Resistance",
|
|
2007
|
+
format: async (v, symbol) => v !== null
|
|
2008
|
+
? `${await backtestKit.formatPrice(symbol, Number(v))} USD`
|
|
2009
|
+
: "N/A",
|
|
2010
|
+
},
|
|
2011
|
+
{
|
|
2012
|
+
key: "fibonacciCurrentLevel",
|
|
2013
|
+
label: "Fibonacci Current Level",
|
|
2014
|
+
format: (v) => String(v),
|
|
2015
|
+
},
|
|
2016
|
+
{
|
|
2017
|
+
key: "bodySize",
|
|
2018
|
+
label: "Body Size",
|
|
2019
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
2020
|
+
},
|
|
2021
|
+
{
|
|
2022
|
+
key: "closePrice",
|
|
2023
|
+
label: "Close Price",
|
|
2024
|
+
format: async (v, symbol) => `${await backtestKit.formatPrice(symbol, Number(v))} USD`,
|
|
2025
|
+
},
|
|
2026
|
+
{
|
|
2027
|
+
key: "date",
|
|
2028
|
+
label: "Timestamp",
|
|
2029
|
+
format: (v) => new Date(v).toISOString(),
|
|
2030
|
+
},
|
|
2031
|
+
];
|
|
2032
|
+
function isUnsafe$1(value) {
|
|
2033
|
+
if (typeof value !== "number") {
|
|
2034
|
+
return true;
|
|
2035
|
+
}
|
|
2036
|
+
if (isNaN(value)) {
|
|
2037
|
+
return true;
|
|
2038
|
+
}
|
|
2039
|
+
if (!isFinite(value)) {
|
|
2040
|
+
return true;
|
|
2041
|
+
}
|
|
2042
|
+
return false;
|
|
2043
|
+
}
|
|
2044
|
+
function calculateFibonacciLevels(candles, endIndex, period = 48) {
|
|
2045
|
+
if (endIndex + 1 < period) {
|
|
2046
|
+
return {
|
|
2047
|
+
nearestSupport: null,
|
|
2048
|
+
nearestResistance: null,
|
|
2049
|
+
currentLevel: "insufficient data",
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
const startIdx = endIndex + 1 - period;
|
|
2053
|
+
const recentCandles = candles.slice(startIdx, endIndex + 1);
|
|
2054
|
+
const highs = recentCandles
|
|
2055
|
+
.map((c) => Number(c.high))
|
|
2056
|
+
.filter((h) => !isUnsafe$1(h));
|
|
2057
|
+
const lows = recentCandles
|
|
2058
|
+
.map((c) => Number(c.low))
|
|
2059
|
+
.filter((l) => !isUnsafe$1(l));
|
|
2060
|
+
if (highs.length === 0 || lows.length === 0) {
|
|
2061
|
+
return {
|
|
2062
|
+
nearestSupport: null,
|
|
2063
|
+
nearestResistance: null,
|
|
2064
|
+
currentLevel: "insufficient data",
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
const high = Math.max(...highs);
|
|
2068
|
+
const low = Math.min(...lows);
|
|
2069
|
+
const range = high - low;
|
|
2070
|
+
const currentPrice = Number(candles[endIndex].close);
|
|
2071
|
+
const retracement = {
|
|
2072
|
+
level0: high,
|
|
2073
|
+
level236: high - range * 0.236,
|
|
2074
|
+
level382: high - range * 0.382,
|
|
2075
|
+
level500: high - range * 0.5,
|
|
2076
|
+
level618: high - range * 0.618,
|
|
2077
|
+
level786: high - range * 0.786,
|
|
2078
|
+
level1000: low,
|
|
2079
|
+
};
|
|
2080
|
+
const extension = {
|
|
2081
|
+
level1272: high + range * 0.272,
|
|
2082
|
+
level1618: high + range * 0.618,
|
|
2083
|
+
level2618: high + range * 1.618,
|
|
2084
|
+
};
|
|
2085
|
+
const tolerance = range * 0.015;
|
|
2086
|
+
let currentLevel = "between levels";
|
|
2087
|
+
if (Math.abs(currentPrice - retracement.level0) < tolerance) {
|
|
2088
|
+
currentLevel = "0.0% (High)";
|
|
2089
|
+
}
|
|
2090
|
+
else if (Math.abs(currentPrice - retracement.level236) < tolerance) {
|
|
2091
|
+
currentLevel = "23.6% Retracement";
|
|
2092
|
+
}
|
|
2093
|
+
else if (Math.abs(currentPrice - retracement.level382) < tolerance) {
|
|
2094
|
+
currentLevel = "38.2% Retracement";
|
|
2095
|
+
}
|
|
2096
|
+
else if (Math.abs(currentPrice - retracement.level500) < tolerance) {
|
|
2097
|
+
currentLevel = "50.0% Retracement";
|
|
2098
|
+
}
|
|
2099
|
+
else if (Math.abs(currentPrice - retracement.level618) < tolerance) {
|
|
2100
|
+
currentLevel = "61.8% Retracement";
|
|
2101
|
+
}
|
|
2102
|
+
else if (Math.abs(currentPrice - retracement.level786) < tolerance) {
|
|
2103
|
+
currentLevel = "78.6% Retracement";
|
|
2104
|
+
}
|
|
2105
|
+
else if (Math.abs(currentPrice - retracement.level1000) < tolerance) {
|
|
2106
|
+
currentLevel = "100% Retracement (Low)";
|
|
2107
|
+
}
|
|
2108
|
+
else if (currentPrice > retracement.level0) {
|
|
2109
|
+
currentLevel = "Above high";
|
|
2110
|
+
}
|
|
2111
|
+
else if (currentPrice < retracement.level1000) {
|
|
2112
|
+
currentLevel = "Below low";
|
|
2113
|
+
}
|
|
2114
|
+
const allRetracementLevels = Object.values(retracement).filter((level) => level !== null);
|
|
2115
|
+
const allExtensionLevels = Object.values(extension).filter((level) => level !== null && level > 0);
|
|
2116
|
+
const resistanceLevels = [...allRetracementLevels, ...allExtensionLevels]
|
|
2117
|
+
.filter((level) => level > currentPrice)
|
|
2118
|
+
.sort((a, b) => a - b);
|
|
2119
|
+
const supportLevels = allRetracementLevels
|
|
2120
|
+
.filter((level) => level < currentPrice)
|
|
2121
|
+
.sort((a, b) => b - a);
|
|
2122
|
+
const nearestResistance = resistanceLevels.length > 0 ? resistanceLevels[0] : null;
|
|
2123
|
+
const nearestSupport = supportLevels.length > 0 ? supportLevels[0] : null;
|
|
2124
|
+
return {
|
|
2125
|
+
nearestSupport,
|
|
2126
|
+
nearestResistance,
|
|
2127
|
+
currentLevel,
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
function calculateSupportResistance(candles, endIndex, window = 20) {
|
|
2131
|
+
const startIdx = Math.max(0, endIndex + 1 - window);
|
|
2132
|
+
const recentHighs = candles
|
|
2133
|
+
.slice(startIdx, endIndex + 1)
|
|
2134
|
+
.map((c) => Number(c.high))
|
|
2135
|
+
.filter((h) => !isUnsafe$1(h));
|
|
2136
|
+
const recentLows = candles
|
|
2137
|
+
.slice(startIdx, endIndex + 1)
|
|
2138
|
+
.map((c) => Number(c.low))
|
|
2139
|
+
.filter((l) => !isUnsafe$1(l));
|
|
2140
|
+
const currentPrice = Number(candles[endIndex].close);
|
|
2141
|
+
const support = recentLows.length > 0 ? Math.min(...recentLows) : currentPrice;
|
|
2142
|
+
const resistance = recentHighs.length > 0 ? Math.max(...recentHighs) : currentPrice;
|
|
2143
|
+
return { support, resistance };
|
|
2144
|
+
}
|
|
2145
|
+
function generateAnalysis(symbol, candles) {
|
|
2146
|
+
const closes = candles.map((candle) => Number(candle.close));
|
|
2147
|
+
const highs = candles.map((candle) => Number(candle.high));
|
|
2148
|
+
const lows = candles.map((candle) => Number(candle.low));
|
|
2149
|
+
const opens = candles.map((candle) => Number(candle.open));
|
|
2150
|
+
const volumes = candles.map((candle) => Number(candle.volume));
|
|
2151
|
+
const shortEMA = new tradingSignals.FasterEMA(12);
|
|
2152
|
+
const longEMA = new tradingSignals.FasterEMA(26);
|
|
2153
|
+
const signalEMA = new tradingSignals.FasterEMA(9);
|
|
2154
|
+
const macd = new tradingSignals.FasterMACD(shortEMA, longEMA, signalEMA);
|
|
2155
|
+
const rsi = new tradingSignals.FasterRSI(14);
|
|
2156
|
+
const stochasticRSI = new tradingSignals.FasterStochasticRSI(14);
|
|
2157
|
+
const bollinger = new tradingSignals.FasterBollingerBands(20, 2);
|
|
2158
|
+
const sma20 = new tradingSignals.FasterSMA(20);
|
|
2159
|
+
const ema13 = new tradingSignals.FasterEMA(13);
|
|
2160
|
+
const ema34 = new tradingSignals.FasterEMA(34);
|
|
2161
|
+
const dema21 = new tradingSignals.FasterDEMA(21);
|
|
2162
|
+
const wma20 = new tradingSignals.FasterWMA(20);
|
|
2163
|
+
const stochastic = new tradingSignals.FasterStochasticOscillator(14, 3, 3);
|
|
2164
|
+
const adx = new tradingSignals.FasterADX(14);
|
|
2165
|
+
const dx = new tradingSignals.FasterDX(14);
|
|
2166
|
+
const cci = new tradingSignals.FasterCCI(20);
|
|
2167
|
+
const atr = new tradingSignals.FasterATR(14);
|
|
2168
|
+
const momentum = new tradingSignals.FasterMOM(8);
|
|
2169
|
+
const priceMomentumIndicator = new tradingSignals.FasterMOM(6);
|
|
2170
|
+
const results = [];
|
|
2171
|
+
candles.forEach((_candle, i) => {
|
|
2172
|
+
const high = highs[i];
|
|
2173
|
+
const low = lows[i];
|
|
2174
|
+
const close = closes[i];
|
|
2175
|
+
const open = opens[i];
|
|
2176
|
+
const volume = volumes[i];
|
|
2177
|
+
const currentPrice = close;
|
|
2178
|
+
// Update all indicators
|
|
2179
|
+
if (!isUnsafe$1(close) && close > 0) {
|
|
2180
|
+
macd.update(close, false);
|
|
2181
|
+
rsi.update(close, false);
|
|
2182
|
+
stochasticRSI.update(close, false);
|
|
2183
|
+
bollinger.update(close, false);
|
|
2184
|
+
sma20.update(close, false);
|
|
2185
|
+
ema13.update(close, false);
|
|
2186
|
+
ema34.update(close, false);
|
|
2187
|
+
dema21.update(close, false);
|
|
2188
|
+
wma20.update(close, false);
|
|
2189
|
+
momentum.update(close, false);
|
|
2190
|
+
priceMomentumIndicator.update(close, false);
|
|
2191
|
+
}
|
|
2192
|
+
if (!isUnsafe$1(high) &&
|
|
2193
|
+
!isUnsafe$1(low) &&
|
|
2194
|
+
!isUnsafe$1(close) &&
|
|
2195
|
+
high >= low &&
|
|
2196
|
+
close > 0) {
|
|
2197
|
+
stochastic.update({ high, low, close }, false);
|
|
2198
|
+
adx.update({ high, low, close }, false);
|
|
2199
|
+
dx.update({ high, low, close }, false);
|
|
2200
|
+
cci.update({ high, low, close }, false);
|
|
2201
|
+
atr.update({ high, low, close }, false);
|
|
2202
|
+
}
|
|
2203
|
+
// Determine minimum warm-up period needed (largest indicator period)
|
|
2204
|
+
// EMA(34) is the largest period
|
|
2205
|
+
// Skip rows until all indicators are warmed up
|
|
2206
|
+
if (i < WARMUP_PERIOD) {
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
const priceChanges = closes
|
|
2210
|
+
.slice(0, i + 1)
|
|
2211
|
+
.slice(1)
|
|
2212
|
+
.map((price, idx) => {
|
|
2213
|
+
const prevPrice = closes.slice(0, i + 1)[idx];
|
|
2214
|
+
return ((price - prevPrice) / prevPrice) * 100;
|
|
2215
|
+
});
|
|
2216
|
+
const volatility = priceChanges.length > 0
|
|
2217
|
+
? Math.sqrt(priceChanges.reduce((sum, change) => sum + change ** 2, 0) /
|
|
2218
|
+
priceChanges.length)
|
|
2219
|
+
: null;
|
|
2220
|
+
const { support, resistance } = calculateSupportResistance(candles, i);
|
|
2221
|
+
const fibonacci = calculateFibonacciLevels(candles, i);
|
|
2222
|
+
const macdResult = macd.getResult();
|
|
2223
|
+
const bollingerResult = bollinger.getResult();
|
|
2224
|
+
const stochasticResult = stochastic.getResult();
|
|
2225
|
+
const adxValue = adx.getResult() ?? null;
|
|
2226
|
+
const plusDI14 = !isUnsafe$1(dx.pdi) ? dx.pdi * 100 : null;
|
|
2227
|
+
const minusDI14 = !isUnsafe$1(dx.mdi) ? dx.mdi * 100 : null;
|
|
2228
|
+
const bollingerBandWidth = bollingerResult &&
|
|
2229
|
+
!isUnsafe$1(bollingerResult.upper) &&
|
|
2230
|
+
!isUnsafe$1(bollingerResult.lower) &&
|
|
2231
|
+
!isUnsafe$1(bollingerResult.middle) &&
|
|
2232
|
+
bollingerResult.middle !== 0
|
|
2233
|
+
? ((bollingerResult.upper - bollingerResult.lower) /
|
|
2234
|
+
bollingerResult.middle) *
|
|
2235
|
+
100
|
|
2236
|
+
: null;
|
|
2237
|
+
const rsiValue = rsi.getResult() ?? null;
|
|
2238
|
+
const stochasticRSIResult = stochasticRSI.getResult();
|
|
2239
|
+
const stochasticRSIValue = !isUnsafe$1(stochasticRSIResult)
|
|
2240
|
+
? stochasticRSIResult * 100
|
|
2241
|
+
: null;
|
|
2242
|
+
const bodySize = Math.abs(close - open);
|
|
2243
|
+
results.push({
|
|
2244
|
+
symbol,
|
|
2245
|
+
rsi14: rsiValue != null && !isUnsafe$1(rsiValue) ? rsiValue : null,
|
|
2246
|
+
stochasticRSI14: stochasticRSIValue,
|
|
2247
|
+
macd12_26_9: macdResult && !isUnsafe$1(macdResult.macd) ? macdResult.macd : null,
|
|
2248
|
+
signal9: macdResult && !isUnsafe$1(macdResult.signal) ? macdResult.signal : null,
|
|
2249
|
+
bollingerUpper20_2: bollingerResult && !isUnsafe$1(bollingerResult.upper)
|
|
2250
|
+
? bollingerResult.upper
|
|
2251
|
+
: null,
|
|
2252
|
+
bollingerMiddle20_2: bollingerResult && !isUnsafe$1(bollingerResult.middle)
|
|
2253
|
+
? bollingerResult.middle
|
|
2254
|
+
: null,
|
|
2255
|
+
bollingerLower20_2: bollingerResult && !isUnsafe$1(bollingerResult.lower)
|
|
2256
|
+
? bollingerResult.lower
|
|
2257
|
+
: null,
|
|
2258
|
+
bollingerWidth20_2: bollingerBandWidth,
|
|
2259
|
+
stochasticK14_3_3: stochasticResult && !isUnsafe$1(stochasticResult.stochK)
|
|
2260
|
+
? stochasticResult.stochK
|
|
2261
|
+
: null,
|
|
2262
|
+
stochasticD14_3_3: stochasticResult && !isUnsafe$1(stochasticResult.stochD)
|
|
2263
|
+
? stochasticResult.stochD
|
|
2264
|
+
: null,
|
|
2265
|
+
adx14: adxValue != null && !isUnsafe$1(adxValue) ? adxValue : null,
|
|
2266
|
+
plusDI14: plusDI14 != null && !isUnsafe$1(plusDI14) ? plusDI14 : null,
|
|
2267
|
+
minusDI14: minusDI14 != null && !isUnsafe$1(minusDI14) ? minusDI14 : null,
|
|
2268
|
+
cci20: cci.getResult() != null && !isUnsafe$1(cci.getResult())
|
|
2269
|
+
? cci.getResult()
|
|
2270
|
+
: null,
|
|
2271
|
+
atr14: atr.getResult() != null && !isUnsafe$1(atr.getResult())
|
|
2272
|
+
? atr.getResult()
|
|
2273
|
+
: null,
|
|
2274
|
+
sma20: sma20.getResult() != null && !isUnsafe$1(sma20.getResult())
|
|
2275
|
+
? sma20.getResult()
|
|
2276
|
+
: null,
|
|
2277
|
+
ema13: ema13.getResult() != null && !isUnsafe$1(ema13.getResult())
|
|
2278
|
+
? ema13.getResult()
|
|
2279
|
+
: null,
|
|
2280
|
+
ema34: ema34.getResult() != null && !isUnsafe$1(ema34.getResult())
|
|
2281
|
+
? ema34.getResult()
|
|
2282
|
+
: null,
|
|
2283
|
+
dema21: dema21.getResult() != null && !isUnsafe$1(dema21.getResult())
|
|
2284
|
+
? dema21.getResult()
|
|
2285
|
+
: null,
|
|
2286
|
+
wma20: wma20.getResult() != null && !isUnsafe$1(wma20.getResult())
|
|
2287
|
+
? wma20.getResult()
|
|
2288
|
+
: null,
|
|
2289
|
+
momentum8: momentum.getResult() != null && !isUnsafe$1(momentum.getResult())
|
|
2290
|
+
? momentum.getResult()
|
|
2291
|
+
: null,
|
|
2292
|
+
support: support != null && !isUnsafe$1(support)
|
|
2293
|
+
? support
|
|
2294
|
+
: !isUnsafe$1(currentPrice)
|
|
2295
|
+
? currentPrice
|
|
2296
|
+
: null,
|
|
2297
|
+
resistance: resistance != null && !isUnsafe$1(resistance)
|
|
2298
|
+
? resistance
|
|
2299
|
+
: !isUnsafe$1(currentPrice)
|
|
2300
|
+
? currentPrice
|
|
2301
|
+
: null,
|
|
2302
|
+
currentPrice: currentPrice != null && !isUnsafe$1(currentPrice) ? currentPrice : null,
|
|
2303
|
+
volume: volume != null && !isUnsafe$1(volume) ? volume : null,
|
|
2304
|
+
volatility,
|
|
2305
|
+
priceMomentum6: priceMomentumIndicator.getResult() != null &&
|
|
2306
|
+
!isUnsafe$1(priceMomentumIndicator.getResult())
|
|
2307
|
+
? priceMomentumIndicator.getResult()
|
|
2308
|
+
: null,
|
|
2309
|
+
fibonacciNearestSupport: fibonacci.nearestSupport,
|
|
2310
|
+
fibonacciNearestResistance: fibonacci.nearestResistance,
|
|
2311
|
+
fibonacciCurrentLevel: fibonacci.currentLevel,
|
|
2312
|
+
bodySize,
|
|
2313
|
+
closePrice: close,
|
|
2314
|
+
date: new Date(),
|
|
2315
|
+
lookbackPeriod: "96 candles (48 hours)",
|
|
2316
|
+
});
|
|
2317
|
+
});
|
|
2318
|
+
// Return only the last TABLE_ROWS_LIMIT rows
|
|
2319
|
+
return results.slice(-TABLE_ROWS_LIMIT);
|
|
2320
|
+
}
|
|
2321
|
+
async function generateHistoryTable(indicators, symbol) {
|
|
2322
|
+
let markdown = "";
|
|
2323
|
+
const currentData = await backtestKit.getDate();
|
|
2324
|
+
markdown += `# 30-Min Candles Analysis for ${symbol} (Historical Data)\n`;
|
|
2325
|
+
markdown += `> Current time: ${currentData.toISOString()}\n\n`;
|
|
2326
|
+
const header = `| ${columns.map((col) => col.label).join(" | ")} |\n`;
|
|
2327
|
+
const separator = `| ${columns.map(() => "---").join(" | ")} |\n`;
|
|
2328
|
+
const tableRows = await Promise.all(indicators.map(async (ind) => {
|
|
2329
|
+
const cells = await Promise.all(columns.map(async (col) => await col.format(ind[col.key], symbol)));
|
|
2330
|
+
return `| ${cells.join(" | ")} |`;
|
|
2331
|
+
}));
|
|
2332
|
+
markdown += header;
|
|
2333
|
+
markdown += separator;
|
|
2334
|
+
markdown += tableRows.join("\n");
|
|
2335
|
+
markdown += "\n\n";
|
|
2336
|
+
markdown += "## Data Sources\n";
|
|
2337
|
+
markdown += "- **Timeframe**: 30-minute candles\n";
|
|
2338
|
+
markdown += "- **Lookback Period**: 96 candles (48 hours)\n";
|
|
2339
|
+
markdown +=
|
|
2340
|
+
"- **RSI(14)**: over previous 14 candles (7 hours on 30m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
2341
|
+
markdown +=
|
|
2342
|
+
"- **Stochastic RSI(14)**: over previous 14 candles (7 hours on 30m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
2343
|
+
markdown +=
|
|
2344
|
+
"- **MACD(12,26,9)**: fast 12 and slow 26 periods on 30m timeframe before row timestamp (Min: -∞, Max: +∞)\n";
|
|
2345
|
+
markdown +=
|
|
2346
|
+
"- **Signal(9)**: over previous 9 candles (4.5 hours on 30m timeframe) before row timestamp (Min: -∞, Max: +∞)\n";
|
|
2347
|
+
markdown +=
|
|
2348
|
+
"- **ADX(14)**: over previous 14 candles (7 hours on 30m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
2349
|
+
markdown +=
|
|
2350
|
+
"- **+DI(14)**: over previous 14 candles (7 hours on 30m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
2351
|
+
markdown +=
|
|
2352
|
+
"- **-DI(14)**: over previous 14 candles (7 hours on 30m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
2353
|
+
markdown +=
|
|
2354
|
+
"- **ATR(14)**: over previous 14 candles (7 hours on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
2355
|
+
markdown +=
|
|
2356
|
+
"- **CCI(20)**: over previous 20 candles (10 hours on 30m timeframe) before row timestamp (Min: -∞, Max: +∞)\n";
|
|
2357
|
+
markdown +=
|
|
2358
|
+
"- **Bollinger Upper(20,2.0)**: over previous 20 candles (10 hours on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
2359
|
+
markdown +=
|
|
2360
|
+
"- **Bollinger Middle(20,2.0)**: over previous 20 candles (10 hours on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
2361
|
+
markdown +=
|
|
2362
|
+
"- **Bollinger Lower(20,2.0)**: over previous 20 candles (10 hours on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
2363
|
+
markdown +=
|
|
2364
|
+
"- **Bollinger Width(20,2.0)**: width percentage before row timestamp (Min: 0%, Max: +∞)\n";
|
|
2365
|
+
markdown +=
|
|
2366
|
+
"- **Stochastic K(14,3,3)**: over previous 14 candles (7 hours on 30m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
2367
|
+
markdown +=
|
|
2368
|
+
"- **Stochastic D(14,3,3)**: over previous 14 candles (7 hours on 30m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
2369
|
+
markdown +=
|
|
2370
|
+
"- **DEMA(21)**: over previous 21 candles (10.5 hours on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2371
|
+
markdown +=
|
|
2372
|
+
"- **WMA(20)**: over previous 20 candles (10 hours on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2373
|
+
markdown +=
|
|
2374
|
+
"- **SMA(20)**: over previous 20 candles (10 hours on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2375
|
+
markdown +=
|
|
2376
|
+
"- **EMA(13)**: over previous 13 candles (6.5 hours on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2377
|
+
markdown +=
|
|
2378
|
+
"- **EMA(34)**: over previous 34 candles (17 hours on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2379
|
+
markdown +=
|
|
2380
|
+
"- **Momentum(8)**: over previous 8 candles (4 hours on 30m timeframe) before row timestamp (Min: -∞ USD, Max: +∞ USD)\n";
|
|
2381
|
+
markdown +=
|
|
2382
|
+
"- **Support**: over previous 20 candles (10 hours on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2383
|
+
markdown +=
|
|
2384
|
+
"- **Resistance**: over previous 20 candles (10 hours on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2385
|
+
markdown +=
|
|
2386
|
+
"- **Price Momentum(6)**: over previous 6 candles (3 hours on 30m timeframe) before row timestamp (Min: -∞ USD, Max: +∞ USD)\n";
|
|
2387
|
+
markdown +=
|
|
2388
|
+
"- **Fibonacci Nearest Support**: nearest support level over 48 candles (24h on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2389
|
+
markdown +=
|
|
2390
|
+
"- **Fibonacci Nearest Resistance**: nearest resistance level over 48 candles (24h on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2391
|
+
markdown +=
|
|
2392
|
+
"- **Fibonacci Current Level**: current level description before row timestamp\n";
|
|
2393
|
+
markdown +=
|
|
2394
|
+
"- **Current Price**: close price at row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2395
|
+
markdown +=
|
|
2396
|
+
"- **Body Size**: candle body size at row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2397
|
+
markdown +=
|
|
2398
|
+
"- **Close Price**: close price at row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2399
|
+
markdown +=
|
|
2400
|
+
"- **Volume**: trading volume at row timestamp (Min: 0, Max: +∞)\n";
|
|
2401
|
+
markdown +=
|
|
2402
|
+
"- **Volatility**: volatility percentage at row timestamp (Min: 0%, Max: +∞)\n";
|
|
2403
|
+
return markdown;
|
|
2404
|
+
}
|
|
2405
|
+
class SwingTermHistoryService {
|
|
2406
|
+
constructor() {
|
|
2407
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
2408
|
+
this.getData = async (symbol, candles) => {
|
|
2409
|
+
this.loggerService.log("swingTermHistoryService getData", {
|
|
2410
|
+
symbol,
|
|
2411
|
+
candles: candles.length,
|
|
2412
|
+
});
|
|
2413
|
+
return generateAnalysis(symbol, candles);
|
|
2414
|
+
};
|
|
2415
|
+
this.getReport = async (symbol) => {
|
|
2416
|
+
this.loggerService.log("swingTermHistoryService getReport", { symbol });
|
|
2417
|
+
const candles = await backtestKit.getCandles(symbol, "30m", 96);
|
|
2418
|
+
const rows = await this.getData(symbol, candles);
|
|
2419
|
+
return generateHistoryTable(rows, symbol);
|
|
2420
|
+
};
|
|
2421
|
+
this.generateHistoryTable = async (symbol, rows) => {
|
|
2422
|
+
this.loggerService.log("swingTermHistoryService generateHistoryTable", {
|
|
2423
|
+
symbol,
|
|
2424
|
+
rowCount: rows.length,
|
|
2425
|
+
});
|
|
2426
|
+
return generateHistoryTable(rows, symbol);
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
const RECENT_CANDLES$3 = 8;
|
|
2432
|
+
class FifteenMinuteCandleHistoryService {
|
|
2433
|
+
constructor() {
|
|
2434
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
2435
|
+
this.getData = async (symbol) => {
|
|
2436
|
+
this.loggerService.log("fifteenMinuteCandleHistoryService getData", { symbol });
|
|
2437
|
+
return backtestKit.getCandles(symbol, "15m", RECENT_CANDLES$3);
|
|
2438
|
+
};
|
|
2439
|
+
this.generateReport = async (symbol, candles) => {
|
|
2440
|
+
this.loggerService.log("fifteenMinuteCandleHistoryService generateReport", { symbol });
|
|
2441
|
+
const averageVolatility = candles.reduce((sum, candle) => sum + ((candle.high - candle.low) / candle.close) * 100, 0) / candles.length;
|
|
2442
|
+
let report = "";
|
|
2443
|
+
const currentData = await backtestKit.getDate();
|
|
2444
|
+
report += `## 15-Minute Candles History (Last ${RECENT_CANDLES$3})\n`;
|
|
2445
|
+
report += `> Current time: ${currentData.toISOString()}\n\n`;
|
|
2446
|
+
for (let index = 0; index < candles.length; index++) {
|
|
2447
|
+
const candle = candles[index];
|
|
2448
|
+
const volatilityPercent = ((candle.high - candle.low) / candle.close) * 100;
|
|
2449
|
+
const isHighVolatility = volatilityPercent > averageVolatility * 1.5;
|
|
2450
|
+
const bodySize = Math.abs(candle.close - candle.open);
|
|
2451
|
+
const candleRange = candle.high - candle.low;
|
|
2452
|
+
const bodyPercent = candleRange > 0 ? (bodySize / candleRange) * 100 : 0;
|
|
2453
|
+
const candleType = candle.close > candle.open
|
|
2454
|
+
? "Green"
|
|
2455
|
+
: candle.close < candle.open
|
|
2456
|
+
? "Red"
|
|
2457
|
+
: "Doji";
|
|
2458
|
+
const formattedTime = new Date(candle.timestamp).toISOString();
|
|
2459
|
+
report += `### 15m Candle ${index + 1} (${candleType}) ${isHighVolatility ? "HIGH-VOLATILITY" : ""}\n`;
|
|
2460
|
+
report += `- **Time**: ${formattedTime}\n`;
|
|
2461
|
+
report += `- **Open**: ${await backtestKit.formatPrice(symbol, candle.open)} USD\n`;
|
|
2462
|
+
report += `- **High**: ${await backtestKit.formatPrice(symbol, candle.high)} USD\n`;
|
|
2463
|
+
report += `- **Low**: ${await backtestKit.formatPrice(symbol, candle.low)} USD\n`;
|
|
2464
|
+
report += `- **Close**: ${await backtestKit.formatPrice(symbol, candle.close)} USD\n`;
|
|
2465
|
+
report += `- **Volume**: ${await backtestKit.formatQuantity(symbol, candle.volume)}\n`;
|
|
2466
|
+
report += `- **15m Volatility**: ${volatilityPercent.toFixed(2)}\n`;
|
|
2467
|
+
report += `- **Body Size**: ${bodyPercent.toFixed(1)}\n\n`;
|
|
2468
|
+
}
|
|
2469
|
+
return report;
|
|
2470
|
+
};
|
|
2471
|
+
this.getReport = async (symbol) => {
|
|
2472
|
+
this.loggerService.log("fifteenMinuteCandleHistoryService getReport", { symbol });
|
|
2473
|
+
const candles = await this.getData(symbol);
|
|
2474
|
+
return this.generateReport(symbol, candles);
|
|
2475
|
+
};
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
const RECENT_CANDLES$2 = 6;
|
|
2480
|
+
class HourCandleHistoryService {
|
|
2481
|
+
constructor() {
|
|
2482
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
2483
|
+
this.getData = async (symbol) => {
|
|
2484
|
+
this.loggerService.log("hourCandleHistoryService getData", { symbol });
|
|
2485
|
+
return backtestKit.getCandles(symbol, "1h", RECENT_CANDLES$2);
|
|
2486
|
+
};
|
|
2487
|
+
this.generateReport = async (symbol, candles) => {
|
|
2488
|
+
this.loggerService.log("hourCandleHistoryService generateReport", { symbol });
|
|
2489
|
+
let markdown = "";
|
|
2490
|
+
const currentData = await backtestKit.getDate();
|
|
2491
|
+
markdown += `## Hourly Candles History (Last ${RECENT_CANDLES$2})\n`;
|
|
2492
|
+
markdown += `> Current time: ${currentData.toISOString()}\n\n`;
|
|
2493
|
+
for (let index = 0; index < candles.length; index++) {
|
|
2494
|
+
const candle = candles[index];
|
|
2495
|
+
const volatilityPercent = ((candle.high - candle.low) / candle.close) * 100;
|
|
2496
|
+
const bodySize = Math.abs(candle.close - candle.open);
|
|
2497
|
+
const candleRange = candle.high - candle.low;
|
|
2498
|
+
const bodyPercent = candleRange > 0 ? (bodySize / candleRange) * 100 : 0;
|
|
2499
|
+
const candleType = candle.close > candle.open
|
|
2500
|
+
? "Green"
|
|
2501
|
+
: candle.close < candle.open
|
|
2502
|
+
? "Red"
|
|
2503
|
+
: "Doji";
|
|
2504
|
+
const formattedTime = new Date(candle.timestamp).toISOString();
|
|
2505
|
+
markdown += `### 1h Candle ${index + 1} (${candleType})\n`;
|
|
2506
|
+
markdown += `- **Time**: ${formattedTime}\n`;
|
|
2507
|
+
markdown += `- **Open**: ${backtestKit.formatPrice(symbol, candle.open)} USD\n`;
|
|
2508
|
+
markdown += `- **High**: ${backtestKit.formatPrice(symbol, candle.high)} USD\n`;
|
|
2509
|
+
markdown += `- **Low**: ${backtestKit.formatPrice(symbol, candle.low)} USD\n`;
|
|
2510
|
+
markdown += `- **Close**: ${backtestKit.formatPrice(symbol, candle.close)} USD\n`;
|
|
2511
|
+
markdown += `- **Volume**: ${backtestKit.formatQuantity(symbol, candle.volume)}\n`;
|
|
2512
|
+
markdown += `- **1h Volatility**: ${volatilityPercent.toFixed(2)}%\n`;
|
|
2513
|
+
markdown += `- **Body Size**: ${bodyPercent.toFixed(1)}%\n\n`;
|
|
2514
|
+
}
|
|
2515
|
+
return markdown;
|
|
2516
|
+
};
|
|
2517
|
+
this.getReport = async (symbol) => {
|
|
2518
|
+
this.loggerService.log("hourCandleHistoryService getReport", { symbol });
|
|
2519
|
+
const candles = await this.getData(symbol);
|
|
2520
|
+
return await this.generateReport(symbol, candles);
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
const RECENT_CANDLES$1 = 15;
|
|
2526
|
+
class OneMinuteCandleHistoryService {
|
|
2527
|
+
constructor() {
|
|
2528
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
2529
|
+
this.getData = async (symbol) => {
|
|
2530
|
+
this.loggerService.log("oneMinuteCandleHistoryService getData", { symbol });
|
|
2531
|
+
return backtestKit.getCandles(symbol, "1m", RECENT_CANDLES$1);
|
|
2532
|
+
};
|
|
2533
|
+
this.generateReport = async (symbol, candles) => {
|
|
2534
|
+
this.loggerService.log("oneMinuteCandleHistoryService generateReport", { symbol });
|
|
2535
|
+
let markdown = "";
|
|
2536
|
+
const currentData = await backtestKit.getDate();
|
|
2537
|
+
markdown += `## One-Minute Candles History (Last ${RECENT_CANDLES$1})\n`;
|
|
2538
|
+
markdown += `> Current time: ${currentData.toISOString()}\n\n`;
|
|
2539
|
+
for (let index = 0; index < candles.length; index++) {
|
|
2540
|
+
const candle = candles[index];
|
|
2541
|
+
const volatilityPercent = ((candle.high - candle.low) / candle.close) * 100;
|
|
2542
|
+
const bodySize = Math.abs(candle.close - candle.open);
|
|
2543
|
+
const candleRange = candle.high - candle.low;
|
|
2544
|
+
const bodyPercent = candleRange > 0 ? (bodySize / candleRange) * 100 : 0;
|
|
2545
|
+
const candleType = candle.close > candle.open ? "Green" : candle.close < candle.open ? "Red" : "Doji";
|
|
2546
|
+
const formattedTime = new Date(candle.timestamp).toISOString();
|
|
2547
|
+
markdown += `### 1m Candle ${index + 1} (${candleType})\n`;
|
|
2548
|
+
markdown += `- **Time**: ${formattedTime}\n`;
|
|
2549
|
+
markdown += `- **Open**: ${backtestKit.formatPrice(symbol, candle.open)} USD\n`;
|
|
2550
|
+
markdown += `- **High**: ${backtestKit.formatPrice(symbol, candle.high)} USD\n`;
|
|
2551
|
+
markdown += `- **Low**: ${backtestKit.formatPrice(symbol, candle.low)} USD\n`;
|
|
2552
|
+
markdown += `- **Close**: ${backtestKit.formatPrice(symbol, candle.close)} USD\n`;
|
|
2553
|
+
markdown += `- **Volume**: ${backtestKit.formatQuantity(symbol, candle.volume)}\n`;
|
|
2554
|
+
markdown += `- **1m Volatility**: ${volatilityPercent.toFixed(2)}%\n`;
|
|
2555
|
+
markdown += `- **Body Size**: ${bodyPercent.toFixed(1)}%\n\n`;
|
|
2556
|
+
}
|
|
2557
|
+
return markdown;
|
|
2558
|
+
};
|
|
2559
|
+
this.getReport = async (symbol) => {
|
|
2560
|
+
this.loggerService.log("oneMinuteCandleHistoryService getReport", { symbol });
|
|
2561
|
+
const candles = await this.getData(symbol);
|
|
2562
|
+
return await this.generateReport(symbol, candles);
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
const RECENT_CANDLES = 6;
|
|
2568
|
+
class ThirtyMinuteCandleHistoryService {
|
|
2569
|
+
constructor() {
|
|
2570
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
2571
|
+
this.getData = async (symbol) => {
|
|
2572
|
+
this.loggerService.log("thirtyMinuteCandleHistoryService getData", { symbol });
|
|
2573
|
+
return backtestKit.getCandles(symbol, "30m", RECENT_CANDLES);
|
|
2574
|
+
};
|
|
2575
|
+
this.generateReport = async (symbol, candles) => {
|
|
2576
|
+
this.loggerService.log("thirtyMinuteCandleHistoryService generateReport", { symbol });
|
|
2577
|
+
let report = "";
|
|
2578
|
+
const currentData = await backtestKit.getDate();
|
|
2579
|
+
report += `## 30-Min Candles History (Last ${RECENT_CANDLES})\n`;
|
|
2580
|
+
report += `> Current time: ${currentData.toISOString()}\n\n`;
|
|
2581
|
+
for (let index = 0; index < candles.length; index++) {
|
|
2582
|
+
const candle = candles[index];
|
|
2583
|
+
const volatilityPercent = ((candle.high - candle.low) / candle.close) * 100;
|
|
2584
|
+
const bodySize = Math.abs(candle.close - candle.open);
|
|
2585
|
+
const candleRange = candle.high - candle.low;
|
|
2586
|
+
const bodyPercent = candleRange > 0 ? (bodySize / candleRange) * 100 : 0;
|
|
2587
|
+
const candleType = candle.close > candle.open
|
|
2588
|
+
? "Green"
|
|
2589
|
+
: candle.close < candle.open
|
|
2590
|
+
? "Red"
|
|
2591
|
+
: "Doji";
|
|
2592
|
+
const formattedTime = new Date(candle.timestamp).toISOString();
|
|
2593
|
+
report += `### 30m Candle ${index + 1} (${candleType})\n`;
|
|
2594
|
+
report += `- **Time**: ${formattedTime}\n`;
|
|
2595
|
+
report += `- **Open**: ${backtestKit.formatPrice(symbol, candle.open)} USD\n`;
|
|
2596
|
+
report += `- **High**: ${backtestKit.formatPrice(symbol, candle.high)} USD\n`;
|
|
2597
|
+
report += `- **Low**: ${backtestKit.formatPrice(symbol, candle.low)} USD\n`;
|
|
2598
|
+
report += `- **Close**: ${backtestKit.formatPrice(symbol, candle.close)} USD\n`;
|
|
2599
|
+
report += `- **Volume**: ${backtestKit.formatQuantity(symbol, candle.volume)}\n`;
|
|
2600
|
+
report += `- **30m Volatility**: ${volatilityPercent.toFixed(2)}%\n`;
|
|
2601
|
+
report += `- **Body Size**: ${bodyPercent.toFixed(1)}%\n\n`;
|
|
2602
|
+
}
|
|
2603
|
+
return report;
|
|
2604
|
+
};
|
|
2605
|
+
this.getReport = async (symbol) => {
|
|
2606
|
+
this.loggerService.log("thirtyMinuteCandleHistoryService getReport", { symbol });
|
|
2607
|
+
const candles = await this.getData(symbol);
|
|
2608
|
+
return await this.generateReport(symbol, candles);
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
function isUnsafe(value) {
|
|
2614
|
+
if (typeof value !== "number") {
|
|
2615
|
+
return true;
|
|
2616
|
+
}
|
|
2617
|
+
if (isNaN(value)) {
|
|
2618
|
+
return true;
|
|
2619
|
+
}
|
|
2620
|
+
if (!isFinite(value)) {
|
|
2621
|
+
return true;
|
|
2622
|
+
}
|
|
2623
|
+
return false;
|
|
2624
|
+
}
|
|
2625
|
+
const MAX_DEPTH_LEVELS = 1000; // Maximum depth for more accurate metrics
|
|
2626
|
+
function processOrderBookSide(orders) {
|
|
2627
|
+
const entries = orders.map((order) => ({
|
|
2628
|
+
price: parseFloat(order.price),
|
|
2629
|
+
quantity: parseFloat(order.quantity),
|
|
2630
|
+
percentage: 0,
|
|
2631
|
+
}));
|
|
2632
|
+
// Calculate percentages
|
|
2633
|
+
const totalVolume = entries.reduce((sum, entry) => sum + entry.quantity, 0);
|
|
2634
|
+
entries.forEach((entry) => {
|
|
2635
|
+
entry.percentage =
|
|
2636
|
+
totalVolume > 0 ? (entry.quantity / totalVolume) * 100 : 0;
|
|
2637
|
+
});
|
|
2638
|
+
return entries;
|
|
2639
|
+
}
|
|
2640
|
+
// Generate simple order book report
|
|
2641
|
+
const generateBookDataReport = async (self, result) => {
|
|
2642
|
+
const currentData = await backtestKit.getDate();
|
|
2643
|
+
let markdown = `# Order Book Analysis for ${result.symbol}\n`;
|
|
2644
|
+
markdown += `> Current time: ${currentData.toISOString()}\n\n`;
|
|
2645
|
+
// Basic order book info
|
|
2646
|
+
markdown += `## Order Book Summary\n`;
|
|
2647
|
+
markdown += `- **Best Bid**: ${!isUnsafe(result.bestBid)
|
|
2648
|
+
? (await backtestKit.formatPrice(result.symbol, result.bestBid)) + " USD"
|
|
2649
|
+
: "N/A"}\n`;
|
|
2650
|
+
markdown += `- **Best Ask**: ${!isUnsafe(result.bestAsk)
|
|
2651
|
+
? (await backtestKit.formatPrice(result.symbol, result.bestAsk)) + " USD"
|
|
2652
|
+
: "N/A"}\n`;
|
|
2653
|
+
markdown += `- **Mid Price**: ${!isUnsafe(result.midPrice)
|
|
2654
|
+
? (await backtestKit.formatPrice(result.symbol, result.midPrice)) + " USD"
|
|
2655
|
+
: "N/A"}\n`;
|
|
2656
|
+
markdown += `- **Spread**: ${!isUnsafe(result.spread)
|
|
2657
|
+
? (await backtestKit.formatPrice(result.symbol, result.spread)) + " USD"
|
|
2658
|
+
: "N/A"}\n`;
|
|
2659
|
+
markdown += `- **Depth Imbalance**: ${!isUnsafe(result.depthImbalance)
|
|
2660
|
+
? (result.depthImbalance * 100).toFixed(1) + "%"
|
|
2661
|
+
: "N/A"}\n\n`;
|
|
2662
|
+
// Top order book levels
|
|
2663
|
+
markdown += `## Top 20 Order Book Levels\n\n`;
|
|
2664
|
+
markdown += `### Bids (Buy Orders)\n`;
|
|
2665
|
+
markdown += `| Price | Quantity | % of Total |\n`;
|
|
2666
|
+
markdown += `|-------|----------|------------|\n`;
|
|
2667
|
+
// Sort bids by percentage (descending) and take top 20
|
|
2668
|
+
const topBids = [...result.bids]
|
|
2669
|
+
.sort((a, b) => b.percentage - a.percentage)
|
|
2670
|
+
.slice(0, 20);
|
|
2671
|
+
for (const bid of topBids) {
|
|
2672
|
+
const priceStr = !isUnsafe(bid.price)
|
|
2673
|
+
? await backtestKit.formatPrice(result.symbol, bid.price)
|
|
2674
|
+
: "N/A";
|
|
2675
|
+
const quantityStr = !isUnsafe(bid.quantity)
|
|
2676
|
+
? await backtestKit.formatQuantity(result.symbol, bid.quantity)
|
|
2677
|
+
: "N/A";
|
|
2678
|
+
const percentageStr = !isUnsafe(bid.percentage)
|
|
2679
|
+
? bid.percentage.toFixed(1) + "%"
|
|
2680
|
+
: "N/A";
|
|
2681
|
+
markdown += `| ${priceStr} | ${quantityStr} | ${percentageStr} |\n`;
|
|
2682
|
+
}
|
|
2683
|
+
markdown += `\n### Asks (Sell Orders)\n`;
|
|
2684
|
+
markdown += `| Price | Quantity | % of Total |\n`;
|
|
2685
|
+
markdown += `|-------|----------|------------|\n`;
|
|
2686
|
+
// Sort asks by percentage (descending) and take top 20
|
|
2687
|
+
const topAsks = [...result.asks]
|
|
2688
|
+
.sort((a, b) => b.percentage - a.percentage)
|
|
2689
|
+
.slice(0, 20);
|
|
2690
|
+
for (const ask of topAsks) {
|
|
2691
|
+
const priceStr = !isUnsafe(ask.price)
|
|
2692
|
+
? await backtestKit.formatPrice(result.symbol, ask.price)
|
|
2693
|
+
: "N/A";
|
|
2694
|
+
const quantityStr = !isUnsafe(ask.quantity)
|
|
2695
|
+
? await backtestKit.formatQuantity(result.symbol, ask.quantity)
|
|
2696
|
+
: "N/A";
|
|
2697
|
+
const percentageStr = !isUnsafe(ask.percentage)
|
|
2698
|
+
? ask.percentage.toFixed(1) + "%"
|
|
2699
|
+
: "N/A";
|
|
2700
|
+
markdown += `| ${priceStr} | ${quantityStr} | ${percentageStr} |\n`;
|
|
2701
|
+
}
|
|
2702
|
+
markdown += `\n`;
|
|
2703
|
+
return markdown;
|
|
2704
|
+
};
|
|
2705
|
+
class BookDataMathService {
|
|
2706
|
+
constructor() {
|
|
2707
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
2708
|
+
this.generateReport = async (symbol, bookData) => {
|
|
2709
|
+
this.loggerService.log("bookDataMathService generateReport", {
|
|
2710
|
+
symbol,
|
|
2711
|
+
});
|
|
2712
|
+
return await generateBookDataReport(this, bookData);
|
|
2713
|
+
};
|
|
2714
|
+
this.getReport = async (symbol) => {
|
|
2715
|
+
this.loggerService.log("bookDataMathService getReport", {
|
|
2716
|
+
symbol,
|
|
2717
|
+
});
|
|
2718
|
+
const bookData = await this.getData(symbol);
|
|
2719
|
+
return await this.generateReport(symbol, bookData);
|
|
2720
|
+
};
|
|
2721
|
+
this.getData = async (symbol) => {
|
|
2722
|
+
this.loggerService.log("bookDataMathService getBookDataAnalysis", {
|
|
2723
|
+
symbol,
|
|
2724
|
+
});
|
|
2725
|
+
const depth = await backtestKit.getOrderBook(symbol, MAX_DEPTH_LEVELS);
|
|
2726
|
+
// Just process raw data - no calculations
|
|
2727
|
+
const bids = processOrderBookSide(depth.bids.sort((a, b) => parseFloat(b.price) - parseFloat(a.price))); // Сортировка по убыванию
|
|
2728
|
+
const asks = processOrderBookSide(depth.asks.sort((a, b) => parseFloat(a.price) - parseFloat(b.price))); // Сортировка по возрастанию
|
|
2729
|
+
const bestBid = bids.length > 0 ? bids[0].price : 0;
|
|
2730
|
+
const bestAsk = asks.length > 0 ? asks[0].price : 0;
|
|
2731
|
+
const midPrice = (bestBid + bestAsk) / 2;
|
|
2732
|
+
const spread = bestAsk - bestBid;
|
|
2733
|
+
// Calculate depth imbalance
|
|
2734
|
+
const totalBidVolume = bids.reduce((sum, bid) => sum + bid.quantity, 0);
|
|
2735
|
+
const totalAskVolume = asks.reduce((sum, ask) => sum + ask.quantity, 0);
|
|
2736
|
+
const depthImbalance = totalBidVolume + totalAskVolume > 0
|
|
2737
|
+
? (totalBidVolume - totalAskVolume) / (totalBidVolume + totalAskVolume)
|
|
2738
|
+
: 0;
|
|
2739
|
+
return {
|
|
2740
|
+
symbol,
|
|
2741
|
+
timestamp: new Date().toISOString(),
|
|
2742
|
+
bids,
|
|
2743
|
+
asks,
|
|
2744
|
+
bestBid,
|
|
2745
|
+
bestAsk,
|
|
2746
|
+
midPrice,
|
|
2747
|
+
spread,
|
|
2748
|
+
depthImbalance,
|
|
2749
|
+
};
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
const NOOP_LOGGER = {
|
|
2755
|
+
log() {
|
|
2756
|
+
},
|
|
2757
|
+
debug() {
|
|
2758
|
+
},
|
|
2759
|
+
info() {
|
|
2760
|
+
},
|
|
2761
|
+
warn() {
|
|
2762
|
+
},
|
|
2763
|
+
};
|
|
2764
|
+
class LoggerService {
|
|
2765
|
+
constructor() {
|
|
2766
|
+
this._commonLogger = NOOP_LOGGER;
|
|
2767
|
+
this.log = async (topic, ...args) => {
|
|
2768
|
+
await this._commonLogger.log(topic, ...args);
|
|
2769
|
+
};
|
|
2770
|
+
this.debug = async (topic, ...args) => {
|
|
2771
|
+
await this._commonLogger.debug(topic, ...args);
|
|
2772
|
+
};
|
|
2773
|
+
this.info = async (topic, ...args) => {
|
|
2774
|
+
await this._commonLogger.info(topic, ...args);
|
|
2775
|
+
};
|
|
2776
|
+
this.warn = async (topic, ...args) => {
|
|
2777
|
+
await this._commonLogger.warn(topic, ...args);
|
|
2778
|
+
};
|
|
2779
|
+
this.setLogger = (logger) => {
|
|
2780
|
+
this._commonLogger = logger;
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
{
|
|
2786
|
+
provide(TYPES.loggerService, () => new LoggerService());
|
|
2787
|
+
}
|
|
2788
|
+
{
|
|
2789
|
+
provide(TYPES.swingTermMathService, () => new SwingTermHistoryService());
|
|
2790
|
+
provide(TYPES.longTermMathService, () => new LongTermHistoryService());
|
|
2791
|
+
provide(TYPES.shortTermMathService, () => new ShortTermHistoryService());
|
|
2792
|
+
provide(TYPES.microTermMathService, () => new MicroTermHistoryService());
|
|
2793
|
+
provide(TYPES.bookDataMathService, () => new BookDataMathService());
|
|
2794
|
+
}
|
|
2795
|
+
{
|
|
2796
|
+
provide(TYPES.fifteenMinuteCandleHistoryService, () => new FifteenMinuteCandleHistoryService());
|
|
2797
|
+
provide(TYPES.hourCandleHistoryService, () => new HourCandleHistoryService());
|
|
2798
|
+
provide(TYPES.oneMinuteCandleHistoryService, () => new OneMinuteCandleHistoryService());
|
|
2799
|
+
provide(TYPES.thirtyMinuteCandleHistoryService, () => new ThirtyMinuteCandleHistoryService());
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
const commonServices = {
|
|
2803
|
+
loggerService: inject(TYPES.loggerService),
|
|
2804
|
+
};
|
|
2805
|
+
const mathServices = {
|
|
2806
|
+
swingTermMathService: inject(TYPES.swingTermMathService),
|
|
2807
|
+
longTermMathService: inject(TYPES.longTermMathService),
|
|
2808
|
+
shortTermMathService: inject(TYPES.shortTermMathService),
|
|
2809
|
+
microTermMathService: inject(TYPES.microTermMathService),
|
|
2810
|
+
bookDataMathService: inject(TYPES.bookDataMathService),
|
|
2811
|
+
};
|
|
2812
|
+
const historyServices = {
|
|
2813
|
+
fifteenMinuteCandleHistoryService: inject(TYPES.fifteenMinuteCandleHistoryService),
|
|
2814
|
+
hourCandleHistoryService: inject(TYPES.hourCandleHistoryService),
|
|
2815
|
+
oneMinuteCandleHistoryService: inject(TYPES.oneMinuteCandleHistoryService),
|
|
2816
|
+
thirtyMinuteCandleHistoryService: inject(TYPES.thirtyMinuteCandleHistoryService),
|
|
2817
|
+
};
|
|
2818
|
+
const signal = {
|
|
2819
|
+
...commonServices,
|
|
2820
|
+
...mathServices,
|
|
2821
|
+
...historyServices,
|
|
2822
|
+
};
|
|
2823
|
+
init();
|
|
2824
|
+
Object.assign(globalThis, { signal });
|
|
2825
|
+
|
|
2826
|
+
const fetchHourHistory = backtestKit.Cache.fn(signal.hourCandleHistoryService.getReport, {
|
|
2827
|
+
interval: "30m",
|
|
2828
|
+
});
|
|
2829
|
+
const fetchThirtyMinuteHistory = backtestKit.Cache.fn(signal.thirtyMinuteCandleHistoryService.getReport, {
|
|
2830
|
+
interval: "15m",
|
|
2831
|
+
});
|
|
2832
|
+
const fetchFifteenMinuteHistory = backtestKit.Cache.fn(signal.fifteenMinuteCandleHistoryService.getReport, {
|
|
2833
|
+
interval: "5m",
|
|
2834
|
+
});
|
|
2835
|
+
const fetchOneMinuteHistory = backtestKit.Cache.fn(signal.oneMinuteCandleHistoryService.getReport, {
|
|
2836
|
+
interval: "1m",
|
|
2837
|
+
});
|
|
2838
|
+
const commitHourHistory = functoolsKit.trycatch(async (symbol, history) => {
|
|
2839
|
+
const hourHistory = await fetchHourHistory(symbol);
|
|
2840
|
+
await history.push({
|
|
2841
|
+
role: "user",
|
|
2842
|
+
content: functoolsKit.str.newline("=== HOURLY CANDLES HISTORY (LAST 6) ===", "", hourHistory),
|
|
2843
|
+
}, {
|
|
2844
|
+
role: "assistant",
|
|
2845
|
+
content: "Hourly candles history received.",
|
|
2846
|
+
});
|
|
2847
|
+
}, {
|
|
2848
|
+
fallback: () => backtestKit.Cache.clear(fetchHourHistory),
|
|
2849
|
+
});
|
|
2850
|
+
const commitThirtyMinuteHistory = functoolsKit.trycatch(async (symbol, history) => {
|
|
2851
|
+
const thirtyMinuteHistory = await fetchThirtyMinuteHistory(symbol);
|
|
2852
|
+
await history.push({
|
|
2853
|
+
role: "user",
|
|
2854
|
+
content: functoolsKit.str.newline("=== 30-MIN CANDLES HISTORY (LAST 6) ===", "", thirtyMinuteHistory),
|
|
2855
|
+
}, {
|
|
2856
|
+
role: "assistant",
|
|
2857
|
+
content: "30-min candles history received.",
|
|
2858
|
+
});
|
|
2859
|
+
}, {
|
|
2860
|
+
fallback: () => backtestKit.Cache.clear(fetchThirtyMinuteHistory),
|
|
2861
|
+
});
|
|
2862
|
+
const commitFifteenMinuteHistory = functoolsKit.trycatch(async (symbol, history) => {
|
|
2863
|
+
const fifteenMinuteHistory = await fetchFifteenMinuteHistory(symbol);
|
|
2864
|
+
await history.push({
|
|
2865
|
+
role: "user",
|
|
2866
|
+
content: functoolsKit.str.newline("=== 15-MINUTE CANDLES HISTORY (LAST 8) ===", "", fifteenMinuteHistory),
|
|
2867
|
+
}, {
|
|
2868
|
+
role: "assistant",
|
|
2869
|
+
content: "15-minute candles history received.",
|
|
2870
|
+
});
|
|
2871
|
+
}, {
|
|
2872
|
+
fallback: () => backtestKit.Cache.clear(fetchFifteenMinuteHistory),
|
|
2873
|
+
});
|
|
2874
|
+
const commitOneMinuteHistory = functoolsKit.trycatch(async (symbol, history) => {
|
|
2875
|
+
const oneMinuteHistory = await fetchOneMinuteHistory(symbol);
|
|
2876
|
+
await history.push({
|
|
2877
|
+
role: "user",
|
|
2878
|
+
content: functoolsKit.str.newline("=== ONE-MINUTE CANDLES HISTORY (LAST 15) ===", "", oneMinuteHistory),
|
|
2879
|
+
}, {
|
|
2880
|
+
role: "assistant",
|
|
2881
|
+
content: "One-minute candles history received.",
|
|
2882
|
+
});
|
|
2883
|
+
}, {
|
|
2884
|
+
fallback: () => backtestKit.Cache.clear(fetchOneMinuteHistory),
|
|
2885
|
+
});
|
|
2886
|
+
|
|
2887
|
+
const fetchMicroTermMath = backtestKit.Cache.fn(signal.microTermMathService.getReport, {
|
|
2888
|
+
interval: "1m",
|
|
2889
|
+
});
|
|
2890
|
+
const fetchShortTermMath = backtestKit.Cache.fn(signal.shortTermMathService.getReport, {
|
|
2891
|
+
interval: "5m",
|
|
2892
|
+
});
|
|
2893
|
+
const fetchSwingTermMath = backtestKit.Cache.fn(signal.swingTermMathService.getReport, {
|
|
2894
|
+
interval: "15m",
|
|
2895
|
+
});
|
|
2896
|
+
const fetchLongTermMath = backtestKit.Cache.fn(signal.longTermMathService.getReport, {
|
|
2897
|
+
interval: "30m",
|
|
2898
|
+
});
|
|
2899
|
+
const commitMicroTermMath = functoolsKit.trycatch(async (symbol, history) => {
|
|
2900
|
+
const microTermMath = await fetchMicroTermMath(symbol);
|
|
2901
|
+
await history.push({
|
|
2902
|
+
role: "user",
|
|
2903
|
+
content: functoolsKit.str.newline("=== 1-MINUTE CANDLES TRADING ANALYSIS (HISTORICAL DATA) ===", "", microTermMath),
|
|
2904
|
+
}, {
|
|
2905
|
+
role: "assistant",
|
|
2906
|
+
content: "1-minute candles trading analysis received.",
|
|
2907
|
+
});
|
|
2908
|
+
}, {
|
|
2909
|
+
fallback: () => backtestKit.Cache.clear(fetchMicroTermMath),
|
|
2910
|
+
});
|
|
2911
|
+
const commitLongTermMath = functoolsKit.trycatch(async (symbol, history) => {
|
|
2912
|
+
const longTermMath = await fetchLongTermMath(symbol);
|
|
2913
|
+
await history.push({
|
|
2914
|
+
role: "user",
|
|
2915
|
+
content: functoolsKit.str.newline("=== 1-HOUR CANDLES TRADING ANALYSIS (HISTORICAL DATA) ===", "", longTermMath),
|
|
2916
|
+
}, {
|
|
2917
|
+
role: "assistant",
|
|
2918
|
+
content: "1-hour candles trading analysis received.",
|
|
2919
|
+
});
|
|
2920
|
+
}, {
|
|
2921
|
+
fallback: () => backtestKit.Cache.clear(fetchLongTermMath),
|
|
2922
|
+
});
|
|
2923
|
+
const commitShortTermMath = functoolsKit.trycatch(async (symbol, history) => {
|
|
2924
|
+
const shortTermMath = await fetchShortTermMath(symbol);
|
|
2925
|
+
await history.push({
|
|
2926
|
+
role: "user",
|
|
2927
|
+
content: functoolsKit.str.newline("=== 15-MINUTE CANDLES TRADING ANALYSIS (HISTORICAL DATA) ===", "", shortTermMath),
|
|
2928
|
+
}, {
|
|
2929
|
+
role: "assistant",
|
|
2930
|
+
content: "15-minute candles trading analysis received.",
|
|
2931
|
+
});
|
|
2932
|
+
}, {
|
|
2933
|
+
fallback: () => backtestKit.Cache.clear(fetchShortTermMath),
|
|
2934
|
+
});
|
|
2935
|
+
const commitSwingTermMath = functoolsKit.trycatch(async (symbol, history) => {
|
|
2936
|
+
const swingTermMath = await fetchSwingTermMath(symbol);
|
|
2937
|
+
await history.push({
|
|
2938
|
+
role: "user",
|
|
2939
|
+
content: functoolsKit.str.newline("=== 30-MIN CANDLES ANALYSIS (HISTORICAL DATA) ===", "", swingTermMath),
|
|
2940
|
+
}, {
|
|
2941
|
+
role: "assistant",
|
|
2942
|
+
content: "30-min candles analysis received.",
|
|
2943
|
+
});
|
|
2944
|
+
}, {
|
|
2945
|
+
fallback: () => backtestKit.Cache.clear(fetchSwingTermMath),
|
|
2946
|
+
});
|
|
2947
|
+
|
|
2948
|
+
const fetchBookData = backtestKit.Cache.fn(signal.bookDataMathService.getReport, {
|
|
2949
|
+
interval: "5m",
|
|
2950
|
+
});
|
|
2951
|
+
const commitBookDataReport = functoolsKit.trycatch(async (symbol, history) => {
|
|
2952
|
+
const mode = await backtestKit.getMode();
|
|
2953
|
+
if (mode === "backtest") {
|
|
2954
|
+
return;
|
|
2955
|
+
}
|
|
2956
|
+
const bookDataReport = await fetchBookData(symbol);
|
|
2957
|
+
await history.push({
|
|
2958
|
+
role: "user",
|
|
2959
|
+
content: functoolsKit.str.newline("=== ORDER BOOK ANALYSIS (TOP 20 LARGEST LEVELS BY VOLUME %, BEST BID/ASK, MID PRICE, SPREAD, DEPTH IMBALANCE) ===", "", bookDataReport),
|
|
2960
|
+
}, {
|
|
2961
|
+
role: "assistant",
|
|
2962
|
+
content: "Order book analysis received. Will use for short-term liquidity assessment, market pressure direction (depth imbalance), and major support/resistance levels.",
|
|
2963
|
+
});
|
|
2964
|
+
}, {
|
|
2965
|
+
fallback: () => backtestKit.Cache.clear(fetchBookData),
|
|
2966
|
+
});
|
|
2967
|
+
const commitHistorySetup = async (symbol, history) => {
|
|
2968
|
+
// Cтакан сделок
|
|
2969
|
+
await commitBookDataReport(symbol, history);
|
|
2970
|
+
// Данные свечей отдельными блоками
|
|
2971
|
+
await commitOneMinuteHistory(symbol, history);
|
|
2972
|
+
await commitFifteenMinuteHistory(symbol, history);
|
|
2973
|
+
await commitThirtyMinuteHistory(symbol, history);
|
|
2974
|
+
await commitHourHistory(symbol, history);
|
|
2975
|
+
// Данные индикаторов и осцилляторов
|
|
2976
|
+
await commitMicroTermMath(symbol, history);
|
|
2977
|
+
await commitShortTermMath(symbol, history);
|
|
2978
|
+
await commitSwingTermMath(symbol, history);
|
|
2979
|
+
await commitLongTermMath(symbol, history);
|
|
2980
|
+
const displayName = await String(symbol).toUpperCase();
|
|
2981
|
+
const currentPrice = await backtestKit.getAveragePrice(symbol);
|
|
2982
|
+
const currentData = await backtestKit.getDate();
|
|
2983
|
+
await history.push({
|
|
2984
|
+
role: "system",
|
|
2985
|
+
content: functoolsKit.str.newline(`Trading symbol: ${displayName}`, `Current price: ${await backtestKit.formatPrice(symbol, currentPrice)} USD`, `Current time: ${currentData.toISOString()}`),
|
|
2986
|
+
});
|
|
2987
|
+
};
|
|
2988
|
+
|
|
2989
|
+
const setLogger = (logger) => {
|
|
2990
|
+
signal.loggerService.setLogger(logger);
|
|
2991
|
+
};
|
|
2992
|
+
|
|
2993
|
+
exports.commitBookDataReport = commitBookDataReport;
|
|
2994
|
+
exports.commitFifteenMinuteHistory = commitFifteenMinuteHistory;
|
|
2995
|
+
exports.commitHistorySetup = commitHistorySetup;
|
|
2996
|
+
exports.commitHourHistory = commitHourHistory;
|
|
2997
|
+
exports.commitLongTermMath = commitLongTermMath;
|
|
2998
|
+
exports.commitMicroTermMath = commitMicroTermMath;
|
|
2999
|
+
exports.commitOneMinuteHistory = commitOneMinuteHistory;
|
|
3000
|
+
exports.commitShortTermMath = commitShortTermMath;
|
|
3001
|
+
exports.commitSwingTermMath = commitSwingTermMath;
|
|
3002
|
+
exports.commitThirtyMinuteHistory = commitThirtyMinuteHistory;
|
|
3003
|
+
exports.lib = signal;
|
|
3004
|
+
exports.setLogger = setLogger;
|