@backtest-kit/signals 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +302 -0
- package/build/index.cjs +3004 -0
- package/build/index.mjs +2991 -0
- package/package.json +81 -0
- package/types.d.ts +294 -0
package/build/index.mjs
ADDED
|
@@ -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 };
|