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.
@@ -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",