@backtest-kit/signals 14.0.0 → 15.0.0
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 +201 -201
- package/build/index.cjs +94 -75
- package/build/index.mjs +94 -75
- package/package.json +87 -87
- package/types.d.ts +10 -11
package/build/index.mjs
CHANGED
|
@@ -138,13 +138,6 @@ const columns$3 = [
|
|
|
138
138
|
? `${await formatPrice(symbol, Number(v))} USD`
|
|
139
139
|
: "N/A",
|
|
140
140
|
},
|
|
141
|
-
{
|
|
142
|
-
key: "atr14_raw",
|
|
143
|
-
label: "ATR(14) Raw",
|
|
144
|
-
format: async (v, symbol) => v !== null
|
|
145
|
-
? `${await formatPrice(symbol, Number(v))} USD`
|
|
146
|
-
: "N/A",
|
|
147
|
-
},
|
|
148
141
|
{
|
|
149
142
|
key: "atr20",
|
|
150
143
|
label: "ATR(20)",
|
|
@@ -207,6 +200,11 @@ const columns$3 = [
|
|
|
207
200
|
? `${await formatPrice(symbol, Number(v))} USD`
|
|
208
201
|
: "N/A",
|
|
209
202
|
},
|
|
203
|
+
{
|
|
204
|
+
key: "volumeTrendRatio",
|
|
205
|
+
label: "Volume Trend Ratio",
|
|
206
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(2)}x` : "N/A"),
|
|
207
|
+
},
|
|
210
208
|
{
|
|
211
209
|
key: "currentPrice",
|
|
212
210
|
label: "Current Price",
|
|
@@ -341,8 +339,8 @@ function calculateFibonacciLevels$2(candles, endIndex) {
|
|
|
341
339
|
"61.8%": high - range * 0.618,
|
|
342
340
|
"78.6%": high - range * 0.786,
|
|
343
341
|
"100.0%": low,
|
|
344
|
-
"127.2%": high - range * 1.272,
|
|
345
|
-
"161.8%": high - range * 1.618,
|
|
342
|
+
"127.2% (downside)": high - range * 1.272,
|
|
343
|
+
"161.8% (downside)": high - range * 1.618,
|
|
346
344
|
};
|
|
347
345
|
const currentPrice = Number(candles[endIndex].close);
|
|
348
346
|
let nearestLevel = {
|
|
@@ -436,20 +434,19 @@ function generateAnalysis$3(symbol, candles) {
|
|
|
436
434
|
if (i < WARMUP_PERIOD$3) {
|
|
437
435
|
return;
|
|
438
436
|
}
|
|
439
|
-
// Volume trend calculation
|
|
440
|
-
const volumeSma6 = new FasterSMA(6);
|
|
441
|
-
const volumeSma6Prev = new FasterSMA(6);
|
|
437
|
+
// Volume trend calculation: average of last 6 candles vs previous 6
|
|
442
438
|
const volumeStart = Math.max(0, i + 1 - 12);
|
|
443
439
|
const prevVolumeData = volumes.slice(volumeStart, Math.min(volumeStart + 6, i + 1));
|
|
444
440
|
const recentVolumeData = volumes.slice(Math.max(0, i + 1 - 6), i + 1);
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const
|
|
441
|
+
const averageOf = (values) => {
|
|
442
|
+
const safe = values.filter((value) => !isUnsafe$4(value));
|
|
443
|
+
if (safe.length === 0) {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
return safe.reduce((sum, value) => sum + value, 0) / safe.length;
|
|
447
|
+
};
|
|
448
|
+
const recentVolumeRaw = averageOf(recentVolumeData);
|
|
449
|
+
const prevVolumeRaw = averageOf(prevVolumeData);
|
|
453
450
|
const recentVolume = !isUnsafe$4(recentVolumeRaw)
|
|
454
451
|
? recentVolumeRaw
|
|
455
452
|
: volumes[i];
|
|
@@ -501,9 +498,6 @@ function generateAnalysis$3(symbol, candles) {
|
|
|
501
498
|
atr14: atr14.getResult() != null && !isUnsafe$4(atr14.getResult())
|
|
502
499
|
? atr14.getResult()
|
|
503
500
|
: null,
|
|
504
|
-
atr14_raw: atr14.getResult() != null && !isUnsafe$4(atr14.getResult())
|
|
505
|
-
? atr14.getResult()
|
|
506
|
-
: null,
|
|
507
501
|
atr20: atr20.getResult() != null && !isUnsafe$4(atr20.getResult())
|
|
508
502
|
? atr20.getResult()
|
|
509
503
|
: null,
|
|
@@ -554,11 +548,12 @@ function generateAnalysis$3(symbol, candles) {
|
|
|
554
548
|
fibonacciDistance: fibonacciNearest.distance,
|
|
555
549
|
bodySize,
|
|
556
550
|
closePrice: close,
|
|
557
|
-
date: new Date(),
|
|
551
|
+
date: new Date(_candle.timestamp),
|
|
558
552
|
lookbackPeriod: "48 candles (48 hours) with SMA(50) from 100 hours",
|
|
559
553
|
});
|
|
560
554
|
});
|
|
561
|
-
|
|
555
|
+
// Return only the last TABLE_ROWS_LIMIT rows
|
|
556
|
+
return results.slice(-TABLE_ROWS_LIMIT$3);
|
|
562
557
|
}
|
|
563
558
|
/**
|
|
564
559
|
* Generates markdown table with long-term technical analysis history.
|
|
@@ -621,9 +616,7 @@ async function generateHistoryTable$3(indicators, symbol) {
|
|
|
621
616
|
markdown +=
|
|
622
617
|
"- **ATR(14)**: over previous 14 candles (14 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
623
618
|
markdown +=
|
|
624
|
-
"- **ATR(
|
|
625
|
-
markdown +=
|
|
626
|
-
"- **ATR(20) Raw**: raw value over previous 20 candles (20 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
619
|
+
"- **ATR(20)**: over previous 20 candles (20 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞)\n";
|
|
627
620
|
markdown +=
|
|
628
621
|
"- **CCI(20)**: over previous 20 candles (20 hours on 1h timeframe) before row timestamp (Min: -∞, Max: +∞)\n";
|
|
629
622
|
markdown +=
|
|
@@ -648,12 +641,14 @@ async function generateHistoryTable$3(indicators, symbol) {
|
|
|
648
641
|
"- **EMA(34)**: over previous 34 candles (34 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
649
642
|
markdown +=
|
|
650
643
|
"- **Momentum(10)**: over previous 10 candles (10 hours on 1h timeframe) before row timestamp (Min: -∞ USD, Max: +∞ USD)\n";
|
|
644
|
+
markdown +=
|
|
645
|
+
"- **Volume Trend Ratio**: average volume of last 6 candles relative to previous 6 candles before row timestamp (Min: 0x, Max: +∞x; above 1x = volume increasing)\n";
|
|
651
646
|
markdown +=
|
|
652
647
|
"- **Support**: over previous 4 candles (4 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
653
648
|
markdown +=
|
|
654
649
|
"- **Resistance**: over previous 4 candles (4 hours on 1h timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
655
650
|
markdown +=
|
|
656
|
-
"- **Fibonacci Nearest Level**: nearest level name before row timestamp\n";
|
|
651
|
+
"- **Fibonacci Nearest Level**: nearest level name before row timestamp; levels marked (downside) lie below the range low\n";
|
|
657
652
|
markdown +=
|
|
658
653
|
"- **Fibonacci Nearest Price**: nearest price level before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
659
654
|
markdown +=
|
|
@@ -768,9 +763,7 @@ class LongTermHistoryService {
|
|
|
768
763
|
this.getReport = async (symbol) => {
|
|
769
764
|
this.loggerService.log("longTermHistoryService getReport", { symbol });
|
|
770
765
|
const fullCandles = await getCandles(symbol, "1h", 100);
|
|
771
|
-
|
|
772
|
-
const allRows = await this.getData(symbol, fullCandles);
|
|
773
|
-
const rows = allRows.slice(-TABLE_ROWS_LIMIT$3);
|
|
766
|
+
const rows = await this.getData(symbol, fullCandles);
|
|
774
767
|
return generateHistoryTable$3(rows, symbol);
|
|
775
768
|
};
|
|
776
769
|
/**
|
|
@@ -1066,6 +1059,11 @@ const columns$2 = [
|
|
|
1066
1059
|
label: "Volume Ratio",
|
|
1067
1060
|
format: (v) => (v !== null ? `${Number(v).toFixed(2)}x` : "N/A"),
|
|
1068
1061
|
},
|
|
1062
|
+
{
|
|
1063
|
+
key: "volumeTrendRatio",
|
|
1064
|
+
label: "Volume Trend Ratio",
|
|
1065
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(2)}x` : "N/A"),
|
|
1066
|
+
},
|
|
1069
1067
|
{
|
|
1070
1068
|
key: "volatility5",
|
|
1071
1069
|
label: "Volatility(5)",
|
|
@@ -1155,9 +1153,11 @@ function calculateVolumeMetrics(candles, endIndex) {
|
|
|
1155
1153
|
const volumeSma5 = new FasterSMA(5);
|
|
1156
1154
|
volumes.forEach((vol) => volumeSma5.update(vol, false));
|
|
1157
1155
|
const avgVolumeRaw = volumeSma5.getResult();
|
|
1158
|
-
const avgVolume = !isUnsafe$3(avgVolumeRaw) ? avgVolumeRaw :
|
|
1156
|
+
const avgVolume = !isUnsafe$3(avgVolumeRaw) ? avgVolumeRaw : null;
|
|
1159
1157
|
const currentVolume = volumes[volumes.length - 1];
|
|
1160
|
-
const volumeRatio = avgVolume > 0 && !isUnsafe$3(currentVolume)
|
|
1158
|
+
const volumeRatio = avgVolume !== null && avgVolume > 0 && !isUnsafe$3(currentVolume)
|
|
1159
|
+
? currentVolume / avgVolume
|
|
1160
|
+
: null;
|
|
1161
1161
|
let volumeTrendRatio = null;
|
|
1162
1162
|
if (volumes.length >= 6) {
|
|
1163
1163
|
const recent3 = volumes.slice(-3);
|
|
@@ -1194,15 +1194,15 @@ function calculatePriceChanges(candles, endIndex) {
|
|
|
1194
1194
|
return { priceChange1m: null, priceChange3m: null, priceChange5m: null };
|
|
1195
1195
|
}
|
|
1196
1196
|
const current = closes[closes.length - 1];
|
|
1197
|
-
const priceChange1m = closes.length >= 2
|
|
1197
|
+
const priceChange1m = closes.length >= 2 && closes[closes.length - 2] > 0
|
|
1198
1198
|
? ((current - closes[closes.length - 2]) / closes[closes.length - 2]) *
|
|
1199
1199
|
100
|
|
1200
1200
|
: null;
|
|
1201
|
-
const priceChange3m = closes.length >= 4
|
|
1201
|
+
const priceChange3m = closes.length >= 4 && closes[closes.length - 4] > 0
|
|
1202
1202
|
? ((current - closes[closes.length - 4]) / closes[closes.length - 4]) *
|
|
1203
1203
|
100
|
|
1204
1204
|
: null;
|
|
1205
|
-
const priceChange5m = closes.length >= 6
|
|
1205
|
+
const priceChange5m = closes.length >= 6 && closes[closes.length - 6] > 0
|
|
1206
1206
|
? ((current - closes[closes.length - 6]) / closes[closes.length - 6]) *
|
|
1207
1207
|
100
|
|
1208
1208
|
: null;
|
|
@@ -1348,9 +1348,9 @@ function generateAnalysis$2(symbol, candles) {
|
|
|
1348
1348
|
sma8.update(close, false);
|
|
1349
1349
|
dema8.update(close, false);
|
|
1350
1350
|
wma5.update(close, false);
|
|
1351
|
-
//
|
|
1352
|
-
// EMA(21)
|
|
1353
|
-
//
|
|
1351
|
+
// Skip rows until the primary indicators are warmed up.
|
|
1352
|
+
// WARMUP_PERIOD covers EMA(21); slower indicators (e.g. StochRSI(14)
|
|
1353
|
+
// needs ~28 candles) render as N/A in the first rows until ready.
|
|
1354
1354
|
if (i < WARMUP_PERIOD$2) {
|
|
1355
1355
|
return;
|
|
1356
1356
|
}
|
|
@@ -1521,7 +1521,7 @@ function generateAnalysis$2(symbol, candles) {
|
|
|
1521
1521
|
squeezeMomentum,
|
|
1522
1522
|
pressureIndex,
|
|
1523
1523
|
closePrice: close,
|
|
1524
|
-
date: new Date(),
|
|
1524
|
+
date: new Date(_candle.timestamp),
|
|
1525
1525
|
lookbackPeriod: "60 candles (60 minutes)",
|
|
1526
1526
|
});
|
|
1527
1527
|
});
|
|
@@ -1607,7 +1607,7 @@ async function generateHistoryTable$2(indicators, symbol) {
|
|
|
1607
1607
|
markdown +=
|
|
1608
1608
|
"- **Bollinger Width(8,2.0)**: width percentage before row timestamp (Min: 0%, Max: +∞)\n";
|
|
1609
1609
|
markdown +=
|
|
1610
|
-
"- **Bollinger Position**: price position within bands before row timestamp (
|
|
1610
|
+
"- **Bollinger Position**: price position within bands before row timestamp (0% = lower band, 100% = upper band; can go below 0% or above 100% when price is outside the bands)\n";
|
|
1611
1611
|
markdown +=
|
|
1612
1612
|
"- **Stochastic K(3,3,3)**: over previous 3 candles (3 minutes on 1m timeframe) before row timestamp (Min: 0, Max: 100)\n";
|
|
1613
1613
|
markdown +=
|
|
@@ -1644,6 +1644,8 @@ async function generateHistoryTable$2(indicators, symbol) {
|
|
|
1644
1644
|
"- **Volume SMA(5)**: over previous 5 candles (5 minutes on 1m timeframe) before row timestamp (Min: 0, Max: +∞)\n";
|
|
1645
1645
|
markdown +=
|
|
1646
1646
|
"- **Volume Ratio**: volume relative to average at row timestamp (Min: 0x, Max: +∞x)\n";
|
|
1647
|
+
markdown +=
|
|
1648
|
+
"- **Volume Trend Ratio**: average volume of last 3 candles relative to previous 3 candles at row timestamp (Min: 0x, Max: +∞x; above 1x = volume increasing)\n";
|
|
1647
1649
|
markdown +=
|
|
1648
1650
|
"- **Support**: over previous 30 candles (30 minutes on 1m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
1649
1651
|
markdown +=
|
|
@@ -1894,6 +1896,11 @@ const columns$1 = [
|
|
|
1894
1896
|
label: "ROC(10) - Rate of Change %",
|
|
1895
1897
|
format: (v) => (v !== null ? `${Number(v).toFixed(3)}%` : "N/A"),
|
|
1896
1898
|
},
|
|
1899
|
+
{
|
|
1900
|
+
key: "volumeTrendRatio",
|
|
1901
|
+
label: "Volume Trend Ratio",
|
|
1902
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(2)}x` : "N/A"),
|
|
1903
|
+
},
|
|
1897
1904
|
{
|
|
1898
1905
|
key: "sma50",
|
|
1899
1906
|
label: "SMA(50)",
|
|
@@ -2068,8 +2075,8 @@ function calculateFibonacciLevels$1(candles, endIndex) {
|
|
|
2068
2075
|
"61.8%": high - range * 0.618,
|
|
2069
2076
|
"78.6%": high - range * 0.786,
|
|
2070
2077
|
"100.0%": low,
|
|
2071
|
-
"127.2%": high - range * 1.272,
|
|
2072
|
-
"161.8%": high - range * 1.618,
|
|
2078
|
+
"127.2% (downside)": high - range * 1.272,
|
|
2079
|
+
"161.8% (downside)": high - range * 1.618,
|
|
2073
2080
|
};
|
|
2074
2081
|
const currentPrice = Number(candles[endIndex].close);
|
|
2075
2082
|
let nearestLevel = {
|
|
@@ -2248,7 +2255,8 @@ function generateAnalysis$1(symbol, candles) {
|
|
|
2248
2255
|
bollingerWidth10_2: bollingerResult &&
|
|
2249
2256
|
!isUnsafe$2(bollingerResult.upper) &&
|
|
2250
2257
|
!isUnsafe$2(bollingerResult.lower) &&
|
|
2251
|
-
!isUnsafe$2(bollingerResult.middle)
|
|
2258
|
+
!isUnsafe$2(bollingerResult.middle) &&
|
|
2259
|
+
bollingerResult.middle !== 0
|
|
2252
2260
|
? ((bollingerResult.upper - bollingerResult.lower) /
|
|
2253
2261
|
bollingerResult.middle) *
|
|
2254
2262
|
100
|
|
@@ -2309,7 +2317,7 @@ function generateAnalysis$1(symbol, candles) {
|
|
|
2309
2317
|
fibonacciDistance: fibonacciNearest.distance,
|
|
2310
2318
|
bodySize,
|
|
2311
2319
|
closePrice: close,
|
|
2312
|
-
date: new Date(),
|
|
2320
|
+
date: new Date(_candle.timestamp),
|
|
2313
2321
|
lookbackPeriod: "144 candles (36 hours)",
|
|
2314
2322
|
});
|
|
2315
2323
|
});
|
|
@@ -2396,6 +2404,8 @@ async function generateHistoryTable$1(indicators, symbol) {
|
|
|
2396
2404
|
"- **ROC(5)**: over previous 5 candles (75 minutes on 15m timeframe) before row timestamp (Min: -∞%, Max: +∞%)\n";
|
|
2397
2405
|
markdown +=
|
|
2398
2406
|
"- **ROC(10)**: over previous 10 candles (150 minutes on 15m timeframe) before row timestamp (Min: -∞%, Max: +∞%)\n";
|
|
2407
|
+
markdown +=
|
|
2408
|
+
"- **Volume Trend Ratio**: average volume of last 8 candles relative to previous 8 candles before row timestamp (Min: 0x, Max: +∞x; above 1x = volume increasing)\n";
|
|
2399
2409
|
markdown +=
|
|
2400
2410
|
"- **SMA(50)**: over previous 50 candles (750 minutes on 15m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2401
2411
|
markdown +=
|
|
@@ -2411,9 +2421,9 @@ async function generateHistoryTable$1(indicators, symbol) {
|
|
|
2411
2421
|
markdown +=
|
|
2412
2422
|
"- **Resistance**: over previous 48 candles (12 hours on 15m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2413
2423
|
markdown +=
|
|
2414
|
-
"- **Fibonacci Nearest Level**: nearest level name before row timestamp\n";
|
|
2424
|
+
"- **Fibonacci Nearest Level**: nearest level name before row timestamp; levels marked (downside) lie below the range low\n";
|
|
2415
2425
|
markdown +=
|
|
2416
|
-
"- **Fibonacci Nearest Price**: nearest price level over 288 candles (
|
|
2426
|
+
"- **Fibonacci Nearest Price**: nearest price level over up to 288 candles (limited by the 144-candle / 36h report history) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2417
2427
|
markdown +=
|
|
2418
2428
|
"- **Fibonacci Distance**: distance to nearest level before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
2419
2429
|
markdown +=
|
|
@@ -2568,6 +2578,11 @@ const TABLE_ROWS_LIMIT = 30;
|
|
|
2568
2578
|
* Ensures all technical indicators (especially EMA(34)) have sufficient data.
|
|
2569
2579
|
*/
|
|
2570
2580
|
const WARMUP_PERIOD = 34;
|
|
2581
|
+
/**
|
|
2582
|
+
* Rolling window (in candles) for the volatility calculation.
|
|
2583
|
+
* Keeps the metric comparable across rows regardless of total history length.
|
|
2584
|
+
*/
|
|
2585
|
+
const VOLATILITY_WINDOW = 20;
|
|
2571
2586
|
const columns = [
|
|
2572
2587
|
{
|
|
2573
2588
|
key: "rsi14",
|
|
@@ -3001,16 +3016,20 @@ function generateAnalysis(symbol, candles) {
|
|
|
3001
3016
|
if (i < WARMUP_PERIOD) {
|
|
3002
3017
|
return;
|
|
3003
3018
|
}
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
const
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3019
|
+
let volatilitySumSq = 0;
|
|
3020
|
+
let volatilityCount = 0;
|
|
3021
|
+
for (let idx = Math.max(1, i + 1 - VOLATILITY_WINDOW); idx <= i; idx++) {
|
|
3022
|
+
const prevPrice = closes[idx - 1];
|
|
3023
|
+
const currPrice = closes[idx];
|
|
3024
|
+
if (isUnsafe$1(prevPrice) || isUnsafe$1(currPrice) || prevPrice <= 0) {
|
|
3025
|
+
continue;
|
|
3026
|
+
}
|
|
3027
|
+
const change = ((currPrice - prevPrice) / prevPrice) * 100;
|
|
3028
|
+
volatilitySumSq += change ** 2;
|
|
3029
|
+
volatilityCount += 1;
|
|
3030
|
+
}
|
|
3031
|
+
const volatility = volatilityCount > 0
|
|
3032
|
+
? Math.sqrt(volatilitySumSq / volatilityCount)
|
|
3014
3033
|
: null;
|
|
3015
3034
|
const { support, resistance } = calculateSupportResistance(candles, i);
|
|
3016
3035
|
const fibonacci = calculateFibonacciLevels(candles, i);
|
|
@@ -3106,7 +3125,7 @@ function generateAnalysis(symbol, candles) {
|
|
|
3106
3125
|
fibonacciPositionPercent: fibonacci.fibonacciPositionPercent,
|
|
3107
3126
|
bodySize,
|
|
3108
3127
|
closePrice: close,
|
|
3109
|
-
date: new Date(),
|
|
3128
|
+
date: new Date(_candle.timestamp),
|
|
3110
3129
|
lookbackPeriod: "96 candles (48 hours)",
|
|
3111
3130
|
});
|
|
3112
3131
|
});
|
|
@@ -3220,7 +3239,7 @@ async function generateHistoryTable(indicators, symbol) {
|
|
|
3220
3239
|
markdown +=
|
|
3221
3240
|
"- **Volume**: trading volume at row timestamp (Min: 0, Max: +∞)\n";
|
|
3222
3241
|
markdown +=
|
|
3223
|
-
"- **Volatility**:
|
|
3242
|
+
"- **Volatility**: RMS of 1-candle percentage changes over previous 20 candles (10 hours on 30m timeframe) before row timestamp (Min: 0%, Max: +∞)\n";
|
|
3224
3243
|
markdown += "\n## Column Descriptions\n\n";
|
|
3225
3244
|
markdown += "**RSI(14) - Relative Strength Index**: Momentum oscillator measuring the speed and magnitude of price changes on 30-minute timeframe. Values above 70 indicate overbought conditions suitable for profit-taking, below 30 indicate oversold conditions suitable for entries.\n\n";
|
|
3226
3245
|
markdown += "**StochRSI(14) - RSI Oscillator**: Applies Stochastic calculation to RSI values, providing more sensitive overbought/oversold signals for swing trading. Range 0-100, with 80+ being overbought and 20- being oversold.\n\n";
|
|
@@ -3625,11 +3644,11 @@ class HourCandleHistoryService {
|
|
|
3625
3644
|
markdown += `### 1h Candle ${index + 1}\n`;
|
|
3626
3645
|
markdown += `- **Price Change**: ${priceChangePercent.toFixed(3)}%\n`;
|
|
3627
3646
|
markdown += `- **Time**: ${formattedTime}\n`;
|
|
3628
|
-
markdown += `- **Open**: ${formatPrice(symbol, candle.open)} USD\n`;
|
|
3629
|
-
markdown += `- **High**: ${formatPrice(symbol, candle.high)} USD\n`;
|
|
3630
|
-
markdown += `- **Low**: ${formatPrice(symbol, candle.low)} USD\n`;
|
|
3631
|
-
markdown += `- **Close**: ${formatPrice(symbol, candle.close)} USD\n`;
|
|
3632
|
-
markdown += `- **Volume**: ${formatQuantity(symbol, candle.volume)}\n`;
|
|
3647
|
+
markdown += `- **Open**: ${await formatPrice(symbol, candle.open)} USD\n`;
|
|
3648
|
+
markdown += `- **High**: ${await formatPrice(symbol, candle.high)} USD\n`;
|
|
3649
|
+
markdown += `- **Low**: ${await formatPrice(symbol, candle.low)} USD\n`;
|
|
3650
|
+
markdown += `- **Close**: ${await formatPrice(symbol, candle.close)} USD\n`;
|
|
3651
|
+
markdown += `- **Volume**: ${await formatQuantity(symbol, candle.volume)}\n`;
|
|
3633
3652
|
markdown += `- **1h Volatility**: ${volatilityPercent.toFixed(2)}%\n`;
|
|
3634
3653
|
markdown += `- **Body Size**: ${bodyPercent.toFixed(1)}%\n\n`;
|
|
3635
3654
|
}
|
|
@@ -3779,7 +3798,7 @@ class OneMinuteCandleHistoryService {
|
|
|
3779
3798
|
markdown += `- **High**: ${await formatPrice(symbol, candle.high)} USD\n`;
|
|
3780
3799
|
markdown += `- **Low**: ${await formatPrice(symbol, candle.low)} USD\n`;
|
|
3781
3800
|
markdown += `- **Close**: ${await formatPrice(symbol, candle.close)} USD\n`;
|
|
3782
|
-
markdown += `- **Volume**: ${formatQuantity(symbol, candle.volume)}\n`;
|
|
3801
|
+
markdown += `- **Volume**: ${await formatQuantity(symbol, candle.volume)}\n`;
|
|
3783
3802
|
markdown += `- **1m Volatility**: ${volatilityPercent.toFixed(2)}%\n`;
|
|
3784
3803
|
markdown += `- **Body Size**: ${bodyPercent.toFixed(1)}%\n\n`;
|
|
3785
3804
|
}
|
|
@@ -3929,7 +3948,7 @@ class ThirtyMinuteCandleHistoryService {
|
|
|
3929
3948
|
report += `- **High**: ${await formatPrice(symbol, candle.high)} USD\n`;
|
|
3930
3949
|
report += `- **Low**: ${await formatPrice(symbol, candle.low)} USD\n`;
|
|
3931
3950
|
report += `- **Close**: ${await formatPrice(symbol, candle.close)} USD\n`;
|
|
3932
|
-
report += `- **Volume**: ${formatQuantity(symbol, candle.volume)}\n`;
|
|
3951
|
+
report += `- **Volume**: ${await formatQuantity(symbol, candle.volume)}\n`;
|
|
3933
3952
|
report += `- **30m Volatility**: ${volatilityPercent.toFixed(2)}%\n`;
|
|
3934
3953
|
report += `- **Body Size**: ${bodyPercent.toFixed(1)}%\n\n`;
|
|
3935
3954
|
}
|
|
@@ -4260,18 +4279,18 @@ class BookDataMathService {
|
|
|
4260
4279
|
});
|
|
4261
4280
|
const depth = await getOrderBook(symbol, MAX_DEPTH_LEVELS);
|
|
4262
4281
|
// Just process raw data - no calculations
|
|
4263
|
-
const bids = processOrderBookSide(depth.bids.sort((a, b) => parseFloat(b.price) - parseFloat(a.price))); // Сортировка по убыванию
|
|
4264
|
-
const asks = processOrderBookSide(depth.asks.sort((a, b) => parseFloat(a.price) - parseFloat(b.price))); // Сортировка по возрастанию
|
|
4265
|
-
const bestBid = bids.length > 0 ? bids[0].price :
|
|
4266
|
-
const bestAsk = asks.length > 0 ? asks[0].price :
|
|
4267
|
-
const midPrice = (bestBid + bestAsk) / 2;
|
|
4268
|
-
const spread = bestAsk - bestBid;
|
|
4282
|
+
const bids = processOrderBookSide([...depth.bids].sort((a, b) => parseFloat(b.price) - parseFloat(a.price))); // Сортировка по убыванию
|
|
4283
|
+
const asks = processOrderBookSide([...depth.asks].sort((a, b) => parseFloat(a.price) - parseFloat(b.price))); // Сортировка по возрастанию
|
|
4284
|
+
const bestBid = bids.length > 0 ? bids[0].price : null;
|
|
4285
|
+
const bestAsk = asks.length > 0 ? asks[0].price : null;
|
|
4286
|
+
const midPrice = bestBid !== null && bestAsk !== null ? (bestBid + bestAsk) / 2 : null;
|
|
4287
|
+
const spread = bestBid !== null && bestAsk !== null ? bestAsk - bestBid : null;
|
|
4269
4288
|
// Calculate depth imbalance
|
|
4270
4289
|
const totalBidVolume = bids.reduce((sum, bid) => sum + bid.quantity, 0);
|
|
4271
4290
|
const totalAskVolume = asks.reduce((sum, ask) => sum + ask.quantity, 0);
|
|
4272
4291
|
const depthImbalance = totalBidVolume + totalAskVolume > 0
|
|
4273
4292
|
? (totalBidVolume - totalAskVolume) / (totalBidVolume + totalAskVolume)
|
|
4274
|
-
:
|
|
4293
|
+
: null;
|
|
4275
4294
|
return {
|
|
4276
4295
|
symbol,
|
|
4277
4296
|
timestamp: new Date().toISOString(),
|
|
@@ -5024,7 +5043,7 @@ const commitHistorySetup = async (symbol, history) => {
|
|
|
5024
5043
|
await commitShortTermMath(symbol, history);
|
|
5025
5044
|
await commitSwingTermMath(symbol, history);
|
|
5026
5045
|
await commitLongTermMath(symbol, history);
|
|
5027
|
-
const displayName =
|
|
5046
|
+
const displayName = String(symbol).toUpperCase();
|
|
5028
5047
|
const currentPrice = await getAveragePrice(symbol);
|
|
5029
5048
|
const currentData = await getDate();
|
|
5030
5049
|
await history.push({
|
package/package.json
CHANGED
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@backtest-kit/signals",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Technical analysis and trading signal generation library for AI-powered trading systems. Computes 50+ indicators across 4 timeframes and generates markdown reports for LLM consumption.",
|
|
5
|
-
"author": {
|
|
6
|
-
"name": "Petr Tripolsky",
|
|
7
|
-
"email": "tripolskypetr@gmail.com",
|
|
8
|
-
"url": "https://github.com/tripolskypetr"
|
|
9
|
-
},
|
|
10
|
-
"funding": {
|
|
11
|
-
"type": "individual",
|
|
12
|
-
"url": "http://paypal.me/tripolskypetr"
|
|
13
|
-
},
|
|
14
|
-
"license": "MIT",
|
|
15
|
-
"homepage": "https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html",
|
|
16
|
-
"keywords": [
|
|
17
|
-
"framework",
|
|
18
|
-
"technical-analysis",
|
|
19
|
-
"trading-bot",
|
|
20
|
-
"algorithmic-trading",
|
|
21
|
-
"quantitative-trading",
|
|
22
|
-
"stock-market",
|
|
23
|
-
"cryptocurrency",
|
|
24
|
-
"forex",
|
|
25
|
-
"strategy-testing",
|
|
26
|
-
"portfolio-management",
|
|
27
|
-
"market-data",
|
|
28
|
-
"ohlcv",
|
|
29
|
-
"candlestick",
|
|
30
|
-
"time-series"
|
|
31
|
-
],
|
|
32
|
-
"files": [
|
|
33
|
-
"build",
|
|
34
|
-
"types.d.ts",
|
|
35
|
-
"README.md"
|
|
36
|
-
],
|
|
37
|
-
"repository": {
|
|
38
|
-
"type": "git",
|
|
39
|
-
"url": "https://github.com/tripolskypetr/backtest-kit",
|
|
40
|
-
"documentation": "https://github.com/tripolskypetr/backtest-kit/tree/master/docs"
|
|
41
|
-
},
|
|
42
|
-
"bugs": {
|
|
43
|
-
"url": "https://github.com/tripolskypetr/backtest-kit/issues"
|
|
44
|
-
},
|
|
45
|
-
"scripts": {
|
|
46
|
-
"build": "rollup -c"
|
|
47
|
-
},
|
|
48
|
-
"main": "build/index.cjs",
|
|
49
|
-
"module": "build/index.mjs",
|
|
50
|
-
"source": "src/index.ts",
|
|
51
|
-
"types": "./types.d.ts",
|
|
52
|
-
"exports": {
|
|
53
|
-
"require": "./build/index.cjs",
|
|
54
|
-
"types": "./types.d.ts",
|
|
55
|
-
"import": "./build/index.mjs",
|
|
56
|
-
"default": "./build/index.cjs"
|
|
57
|
-
},
|
|
58
|
-
"devDependencies": {
|
|
59
|
-
"@rollup/plugin-typescript": "11.1.6",
|
|
60
|
-
"@types/node": "22.9.0",
|
|
61
|
-
"glob": "11.0.1",
|
|
62
|
-
"rimraf": "6.0.1",
|
|
63
|
-
"rollup": "3.29.5",
|
|
64
|
-
"rollup-plugin-dts": "6.1.1",
|
|
65
|
-
"rollup-plugin-peer-deps-external": "2.2.4",
|
|
66
|
-
"ts-morph": "27.0.2",
|
|
67
|
-
"tslib": "2.7.0",
|
|
68
|
-
"typedoc": "0.27.9",
|
|
69
|
-
"backtest-kit": "
|
|
70
|
-
"worker-testbed": "
|
|
71
|
-
},
|
|
72
|
-
"peerDependencies": {
|
|
73
|
-
"backtest-kit": "^
|
|
74
|
-
"typescript": "^5.0.0"
|
|
75
|
-
},
|
|
76
|
-
"dependencies": {
|
|
77
|
-
"di-kit": "^1.1.1",
|
|
78
|
-
"di-scoped": "^1.0.21",
|
|
79
|
-
"functools-kit": "^
|
|
80
|
-
"trading-signals": "^6.9.1",
|
|
81
|
-
"get-moment-stamp": "^2.0.0",
|
|
82
|
-
"agent-swarm-kit": "^
|
|
83
|
-
},
|
|
84
|
-
"publishConfig": {
|
|
85
|
-
"access": "public"
|
|
86
|
-
}
|
|
87
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@backtest-kit/signals",
|
|
3
|
+
"version": "15.0.0",
|
|
4
|
+
"description": "Technical analysis and trading signal generation library for AI-powered trading systems. Computes 50+ indicators across 4 timeframes and generates markdown reports for LLM consumption.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Petr Tripolsky",
|
|
7
|
+
"email": "tripolskypetr@gmail.com",
|
|
8
|
+
"url": "https://github.com/tripolskypetr"
|
|
9
|
+
},
|
|
10
|
+
"funding": {
|
|
11
|
+
"type": "individual",
|
|
12
|
+
"url": "http://paypal.me/tripolskypetr"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"homepage": "https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"framework",
|
|
18
|
+
"technical-analysis",
|
|
19
|
+
"trading-bot",
|
|
20
|
+
"algorithmic-trading",
|
|
21
|
+
"quantitative-trading",
|
|
22
|
+
"stock-market",
|
|
23
|
+
"cryptocurrency",
|
|
24
|
+
"forex",
|
|
25
|
+
"strategy-testing",
|
|
26
|
+
"portfolio-management",
|
|
27
|
+
"market-data",
|
|
28
|
+
"ohlcv",
|
|
29
|
+
"candlestick",
|
|
30
|
+
"time-series"
|
|
31
|
+
],
|
|
32
|
+
"files": [
|
|
33
|
+
"build",
|
|
34
|
+
"types.d.ts",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/tripolskypetr/backtest-kit",
|
|
40
|
+
"documentation": "https://github.com/tripolskypetr/backtest-kit/tree/master/docs"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/tripolskypetr/backtest-kit/issues"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "rollup -c"
|
|
47
|
+
},
|
|
48
|
+
"main": "build/index.cjs",
|
|
49
|
+
"module": "build/index.mjs",
|
|
50
|
+
"source": "src/index.ts",
|
|
51
|
+
"types": "./types.d.ts",
|
|
52
|
+
"exports": {
|
|
53
|
+
"require": "./build/index.cjs",
|
|
54
|
+
"types": "./types.d.ts",
|
|
55
|
+
"import": "./build/index.mjs",
|
|
56
|
+
"default": "./build/index.cjs"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@rollup/plugin-typescript": "11.1.6",
|
|
60
|
+
"@types/node": "22.9.0",
|
|
61
|
+
"glob": "11.0.1",
|
|
62
|
+
"rimraf": "6.0.1",
|
|
63
|
+
"rollup": "3.29.5",
|
|
64
|
+
"rollup-plugin-dts": "6.1.1",
|
|
65
|
+
"rollup-plugin-peer-deps-external": "2.2.4",
|
|
66
|
+
"ts-morph": "27.0.2",
|
|
67
|
+
"tslib": "2.7.0",
|
|
68
|
+
"typedoc": "0.27.9",
|
|
69
|
+
"backtest-kit": "15.0.0",
|
|
70
|
+
"worker-testbed": "3.0.0"
|
|
71
|
+
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"backtest-kit": "^15.0.0",
|
|
74
|
+
"typescript": "^5.0.0"
|
|
75
|
+
},
|
|
76
|
+
"dependencies": {
|
|
77
|
+
"di-kit": "^1.1.1",
|
|
78
|
+
"di-scoped": "^1.0.21",
|
|
79
|
+
"functools-kit": "^4.0.0",
|
|
80
|
+
"trading-signals": "^6.9.1",
|
|
81
|
+
"get-moment-stamp": "^2.0.0",
|
|
82
|
+
"agent-swarm-kit": "^4.0.0"
|
|
83
|
+
},
|
|
84
|
+
"publishConfig": {
|
|
85
|
+
"access": "public"
|
|
86
|
+
}
|
|
87
|
+
}
|
package/types.d.ts
CHANGED
|
@@ -632,7 +632,6 @@ interface ILongTermRow {
|
|
|
632
632
|
pdi14: number | null;
|
|
633
633
|
ndi14: number | null;
|
|
634
634
|
atr14: number | null;
|
|
635
|
-
atr14_raw: number | null;
|
|
636
635
|
atr20: number | null;
|
|
637
636
|
cci20: number | null;
|
|
638
637
|
bollinger20_2_upper: number | null;
|
|
@@ -1543,16 +1542,16 @@ interface IBookDataAnalysis {
|
|
|
1543
1542
|
bids: IOrderBookEntry[];
|
|
1544
1543
|
/** Ask (sell) levels with percentages */
|
|
1545
1544
|
asks: IOrderBookEntry[];
|
|
1546
|
-
/** Highest bid price */
|
|
1547
|
-
bestBid: number;
|
|
1548
|
-
/** Lowest ask price */
|
|
1549
|
-
bestAsk: number;
|
|
1550
|
-
/** Mid price: (bestBid + bestAsk) / 2 */
|
|
1551
|
-
midPrice: number;
|
|
1552
|
-
/** Spread: bestAsk - bestBid */
|
|
1553
|
-
spread: number;
|
|
1554
|
-
/** Depth imbalance: (bidVol - askVol) / (bidVol + askVol) */
|
|
1555
|
-
depthImbalance: number;
|
|
1545
|
+
/** Highest bid price (null when the book side is empty) */
|
|
1546
|
+
bestBid: number | null;
|
|
1547
|
+
/** Lowest ask price (null when the book side is empty) */
|
|
1548
|
+
bestAsk: number | null;
|
|
1549
|
+
/** Mid price: (bestBid + bestAsk) / 2 (null when either side is empty) */
|
|
1550
|
+
midPrice: number | null;
|
|
1551
|
+
/** Spread: bestAsk - bestBid (null when either side is empty) */
|
|
1552
|
+
spread: number | null;
|
|
1553
|
+
/** Depth imbalance: (bidVol - askVol) / (bidVol + askVol) (null when the book is empty) */
|
|
1554
|
+
depthImbalance: number | null;
|
|
1556
1555
|
}
|
|
1557
1556
|
/**
|
|
1558
1557
|
* Service for order book analysis and markdown report generation.
|