@backtest-kit/signals 0.0.3 → 0.0.5
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/build/index.cjs +60 -109
- package/build/index.mjs +60 -109
- package/package.json +1 -1
- package/types.d.ts +5 -5
package/build/index.cjs
CHANGED
|
@@ -458,13 +458,9 @@ function generateAnalysis$3(symbol, candles) {
|
|
|
458
458
|
const prevVolume = !isUnsafe$4(prevVolumeRaw)
|
|
459
459
|
? prevVolumeRaw
|
|
460
460
|
: volumes[Math.max(0, i - 6)];
|
|
461
|
-
const
|
|
462
|
-
? recentVolume
|
|
463
|
-
|
|
464
|
-
: recentVolume < prevVolume * 0.9
|
|
465
|
-
? "decreasing"
|
|
466
|
-
: "stable"
|
|
467
|
-
: "stable";
|
|
461
|
+
const volumeTrendRatio = !isUnsafe$4(recentVolume) && !isUnsafe$4(prevVolume) && prevVolume > 0
|
|
462
|
+
? recentVolume / prevVolume
|
|
463
|
+
: null;
|
|
468
464
|
// Support/Resistance calculation
|
|
469
465
|
const pivotPeriod = Math.min(4, i + 1);
|
|
470
466
|
const startIdx = i + 1 - pivotPeriod;
|
|
@@ -543,7 +539,7 @@ function generateAnalysis$3(symbol, candles) {
|
|
|
543
539
|
cci20: cci.getResult() != null && !isUnsafe$4(cci.getResult())
|
|
544
540
|
? cci.getResult()
|
|
545
541
|
: null,
|
|
546
|
-
|
|
542
|
+
volumeTrendRatio,
|
|
547
543
|
support: support != null && !isUnsafe$4(support)
|
|
548
544
|
? support
|
|
549
545
|
: !isUnsafe$4(currentPrice)
|
|
@@ -596,7 +592,7 @@ async function generateHistoryTable$3(indicators, symbol) {
|
|
|
596
592
|
let markdown = "";
|
|
597
593
|
const currentData = await backtestKit.getDate();
|
|
598
594
|
markdown += `# 1-Hour Candles Trading Analysis for ${symbol} (Historical Data)\n`;
|
|
599
|
-
markdown += `> Current
|
|
595
|
+
markdown += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
600
596
|
const header = `| ${columns$3.map((col) => col.label).join(" | ")} |\n`;
|
|
601
597
|
const separator = `| ${columns$3.map(() => "---").join(" | ")} |\n`;
|
|
602
598
|
const tableRows = await Promise.all(indicators.map(async (ind) => {
|
|
@@ -1120,26 +1116,26 @@ function isUnsafe$3(value) {
|
|
|
1120
1116
|
/**
|
|
1121
1117
|
* Calculates volume metrics including SMA, ratio, and trend.
|
|
1122
1118
|
*
|
|
1123
|
-
* Computes volume SMA(5), current volume to average ratio, and volume trend
|
|
1124
|
-
* by comparing recent 3 candles to previous 3 candles. Returns
|
|
1125
|
-
*
|
|
1119
|
+
* Computes volume SMA(5), current volume to average ratio, and volume trend ratio
|
|
1120
|
+
* by comparing recent 3 candles to previous 3 candles. Returns numeric ratio
|
|
1121
|
+
* of recent volume average to previous volume average (e.g., 1.2 = 20% increase).
|
|
1126
1122
|
*
|
|
1127
1123
|
* @param candles - Array of candle data
|
|
1128
1124
|
* @param endIndex - Index of current candle in array
|
|
1129
|
-
* @returns Object with volumeSma5, volumeRatio, and
|
|
1125
|
+
* @returns Object with volumeSma5, volumeRatio, and volumeTrendRatio
|
|
1130
1126
|
*
|
|
1131
1127
|
* @example
|
|
1132
1128
|
* ```typescript
|
|
1133
1129
|
* const candles = await getCandles('BTCUSDT', '1m', 60);
|
|
1134
1130
|
* const metrics = calculateVolumeMetrics(candles, 59);
|
|
1135
1131
|
* console.log(metrics);
|
|
1136
|
-
* // { volumeSma5: 1500000, volumeRatio: 1.25,
|
|
1132
|
+
* // { volumeSma5: 1500000, volumeRatio: 1.25, volumeTrendRatio: 1.15 }
|
|
1137
1133
|
* ```
|
|
1138
1134
|
*/
|
|
1139
1135
|
function calculateVolumeMetrics(candles, endIndex) {
|
|
1140
1136
|
const volumes = candles.slice(0, endIndex + 1).map((c) => Number(c.volume));
|
|
1141
1137
|
if (volumes.length < 5) {
|
|
1142
|
-
return { volumeSma5: null, volumeRatio: null,
|
|
1138
|
+
return { volumeSma5: null, volumeRatio: null, volumeTrendRatio: null };
|
|
1143
1139
|
}
|
|
1144
1140
|
const volumeSma5 = new tradingSignals.FasterSMA(5);
|
|
1145
1141
|
volumes.forEach((vol) => volumeSma5.update(vol, false));
|
|
@@ -1147,20 +1143,17 @@ function calculateVolumeMetrics(candles, endIndex) {
|
|
|
1147
1143
|
const avgVolume = !isUnsafe$3(avgVolumeRaw) ? avgVolumeRaw : 0;
|
|
1148
1144
|
const currentVolume = volumes[volumes.length - 1];
|
|
1149
1145
|
const volumeRatio = avgVolume > 0 && !isUnsafe$3(currentVolume) ? currentVolume / avgVolume : 1;
|
|
1150
|
-
let
|
|
1146
|
+
let volumeTrendRatio = null;
|
|
1151
1147
|
if (volumes.length >= 6) {
|
|
1152
1148
|
const recent3 = volumes.slice(-3);
|
|
1153
1149
|
const prev3 = volumes.slice(-6, -3);
|
|
1154
1150
|
if (prev3.length >= 3) {
|
|
1155
1151
|
const recentAvg = recent3.reduce((a, b) => a + b, 0) / 3;
|
|
1156
1152
|
const prevAvg = prev3.reduce((a, b) => a + b, 0) / 3;
|
|
1157
|
-
|
|
1158
|
-
volumeTrend = "increasing";
|
|
1159
|
-
else if (recentAvg < prevAvg * 0.8)
|
|
1160
|
-
volumeTrend = "decreasing";
|
|
1153
|
+
volumeTrendRatio = prevAvg > 0 ? recentAvg / prevAvg : null;
|
|
1161
1154
|
}
|
|
1162
1155
|
}
|
|
1163
|
-
return { volumeSma5: avgVolume, volumeRatio,
|
|
1156
|
+
return { volumeSma5: avgVolume, volumeRatio, volumeTrendRatio };
|
|
1164
1157
|
}
|
|
1165
1158
|
/**
|
|
1166
1159
|
* Calculates price change percentages over 1, 3, and 5 minute periods.
|
|
@@ -1493,7 +1486,7 @@ function generateAnalysis$2(symbol, candles) {
|
|
|
1493
1486
|
: null,
|
|
1494
1487
|
volumeSma5: volumeMetrics.volumeSma5,
|
|
1495
1488
|
volumeRatio: volumeMetrics.volumeRatio,
|
|
1496
|
-
|
|
1489
|
+
volumeTrendRatio: volumeMetrics.volumeTrendRatio,
|
|
1497
1490
|
currentPrice: currentPrice != null && !isUnsafe$3(currentPrice) ? currentPrice : null,
|
|
1498
1491
|
priceChange1m: priceChanges.priceChange1m,
|
|
1499
1492
|
priceChange3m: priceChanges.priceChange3m,
|
|
@@ -1550,7 +1543,7 @@ async function generateHistoryTable$2(indicators, symbol) {
|
|
|
1550
1543
|
let markdown = "";
|
|
1551
1544
|
const currentData = await backtestKit.getDate();
|
|
1552
1545
|
markdown += `# 1-Minute Candles Trading Analysis for ${symbol} (Historical Data)\n`;
|
|
1553
|
-
markdown += `> Current
|
|
1546
|
+
markdown += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
1554
1547
|
const header = `| ${columns$2.map((col) => col.label).join(" | ")} |\n`;
|
|
1555
1548
|
const separator = `| ${columns$2.map(() => "---").join(" | ")} |\n`;
|
|
1556
1549
|
const tableRows = await Promise.all(indicators.map(async (ind) => {
|
|
@@ -2056,38 +2049,33 @@ function calculateFibonacciLevels$1(candles, endIndex) {
|
|
|
2056
2049
|
return nearestLevel;
|
|
2057
2050
|
}
|
|
2058
2051
|
/**
|
|
2059
|
-
*
|
|
2052
|
+
* Calculates volume trend ratio by comparing recent vs older volume averages.
|
|
2060
2053
|
*
|
|
2061
2054
|
* Compares average volume of last 8 candles against previous 8 candles.
|
|
2062
|
-
* Returns
|
|
2063
|
-
* "decreasing" if recent < 80% of older volume, otherwise "stable".
|
|
2055
|
+
* Returns ratio of recent volume to older volume (e.g., 1.2 means 20% increase).
|
|
2064
2056
|
*
|
|
2065
2057
|
* @param candles - Array of candle data
|
|
2066
2058
|
* @param endIndex - Index of current candle in array
|
|
2067
|
-
* @returns Volume trend
|
|
2059
|
+
* @returns Volume trend ratio (recent/older), or null if insufficient data
|
|
2068
2060
|
*
|
|
2069
2061
|
* @example
|
|
2070
2062
|
* ```typescript
|
|
2071
2063
|
* const candles = await getCandles('ETHUSDT', '15m', 100);
|
|
2072
|
-
* const
|
|
2073
|
-
* console.log(
|
|
2064
|
+
* const ratio = calculateVolumeTrendRatio(candles, 99);
|
|
2065
|
+
* console.log(ratio); // 1.25 (25% increase)
|
|
2074
2066
|
* ```
|
|
2075
2067
|
*/
|
|
2076
|
-
function
|
|
2068
|
+
function calculateVolumeTrendRatio(candles, endIndex) {
|
|
2077
2069
|
const volumes = candles.slice(0, endIndex + 1).map((c) => Number(c.volume));
|
|
2078
2070
|
if (volumes.length < 16)
|
|
2079
|
-
return
|
|
2071
|
+
return null;
|
|
2080
2072
|
const recentVolumes = volumes.slice(-8);
|
|
2081
2073
|
const olderVolumes = volumes.slice(-16, -8);
|
|
2082
2074
|
if (recentVolumes.length < 4 || olderVolumes.length < 4)
|
|
2083
|
-
return
|
|
2075
|
+
return null;
|
|
2084
2076
|
const recentAvg = recentVolumes.reduce((sum, vol) => sum + vol, 0) / recentVolumes.length;
|
|
2085
2077
|
const olderAvg = olderVolumes.reduce((sum, vol) => sum + vol, 0) / olderVolumes.length;
|
|
2086
|
-
|
|
2087
|
-
return "increasing";
|
|
2088
|
-
if (recentAvg < olderAvg * 0.8)
|
|
2089
|
-
return "decreasing";
|
|
2090
|
-
return "stable";
|
|
2078
|
+
return olderAvg > 0 ? recentAvg / olderAvg : null;
|
|
2091
2079
|
}
|
|
2092
2080
|
/**
|
|
2093
2081
|
* Generates comprehensive technical analysis for 15-minute candles.
|
|
@@ -2168,7 +2156,7 @@ function generateAnalysis$1(symbol, candles) {
|
|
|
2168
2156
|
if (i < WARMUP_PERIOD$1) {
|
|
2169
2157
|
return;
|
|
2170
2158
|
}
|
|
2171
|
-
const
|
|
2159
|
+
const volumeTrendRatio = calculateVolumeTrendRatio(candles, i);
|
|
2172
2160
|
const pivotPeriod = Math.min(48, i + 1);
|
|
2173
2161
|
const startIdx = i + 1 - pivotPeriod;
|
|
2174
2162
|
const recentHighs = highs
|
|
@@ -2267,7 +2255,7 @@ function generateAnalysis$1(symbol, candles) {
|
|
|
2267
2255
|
roc10: roc10.getResult() != null && !isUnsafe$2(roc10.getResult())
|
|
2268
2256
|
? roc10.getResult()
|
|
2269
2257
|
: null,
|
|
2270
|
-
|
|
2258
|
+
volumeTrendRatio,
|
|
2271
2259
|
support: support != null && !isUnsafe$2(support)
|
|
2272
2260
|
? support
|
|
2273
2261
|
: !isUnsafe$2(currentPrice)
|
|
@@ -2321,7 +2309,7 @@ async function generateHistoryTable$1(indicators, symbol) {
|
|
|
2321
2309
|
let markdown = "";
|
|
2322
2310
|
const currentData = await backtestKit.getDate();
|
|
2323
2311
|
markdown += `# 15-Minute Candles Trading Analysis for ${symbol} (Historical Data)\n`;
|
|
2324
|
-
markdown += `> Current
|
|
2312
|
+
markdown += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
2325
2313
|
const header = `| ${columns$1.map((col) => col.label).join(" | ")} |\n`;
|
|
2326
2314
|
const separator = `| ${columns$1.map(() => "---").join(" | ")} |\n`;
|
|
2327
2315
|
const tableRows = await Promise.all(indicators.map(async (ind) => {
|
|
@@ -2702,9 +2690,9 @@ const columns = [
|
|
|
2702
2690
|
: "N/A",
|
|
2703
2691
|
},
|
|
2704
2692
|
{
|
|
2705
|
-
key: "
|
|
2706
|
-
label: "Fibonacci
|
|
2707
|
-
format: (v) =>
|
|
2693
|
+
key: "fibonacciPositionPercent",
|
|
2694
|
+
label: "Fibonacci Position %",
|
|
2695
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(2)}%` : "N/A"),
|
|
2708
2696
|
},
|
|
2709
2697
|
{
|
|
2710
2698
|
key: "bodySize",
|
|
@@ -2781,7 +2769,7 @@ function calculateFibonacciLevels(candles, endIndex, period = 48) {
|
|
|
2781
2769
|
return {
|
|
2782
2770
|
nearestSupport: null,
|
|
2783
2771
|
nearestResistance: null,
|
|
2784
|
-
|
|
2772
|
+
fibonacciPositionPercent: null,
|
|
2785
2773
|
};
|
|
2786
2774
|
}
|
|
2787
2775
|
const startIdx = endIndex + 1 - period;
|
|
@@ -2796,13 +2784,15 @@ function calculateFibonacciLevels(candles, endIndex, period = 48) {
|
|
|
2796
2784
|
return {
|
|
2797
2785
|
nearestSupport: null,
|
|
2798
2786
|
nearestResistance: null,
|
|
2799
|
-
|
|
2787
|
+
fibonacciPositionPercent: null,
|
|
2800
2788
|
};
|
|
2801
2789
|
}
|
|
2802
2790
|
const high = Math.max(...highs);
|
|
2803
2791
|
const low = Math.min(...lows);
|
|
2804
2792
|
const range = high - low;
|
|
2805
2793
|
const currentPrice = Number(candles[endIndex].close);
|
|
2794
|
+
// Calculate position as percentage from low to high (0% = low, 100% = high)
|
|
2795
|
+
const fibonacciPositionPercent = range > 0 ? ((currentPrice - low) / range) * 100 : null;
|
|
2806
2796
|
const retracement = {
|
|
2807
2797
|
level0: high,
|
|
2808
2798
|
level236: high - range * 0.236,
|
|
@@ -2817,35 +2807,6 @@ function calculateFibonacciLevels(candles, endIndex, period = 48) {
|
|
|
2817
2807
|
level1618: high + range * 0.618,
|
|
2818
2808
|
level2618: high + range * 1.618,
|
|
2819
2809
|
};
|
|
2820
|
-
const tolerance = range * 0.015;
|
|
2821
|
-
let currentLevel = "between levels";
|
|
2822
|
-
if (Math.abs(currentPrice - retracement.level0) < tolerance) {
|
|
2823
|
-
currentLevel = "0.0% (High)";
|
|
2824
|
-
}
|
|
2825
|
-
else if (Math.abs(currentPrice - retracement.level236) < tolerance) {
|
|
2826
|
-
currentLevel = "23.6% Retracement";
|
|
2827
|
-
}
|
|
2828
|
-
else if (Math.abs(currentPrice - retracement.level382) < tolerance) {
|
|
2829
|
-
currentLevel = "38.2% Retracement";
|
|
2830
|
-
}
|
|
2831
|
-
else if (Math.abs(currentPrice - retracement.level500) < tolerance) {
|
|
2832
|
-
currentLevel = "50.0% Retracement";
|
|
2833
|
-
}
|
|
2834
|
-
else if (Math.abs(currentPrice - retracement.level618) < tolerance) {
|
|
2835
|
-
currentLevel = "61.8% Retracement";
|
|
2836
|
-
}
|
|
2837
|
-
else if (Math.abs(currentPrice - retracement.level786) < tolerance) {
|
|
2838
|
-
currentLevel = "78.6% Retracement";
|
|
2839
|
-
}
|
|
2840
|
-
else if (Math.abs(currentPrice - retracement.level1000) < tolerance) {
|
|
2841
|
-
currentLevel = "100% Retracement (Low)";
|
|
2842
|
-
}
|
|
2843
|
-
else if (currentPrice > retracement.level0) {
|
|
2844
|
-
currentLevel = "Above high";
|
|
2845
|
-
}
|
|
2846
|
-
else if (currentPrice < retracement.level1000) {
|
|
2847
|
-
currentLevel = "Below low";
|
|
2848
|
-
}
|
|
2849
2810
|
const allRetracementLevels = Object.values(retracement).filter((level) => level !== null);
|
|
2850
2811
|
const allExtensionLevels = Object.values(extension).filter((level) => level !== null && level > 0);
|
|
2851
2812
|
const resistanceLevels = [...allRetracementLevels, ...allExtensionLevels]
|
|
@@ -2859,7 +2820,7 @@ function calculateFibonacciLevels(candles, endIndex, period = 48) {
|
|
|
2859
2820
|
return {
|
|
2860
2821
|
nearestSupport,
|
|
2861
2822
|
nearestResistance,
|
|
2862
|
-
|
|
2823
|
+
fibonacciPositionPercent,
|
|
2863
2824
|
};
|
|
2864
2825
|
}
|
|
2865
2826
|
/**
|
|
@@ -2918,7 +2879,7 @@ function calculateSupportResistance(candles, endIndex, window = 20) {
|
|
|
2918
2879
|
* const candles = await getCandles('BTCUSDT', '30m', 96);
|
|
2919
2880
|
* const analysis = generateAnalysis('BTCUSDT', candles);
|
|
2920
2881
|
* console.log(analysis[0].rsi14); // 52.45
|
|
2921
|
-
* console.log(analysis[0].
|
|
2882
|
+
* console.log(analysis[0].fibonacciPositionPercent); // 50.25
|
|
2922
2883
|
* ```
|
|
2923
2884
|
*/
|
|
2924
2885
|
function generateAnalysis(symbol, candles) {
|
|
@@ -3087,7 +3048,7 @@ function generateAnalysis(symbol, candles) {
|
|
|
3087
3048
|
: null,
|
|
3088
3049
|
fibonacciNearestSupport: fibonacci.nearestSupport,
|
|
3089
3050
|
fibonacciNearestResistance: fibonacci.nearestResistance,
|
|
3090
|
-
|
|
3051
|
+
fibonacciPositionPercent: fibonacci.fibonacciPositionPercent,
|
|
3091
3052
|
bodySize,
|
|
3092
3053
|
closePrice: close,
|
|
3093
3054
|
date: new Date(),
|
|
@@ -3127,7 +3088,7 @@ async function generateHistoryTable(indicators, symbol) {
|
|
|
3127
3088
|
let markdown = "";
|
|
3128
3089
|
const currentData = await backtestKit.getDate();
|
|
3129
3090
|
markdown += `# 30-Min Candles Analysis for ${symbol} (Historical Data)\n`;
|
|
3130
|
-
markdown += `> Current
|
|
3091
|
+
markdown += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
3131
3092
|
const header = `| ${columns.map((col) => col.label).join(" | ")} |\n`;
|
|
3132
3093
|
const separator = `| ${columns.map(() => "---").join(" | ")} |\n`;
|
|
3133
3094
|
const tableRows = await Promise.all(indicators.map(async (ind) => {
|
|
@@ -3194,7 +3155,7 @@ async function generateHistoryTable(indicators, symbol) {
|
|
|
3194
3155
|
markdown +=
|
|
3195
3156
|
"- **Fibonacci Nearest Resistance**: nearest resistance level over 48 candles (24h on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
3196
3157
|
markdown +=
|
|
3197
|
-
"- **Fibonacci
|
|
3158
|
+
"- **Fibonacci Position %**: price position in high-low range before row timestamp (Min: 0%, Max: 100%+)\n";
|
|
3198
3159
|
markdown +=
|
|
3199
3160
|
"- **Current Price**: close price at row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
3200
3161
|
markdown +=
|
|
@@ -3239,7 +3200,7 @@ async function generateHistoryTable(indicators, symbol) {
|
|
|
3239
3200
|
* const candles = await getCandles('ETHUSDT', '30m', 96);
|
|
3240
3201
|
* const rows = await service.getData('ETHUSDT', candles);
|
|
3241
3202
|
* console.log(rows[0].rsi14); // 52.45
|
|
3242
|
-
* console.log(rows[0].
|
|
3203
|
+
* console.log(rows[0].fibonacciPositionPercent); // 50.25
|
|
3243
3204
|
* ```
|
|
3244
3205
|
*/
|
|
3245
3206
|
class SwingTermHistoryService {
|
|
@@ -3423,33 +3384,28 @@ class FifteenMinuteCandleHistoryService {
|
|
|
3423
3384
|
*/
|
|
3424
3385
|
this.generateReport = async (symbol, candles) => {
|
|
3425
3386
|
this.loggerService.log("fifteenMinuteCandleHistoryService generateReport", { symbol });
|
|
3426
|
-
const averageVolatility = candles.reduce((sum, candle) => sum + ((candle.high - candle.low) / candle.close) * 100, 0) / candles.length;
|
|
3427
3387
|
let report = "";
|
|
3428
3388
|
const currentData = await backtestKit.getDate();
|
|
3429
3389
|
report += `## 15-Minute Candles History (Last ${RECENT_CANDLES$3})\n`;
|
|
3430
|
-
report += `> Current
|
|
3390
|
+
report += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
3431
3391
|
for (let index = 0; index < candles.length; index++) {
|
|
3432
3392
|
const candle = candles[index];
|
|
3433
3393
|
const volatilityPercent = ((candle.high - candle.low) / candle.close) * 100;
|
|
3434
|
-
const isHighVolatility = volatilityPercent > averageVolatility * 1.5;
|
|
3435
3394
|
const bodySize = Math.abs(candle.close - candle.open);
|
|
3436
3395
|
const candleRange = candle.high - candle.low;
|
|
3437
3396
|
const bodyPercent = candleRange > 0 ? (bodySize / candleRange) * 100 : 0;
|
|
3438
|
-
const
|
|
3439
|
-
? "Green"
|
|
3440
|
-
: candle.close < candle.open
|
|
3441
|
-
? "Red"
|
|
3442
|
-
: "Doji";
|
|
3397
|
+
const priceChangePercent = candle.open > 0 ? ((candle.close - candle.open) / candle.open) * 100 : 0;
|
|
3443
3398
|
const formattedTime = new Date(candle.timestamp).toISOString();
|
|
3444
|
-
report += `### 15m Candle ${index + 1}
|
|
3399
|
+
report += `### 15m Candle ${index + 1}\n`;
|
|
3400
|
+
report += `- **Price Change**: ${priceChangePercent.toFixed(3)}%\n`;
|
|
3445
3401
|
report += `- **Time**: ${formattedTime}\n`;
|
|
3446
3402
|
report += `- **Open**: ${await backtestKit.formatPrice(symbol, candle.open)} USD\n`;
|
|
3447
3403
|
report += `- **High**: ${await backtestKit.formatPrice(symbol, candle.high)} USD\n`;
|
|
3448
3404
|
report += `- **Low**: ${await backtestKit.formatPrice(symbol, candle.low)} USD\n`;
|
|
3449
3405
|
report += `- **Close**: ${await backtestKit.formatPrice(symbol, candle.close)} USD\n`;
|
|
3450
3406
|
report += `- **Volume**: ${await backtestKit.formatQuantity(symbol, candle.volume)}\n`;
|
|
3451
|
-
report += `- **15m Volatility**: ${volatilityPercent.toFixed(2)}
|
|
3452
|
-
report += `- **Body Size**: ${bodyPercent.toFixed(1)}
|
|
3407
|
+
report += `- **15m Volatility**: ${volatilityPercent.toFixed(2)}%\n`;
|
|
3408
|
+
report += `- **Body Size**: ${bodyPercent.toFixed(1)}%\n\n`;
|
|
3453
3409
|
}
|
|
3454
3410
|
return report;
|
|
3455
3411
|
};
|
|
@@ -3581,20 +3537,17 @@ class HourCandleHistoryService {
|
|
|
3581
3537
|
let markdown = "";
|
|
3582
3538
|
const currentData = await backtestKit.getDate();
|
|
3583
3539
|
markdown += `## Hourly Candles History (Last ${RECENT_CANDLES$2})\n`;
|
|
3584
|
-
markdown += `> Current
|
|
3540
|
+
markdown += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
3585
3541
|
for (let index = 0; index < candles.length; index++) {
|
|
3586
3542
|
const candle = candles[index];
|
|
3587
3543
|
const volatilityPercent = ((candle.high - candle.low) / candle.close) * 100;
|
|
3588
3544
|
const bodySize = Math.abs(candle.close - candle.open);
|
|
3589
3545
|
const candleRange = candle.high - candle.low;
|
|
3590
3546
|
const bodyPercent = candleRange > 0 ? (bodySize / candleRange) * 100 : 0;
|
|
3591
|
-
const
|
|
3592
|
-
? "Green"
|
|
3593
|
-
: candle.close < candle.open
|
|
3594
|
-
? "Red"
|
|
3595
|
-
: "Doji";
|
|
3547
|
+
const priceChangePercent = candle.open > 0 ? ((candle.close - candle.open) / candle.open) * 100 : 0;
|
|
3596
3548
|
const formattedTime = new Date(candle.timestamp).toISOString();
|
|
3597
|
-
markdown += `### 1h Candle ${index + 1}
|
|
3549
|
+
markdown += `### 1h Candle ${index + 1}\n`;
|
|
3550
|
+
markdown += `- **Price Change**: ${priceChangePercent.toFixed(3)}%\n`;
|
|
3598
3551
|
markdown += `- **Time**: ${formattedTime}\n`;
|
|
3599
3552
|
markdown += `- **Open**: ${backtestKit.formatPrice(symbol, candle.open)} USD\n`;
|
|
3600
3553
|
markdown += `- **High**: ${backtestKit.formatPrice(symbol, candle.high)} USD\n`;
|
|
@@ -3734,16 +3687,17 @@ class OneMinuteCandleHistoryService {
|
|
|
3734
3687
|
let markdown = "";
|
|
3735
3688
|
const currentData = await backtestKit.getDate();
|
|
3736
3689
|
markdown += `## One-Minute Candles History (Last ${RECENT_CANDLES$1})\n`;
|
|
3737
|
-
markdown += `> Current
|
|
3690
|
+
markdown += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
3738
3691
|
for (let index = 0; index < candles.length; index++) {
|
|
3739
3692
|
const candle = candles[index];
|
|
3740
3693
|
const volatilityPercent = ((candle.high - candle.low) / candle.close) * 100;
|
|
3741
3694
|
const bodySize = Math.abs(candle.close - candle.open);
|
|
3742
3695
|
const candleRange = candle.high - candle.low;
|
|
3743
3696
|
const bodyPercent = candleRange > 0 ? (bodySize / candleRange) * 100 : 0;
|
|
3744
|
-
const
|
|
3697
|
+
const priceChangePercent = candle.open > 0 ? ((candle.close - candle.open) / candle.open) * 100 : 0;
|
|
3745
3698
|
const formattedTime = new Date(candle.timestamp).toISOString();
|
|
3746
|
-
markdown += `### 1m Candle ${index + 1}
|
|
3699
|
+
markdown += `### 1m Candle ${index + 1}\n`;
|
|
3700
|
+
markdown += `- **Price Change**: ${priceChangePercent.toFixed(3)}%\n`;
|
|
3747
3701
|
markdown += `- **Time**: ${formattedTime}\n`;
|
|
3748
3702
|
markdown += `- **Open**: ${backtestKit.formatPrice(symbol, candle.open)} USD\n`;
|
|
3749
3703
|
markdown += `- **High**: ${backtestKit.formatPrice(symbol, candle.high)} USD\n`;
|
|
@@ -3883,20 +3837,17 @@ class ThirtyMinuteCandleHistoryService {
|
|
|
3883
3837
|
let report = "";
|
|
3884
3838
|
const currentData = await backtestKit.getDate();
|
|
3885
3839
|
report += `## 30-Min Candles History (Last ${RECENT_CANDLES})\n`;
|
|
3886
|
-
report += `> Current
|
|
3840
|
+
report += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
3887
3841
|
for (let index = 0; index < candles.length; index++) {
|
|
3888
3842
|
const candle = candles[index];
|
|
3889
3843
|
const volatilityPercent = ((candle.high - candle.low) / candle.close) * 100;
|
|
3890
3844
|
const bodySize = Math.abs(candle.close - candle.open);
|
|
3891
3845
|
const candleRange = candle.high - candle.low;
|
|
3892
3846
|
const bodyPercent = candleRange > 0 ? (bodySize / candleRange) * 100 : 0;
|
|
3893
|
-
const
|
|
3894
|
-
? "Green"
|
|
3895
|
-
: candle.close < candle.open
|
|
3896
|
-
? "Red"
|
|
3897
|
-
: "Doji";
|
|
3847
|
+
const priceChangePercent = candle.open > 0 ? ((candle.close - candle.open) / candle.open) * 100 : 0;
|
|
3898
3848
|
const formattedTime = new Date(candle.timestamp).toISOString();
|
|
3899
|
-
report += `### 30m Candle ${index + 1}
|
|
3849
|
+
report += `### 30m Candle ${index + 1}\n`;
|
|
3850
|
+
report += `- **Price Change**: ${priceChangePercent.toFixed(3)}%\n`;
|
|
3900
3851
|
report += `- **Time**: ${formattedTime}\n`;
|
|
3901
3852
|
report += `- **Open**: ${backtestKit.formatPrice(symbol, candle.open)} USD\n`;
|
|
3902
3853
|
report += `- **High**: ${backtestKit.formatPrice(symbol, candle.high)} USD\n`;
|
|
@@ -4057,7 +4008,7 @@ function processOrderBookSide(orders) {
|
|
|
4057
4008
|
const generateBookDataReport = async (self, result) => {
|
|
4058
4009
|
const currentData = await backtestKit.getDate();
|
|
4059
4010
|
let markdown = `# Order Book Analysis for ${result.symbol}\n`;
|
|
4060
|
-
markdown += `> Current
|
|
4011
|
+
markdown += `> Current trading pair: ${String(result.symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
4061
4012
|
// Basic order book info
|
|
4062
4013
|
markdown += `## Order Book Summary\n`;
|
|
4063
4014
|
markdown += `- **Best Bid**: ${!isUnsafe(result.bestBid)
|
package/build/index.mjs
CHANGED
|
@@ -456,13 +456,9 @@ function generateAnalysis$3(symbol, candles) {
|
|
|
456
456
|
const prevVolume = !isUnsafe$4(prevVolumeRaw)
|
|
457
457
|
? prevVolumeRaw
|
|
458
458
|
: volumes[Math.max(0, i - 6)];
|
|
459
|
-
const
|
|
460
|
-
? recentVolume
|
|
461
|
-
|
|
462
|
-
: recentVolume < prevVolume * 0.9
|
|
463
|
-
? "decreasing"
|
|
464
|
-
: "stable"
|
|
465
|
-
: "stable";
|
|
459
|
+
const volumeTrendRatio = !isUnsafe$4(recentVolume) && !isUnsafe$4(prevVolume) && prevVolume > 0
|
|
460
|
+
? recentVolume / prevVolume
|
|
461
|
+
: null;
|
|
466
462
|
// Support/Resistance calculation
|
|
467
463
|
const pivotPeriod = Math.min(4, i + 1);
|
|
468
464
|
const startIdx = i + 1 - pivotPeriod;
|
|
@@ -541,7 +537,7 @@ function generateAnalysis$3(symbol, candles) {
|
|
|
541
537
|
cci20: cci.getResult() != null && !isUnsafe$4(cci.getResult())
|
|
542
538
|
? cci.getResult()
|
|
543
539
|
: null,
|
|
544
|
-
|
|
540
|
+
volumeTrendRatio,
|
|
545
541
|
support: support != null && !isUnsafe$4(support)
|
|
546
542
|
? support
|
|
547
543
|
: !isUnsafe$4(currentPrice)
|
|
@@ -594,7 +590,7 @@ async function generateHistoryTable$3(indicators, symbol) {
|
|
|
594
590
|
let markdown = "";
|
|
595
591
|
const currentData = await getDate();
|
|
596
592
|
markdown += `# 1-Hour Candles Trading Analysis for ${symbol} (Historical Data)\n`;
|
|
597
|
-
markdown += `> Current
|
|
593
|
+
markdown += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
598
594
|
const header = `| ${columns$3.map((col) => col.label).join(" | ")} |\n`;
|
|
599
595
|
const separator = `| ${columns$3.map(() => "---").join(" | ")} |\n`;
|
|
600
596
|
const tableRows = await Promise.all(indicators.map(async (ind) => {
|
|
@@ -1118,26 +1114,26 @@ function isUnsafe$3(value) {
|
|
|
1118
1114
|
/**
|
|
1119
1115
|
* Calculates volume metrics including SMA, ratio, and trend.
|
|
1120
1116
|
*
|
|
1121
|
-
* Computes volume SMA(5), current volume to average ratio, and volume trend
|
|
1122
|
-
* by comparing recent 3 candles to previous 3 candles. Returns
|
|
1123
|
-
*
|
|
1117
|
+
* Computes volume SMA(5), current volume to average ratio, and volume trend ratio
|
|
1118
|
+
* by comparing recent 3 candles to previous 3 candles. Returns numeric ratio
|
|
1119
|
+
* of recent volume average to previous volume average (e.g., 1.2 = 20% increase).
|
|
1124
1120
|
*
|
|
1125
1121
|
* @param candles - Array of candle data
|
|
1126
1122
|
* @param endIndex - Index of current candle in array
|
|
1127
|
-
* @returns Object with volumeSma5, volumeRatio, and
|
|
1123
|
+
* @returns Object with volumeSma5, volumeRatio, and volumeTrendRatio
|
|
1128
1124
|
*
|
|
1129
1125
|
* @example
|
|
1130
1126
|
* ```typescript
|
|
1131
1127
|
* const candles = await getCandles('BTCUSDT', '1m', 60);
|
|
1132
1128
|
* const metrics = calculateVolumeMetrics(candles, 59);
|
|
1133
1129
|
* console.log(metrics);
|
|
1134
|
-
* // { volumeSma5: 1500000, volumeRatio: 1.25,
|
|
1130
|
+
* // { volumeSma5: 1500000, volumeRatio: 1.25, volumeTrendRatio: 1.15 }
|
|
1135
1131
|
* ```
|
|
1136
1132
|
*/
|
|
1137
1133
|
function calculateVolumeMetrics(candles, endIndex) {
|
|
1138
1134
|
const volumes = candles.slice(0, endIndex + 1).map((c) => Number(c.volume));
|
|
1139
1135
|
if (volumes.length < 5) {
|
|
1140
|
-
return { volumeSma5: null, volumeRatio: null,
|
|
1136
|
+
return { volumeSma5: null, volumeRatio: null, volumeTrendRatio: null };
|
|
1141
1137
|
}
|
|
1142
1138
|
const volumeSma5 = new FasterSMA(5);
|
|
1143
1139
|
volumes.forEach((vol) => volumeSma5.update(vol, false));
|
|
@@ -1145,20 +1141,17 @@ function calculateVolumeMetrics(candles, endIndex) {
|
|
|
1145
1141
|
const avgVolume = !isUnsafe$3(avgVolumeRaw) ? avgVolumeRaw : 0;
|
|
1146
1142
|
const currentVolume = volumes[volumes.length - 1];
|
|
1147
1143
|
const volumeRatio = avgVolume > 0 && !isUnsafe$3(currentVolume) ? currentVolume / avgVolume : 1;
|
|
1148
|
-
let
|
|
1144
|
+
let volumeTrendRatio = null;
|
|
1149
1145
|
if (volumes.length >= 6) {
|
|
1150
1146
|
const recent3 = volumes.slice(-3);
|
|
1151
1147
|
const prev3 = volumes.slice(-6, -3);
|
|
1152
1148
|
if (prev3.length >= 3) {
|
|
1153
1149
|
const recentAvg = recent3.reduce((a, b) => a + b, 0) / 3;
|
|
1154
1150
|
const prevAvg = prev3.reduce((a, b) => a + b, 0) / 3;
|
|
1155
|
-
|
|
1156
|
-
volumeTrend = "increasing";
|
|
1157
|
-
else if (recentAvg < prevAvg * 0.8)
|
|
1158
|
-
volumeTrend = "decreasing";
|
|
1151
|
+
volumeTrendRatio = prevAvg > 0 ? recentAvg / prevAvg : null;
|
|
1159
1152
|
}
|
|
1160
1153
|
}
|
|
1161
|
-
return { volumeSma5: avgVolume, volumeRatio,
|
|
1154
|
+
return { volumeSma5: avgVolume, volumeRatio, volumeTrendRatio };
|
|
1162
1155
|
}
|
|
1163
1156
|
/**
|
|
1164
1157
|
* Calculates price change percentages over 1, 3, and 5 minute periods.
|
|
@@ -1491,7 +1484,7 @@ function generateAnalysis$2(symbol, candles) {
|
|
|
1491
1484
|
: null,
|
|
1492
1485
|
volumeSma5: volumeMetrics.volumeSma5,
|
|
1493
1486
|
volumeRatio: volumeMetrics.volumeRatio,
|
|
1494
|
-
|
|
1487
|
+
volumeTrendRatio: volumeMetrics.volumeTrendRatio,
|
|
1495
1488
|
currentPrice: currentPrice != null && !isUnsafe$3(currentPrice) ? currentPrice : null,
|
|
1496
1489
|
priceChange1m: priceChanges.priceChange1m,
|
|
1497
1490
|
priceChange3m: priceChanges.priceChange3m,
|
|
@@ -1548,7 +1541,7 @@ async function generateHistoryTable$2(indicators, symbol) {
|
|
|
1548
1541
|
let markdown = "";
|
|
1549
1542
|
const currentData = await getDate();
|
|
1550
1543
|
markdown += `# 1-Minute Candles Trading Analysis for ${symbol} (Historical Data)\n`;
|
|
1551
|
-
markdown += `> Current
|
|
1544
|
+
markdown += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
1552
1545
|
const header = `| ${columns$2.map((col) => col.label).join(" | ")} |\n`;
|
|
1553
1546
|
const separator = `| ${columns$2.map(() => "---").join(" | ")} |\n`;
|
|
1554
1547
|
const tableRows = await Promise.all(indicators.map(async (ind) => {
|
|
@@ -2054,38 +2047,33 @@ function calculateFibonacciLevels$1(candles, endIndex) {
|
|
|
2054
2047
|
return nearestLevel;
|
|
2055
2048
|
}
|
|
2056
2049
|
/**
|
|
2057
|
-
*
|
|
2050
|
+
* Calculates volume trend ratio by comparing recent vs older volume averages.
|
|
2058
2051
|
*
|
|
2059
2052
|
* Compares average volume of last 8 candles against previous 8 candles.
|
|
2060
|
-
* Returns
|
|
2061
|
-
* "decreasing" if recent < 80% of older volume, otherwise "stable".
|
|
2053
|
+
* Returns ratio of recent volume to older volume (e.g., 1.2 means 20% increase).
|
|
2062
2054
|
*
|
|
2063
2055
|
* @param candles - Array of candle data
|
|
2064
2056
|
* @param endIndex - Index of current candle in array
|
|
2065
|
-
* @returns Volume trend
|
|
2057
|
+
* @returns Volume trend ratio (recent/older), or null if insufficient data
|
|
2066
2058
|
*
|
|
2067
2059
|
* @example
|
|
2068
2060
|
* ```typescript
|
|
2069
2061
|
* const candles = await getCandles('ETHUSDT', '15m', 100);
|
|
2070
|
-
* const
|
|
2071
|
-
* console.log(
|
|
2062
|
+
* const ratio = calculateVolumeTrendRatio(candles, 99);
|
|
2063
|
+
* console.log(ratio); // 1.25 (25% increase)
|
|
2072
2064
|
* ```
|
|
2073
2065
|
*/
|
|
2074
|
-
function
|
|
2066
|
+
function calculateVolumeTrendRatio(candles, endIndex) {
|
|
2075
2067
|
const volumes = candles.slice(0, endIndex + 1).map((c) => Number(c.volume));
|
|
2076
2068
|
if (volumes.length < 16)
|
|
2077
|
-
return
|
|
2069
|
+
return null;
|
|
2078
2070
|
const recentVolumes = volumes.slice(-8);
|
|
2079
2071
|
const olderVolumes = volumes.slice(-16, -8);
|
|
2080
2072
|
if (recentVolumes.length < 4 || olderVolumes.length < 4)
|
|
2081
|
-
return
|
|
2073
|
+
return null;
|
|
2082
2074
|
const recentAvg = recentVolumes.reduce((sum, vol) => sum + vol, 0) / recentVolumes.length;
|
|
2083
2075
|
const olderAvg = olderVolumes.reduce((sum, vol) => sum + vol, 0) / olderVolumes.length;
|
|
2084
|
-
|
|
2085
|
-
return "increasing";
|
|
2086
|
-
if (recentAvg < olderAvg * 0.8)
|
|
2087
|
-
return "decreasing";
|
|
2088
|
-
return "stable";
|
|
2076
|
+
return olderAvg > 0 ? recentAvg / olderAvg : null;
|
|
2089
2077
|
}
|
|
2090
2078
|
/**
|
|
2091
2079
|
* Generates comprehensive technical analysis for 15-minute candles.
|
|
@@ -2166,7 +2154,7 @@ function generateAnalysis$1(symbol, candles) {
|
|
|
2166
2154
|
if (i < WARMUP_PERIOD$1) {
|
|
2167
2155
|
return;
|
|
2168
2156
|
}
|
|
2169
|
-
const
|
|
2157
|
+
const volumeTrendRatio = calculateVolumeTrendRatio(candles, i);
|
|
2170
2158
|
const pivotPeriod = Math.min(48, i + 1);
|
|
2171
2159
|
const startIdx = i + 1 - pivotPeriod;
|
|
2172
2160
|
const recentHighs = highs
|
|
@@ -2265,7 +2253,7 @@ function generateAnalysis$1(symbol, candles) {
|
|
|
2265
2253
|
roc10: roc10.getResult() != null && !isUnsafe$2(roc10.getResult())
|
|
2266
2254
|
? roc10.getResult()
|
|
2267
2255
|
: null,
|
|
2268
|
-
|
|
2256
|
+
volumeTrendRatio,
|
|
2269
2257
|
support: support != null && !isUnsafe$2(support)
|
|
2270
2258
|
? support
|
|
2271
2259
|
: !isUnsafe$2(currentPrice)
|
|
@@ -2319,7 +2307,7 @@ async function generateHistoryTable$1(indicators, symbol) {
|
|
|
2319
2307
|
let markdown = "";
|
|
2320
2308
|
const currentData = await getDate();
|
|
2321
2309
|
markdown += `# 15-Minute Candles Trading Analysis for ${symbol} (Historical Data)\n`;
|
|
2322
|
-
markdown += `> Current
|
|
2310
|
+
markdown += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
2323
2311
|
const header = `| ${columns$1.map((col) => col.label).join(" | ")} |\n`;
|
|
2324
2312
|
const separator = `| ${columns$1.map(() => "---").join(" | ")} |\n`;
|
|
2325
2313
|
const tableRows = await Promise.all(indicators.map(async (ind) => {
|
|
@@ -2700,9 +2688,9 @@ const columns = [
|
|
|
2700
2688
|
: "N/A",
|
|
2701
2689
|
},
|
|
2702
2690
|
{
|
|
2703
|
-
key: "
|
|
2704
|
-
label: "Fibonacci
|
|
2705
|
-
format: (v) =>
|
|
2691
|
+
key: "fibonacciPositionPercent",
|
|
2692
|
+
label: "Fibonacci Position %",
|
|
2693
|
+
format: (v) => (v !== null ? `${Number(v).toFixed(2)}%` : "N/A"),
|
|
2706
2694
|
},
|
|
2707
2695
|
{
|
|
2708
2696
|
key: "bodySize",
|
|
@@ -2779,7 +2767,7 @@ function calculateFibonacciLevels(candles, endIndex, period = 48) {
|
|
|
2779
2767
|
return {
|
|
2780
2768
|
nearestSupport: null,
|
|
2781
2769
|
nearestResistance: null,
|
|
2782
|
-
|
|
2770
|
+
fibonacciPositionPercent: null,
|
|
2783
2771
|
};
|
|
2784
2772
|
}
|
|
2785
2773
|
const startIdx = endIndex + 1 - period;
|
|
@@ -2794,13 +2782,15 @@ function calculateFibonacciLevels(candles, endIndex, period = 48) {
|
|
|
2794
2782
|
return {
|
|
2795
2783
|
nearestSupport: null,
|
|
2796
2784
|
nearestResistance: null,
|
|
2797
|
-
|
|
2785
|
+
fibonacciPositionPercent: null,
|
|
2798
2786
|
};
|
|
2799
2787
|
}
|
|
2800
2788
|
const high = Math.max(...highs);
|
|
2801
2789
|
const low = Math.min(...lows);
|
|
2802
2790
|
const range = high - low;
|
|
2803
2791
|
const currentPrice = Number(candles[endIndex].close);
|
|
2792
|
+
// Calculate position as percentage from low to high (0% = low, 100% = high)
|
|
2793
|
+
const fibonacciPositionPercent = range > 0 ? ((currentPrice - low) / range) * 100 : null;
|
|
2804
2794
|
const retracement = {
|
|
2805
2795
|
level0: high,
|
|
2806
2796
|
level236: high - range * 0.236,
|
|
@@ -2815,35 +2805,6 @@ function calculateFibonacciLevels(candles, endIndex, period = 48) {
|
|
|
2815
2805
|
level1618: high + range * 0.618,
|
|
2816
2806
|
level2618: high + range * 1.618,
|
|
2817
2807
|
};
|
|
2818
|
-
const tolerance = range * 0.015;
|
|
2819
|
-
let currentLevel = "between levels";
|
|
2820
|
-
if (Math.abs(currentPrice - retracement.level0) < tolerance) {
|
|
2821
|
-
currentLevel = "0.0% (High)";
|
|
2822
|
-
}
|
|
2823
|
-
else if (Math.abs(currentPrice - retracement.level236) < tolerance) {
|
|
2824
|
-
currentLevel = "23.6% Retracement";
|
|
2825
|
-
}
|
|
2826
|
-
else if (Math.abs(currentPrice - retracement.level382) < tolerance) {
|
|
2827
|
-
currentLevel = "38.2% Retracement";
|
|
2828
|
-
}
|
|
2829
|
-
else if (Math.abs(currentPrice - retracement.level500) < tolerance) {
|
|
2830
|
-
currentLevel = "50.0% Retracement";
|
|
2831
|
-
}
|
|
2832
|
-
else if (Math.abs(currentPrice - retracement.level618) < tolerance) {
|
|
2833
|
-
currentLevel = "61.8% Retracement";
|
|
2834
|
-
}
|
|
2835
|
-
else if (Math.abs(currentPrice - retracement.level786) < tolerance) {
|
|
2836
|
-
currentLevel = "78.6% Retracement";
|
|
2837
|
-
}
|
|
2838
|
-
else if (Math.abs(currentPrice - retracement.level1000) < tolerance) {
|
|
2839
|
-
currentLevel = "100% Retracement (Low)";
|
|
2840
|
-
}
|
|
2841
|
-
else if (currentPrice > retracement.level0) {
|
|
2842
|
-
currentLevel = "Above high";
|
|
2843
|
-
}
|
|
2844
|
-
else if (currentPrice < retracement.level1000) {
|
|
2845
|
-
currentLevel = "Below low";
|
|
2846
|
-
}
|
|
2847
2808
|
const allRetracementLevels = Object.values(retracement).filter((level) => level !== null);
|
|
2848
2809
|
const allExtensionLevels = Object.values(extension).filter((level) => level !== null && level > 0);
|
|
2849
2810
|
const resistanceLevels = [...allRetracementLevels, ...allExtensionLevels]
|
|
@@ -2857,7 +2818,7 @@ function calculateFibonacciLevels(candles, endIndex, period = 48) {
|
|
|
2857
2818
|
return {
|
|
2858
2819
|
nearestSupport,
|
|
2859
2820
|
nearestResistance,
|
|
2860
|
-
|
|
2821
|
+
fibonacciPositionPercent,
|
|
2861
2822
|
};
|
|
2862
2823
|
}
|
|
2863
2824
|
/**
|
|
@@ -2916,7 +2877,7 @@ function calculateSupportResistance(candles, endIndex, window = 20) {
|
|
|
2916
2877
|
* const candles = await getCandles('BTCUSDT', '30m', 96);
|
|
2917
2878
|
* const analysis = generateAnalysis('BTCUSDT', candles);
|
|
2918
2879
|
* console.log(analysis[0].rsi14); // 52.45
|
|
2919
|
-
* console.log(analysis[0].
|
|
2880
|
+
* console.log(analysis[0].fibonacciPositionPercent); // 50.25
|
|
2920
2881
|
* ```
|
|
2921
2882
|
*/
|
|
2922
2883
|
function generateAnalysis(symbol, candles) {
|
|
@@ -3085,7 +3046,7 @@ function generateAnalysis(symbol, candles) {
|
|
|
3085
3046
|
: null,
|
|
3086
3047
|
fibonacciNearestSupport: fibonacci.nearestSupport,
|
|
3087
3048
|
fibonacciNearestResistance: fibonacci.nearestResistance,
|
|
3088
|
-
|
|
3049
|
+
fibonacciPositionPercent: fibonacci.fibonacciPositionPercent,
|
|
3089
3050
|
bodySize,
|
|
3090
3051
|
closePrice: close,
|
|
3091
3052
|
date: new Date(),
|
|
@@ -3125,7 +3086,7 @@ async function generateHistoryTable(indicators, symbol) {
|
|
|
3125
3086
|
let markdown = "";
|
|
3126
3087
|
const currentData = await getDate();
|
|
3127
3088
|
markdown += `# 30-Min Candles Analysis for ${symbol} (Historical Data)\n`;
|
|
3128
|
-
markdown += `> Current
|
|
3089
|
+
markdown += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
3129
3090
|
const header = `| ${columns.map((col) => col.label).join(" | ")} |\n`;
|
|
3130
3091
|
const separator = `| ${columns.map(() => "---").join(" | ")} |\n`;
|
|
3131
3092
|
const tableRows = await Promise.all(indicators.map(async (ind) => {
|
|
@@ -3192,7 +3153,7 @@ async function generateHistoryTable(indicators, symbol) {
|
|
|
3192
3153
|
markdown +=
|
|
3193
3154
|
"- **Fibonacci Nearest Resistance**: nearest resistance level over 48 candles (24h on 30m timeframe) before row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
3194
3155
|
markdown +=
|
|
3195
|
-
"- **Fibonacci
|
|
3156
|
+
"- **Fibonacci Position %**: price position in high-low range before row timestamp (Min: 0%, Max: 100%+)\n";
|
|
3196
3157
|
markdown +=
|
|
3197
3158
|
"- **Current Price**: close price at row timestamp (Min: 0 USD, Max: +∞ USD)\n";
|
|
3198
3159
|
markdown +=
|
|
@@ -3237,7 +3198,7 @@ async function generateHistoryTable(indicators, symbol) {
|
|
|
3237
3198
|
* const candles = await getCandles('ETHUSDT', '30m', 96);
|
|
3238
3199
|
* const rows = await service.getData('ETHUSDT', candles);
|
|
3239
3200
|
* console.log(rows[0].rsi14); // 52.45
|
|
3240
|
-
* console.log(rows[0].
|
|
3201
|
+
* console.log(rows[0].fibonacciPositionPercent); // 50.25
|
|
3241
3202
|
* ```
|
|
3242
3203
|
*/
|
|
3243
3204
|
class SwingTermHistoryService {
|
|
@@ -3421,33 +3382,28 @@ class FifteenMinuteCandleHistoryService {
|
|
|
3421
3382
|
*/
|
|
3422
3383
|
this.generateReport = async (symbol, candles) => {
|
|
3423
3384
|
this.loggerService.log("fifteenMinuteCandleHistoryService generateReport", { symbol });
|
|
3424
|
-
const averageVolatility = candles.reduce((sum, candle) => sum + ((candle.high - candle.low) / candle.close) * 100, 0) / candles.length;
|
|
3425
3385
|
let report = "";
|
|
3426
3386
|
const currentData = await getDate();
|
|
3427
3387
|
report += `## 15-Minute Candles History (Last ${RECENT_CANDLES$3})\n`;
|
|
3428
|
-
report += `> Current
|
|
3388
|
+
report += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
3429
3389
|
for (let index = 0; index < candles.length; index++) {
|
|
3430
3390
|
const candle = candles[index];
|
|
3431
3391
|
const volatilityPercent = ((candle.high - candle.low) / candle.close) * 100;
|
|
3432
|
-
const isHighVolatility = volatilityPercent > averageVolatility * 1.5;
|
|
3433
3392
|
const bodySize = Math.abs(candle.close - candle.open);
|
|
3434
3393
|
const candleRange = candle.high - candle.low;
|
|
3435
3394
|
const bodyPercent = candleRange > 0 ? (bodySize / candleRange) * 100 : 0;
|
|
3436
|
-
const
|
|
3437
|
-
? "Green"
|
|
3438
|
-
: candle.close < candle.open
|
|
3439
|
-
? "Red"
|
|
3440
|
-
: "Doji";
|
|
3395
|
+
const priceChangePercent = candle.open > 0 ? ((candle.close - candle.open) / candle.open) * 100 : 0;
|
|
3441
3396
|
const formattedTime = new Date(candle.timestamp).toISOString();
|
|
3442
|
-
report += `### 15m Candle ${index + 1}
|
|
3397
|
+
report += `### 15m Candle ${index + 1}\n`;
|
|
3398
|
+
report += `- **Price Change**: ${priceChangePercent.toFixed(3)}%\n`;
|
|
3443
3399
|
report += `- **Time**: ${formattedTime}\n`;
|
|
3444
3400
|
report += `- **Open**: ${await formatPrice(symbol, candle.open)} USD\n`;
|
|
3445
3401
|
report += `- **High**: ${await formatPrice(symbol, candle.high)} USD\n`;
|
|
3446
3402
|
report += `- **Low**: ${await formatPrice(symbol, candle.low)} USD\n`;
|
|
3447
3403
|
report += `- **Close**: ${await formatPrice(symbol, candle.close)} USD\n`;
|
|
3448
3404
|
report += `- **Volume**: ${await formatQuantity(symbol, candle.volume)}\n`;
|
|
3449
|
-
report += `- **15m Volatility**: ${volatilityPercent.toFixed(2)}
|
|
3450
|
-
report += `- **Body Size**: ${bodyPercent.toFixed(1)}
|
|
3405
|
+
report += `- **15m Volatility**: ${volatilityPercent.toFixed(2)}%\n`;
|
|
3406
|
+
report += `- **Body Size**: ${bodyPercent.toFixed(1)}%\n\n`;
|
|
3451
3407
|
}
|
|
3452
3408
|
return report;
|
|
3453
3409
|
};
|
|
@@ -3579,20 +3535,17 @@ class HourCandleHistoryService {
|
|
|
3579
3535
|
let markdown = "";
|
|
3580
3536
|
const currentData = await getDate();
|
|
3581
3537
|
markdown += `## Hourly Candles History (Last ${RECENT_CANDLES$2})\n`;
|
|
3582
|
-
markdown += `> Current
|
|
3538
|
+
markdown += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
3583
3539
|
for (let index = 0; index < candles.length; index++) {
|
|
3584
3540
|
const candle = candles[index];
|
|
3585
3541
|
const volatilityPercent = ((candle.high - candle.low) / candle.close) * 100;
|
|
3586
3542
|
const bodySize = Math.abs(candle.close - candle.open);
|
|
3587
3543
|
const candleRange = candle.high - candle.low;
|
|
3588
3544
|
const bodyPercent = candleRange > 0 ? (bodySize / candleRange) * 100 : 0;
|
|
3589
|
-
const
|
|
3590
|
-
? "Green"
|
|
3591
|
-
: candle.close < candle.open
|
|
3592
|
-
? "Red"
|
|
3593
|
-
: "Doji";
|
|
3545
|
+
const priceChangePercent = candle.open > 0 ? ((candle.close - candle.open) / candle.open) * 100 : 0;
|
|
3594
3546
|
const formattedTime = new Date(candle.timestamp).toISOString();
|
|
3595
|
-
markdown += `### 1h Candle ${index + 1}
|
|
3547
|
+
markdown += `### 1h Candle ${index + 1}\n`;
|
|
3548
|
+
markdown += `- **Price Change**: ${priceChangePercent.toFixed(3)}%\n`;
|
|
3596
3549
|
markdown += `- **Time**: ${formattedTime}\n`;
|
|
3597
3550
|
markdown += `- **Open**: ${formatPrice(symbol, candle.open)} USD\n`;
|
|
3598
3551
|
markdown += `- **High**: ${formatPrice(symbol, candle.high)} USD\n`;
|
|
@@ -3732,16 +3685,17 @@ class OneMinuteCandleHistoryService {
|
|
|
3732
3685
|
let markdown = "";
|
|
3733
3686
|
const currentData = await getDate();
|
|
3734
3687
|
markdown += `## One-Minute Candles History (Last ${RECENT_CANDLES$1})\n`;
|
|
3735
|
-
markdown += `> Current
|
|
3688
|
+
markdown += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
3736
3689
|
for (let index = 0; index < candles.length; index++) {
|
|
3737
3690
|
const candle = candles[index];
|
|
3738
3691
|
const volatilityPercent = ((candle.high - candle.low) / candle.close) * 100;
|
|
3739
3692
|
const bodySize = Math.abs(candle.close - candle.open);
|
|
3740
3693
|
const candleRange = candle.high - candle.low;
|
|
3741
3694
|
const bodyPercent = candleRange > 0 ? (bodySize / candleRange) * 100 : 0;
|
|
3742
|
-
const
|
|
3695
|
+
const priceChangePercent = candle.open > 0 ? ((candle.close - candle.open) / candle.open) * 100 : 0;
|
|
3743
3696
|
const formattedTime = new Date(candle.timestamp).toISOString();
|
|
3744
|
-
markdown += `### 1m Candle ${index + 1}
|
|
3697
|
+
markdown += `### 1m Candle ${index + 1}\n`;
|
|
3698
|
+
markdown += `- **Price Change**: ${priceChangePercent.toFixed(3)}%\n`;
|
|
3745
3699
|
markdown += `- **Time**: ${formattedTime}\n`;
|
|
3746
3700
|
markdown += `- **Open**: ${formatPrice(symbol, candle.open)} USD\n`;
|
|
3747
3701
|
markdown += `- **High**: ${formatPrice(symbol, candle.high)} USD\n`;
|
|
@@ -3881,20 +3835,17 @@ class ThirtyMinuteCandleHistoryService {
|
|
|
3881
3835
|
let report = "";
|
|
3882
3836
|
const currentData = await getDate();
|
|
3883
3837
|
report += `## 30-Min Candles History (Last ${RECENT_CANDLES})\n`;
|
|
3884
|
-
report += `> Current
|
|
3838
|
+
report += `> Current trading pair: ${String(symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
3885
3839
|
for (let index = 0; index < candles.length; index++) {
|
|
3886
3840
|
const candle = candles[index];
|
|
3887
3841
|
const volatilityPercent = ((candle.high - candle.low) / candle.close) * 100;
|
|
3888
3842
|
const bodySize = Math.abs(candle.close - candle.open);
|
|
3889
3843
|
const candleRange = candle.high - candle.low;
|
|
3890
3844
|
const bodyPercent = candleRange > 0 ? (bodySize / candleRange) * 100 : 0;
|
|
3891
|
-
const
|
|
3892
|
-
? "Green"
|
|
3893
|
-
: candle.close < candle.open
|
|
3894
|
-
? "Red"
|
|
3895
|
-
: "Doji";
|
|
3845
|
+
const priceChangePercent = candle.open > 0 ? ((candle.close - candle.open) / candle.open) * 100 : 0;
|
|
3896
3846
|
const formattedTime = new Date(candle.timestamp).toISOString();
|
|
3897
|
-
report += `### 30m Candle ${index + 1}
|
|
3847
|
+
report += `### 30m Candle ${index + 1}\n`;
|
|
3848
|
+
report += `- **Price Change**: ${priceChangePercent.toFixed(3)}%\n`;
|
|
3898
3849
|
report += `- **Time**: ${formattedTime}\n`;
|
|
3899
3850
|
report += `- **Open**: ${formatPrice(symbol, candle.open)} USD\n`;
|
|
3900
3851
|
report += `- **High**: ${formatPrice(symbol, candle.high)} USD\n`;
|
|
@@ -4055,7 +4006,7 @@ function processOrderBookSide(orders) {
|
|
|
4055
4006
|
const generateBookDataReport = async (self, result) => {
|
|
4056
4007
|
const currentData = await getDate();
|
|
4057
4008
|
let markdown = `# Order Book Analysis for ${result.symbol}\n`;
|
|
4058
|
-
markdown += `> Current
|
|
4009
|
+
markdown += `> Current trading pair: ${String(result.symbol).toUpperCase()} Current datetime: ${currentData.toISOString()}\n\n`;
|
|
4059
4010
|
// Basic order book info
|
|
4060
4011
|
markdown += `## Order Book Summary\n`;
|
|
4061
4012
|
markdown += `- **Best Bid**: ${!isUnsafe(result.bestBid)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backtest-kit/signals",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
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
5
|
"author": {
|
|
6
6
|
"name": "Petr Tripolsky",
|
package/types.d.ts
CHANGED
|
@@ -504,7 +504,7 @@ interface ISwingTermRow {
|
|
|
504
504
|
priceMomentum6: number | null;
|
|
505
505
|
fibonacciNearestSupport: number | null;
|
|
506
506
|
fibonacciNearestResistance: number | null;
|
|
507
|
-
|
|
507
|
+
fibonacciPositionPercent: number | null;
|
|
508
508
|
bodySize: number;
|
|
509
509
|
closePrice: number;
|
|
510
510
|
date: Date;
|
|
@@ -542,7 +542,7 @@ interface ISwingTermRow {
|
|
|
542
542
|
* const candles = await getCandles('ETHUSDT', '30m', 96);
|
|
543
543
|
* const rows = await service.getData('ETHUSDT', candles);
|
|
544
544
|
* console.log(rows[0].rsi14); // 52.45
|
|
545
|
-
* console.log(rows[0].
|
|
545
|
+
* console.log(rows[0].fibonacciPositionPercent); // 50.25
|
|
546
546
|
* ```
|
|
547
547
|
*/
|
|
548
548
|
declare class SwingTermHistoryService {
|
|
@@ -649,7 +649,7 @@ interface ILongTermRow {
|
|
|
649
649
|
currentPrice: number;
|
|
650
650
|
support: number;
|
|
651
651
|
resistance: number;
|
|
652
|
-
|
|
652
|
+
volumeTrendRatio: number | null;
|
|
653
653
|
fibonacciNearestLevel: string;
|
|
654
654
|
fibonacciNearestPrice: number;
|
|
655
655
|
fibonacciDistance: number;
|
|
@@ -780,7 +780,7 @@ interface IShortTermRow {
|
|
|
780
780
|
momentum8: number | null;
|
|
781
781
|
roc5: number | null;
|
|
782
782
|
roc10: number | null;
|
|
783
|
-
|
|
783
|
+
volumeTrendRatio: number | null;
|
|
784
784
|
support: number;
|
|
785
785
|
resistance: number;
|
|
786
786
|
currentPrice: number;
|
|
@@ -940,7 +940,7 @@ interface IMicroTermRow {
|
|
|
940
940
|
wma5: number | null;
|
|
941
941
|
volumeSma5: number | null;
|
|
942
942
|
volumeRatio: number | null;
|
|
943
|
-
|
|
943
|
+
volumeTrendRatio: number | null;
|
|
944
944
|
currentPrice: number;
|
|
945
945
|
priceChange1m: number | null;
|
|
946
946
|
priceChange3m: number | null;
|