prab-cli 1.2.6 → 1.2.7
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/dist/index.js +41 -0
- package/dist/lib/crypto/analyzer.js +397 -93
- package/dist/lib/crypto/data-fetcher.js +342 -50
- package/dist/lib/crypto/index.js +12 -1
- package/dist/lib/crypto/signal-generator.js +278 -3
- package/dist/lib/crypto/strategy.js +673 -0
- package/dist/lib/slash-commands.js +6 -0
- package/dist/server/index.js +70 -0
- package/package.json +3 -3
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Smart Trend Confluence Strategy
|
|
4
|
+
* High-probability trading strategy combining multiple factors
|
|
5
|
+
*
|
|
6
|
+
* Components:
|
|
7
|
+
* 1. Multi-timeframe trend alignment
|
|
8
|
+
* 2. EMA pullback detection
|
|
9
|
+
* 3. Candlestick pattern recognition
|
|
10
|
+
* 4. Key level detection (S/R, Order Blocks)
|
|
11
|
+
* 5. Volume confirmation
|
|
12
|
+
* 6. Confluence scoring system
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.detectCandlePattern = detectCandlePattern;
|
|
16
|
+
exports.detectKeyLevels = detectKeyLevels;
|
|
17
|
+
exports.detectPullback = detectPullback;
|
|
18
|
+
exports.analyzeVolume = analyzeVolume;
|
|
19
|
+
exports.calculateRSI = calculateRSI;
|
|
20
|
+
exports.analyzeTimeframe = analyzeTimeframe;
|
|
21
|
+
exports.calculateConfluenceScore = calculateConfluenceScore;
|
|
22
|
+
exports.generateStrategySignal = generateStrategySignal;
|
|
23
|
+
const data_fetcher_1 = require("./data-fetcher");
|
|
24
|
+
const analyzer_1 = require("./analyzer");
|
|
25
|
+
// ============================================
|
|
26
|
+
// CANDLE PATTERN DETECTION
|
|
27
|
+
// ============================================
|
|
28
|
+
/**
|
|
29
|
+
* Calculate candle body and wick ratios
|
|
30
|
+
*/
|
|
31
|
+
function getCandleMetrics(candle) {
|
|
32
|
+
const body = Math.abs(candle.close - candle.open);
|
|
33
|
+
const totalRange = candle.high - candle.low;
|
|
34
|
+
const upperWick = candle.high - Math.max(candle.open, candle.close);
|
|
35
|
+
const lowerWick = Math.min(candle.open, candle.close) - candle.low;
|
|
36
|
+
const isBullish = candle.close > candle.open;
|
|
37
|
+
return {
|
|
38
|
+
body,
|
|
39
|
+
totalRange,
|
|
40
|
+
upperWick,
|
|
41
|
+
lowerWick,
|
|
42
|
+
isBullish,
|
|
43
|
+
bodyRatio: totalRange > 0 ? body / totalRange : 0,
|
|
44
|
+
upperWickRatio: totalRange > 0 ? upperWick / totalRange : 0,
|
|
45
|
+
lowerWickRatio: totalRange > 0 ? lowerWick / totalRange : 0,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Detect candlestick patterns
|
|
50
|
+
*/
|
|
51
|
+
function detectCandlePattern(candles) {
|
|
52
|
+
if (candles.length < 3) {
|
|
53
|
+
return { type: "none", strength: 0, description: "Insufficient data" };
|
|
54
|
+
}
|
|
55
|
+
const current = candles[candles.length - 1];
|
|
56
|
+
const previous = candles[candles.length - 2];
|
|
57
|
+
const currentMetrics = getCandleMetrics(current);
|
|
58
|
+
const previousMetrics = getCandleMetrics(previous);
|
|
59
|
+
// Bullish Engulfing
|
|
60
|
+
if (!previousMetrics.isBullish &&
|
|
61
|
+
currentMetrics.isBullish &&
|
|
62
|
+
current.open < previous.close &&
|
|
63
|
+
current.close > previous.open &&
|
|
64
|
+
currentMetrics.body > previousMetrics.body * 1.2) {
|
|
65
|
+
const strength = Math.min(100, 60 + (currentMetrics.body / previousMetrics.body) * 20);
|
|
66
|
+
return {
|
|
67
|
+
type: "bullish_engulfing",
|
|
68
|
+
strength,
|
|
69
|
+
description: "Bullish engulfing - strong reversal signal",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Bearish Engulfing
|
|
73
|
+
if (previousMetrics.isBullish &&
|
|
74
|
+
!currentMetrics.isBullish &&
|
|
75
|
+
current.open > previous.close &&
|
|
76
|
+
current.close < previous.open &&
|
|
77
|
+
currentMetrics.body > previousMetrics.body * 1.2) {
|
|
78
|
+
const strength = Math.min(100, 60 + (currentMetrics.body / previousMetrics.body) * 20);
|
|
79
|
+
return {
|
|
80
|
+
type: "bearish_engulfing",
|
|
81
|
+
strength,
|
|
82
|
+
description: "Bearish engulfing - strong reversal signal",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// Bullish Pin Bar (Hammer) - long lower wick, small body at top
|
|
86
|
+
if (currentMetrics.lowerWickRatio > 0.6 &&
|
|
87
|
+
currentMetrics.bodyRatio < 0.3 &&
|
|
88
|
+
currentMetrics.upperWickRatio < 0.15) {
|
|
89
|
+
const strength = Math.min(100, 50 + currentMetrics.lowerWickRatio * 50);
|
|
90
|
+
return {
|
|
91
|
+
type: "bullish_pinbar",
|
|
92
|
+
strength,
|
|
93
|
+
description: "Bullish pin bar - rejection of lower prices",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// Bearish Pin Bar (Shooting Star) - long upper wick, small body at bottom
|
|
97
|
+
if (currentMetrics.upperWickRatio > 0.6 &&
|
|
98
|
+
currentMetrics.bodyRatio < 0.3 &&
|
|
99
|
+
currentMetrics.lowerWickRatio < 0.15) {
|
|
100
|
+
const strength = Math.min(100, 50 + currentMetrics.upperWickRatio * 50);
|
|
101
|
+
return {
|
|
102
|
+
type: "bearish_pinbar",
|
|
103
|
+
strength,
|
|
104
|
+
description: "Bearish pin bar - rejection of higher prices",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// Bullish Hammer (in downtrend context)
|
|
108
|
+
if (currentMetrics.lowerWickRatio > 0.5 &&
|
|
109
|
+
currentMetrics.bodyRatio < 0.4 &&
|
|
110
|
+
currentMetrics.isBullish) {
|
|
111
|
+
const strength = Math.min(100, 40 + currentMetrics.lowerWickRatio * 40);
|
|
112
|
+
return {
|
|
113
|
+
type: "bullish_hammer",
|
|
114
|
+
strength,
|
|
115
|
+
description: "Bullish hammer - potential reversal",
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// Bearish Hammer (Hanging Man)
|
|
119
|
+
if (currentMetrics.lowerWickRatio > 0.5 &&
|
|
120
|
+
currentMetrics.bodyRatio < 0.4 &&
|
|
121
|
+
!currentMetrics.isBullish) {
|
|
122
|
+
const strength = Math.min(100, 40 + currentMetrics.lowerWickRatio * 40);
|
|
123
|
+
return {
|
|
124
|
+
type: "bearish_hammer",
|
|
125
|
+
strength,
|
|
126
|
+
description: "Bearish hanging man - potential reversal",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// Doji - very small body
|
|
130
|
+
if (currentMetrics.bodyRatio < 0.1) {
|
|
131
|
+
return {
|
|
132
|
+
type: "doji",
|
|
133
|
+
strength: 30,
|
|
134
|
+
description: "Doji - indecision, wait for confirmation",
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return { type: "none", strength: 0, description: "No significant pattern" };
|
|
138
|
+
}
|
|
139
|
+
// ============================================
|
|
140
|
+
// KEY LEVEL DETECTION
|
|
141
|
+
// ============================================
|
|
142
|
+
/**
|
|
143
|
+
* Find support and resistance levels from price action
|
|
144
|
+
*/
|
|
145
|
+
function detectKeyLevels(candles, currentPrice) {
|
|
146
|
+
const levels = [];
|
|
147
|
+
const lookback = Math.min(100, candles.length);
|
|
148
|
+
const tolerance = currentPrice * 0.005; // 0.5% tolerance for grouping
|
|
149
|
+
// Find swing highs and lows
|
|
150
|
+
const swingHighs = [];
|
|
151
|
+
const swingLows = [];
|
|
152
|
+
for (let i = 2; i < lookback - 2; i++) {
|
|
153
|
+
const idx = candles.length - lookback + i;
|
|
154
|
+
const candle = candles[idx];
|
|
155
|
+
const prev1 = candles[idx - 1];
|
|
156
|
+
const prev2 = candles[idx - 2];
|
|
157
|
+
const next1 = candles[idx + 1];
|
|
158
|
+
const next2 = candles[idx + 2];
|
|
159
|
+
// Swing high
|
|
160
|
+
if (candle.high > prev1.high &&
|
|
161
|
+
candle.high > prev2.high &&
|
|
162
|
+
candle.high > next1.high &&
|
|
163
|
+
candle.high > next2.high) {
|
|
164
|
+
swingHighs.push(candle.high);
|
|
165
|
+
}
|
|
166
|
+
// Swing low
|
|
167
|
+
if (candle.low < prev1.low &&
|
|
168
|
+
candle.low < prev2.low &&
|
|
169
|
+
candle.low < next1.low &&
|
|
170
|
+
candle.low < next2.low) {
|
|
171
|
+
swingLows.push(candle.low);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Group similar levels and count touches
|
|
175
|
+
const groupLevels = (prices, type) => {
|
|
176
|
+
const grouped = [];
|
|
177
|
+
for (const price of prices) {
|
|
178
|
+
const existing = grouped.find((g) => Math.abs(g.price - price) < tolerance);
|
|
179
|
+
if (existing) {
|
|
180
|
+
existing.count++;
|
|
181
|
+
existing.price = (existing.price + price) / 2; // Average
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
grouped.push({ price, count: 1 });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return grouped
|
|
188
|
+
.filter((g) => g.count >= 2) // At least 2 touches
|
|
189
|
+
.map((g) => ({
|
|
190
|
+
price: g.price,
|
|
191
|
+
type,
|
|
192
|
+
strength: Math.min(100, 30 + g.count * 20),
|
|
193
|
+
distance: ((g.price - currentPrice) / currentPrice) * 100,
|
|
194
|
+
}))
|
|
195
|
+
.sort((a, b) => Math.abs(a.distance) - Math.abs(b.distance))
|
|
196
|
+
.slice(0, 3); // Top 3 nearest
|
|
197
|
+
};
|
|
198
|
+
const supports = groupLevels(swingLows, "support");
|
|
199
|
+
const resistances = groupLevels(swingHighs, "resistance");
|
|
200
|
+
// Detect Order Blocks (last opposite candle before strong move)
|
|
201
|
+
const orderBlocks = detectOrderBlocks(candles, currentPrice);
|
|
202
|
+
return [...supports, ...resistances, ...orderBlocks].sort((a, b) => Math.abs(a.distance) - Math.abs(b.distance));
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Detect Order Blocks (ICT concept)
|
|
206
|
+
* An order block is the last opposite candle before a strong move
|
|
207
|
+
*/
|
|
208
|
+
function detectOrderBlocks(candles, currentPrice) {
|
|
209
|
+
const orderBlocks = [];
|
|
210
|
+
const lookback = Math.min(50, candles.length - 5);
|
|
211
|
+
for (let i = 5; i < lookback; i++) {
|
|
212
|
+
const idx = candles.length - i;
|
|
213
|
+
const candle = candles[idx];
|
|
214
|
+
const nextCandles = candles.slice(idx + 1, idx + 4);
|
|
215
|
+
if (nextCandles.length < 3)
|
|
216
|
+
continue;
|
|
217
|
+
const isBullishCandle = candle.close > candle.open;
|
|
218
|
+
const moveAfter = nextCandles.reduce((sum, c) => sum + (c.close - c.open), 0);
|
|
219
|
+
const avgBodySize = candles.slice(idx - 10, idx).reduce((sum, c) => sum + Math.abs(c.close - c.open), 0) / 10;
|
|
220
|
+
// Bullish Order Block: bearish candle followed by strong bullish move
|
|
221
|
+
if (!isBullishCandle && moveAfter > avgBodySize * 3) {
|
|
222
|
+
const obLow = candle.low;
|
|
223
|
+
const distance = ((obLow - currentPrice) / currentPrice) * 100;
|
|
224
|
+
if (Math.abs(distance) < 5) {
|
|
225
|
+
// Within 5%
|
|
226
|
+
orderBlocks.push({
|
|
227
|
+
price: obLow,
|
|
228
|
+
type: "order_block_bullish",
|
|
229
|
+
strength: Math.min(100, 50 + (moveAfter / avgBodySize) * 10),
|
|
230
|
+
distance,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Bearish Order Block: bullish candle followed by strong bearish move
|
|
235
|
+
if (isBullishCandle && moveAfter < -avgBodySize * 3) {
|
|
236
|
+
const obHigh = candle.high;
|
|
237
|
+
const distance = ((obHigh - currentPrice) / currentPrice) * 100;
|
|
238
|
+
if (Math.abs(distance) < 5) {
|
|
239
|
+
orderBlocks.push({
|
|
240
|
+
price: obHigh,
|
|
241
|
+
type: "order_block_bearish",
|
|
242
|
+
strength: Math.min(100, 50 + (Math.abs(moveAfter) / avgBodySize) * 10),
|
|
243
|
+
distance,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return orderBlocks.slice(0, 2); // Top 2 nearest
|
|
249
|
+
}
|
|
250
|
+
// ============================================
|
|
251
|
+
// PULLBACK DETECTION
|
|
252
|
+
// ============================================
|
|
253
|
+
/**
|
|
254
|
+
* Detect if price is pulling back to EMA zone
|
|
255
|
+
*/
|
|
256
|
+
function detectPullback(currentPrice, ema10, ema20, trend) {
|
|
257
|
+
const distanceToEMA10 = ((currentPrice - ema10) / ema10) * 100;
|
|
258
|
+
const distanceToEMA20 = ((currentPrice - ema20) / ema20) * 100;
|
|
259
|
+
// Check if price is in the "value zone" between EMA10 and EMA20
|
|
260
|
+
const inEMAZone = (currentPrice >= Math.min(ema10, ema20) && currentPrice <= Math.max(ema10, ema20)) ||
|
|
261
|
+
Math.abs(distanceToEMA10) < 0.3 ||
|
|
262
|
+
Math.abs(distanceToEMA20) < 0.3;
|
|
263
|
+
// Determine if this is a valid pullback
|
|
264
|
+
let isPullback = false;
|
|
265
|
+
let quality = "none";
|
|
266
|
+
if (trend === "bullish") {
|
|
267
|
+
// In uptrend, pullback is when price comes down to EMAs
|
|
268
|
+
if (distanceToEMA10 <= 0.5 && distanceToEMA10 >= -1) {
|
|
269
|
+
isPullback = true;
|
|
270
|
+
quality = "optimal";
|
|
271
|
+
}
|
|
272
|
+
else if (distanceToEMA10 <= 1.5 && distanceToEMA10 >= -2) {
|
|
273
|
+
isPullback = true;
|
|
274
|
+
quality = "acceptable";
|
|
275
|
+
}
|
|
276
|
+
else if (distanceToEMA10 > 2) {
|
|
277
|
+
quality = "extended";
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else if (trend === "bearish") {
|
|
281
|
+
// In downtrend, pullback is when price comes up to EMAs
|
|
282
|
+
if (distanceToEMA10 >= -0.5 && distanceToEMA10 <= 1) {
|
|
283
|
+
isPullback = true;
|
|
284
|
+
quality = "optimal";
|
|
285
|
+
}
|
|
286
|
+
else if (distanceToEMA10 >= -1.5 && distanceToEMA10 <= 2) {
|
|
287
|
+
isPullback = true;
|
|
288
|
+
quality = "acceptable";
|
|
289
|
+
}
|
|
290
|
+
else if (distanceToEMA10 < -2) {
|
|
291
|
+
quality = "extended";
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
isPullback,
|
|
296
|
+
inEMAZone,
|
|
297
|
+
distanceToEMA10,
|
|
298
|
+
distanceToEMA20,
|
|
299
|
+
quality,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
// ============================================
|
|
303
|
+
// VOLUME ANALYSIS
|
|
304
|
+
// ============================================
|
|
305
|
+
/**
|
|
306
|
+
* Analyze volume patterns
|
|
307
|
+
*/
|
|
308
|
+
function analyzeVolume(candles) {
|
|
309
|
+
const lookback = Math.min(20, candles.length);
|
|
310
|
+
const recentCandles = candles.slice(-lookback);
|
|
311
|
+
const volumes = recentCandles.map((c) => c.volume);
|
|
312
|
+
const averageVolume = volumes.slice(0, -1).reduce((a, b) => a + b, 0) / (volumes.length - 1);
|
|
313
|
+
const currentVolume = volumes[volumes.length - 1];
|
|
314
|
+
const volumeRatio = currentVolume / averageVolume;
|
|
315
|
+
// Determine volume trend (last 5 candles)
|
|
316
|
+
const recentVolumes = volumes.slice(-5);
|
|
317
|
+
let trend = "stable";
|
|
318
|
+
if (recentVolumes.length >= 5) {
|
|
319
|
+
const firstHalf = (recentVolumes[0] + recentVolumes[1]) / 2;
|
|
320
|
+
const secondHalf = (recentVolumes[3] + recentVolumes[4]) / 2;
|
|
321
|
+
if (secondHalf > firstHalf * 1.2) {
|
|
322
|
+
trend = "increasing";
|
|
323
|
+
}
|
|
324
|
+
else if (secondHalf < firstHalf * 0.8) {
|
|
325
|
+
trend = "decreasing";
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
currentVolume,
|
|
330
|
+
averageVolume,
|
|
331
|
+
volumeRatio,
|
|
332
|
+
isAboveAverage: volumeRatio > 1.0,
|
|
333
|
+
trend,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
// ============================================
|
|
337
|
+
// RSI CALCULATION
|
|
338
|
+
// ============================================
|
|
339
|
+
/**
|
|
340
|
+
* Calculate RSI
|
|
341
|
+
*/
|
|
342
|
+
function calculateRSI(candles, period = 14) {
|
|
343
|
+
if (candles.length < period + 1)
|
|
344
|
+
return 50;
|
|
345
|
+
const changes = [];
|
|
346
|
+
for (let i = 1; i < candles.length; i++) {
|
|
347
|
+
changes.push(candles[i].close - candles[i - 1].close);
|
|
348
|
+
}
|
|
349
|
+
const recentChanges = changes.slice(-period);
|
|
350
|
+
let gains = 0;
|
|
351
|
+
let losses = 0;
|
|
352
|
+
for (const change of recentChanges) {
|
|
353
|
+
if (change > 0)
|
|
354
|
+
gains += change;
|
|
355
|
+
else
|
|
356
|
+
losses += Math.abs(change);
|
|
357
|
+
}
|
|
358
|
+
const avgGain = gains / period;
|
|
359
|
+
const avgLoss = losses / period;
|
|
360
|
+
if (avgLoss === 0)
|
|
361
|
+
return 100;
|
|
362
|
+
const rs = avgGain / avgLoss;
|
|
363
|
+
return 100 - 100 / (1 + rs);
|
|
364
|
+
}
|
|
365
|
+
// ============================================
|
|
366
|
+
// TIMEFRAME ANALYSIS
|
|
367
|
+
// ============================================
|
|
368
|
+
/**
|
|
369
|
+
* Analyze a single timeframe
|
|
370
|
+
*/
|
|
371
|
+
function analyzeTimeframe(data, interval) {
|
|
372
|
+
const closePrices = data.candles.map((c) => c.close);
|
|
373
|
+
const ema10 = (0, analyzer_1.calculateEMA)(closePrices, 10);
|
|
374
|
+
const ema20 = (0, analyzer_1.calculateEMA)(closePrices, 20);
|
|
375
|
+
const ema50 = (0, analyzer_1.calculateEMA)(closePrices, 50);
|
|
376
|
+
const ema200 = (0, analyzer_1.calculateEMA)(closePrices, 200);
|
|
377
|
+
const currentEMA10 = ema10[ema10.length - 1] || 0;
|
|
378
|
+
const currentEMA20 = ema20[ema20.length - 1] || 0;
|
|
379
|
+
const currentEMA50 = ema50[ema50.length - 1] || 0;
|
|
380
|
+
const currentEMA200 = ema200[ema200.length - 1] || 0;
|
|
381
|
+
const emaTilt = (0, analyzer_1.calculateEMATilt)(ema10);
|
|
382
|
+
const rsi = calculateRSI(data.candles);
|
|
383
|
+
// Determine trend
|
|
384
|
+
let trend = "neutral";
|
|
385
|
+
if (currentEMA10 > currentEMA20 && currentEMA20 > currentEMA50) {
|
|
386
|
+
trend = "bullish";
|
|
387
|
+
}
|
|
388
|
+
else if (currentEMA10 < currentEMA20 && currentEMA20 < currentEMA50) {
|
|
389
|
+
trend = "bearish";
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
interval,
|
|
393
|
+
trend,
|
|
394
|
+
ema10: currentEMA10,
|
|
395
|
+
ema20: currentEMA20,
|
|
396
|
+
ema50: currentEMA50,
|
|
397
|
+
ema200: currentEMA200,
|
|
398
|
+
emaTilt,
|
|
399
|
+
rsi,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
// ============================================
|
|
403
|
+
// CONFLUENCE SCORING
|
|
404
|
+
// ============================================
|
|
405
|
+
/**
|
|
406
|
+
* Calculate confluence score
|
|
407
|
+
*/
|
|
408
|
+
function calculateConfluenceScore(htfAnalysis, ltfAnalysis, pullback, keyLevels, volume, candlePattern, direction) {
|
|
409
|
+
const breakdown = {
|
|
410
|
+
trendAlignment: 0,
|
|
411
|
+
pullbackQuality: 0,
|
|
412
|
+
keyLevel: 0,
|
|
413
|
+
volume: 0,
|
|
414
|
+
rsiRange: 0,
|
|
415
|
+
};
|
|
416
|
+
const factors = [];
|
|
417
|
+
// 1. Trend Alignment (Max 30 points)
|
|
418
|
+
const htfTrendMatch = (direction === "long" && htfAnalysis.trend === "bullish") ||
|
|
419
|
+
(direction === "short" && htfAnalysis.trend === "bearish");
|
|
420
|
+
const ltfTrendMatch = (direction === "long" && ltfAnalysis.trend === "bullish") ||
|
|
421
|
+
(direction === "short" && ltfAnalysis.trend === "bearish");
|
|
422
|
+
if (htfTrendMatch && ltfTrendMatch) {
|
|
423
|
+
breakdown.trendAlignment = 30;
|
|
424
|
+
factors.push("✓ Both timeframes aligned with trade direction");
|
|
425
|
+
}
|
|
426
|
+
else if (htfTrendMatch) {
|
|
427
|
+
breakdown.trendAlignment = 20;
|
|
428
|
+
factors.push("✓ Higher timeframe confirms direction");
|
|
429
|
+
}
|
|
430
|
+
else if (ltfTrendMatch) {
|
|
431
|
+
breakdown.trendAlignment = 10;
|
|
432
|
+
factors.push("○ Only lower timeframe confirms (weaker)");
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
factors.push("✗ Trend not aligned with trade direction");
|
|
436
|
+
}
|
|
437
|
+
// EMA structure bonus
|
|
438
|
+
if (direction === "long" && htfAnalysis.ema50 > htfAnalysis.ema200) {
|
|
439
|
+
breakdown.trendAlignment = Math.min(30, breakdown.trendAlignment + 5);
|
|
440
|
+
factors.push("✓ EMA50 > EMA200 on higher TF (bullish structure)");
|
|
441
|
+
}
|
|
442
|
+
else if (direction === "short" && htfAnalysis.ema50 < htfAnalysis.ema200) {
|
|
443
|
+
breakdown.trendAlignment = Math.min(30, breakdown.trendAlignment + 5);
|
|
444
|
+
factors.push("✓ EMA50 < EMA200 on higher TF (bearish structure)");
|
|
445
|
+
}
|
|
446
|
+
// 2. Pullback Quality (Max 25 points)
|
|
447
|
+
if (pullback.quality === "optimal") {
|
|
448
|
+
breakdown.pullbackQuality = 25;
|
|
449
|
+
factors.push("✓ Optimal pullback to EMA zone");
|
|
450
|
+
}
|
|
451
|
+
else if (pullback.quality === "acceptable") {
|
|
452
|
+
breakdown.pullbackQuality = 15;
|
|
453
|
+
factors.push("○ Acceptable pullback distance");
|
|
454
|
+
}
|
|
455
|
+
else if (pullback.quality === "extended") {
|
|
456
|
+
breakdown.pullbackQuality = 5;
|
|
457
|
+
factors.push("✗ Price extended from EMAs - wait for pullback");
|
|
458
|
+
}
|
|
459
|
+
// Candle pattern bonus
|
|
460
|
+
if (candlePattern.type !== "none" && candlePattern.type !== "doji") {
|
|
461
|
+
const patternBonus = Math.round(candlePattern.strength / 10);
|
|
462
|
+
breakdown.pullbackQuality = Math.min(25, breakdown.pullbackQuality + patternBonus);
|
|
463
|
+
factors.push(`✓ ${candlePattern.description}`);
|
|
464
|
+
}
|
|
465
|
+
// 3. Key Level (Max 20 points)
|
|
466
|
+
const nearbyLevel = keyLevels.find((l) => Math.abs(l.distance) < 1.5);
|
|
467
|
+
if (nearbyLevel) {
|
|
468
|
+
const levelScore = Math.round((nearbyLevel.strength / 100) * 20);
|
|
469
|
+
breakdown.keyLevel = levelScore;
|
|
470
|
+
if (nearbyLevel.type.includes("order_block")) {
|
|
471
|
+
factors.push(`✓ At ${nearbyLevel.type.replace(/_/g, " ")} ($${nearbyLevel.price.toFixed(2)})`);
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
factors.push(`✓ Near ${nearbyLevel.type} level ($${nearbyLevel.price.toFixed(2)}, ${Math.abs(nearbyLevel.distance).toFixed(1)}% away)`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
factors.push("○ No significant key level nearby");
|
|
479
|
+
}
|
|
480
|
+
// 4. Volume (Max 15 points)
|
|
481
|
+
if (volume.volumeRatio > 1.5) {
|
|
482
|
+
breakdown.volume = 15;
|
|
483
|
+
factors.push(`✓ Strong volume (${volume.volumeRatio.toFixed(1)}x average)`);
|
|
484
|
+
}
|
|
485
|
+
else if (volume.volumeRatio > 1.2) {
|
|
486
|
+
breakdown.volume = 12;
|
|
487
|
+
factors.push(`✓ Above average volume (${volume.volumeRatio.toFixed(1)}x)`);
|
|
488
|
+
}
|
|
489
|
+
else if (volume.volumeRatio > 1.0) {
|
|
490
|
+
breakdown.volume = 8;
|
|
491
|
+
factors.push(`○ Average volume (${volume.volumeRatio.toFixed(1)}x)`);
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
breakdown.volume = 3;
|
|
495
|
+
factors.push(`✗ Below average volume (${volume.volumeRatio.toFixed(1)}x)`);
|
|
496
|
+
}
|
|
497
|
+
// Volume trend bonus
|
|
498
|
+
if (volume.trend === "increasing") {
|
|
499
|
+
breakdown.volume = Math.min(15, breakdown.volume + 3);
|
|
500
|
+
factors.push("✓ Volume increasing");
|
|
501
|
+
}
|
|
502
|
+
// 5. RSI Range (Max 10 points)
|
|
503
|
+
const ltfRSI = ltfAnalysis.rsi;
|
|
504
|
+
if (direction === "long") {
|
|
505
|
+
if (ltfRSI >= 30 && ltfRSI <= 50) {
|
|
506
|
+
breakdown.rsiRange = 10;
|
|
507
|
+
factors.push(`✓ RSI in optimal buy zone (${ltfRSI.toFixed(0)})`);
|
|
508
|
+
}
|
|
509
|
+
else if (ltfRSI >= 25 && ltfRSI <= 60) {
|
|
510
|
+
breakdown.rsiRange = 7;
|
|
511
|
+
factors.push(`○ RSI acceptable for long (${ltfRSI.toFixed(0)})`);
|
|
512
|
+
}
|
|
513
|
+
else if (ltfRSI > 70) {
|
|
514
|
+
breakdown.rsiRange = 0;
|
|
515
|
+
factors.push(`✗ RSI overbought (${ltfRSI.toFixed(0)}) - risky for long`);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
breakdown.rsiRange = 4;
|
|
519
|
+
factors.push(`○ RSI at ${ltfRSI.toFixed(0)}`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
if (ltfRSI >= 50 && ltfRSI <= 70) {
|
|
524
|
+
breakdown.rsiRange = 10;
|
|
525
|
+
factors.push(`✓ RSI in optimal sell zone (${ltfRSI.toFixed(0)})`);
|
|
526
|
+
}
|
|
527
|
+
else if (ltfRSI >= 40 && ltfRSI <= 75) {
|
|
528
|
+
breakdown.rsiRange = 7;
|
|
529
|
+
factors.push(`○ RSI acceptable for short (${ltfRSI.toFixed(0)})`);
|
|
530
|
+
}
|
|
531
|
+
else if (ltfRSI < 30) {
|
|
532
|
+
breakdown.rsiRange = 0;
|
|
533
|
+
factors.push(`✗ RSI oversold (${ltfRSI.toFixed(0)}) - risky for short`);
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
breakdown.rsiRange = 4;
|
|
537
|
+
factors.push(`○ RSI at ${ltfRSI.toFixed(0)}`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
const total = breakdown.trendAlignment +
|
|
541
|
+
breakdown.pullbackQuality +
|
|
542
|
+
breakdown.keyLevel +
|
|
543
|
+
breakdown.volume +
|
|
544
|
+
breakdown.rsiRange;
|
|
545
|
+
return {
|
|
546
|
+
total,
|
|
547
|
+
breakdown,
|
|
548
|
+
meetsMinimum: total >= 70,
|
|
549
|
+
factors,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
// ============================================
|
|
553
|
+
// MAIN STRATEGY FUNCTION
|
|
554
|
+
// ============================================
|
|
555
|
+
/**
|
|
556
|
+
* Generate strategy signal with full confluence analysis
|
|
557
|
+
*/
|
|
558
|
+
async function generateStrategySignal(symbol, htfInterval = "4h", ltfInterval = "1h") {
|
|
559
|
+
// Fetch data for both timeframes
|
|
560
|
+
const [htfData, ltfData] = await Promise.all([
|
|
561
|
+
(0, data_fetcher_1.fetchCryptoData)(symbol, htfInterval, 250),
|
|
562
|
+
(0, data_fetcher_1.fetchCryptoData)(symbol, ltfInterval, 250),
|
|
563
|
+
]);
|
|
564
|
+
const currentPrice = ltfData.currentPrice;
|
|
565
|
+
const reasoning = [];
|
|
566
|
+
const warnings = [];
|
|
567
|
+
// Analyze both timeframes
|
|
568
|
+
const htfAnalysis = analyzeTimeframe(htfData, htfInterval);
|
|
569
|
+
const ltfAnalysis = analyzeTimeframe(ltfData, ltfInterval);
|
|
570
|
+
// Detect components
|
|
571
|
+
const candlePattern = detectCandlePattern(ltfData.candles);
|
|
572
|
+
const keyLevels = detectKeyLevels(ltfData.candles, currentPrice);
|
|
573
|
+
const volume = analyzeVolume(ltfData.candles);
|
|
574
|
+
// Determine potential direction based on higher timeframe
|
|
575
|
+
let direction = "none";
|
|
576
|
+
if (htfAnalysis.trend === "bullish" && htfAnalysis.ema50 > htfAnalysis.ema200) {
|
|
577
|
+
direction = "long";
|
|
578
|
+
reasoning.push(`Higher TF (${htfInterval}) shows bullish trend`);
|
|
579
|
+
}
|
|
580
|
+
else if (htfAnalysis.trend === "bearish" && htfAnalysis.ema50 < htfAnalysis.ema200) {
|
|
581
|
+
direction = "short";
|
|
582
|
+
reasoning.push(`Higher TF (${htfInterval}) shows bearish trend`);
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
reasoning.push(`Higher TF (${htfInterval}) trend unclear - waiting for direction`);
|
|
586
|
+
}
|
|
587
|
+
// Detect pullback
|
|
588
|
+
const pullback = detectPullback(currentPrice, ltfAnalysis.ema10, ltfAnalysis.ema20, ltfAnalysis.trend);
|
|
589
|
+
// Calculate confluence score
|
|
590
|
+
const score = direction !== "none"
|
|
591
|
+
? calculateConfluenceScore(htfAnalysis, ltfAnalysis, pullback, keyLevels, volume, candlePattern, direction)
|
|
592
|
+
: {
|
|
593
|
+
total: 0,
|
|
594
|
+
breakdown: { trendAlignment: 0, pullbackQuality: 0, keyLevel: 0, volume: 0, rsiRange: 0 },
|
|
595
|
+
meetsMinimum: false,
|
|
596
|
+
factors: ["No clear direction from higher timeframe"],
|
|
597
|
+
};
|
|
598
|
+
// Calculate entry, stop loss, take profits
|
|
599
|
+
const entry = currentPrice;
|
|
600
|
+
let stopLoss = 0;
|
|
601
|
+
let takeProfit1 = 0;
|
|
602
|
+
let takeProfit2 = 0;
|
|
603
|
+
let riskRewardRatio = 0;
|
|
604
|
+
if (direction === "long") {
|
|
605
|
+
// Stop loss below EMA20 or recent swing low
|
|
606
|
+
const recentLow = Math.min(...ltfData.candles.slice(-10).map((c) => c.low));
|
|
607
|
+
stopLoss = Math.min(ltfAnalysis.ema20 * 0.995, recentLow);
|
|
608
|
+
const risk = entry - stopLoss;
|
|
609
|
+
takeProfit1 = entry + risk * 1.5;
|
|
610
|
+
takeProfit2 = entry + risk * 2.5;
|
|
611
|
+
riskRewardRatio = 2.5;
|
|
612
|
+
}
|
|
613
|
+
else if (direction === "short") {
|
|
614
|
+
// Stop loss above EMA20 or recent swing high
|
|
615
|
+
const recentHigh = Math.max(...ltfData.candles.slice(-10).map((c) => c.high));
|
|
616
|
+
stopLoss = Math.max(ltfAnalysis.ema20 * 1.005, recentHigh);
|
|
617
|
+
const risk = stopLoss - entry;
|
|
618
|
+
takeProfit1 = entry - risk * 1.5;
|
|
619
|
+
takeProfit2 = entry - risk * 2.5;
|
|
620
|
+
riskRewardRatio = 2.5;
|
|
621
|
+
}
|
|
622
|
+
// Determine final signal
|
|
623
|
+
let signal = "NO_TRADE";
|
|
624
|
+
if (direction !== "none" && score.meetsMinimum) {
|
|
625
|
+
if (score.total >= 85) {
|
|
626
|
+
signal = direction === "long" ? "STRONG_BUY" : "STRONG_SELL";
|
|
627
|
+
reasoning.push(`Strong confluence score (${score.total}/100)`);
|
|
628
|
+
}
|
|
629
|
+
else if (score.total >= 70) {
|
|
630
|
+
signal = direction === "long" ? "BUY" : "SELL";
|
|
631
|
+
reasoning.push(`Good confluence score (${score.total}/100)`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
else if (direction !== "none" && score.total >= 50) {
|
|
635
|
+
signal = "WAIT";
|
|
636
|
+
reasoning.push(`Confluence score below minimum (${score.total}/100) - wait for better setup`);
|
|
637
|
+
}
|
|
638
|
+
// Add warnings
|
|
639
|
+
if (htfAnalysis.rsi > 75) {
|
|
640
|
+
warnings.push("Higher TF RSI overbought - caution on longs");
|
|
641
|
+
}
|
|
642
|
+
else if (htfAnalysis.rsi < 25) {
|
|
643
|
+
warnings.push("Higher TF RSI oversold - caution on shorts");
|
|
644
|
+
}
|
|
645
|
+
if (volume.volumeRatio < 0.8) {
|
|
646
|
+
warnings.push("Low volume - signals may be less reliable");
|
|
647
|
+
}
|
|
648
|
+
if (pullback.quality === "extended") {
|
|
649
|
+
warnings.push("Price extended from EMAs - consider waiting for pullback");
|
|
650
|
+
}
|
|
651
|
+
// Check for conflicting signals
|
|
652
|
+
if (htfAnalysis.trend !== ltfAnalysis.trend && ltfAnalysis.trend !== "neutral") {
|
|
653
|
+
warnings.push(`Timeframe conflict: ${htfInterval} is ${htfAnalysis.trend}, ${ltfInterval} is ${ltfAnalysis.trend}`);
|
|
654
|
+
}
|
|
655
|
+
return {
|
|
656
|
+
signal,
|
|
657
|
+
score,
|
|
658
|
+
direction,
|
|
659
|
+
entry,
|
|
660
|
+
stopLoss,
|
|
661
|
+
takeProfit1,
|
|
662
|
+
takeProfit2,
|
|
663
|
+
riskRewardRatio,
|
|
664
|
+
higherTimeframe: htfAnalysis,
|
|
665
|
+
lowerTimeframe: ltfAnalysis,
|
|
666
|
+
pullback,
|
|
667
|
+
candlePattern,
|
|
668
|
+
keyLevels,
|
|
669
|
+
volume,
|
|
670
|
+
reasoning,
|
|
671
|
+
warnings,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
@@ -62,6 +62,12 @@ exports.SLASH_COMMANDS = [
|
|
|
62
62
|
shortcut: "ict",
|
|
63
63
|
action: "ict",
|
|
64
64
|
},
|
|
65
|
+
{
|
|
66
|
+
name: "/smart",
|
|
67
|
+
description: "Smart Trend Confluence - High probability multi-TF strategy (70+ score)",
|
|
68
|
+
shortcut: "sm",
|
|
69
|
+
action: "smart",
|
|
70
|
+
},
|
|
65
71
|
{
|
|
66
72
|
name: "/model",
|
|
67
73
|
description: "Switch between available AI models",
|