@backtest-kit/signals 0.0.4 → 0.0.6
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 +51 -100
- package/build/index.mjs +51 -100
- package/package.json +3 -3
- 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)
|
|
@@ -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,
|
|
@@ -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)
|
|
@@ -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(),
|
|
@@ -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,7 +3384,6 @@ 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`;
|
|
@@ -3431,25 +3391,21 @@ class FifteenMinuteCandleHistoryService {
|
|
|
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
|
};
|
|
@@ -3588,13 +3544,10 @@ class HourCandleHistoryService {
|
|
|
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`;
|
|
@@ -3741,9 +3694,10 @@ class OneMinuteCandleHistoryService {
|
|
|
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`;
|
|
@@ -3890,13 +3844,10 @@ class ThirtyMinuteCandleHistoryService {
|
|
|
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`;
|
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)
|
|
@@ -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,
|
|
@@ -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)
|
|
@@ -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(),
|
|
@@ -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,7 +3382,6 @@ 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`;
|
|
@@ -3429,25 +3389,21 @@ class FifteenMinuteCandleHistoryService {
|
|
|
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
|
};
|
|
@@ -3586,13 +3542,10 @@ class HourCandleHistoryService {
|
|
|
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`;
|
|
@@ -3739,9 +3692,10 @@ class OneMinuteCandleHistoryService {
|
|
|
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`;
|
|
@@ -3888,13 +3842,10 @@ class ThirtyMinuteCandleHistoryService {
|
|
|
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`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backtest-kit/signals",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
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",
|
|
@@ -69,7 +69,6 @@
|
|
|
69
69
|
"worker-testbed": "1.0.12"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"agent-swarm-kit": "^1.1.180",
|
|
73
72
|
"backtest-kit": "^1.12.1",
|
|
74
73
|
"typescript": "^5.0.0"
|
|
75
74
|
},
|
|
@@ -78,7 +77,8 @@
|
|
|
78
77
|
"di-scoped": "^1.0.20",
|
|
79
78
|
"functools-kit": "^1.0.95",
|
|
80
79
|
"trading-signals": "^6.9.1",
|
|
81
|
-
"get-moment-stamp": "^1.1.1"
|
|
80
|
+
"get-moment-stamp": "^1.1.1",
|
|
81
|
+
"agent-swarm-kit": "^1.1.180"
|
|
82
82
|
},
|
|
83
83
|
"publishConfig": {
|
|
84
84
|
"access": "public"
|
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;
|