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