prab-cli 1.2.4 → 1.2.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.
@@ -0,0 +1,803 @@
1
+ "use strict";
2
+ /**
3
+ * Smart Trading Strategy Engine
4
+ * Combines multiple factors for high-probability trade setups
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.generateTradeSetup = generateTradeSetup;
11
+ exports.displayTradeSetup = displayTradeSetup;
12
+ exports.runStrategy = runStrategy;
13
+ /* global fetch */
14
+ const chalk_1 = __importDefault(require("chalk"));
15
+ const ora_1 = __importDefault(require("ora"));
16
+ const data_fetcher_1 = require("./data-fetcher");
17
+ const indicators_1 = require("./indicators");
18
+ const smc_indicators_1 = require("./smc-indicators");
19
+ const news_fetcher_1 = require("./news-fetcher");
20
+ // Default config
21
+ const DEFAULT_CONFIG = {
22
+ maxLeverage: 20,
23
+ riskPerTrade: 2, // 2% per trade
24
+ style: "moderate",
25
+ direction: "both",
26
+ };
27
+ // ============================================
28
+ // HELPER FUNCTIONS
29
+ // ============================================
30
+ function calculateEMAs(closes) {
31
+ const ema = (data, period) => {
32
+ const k = 2 / (period + 1);
33
+ let emaValue = data.slice(0, period).reduce((a, b) => a + b, 0) / period;
34
+ for (let i = period; i < data.length; i++) {
35
+ emaValue = data[i] * k + emaValue * (1 - k);
36
+ }
37
+ return emaValue;
38
+ };
39
+ const ema9 = ema(closes, 9);
40
+ const ema21 = ema(closes, 21);
41
+ const ema50 = ema(closes, 50);
42
+ const ema200 = closes.length >= 200 ? ema(closes, 200) : ema50;
43
+ // Check alignment
44
+ let alignment = "neutral";
45
+ if (ema9 > ema21 && ema21 > ema50 && ema50 > ema200) {
46
+ alignment = "bullish";
47
+ }
48
+ else if (ema9 < ema21 && ema21 < ema50 && ema50 < ema200) {
49
+ alignment = "bearish";
50
+ }
51
+ return { ema9, ema21, ema50, ema200, alignment };
52
+ }
53
+ function detectRSIDivergence(closes, rsiValues) {
54
+ if (rsiValues.length < 20)
55
+ return { type: "none", strength: 0 };
56
+ const recentCloses = closes.slice(-20);
57
+ const recentRSI = rsiValues.slice(-20);
58
+ // Find local lows/highs in price
59
+ const priceLows = [];
60
+ const priceHighs = [];
61
+ const rsiAtLows = [];
62
+ const rsiAtHighs = [];
63
+ for (let i = 2; i < recentCloses.length - 2; i++) {
64
+ if (recentCloses[i] < recentCloses[i - 1] &&
65
+ recentCloses[i] < recentCloses[i - 2] &&
66
+ recentCloses[i] < recentCloses[i + 1] &&
67
+ recentCloses[i] < recentCloses[i + 2]) {
68
+ priceLows.push(recentCloses[i]);
69
+ rsiAtLows.push(recentRSI[i]);
70
+ }
71
+ if (recentCloses[i] > recentCloses[i - 1] &&
72
+ recentCloses[i] > recentCloses[i - 2] &&
73
+ recentCloses[i] > recentCloses[i + 1] &&
74
+ recentCloses[i] > recentCloses[i + 2]) {
75
+ priceHighs.push(recentCloses[i]);
76
+ rsiAtHighs.push(recentRSI[i]);
77
+ }
78
+ }
79
+ // Bullish divergence: price makes lower low, RSI makes higher low
80
+ if (priceLows.length >= 2) {
81
+ const lastTwoLows = priceLows.slice(-2);
82
+ const lastTwoRSILows = rsiAtLows.slice(-2);
83
+ if (lastTwoLows[1] < lastTwoLows[0] && lastTwoRSILows[1] > lastTwoRSILows[0]) {
84
+ return { type: "bullish", strength: 70 };
85
+ }
86
+ }
87
+ // Bearish divergence: price makes higher high, RSI makes lower high
88
+ if (priceHighs.length >= 2) {
89
+ const lastTwoHighs = priceHighs.slice(-2);
90
+ const lastTwoRSIHighs = rsiAtHighs.slice(-2);
91
+ if (lastTwoHighs[1] > lastTwoHighs[0] && lastTwoRSIHighs[1] < lastTwoRSIHighs[0]) {
92
+ return { type: "bearish", strength: 70 };
93
+ }
94
+ }
95
+ return { type: "none", strength: 0 };
96
+ }
97
+ function calculateLeverage(atr, price, confidence, config) {
98
+ // ATR as percentage of price
99
+ const atrPercent = (atr / price) * 100;
100
+ // Base leverage calculation
101
+ // Higher volatility = lower leverage
102
+ let baseLeverage;
103
+ if (atrPercent > 5) {
104
+ baseLeverage = 3; // Very high volatility
105
+ }
106
+ else if (atrPercent > 3) {
107
+ baseLeverage = 5;
108
+ }
109
+ else if (atrPercent > 2) {
110
+ baseLeverage = 10;
111
+ }
112
+ else if (atrPercent > 1) {
113
+ baseLeverage = 15;
114
+ }
115
+ else {
116
+ baseLeverage = 20;
117
+ }
118
+ // Adjust based on confidence
119
+ const confidenceMultiplier = confidence >= 80 ? 1.2 : confidence >= 60 ? 1 : 0.8;
120
+ // Adjust based on style
121
+ const styleMultiplier = config.style === "conservative" ? 0.5 : config.style === "aggressive" ? 1.5 : 1;
122
+ const recommended = Math.min(Math.round(baseLeverage * confidenceMultiplier * styleMultiplier), config.maxLeverage);
123
+ return {
124
+ recommended: Math.max(1, recommended),
125
+ max: config.maxLeverage,
126
+ };
127
+ }
128
+ function calculatePositionSize(entryPrice, stopLoss, riskPercent) {
129
+ const riskPerUnit = Math.abs(entryPrice - stopLoss);
130
+ const riskRatio = riskPerUnit / entryPrice;
131
+ // Position size as % of account
132
+ // If risk per trade is 2% and stop is 5% away, position size = 2/5 = 40%
133
+ const positionSize = riskPercent / (riskRatio * 100);
134
+ return Math.min(positionSize, 100);
135
+ }
136
+ function generateTrailingStops(entry, tp1, tp2, tp3, stopLoss, direction) {
137
+ const stops = [];
138
+ if (direction === "LONG") {
139
+ // Move stop to breakeven when TP1 hit
140
+ stops.push({
141
+ trigger: tp1,
142
+ newStop: entry * 1.001, // Slightly above entry
143
+ description: "Move stop to breakeven when TP1 reached",
144
+ });
145
+ // Move stop to TP1 when TP2 hit
146
+ stops.push({
147
+ trigger: tp2,
148
+ newStop: tp1,
149
+ description: "Move stop to TP1 level when TP2 reached",
150
+ });
151
+ // Move stop to TP2 when approaching TP3
152
+ const tp2_tp3_mid = (tp2 + tp3) / 2;
153
+ stops.push({
154
+ trigger: tp2_tp3_mid,
155
+ newStop: tp2,
156
+ description: "Move stop to TP2 level when halfway to TP3",
157
+ });
158
+ }
159
+ else {
160
+ // SHORT positions - reverse logic
161
+ stops.push({
162
+ trigger: tp1,
163
+ newStop: entry * 0.999,
164
+ description: "Move stop to breakeven when TP1 reached",
165
+ });
166
+ stops.push({
167
+ trigger: tp2,
168
+ newStop: tp1,
169
+ description: "Move stop to TP1 level when TP2 reached",
170
+ });
171
+ const tp2_tp3_mid = (tp2 + tp3) / 2;
172
+ stops.push({
173
+ trigger: tp2_tp3_mid,
174
+ newStop: tp2,
175
+ description: "Move stop to TP2 level when halfway to TP3",
176
+ });
177
+ }
178
+ return stops;
179
+ }
180
+ // ============================================
181
+ // MULTI-TIMEFRAME ANALYSIS
182
+ // ============================================
183
+ async function analyzeTimeframe(symbol, interval) {
184
+ try {
185
+ const candles = await (0, data_fetcher_1.fetchOHLCV)(symbol, interval, 100);
186
+ if (candles.length < 50)
187
+ return "neutral";
188
+ const closes = candles.map((c) => c.close);
189
+ const emas = calculateEMAs(closes);
190
+ const rsi = (0, indicators_1.calculateRSI)(closes, 14);
191
+ const macd = (0, indicators_1.calculateMACD)(closes);
192
+ const trend = (0, indicators_1.analyzeTrend)(candles);
193
+ let bullishPoints = 0;
194
+ let bearishPoints = 0;
195
+ // EMA alignment
196
+ if (emas.alignment === "bullish")
197
+ bullishPoints += 2;
198
+ else if (emas.alignment === "bearish")
199
+ bearishPoints += 2;
200
+ // RSI
201
+ if (rsi.current < 40)
202
+ bullishPoints += 1;
203
+ else if (rsi.current > 60)
204
+ bearishPoints += 1;
205
+ // MACD
206
+ if (macd.crossover === "bullish")
207
+ bullishPoints += 1;
208
+ else if (macd.crossover === "bearish")
209
+ bearishPoints += 1;
210
+ // Trend
211
+ if (trend.direction === "bullish")
212
+ bullishPoints += 1;
213
+ else if (trend.direction === "bearish")
214
+ bearishPoints += 1;
215
+ if (bullishPoints >= 3)
216
+ return "bullish";
217
+ if (bearishPoints >= 3)
218
+ return "bearish";
219
+ return "neutral";
220
+ }
221
+ catch {
222
+ return "neutral";
223
+ }
224
+ }
225
+ async function getMultiTimeframeAnalysis(symbol) {
226
+ const [m15, h1, h4] = await Promise.all([
227
+ analyzeTimeframe(symbol, "15m"),
228
+ analyzeTimeframe(symbol, "1h"),
229
+ analyzeTimeframe(symbol, "4h"),
230
+ ]);
231
+ // Confirmed if all timeframes align
232
+ const confirmed = (m15 === "bullish" && h1 === "bullish" && h4 === "bullish") ||
233
+ (m15 === "bearish" && h1 === "bearish" && h4 === "bearish");
234
+ return { m15, h1, h4, confirmed };
235
+ }
236
+ // ============================================
237
+ // NEWS SENTIMENT ANALYSIS
238
+ // ============================================
239
+ async function getNewsSentiment(symbol) {
240
+ try {
241
+ // Extract coin from symbol (BTCUSDT -> BTC)
242
+ const coin = symbol.replace(/USDT$/, "").replace(/USD$/, "");
243
+ const newsResult = await (0, news_fetcher_1.fetchCryptoNews)(coin);
244
+ if (newsResult.news.length === 0) {
245
+ return { score: 50, status: "No recent news" };
246
+ }
247
+ const positive = newsResult.news.filter((n) => n.sentiment === "positive").length;
248
+ const negative = newsResult.news.filter((n) => n.sentiment === "negative").length;
249
+ const total = newsResult.news.length;
250
+ // Score: 0-100, 50 is neutral
251
+ const score = 50 + ((positive - negative) / total) * 50;
252
+ let status;
253
+ if (score > 65)
254
+ status = "Bullish news sentiment";
255
+ else if (score < 35)
256
+ status = "Bearish news sentiment";
257
+ else
258
+ status = "Neutral news sentiment";
259
+ return { score: Math.round(score), status };
260
+ }
261
+ catch {
262
+ return { score: 50, status: "Unable to fetch news" };
263
+ }
264
+ }
265
+ // ============================================
266
+ // VOLUME & WHALE ANALYSIS
267
+ // ============================================
268
+ async function getVolumeWhaleScore(candles, symbol) {
269
+ const volumes = candles.map((c) => c.volume);
270
+ const recentVolume = volumes.slice(-5).reduce((a, b) => a + b, 0) / 5;
271
+ const avgVolume = volumes.slice(-20).reduce((a, b) => a + b, 0) / 20;
272
+ const volumeRatio = recentVolume / avgVolume;
273
+ // Analyze recent candle volume spikes
274
+ const closes = candles.map((c) => c.close);
275
+ const lastCandle = candles[candles.length - 1];
276
+ const priceUp = lastCandle.close > lastCandle.open;
277
+ let score = 50;
278
+ let status = "Normal volume";
279
+ if (volumeRatio > 2) {
280
+ if (priceUp) {
281
+ score = 75;
282
+ status = "High volume buying (whale accumulation possible)";
283
+ }
284
+ else {
285
+ score = 25;
286
+ status = "High volume selling (whale distribution possible)";
287
+ }
288
+ }
289
+ else if (volumeRatio > 1.5) {
290
+ if (priceUp) {
291
+ score = 65;
292
+ status = "Above average buying volume";
293
+ }
294
+ else {
295
+ score = 35;
296
+ status = "Above average selling volume";
297
+ }
298
+ }
299
+ else if (volumeRatio < 0.5) {
300
+ score = 50;
301
+ status = "Low volume - weak move";
302
+ }
303
+ return { score, status };
304
+ }
305
+ // ============================================
306
+ // MAIN STRATEGY FUNCTION
307
+ // ============================================
308
+ async function generateTradeSetup(symbol, config = DEFAULT_CONFIG) {
309
+ const spinner = (0, ora_1.default)(`Analyzing ${symbol} for trade setup...`).start();
310
+ try {
311
+ // Fetch main data (1h timeframe)
312
+ spinner.text = "Fetching market data...";
313
+ const data = await (0, data_fetcher_1.fetchCryptoData)(symbol, "1h", 200);
314
+ const candles = data.candles;
315
+ const closes = candles.map((c) => c.close);
316
+ const highs = candles.map((c) => c.high);
317
+ const lows = candles.map((c) => c.low);
318
+ const currentPrice = data.currentPrice;
319
+ // =====================
320
+ // TECHNICAL ANALYSIS
321
+ // =====================
322
+ spinner.text = "Calculating technical indicators...";
323
+ // EMAs
324
+ const emas = calculateEMAs(closes);
325
+ // RSI
326
+ const rsi = (0, indicators_1.calculateRSI)(closes, 14);
327
+ const rsiDivergence = detectRSIDivergence(closes, rsi.values);
328
+ // MACD
329
+ const macd = (0, indicators_1.calculateMACD)(closes);
330
+ // Bollinger Bands
331
+ const bb = (0, indicators_1.calculateBollingerBands)(closes);
332
+ // ATR for volatility
333
+ const atr = (0, indicators_1.calculateATR)(candles, 14);
334
+ // Trend
335
+ const trend = (0, indicators_1.analyzeTrend)(candles);
336
+ // Support/Resistance
337
+ const sr = (0, indicators_1.calculateSupportResistance)(candles);
338
+ // Calculate technical score
339
+ let technicalScore = 50;
340
+ let technicalBias = "neutral";
341
+ // EMA scoring
342
+ if (emas.alignment === "bullish") {
343
+ technicalScore += 15;
344
+ technicalBias = "bullish";
345
+ }
346
+ else if (emas.alignment === "bearish") {
347
+ technicalScore -= 15;
348
+ technicalBias = "bearish";
349
+ }
350
+ // Price vs EMAs
351
+ if (currentPrice > emas.ema9 && currentPrice > emas.ema21) {
352
+ technicalScore += 5;
353
+ }
354
+ else if (currentPrice < emas.ema9 && currentPrice < emas.ema21) {
355
+ technicalScore -= 5;
356
+ }
357
+ // RSI scoring
358
+ if (rsi.current < 30) {
359
+ technicalScore += 10; // Oversold = bullish
360
+ }
361
+ else if (rsi.current > 70) {
362
+ technicalScore -= 10; // Overbought = bearish
363
+ }
364
+ // RSI divergence
365
+ if (rsiDivergence.type === "bullish") {
366
+ technicalScore += 15;
367
+ }
368
+ else if (rsiDivergence.type === "bearish") {
369
+ technicalScore -= 15;
370
+ }
371
+ // MACD scoring
372
+ if (macd.crossover === "bullish") {
373
+ technicalScore += 10;
374
+ }
375
+ else if (macd.crossover === "bearish") {
376
+ technicalScore -= 10;
377
+ }
378
+ // Bollinger Band scoring
379
+ if (currentPrice <= bb.current.lower) {
380
+ technicalScore += 10; // At lower band = potential bounce
381
+ }
382
+ else if (currentPrice >= bb.current.upper) {
383
+ technicalScore -= 10; // At upper band = potential rejection
384
+ }
385
+ technicalScore = Math.max(0, Math.min(100, technicalScore));
386
+ // =====================
387
+ // SMC ANALYSIS
388
+ // =====================
389
+ spinner.text = "Analyzing Smart Money Concepts...";
390
+ const smcCandles = candles.slice(-50);
391
+ const orderBlocks = (0, smc_indicators_1.findOrderBlocks)(smcCandles);
392
+ const fvgs = (0, smc_indicators_1.findFairValueGaps)(candles.slice(-30));
393
+ // Note: liquidityZones requires swingPoints, but we're not using it for scoring
394
+ // const swingPoints = findSwingPoints(smcCandles);
395
+ // const liquidityZones = findLiquidityZones(smcCandles, swingPoints);
396
+ let smcScore = 50;
397
+ let smcStatus = "No clear SMC setup";
398
+ // Check if price is near bullish order block
399
+ const bullishOBs = orderBlocks.filter((ob) => ob.type === "bullish");
400
+ const bearishOBs = orderBlocks.filter((ob) => ob.type === "bearish");
401
+ for (const ob of bullishOBs) {
402
+ if (currentPrice >= ob.bottom && currentPrice <= ob.top * 1.01) {
403
+ smcScore += 20;
404
+ smcStatus = "Price at bullish order block";
405
+ break;
406
+ }
407
+ }
408
+ for (const ob of bearishOBs) {
409
+ if (currentPrice <= ob.top && currentPrice >= ob.bottom * 0.99) {
410
+ smcScore -= 20;
411
+ smcStatus = "Price at bearish order block";
412
+ break;
413
+ }
414
+ }
415
+ // Check FVG
416
+ const bullishFVGs = fvgs.filter((f) => f.type === "bullish" && !f.filled);
417
+ const bearishFVGs = fvgs.filter((f) => f.type === "bearish" && !f.filled);
418
+ if (bullishFVGs.length > 0) {
419
+ const nearestBullishFVG = bullishFVGs[bullishFVGs.length - 1];
420
+ if (currentPrice >= nearestBullishFVG.bottom && currentPrice <= nearestBullishFVG.top) {
421
+ smcScore += 15;
422
+ smcStatus = "Price filling bullish FVG";
423
+ }
424
+ }
425
+ if (bearishFVGs.length > 0) {
426
+ const nearestBearishFVG = bearishFVGs[bearishFVGs.length - 1];
427
+ if (currentPrice >= nearestBearishFVG.bottom && currentPrice <= nearestBearishFVG.top) {
428
+ smcScore -= 15;
429
+ smcStatus = "Price filling bearish FVG";
430
+ }
431
+ }
432
+ smcScore = Math.max(0, Math.min(100, smcScore));
433
+ // =====================
434
+ // VOLUME & WHALE ANALYSIS
435
+ // =====================
436
+ spinner.text = "Analyzing volume and whale activity...";
437
+ const volumeWhale = await getVolumeWhaleScore(candles, symbol);
438
+ // =====================
439
+ // NEWS SENTIMENT
440
+ // =====================
441
+ spinner.text = "Checking news sentiment...";
442
+ const news = await getNewsSentiment(symbol);
443
+ // =====================
444
+ // MULTI-TIMEFRAME
445
+ // =====================
446
+ spinner.text = "Performing multi-timeframe analysis...";
447
+ const mtf = await getMultiTimeframeAnalysis(symbol);
448
+ // =====================
449
+ // CALCULATE OVERALL SCORE & DIRECTION
450
+ // =====================
451
+ const overallScore = Math.round(technicalScore * 0.4 + smcScore * 0.25 + volumeWhale.score * 0.2 + news.score * 0.15);
452
+ // Determine direction
453
+ let direction = "NO_TRADE";
454
+ const confidence = overallScore;
455
+ if (config.direction === "both" || config.direction === "long") {
456
+ if (overallScore >= 65 && (mtf.h1 === "bullish" || mtf.h4 === "bullish")) {
457
+ direction = "LONG";
458
+ }
459
+ }
460
+ if (config.direction === "both" || config.direction === "short") {
461
+ if (overallScore <= 35 && (mtf.h1 === "bearish" || mtf.h4 === "bearish")) {
462
+ direction = "SHORT";
463
+ }
464
+ }
465
+ // =====================
466
+ // CALCULATE ENTRY, SL, TP
467
+ // =====================
468
+ let entryPrice = currentPrice;
469
+ let stopLoss;
470
+ let tp1, tp2, tp3;
471
+ const atrValue = atr.current;
472
+ if (direction === "LONG") {
473
+ // Entry at current price or slightly below
474
+ entryPrice = currentPrice;
475
+ // Stop loss below recent support or 1.5x ATR
476
+ const supportStop = sr.nearestSupport * 0.995;
477
+ const atrStop = currentPrice - atrValue * 1.5;
478
+ stopLoss = Math.max(supportStop, atrStop);
479
+ // Take profits
480
+ const riskAmount = entryPrice - stopLoss;
481
+ tp1 = entryPrice + riskAmount * 1.5; // 1:1.5 RR
482
+ tp2 = entryPrice + riskAmount * 2.5; // 1:2.5 RR
483
+ tp3 = entryPrice + riskAmount * 4; // 1:4 RR
484
+ }
485
+ else if (direction === "SHORT") {
486
+ entryPrice = currentPrice;
487
+ // Stop loss above recent resistance or 1.5x ATR
488
+ const resistanceStop = sr.nearestResistance * 1.005;
489
+ const atrStop = currentPrice + atrValue * 1.5;
490
+ stopLoss = Math.min(resistanceStop, atrStop);
491
+ // Take profits
492
+ const riskAmount = stopLoss - entryPrice;
493
+ tp1 = entryPrice - riskAmount * 1.5;
494
+ tp2 = entryPrice - riskAmount * 2.5;
495
+ tp3 = entryPrice - riskAmount * 4;
496
+ }
497
+ else {
498
+ // No trade
499
+ stopLoss = currentPrice;
500
+ tp1 = currentPrice;
501
+ tp2 = currentPrice;
502
+ tp3 = currentPrice;
503
+ }
504
+ // =====================
505
+ // RISK MANAGEMENT
506
+ // =====================
507
+ const leverage = calculateLeverage(atrValue, currentPrice, confidence, config);
508
+ const riskRewardRatio = direction !== "NO_TRADE" ? Math.abs(tp2 - entryPrice) / Math.abs(entryPrice - stopLoss) : 0;
509
+ const positionSize = calculatePositionSize(entryPrice, stopLoss, config.riskPerTrade);
510
+ // =====================
511
+ // TRAILING STOPS
512
+ // =====================
513
+ const trailingStops = direction !== "NO_TRADE"
514
+ ? generateTrailingStops(entryPrice, tp1, tp2, tp3, stopLoss, direction)
515
+ : [];
516
+ // =====================
517
+ // ALERT STATUS
518
+ // =====================
519
+ let alertStatus = "FORMING";
520
+ const alertReasons = [];
521
+ if (direction !== "NO_TRADE") {
522
+ if (mtf.confirmed) {
523
+ alertStatus = "READY";
524
+ alertReasons.push("Multi-timeframe alignment confirmed");
525
+ }
526
+ if (confidence >= 70) {
527
+ alertStatus = "READY";
528
+ alertReasons.push(`High confidence setup (${confidence}%)`);
529
+ }
530
+ if (rsiDivergence.type !== "none") {
531
+ alertReasons.push(`RSI ${rsiDivergence.type} divergence detected`);
532
+ }
533
+ if (smcScore > 60 || smcScore < 40) {
534
+ alertReasons.push(smcStatus);
535
+ }
536
+ }
537
+ else {
538
+ alertReasons.push("No clear setup - waiting for better conditions");
539
+ }
540
+ // =====================
541
+ // WARNINGS
542
+ // =====================
543
+ const warnings = [];
544
+ if (atrValue / currentPrice > 0.03) {
545
+ warnings.push("High volatility - consider reducing position size");
546
+ }
547
+ if (!mtf.confirmed && direction !== "NO_TRADE") {
548
+ warnings.push("Multi-timeframe not aligned - trade with caution");
549
+ }
550
+ if (rsi.current > 80 && direction === "LONG") {
551
+ warnings.push("RSI extremely overbought - potential pullback");
552
+ }
553
+ if (rsi.current < 20 && direction === "SHORT") {
554
+ warnings.push("RSI extremely oversold - potential bounce");
555
+ }
556
+ spinner.succeed(`Analysis complete for ${symbol}`);
557
+ return {
558
+ symbol: data.symbol,
559
+ direction,
560
+ confidence,
561
+ entryPrice,
562
+ stopLoss,
563
+ takeProfit1: tp1,
564
+ takeProfit2: tp2,
565
+ takeProfit3: tp3,
566
+ recommendedLeverage: leverage.recommended,
567
+ maxLeverage: leverage.max,
568
+ riskRewardRatio,
569
+ positionSizePercent: positionSize,
570
+ technicalScore,
571
+ smcScore,
572
+ volumeWhaleScore: volumeWhale.score,
573
+ newsScore: news.score,
574
+ overallScore,
575
+ mtfAlignment: {
576
+ m15: mtf.m15,
577
+ h1: mtf.h1,
578
+ h4: mtf.h4,
579
+ },
580
+ mtfConfirmed: mtf.confirmed,
581
+ trailingStopLevels: trailingStops,
582
+ alertStatus,
583
+ alertReasons,
584
+ analysis: {
585
+ emaStatus: `EMA alignment: ${emas.alignment}`,
586
+ rsiStatus: `RSI: ${rsi.current.toFixed(1)} (${rsi.current < 30 ? "oversold" : rsi.current > 70 ? "overbought" : "neutral"})`,
587
+ macdStatus: `MACD: ${macd.crossover} crossover`,
588
+ bbStatus: `BB: Price at ${currentPrice <= bb.current.lower ? "lower" : currentPrice >= bb.current.upper ? "upper" : "middle"} band`,
589
+ smcStatus,
590
+ volumeStatus: volumeWhale.status,
591
+ newsStatus: news.status,
592
+ },
593
+ warnings,
594
+ timestamp: Date.now(),
595
+ };
596
+ }
597
+ catch (error) {
598
+ spinner.fail(`Failed to analyze ${symbol}`);
599
+ throw error;
600
+ }
601
+ }
602
+ // ============================================
603
+ // DISPLAY FUNCTION
604
+ // ============================================
605
+ function displayTradeSetup(setup) {
606
+ console.log("");
607
+ // Header based on direction
608
+ const headerColor = setup.direction === "LONG"
609
+ ? chalk_1.default.green
610
+ : setup.direction === "SHORT"
611
+ ? chalk_1.default.red
612
+ : chalk_1.default.yellow;
613
+ const directionEmoji = setup.direction === "LONG" ? "🟢" : setup.direction === "SHORT" ? "🔴" : "⚪";
614
+ console.log(headerColor(" ╔═══════════════════════════════════════════════════════════════════════╗"));
615
+ console.log(headerColor(` ║ ${directionEmoji} SMART TRADING STRATEGY - ${setup.symbol.padEnd(20)} ║`));
616
+ console.log(headerColor(" ╚═══════════════════════════════════════════════════════════════════════╝"));
617
+ console.log("");
618
+ // Direction and Confidence
619
+ const directionBadge = setup.direction === "LONG"
620
+ ? chalk_1.default.bgGreen.white(" LONG ")
621
+ : setup.direction === "SHORT"
622
+ ? chalk_1.default.bgRed.white(" SHORT ")
623
+ : chalk_1.default.bgYellow.black(" NO TRADE ");
624
+ const confidenceBar = getConfidenceBar(setup.confidence);
625
+ console.log(` ${directionBadge} Confidence: ${confidenceBar} ${setup.confidence}%`);
626
+ console.log("");
627
+ // Alert Status
628
+ const alertColor = setup.alertStatus === "READY"
629
+ ? chalk_1.default.green
630
+ : setup.alertStatus === "FORMING"
631
+ ? chalk_1.default.yellow
632
+ : chalk_1.default.gray;
633
+ console.log(` ${chalk_1.default.cyan("📢 Alert:")} ${alertColor(setup.alertStatus)}`);
634
+ for (const reason of setup.alertReasons) {
635
+ console.log(` ${chalk_1.default.dim("•")} ${chalk_1.default.gray(reason)}`);
636
+ }
637
+ console.log("");
638
+ if (setup.direction !== "NO_TRADE") {
639
+ // Entry & Exit Box
640
+ console.log(chalk_1.default.cyan(" ┌─────────────────────────────────────────────────────────────────────┐"));
641
+ console.log(chalk_1.default.cyan(" │ 📍 ENTRY & EXIT LEVELS │"));
642
+ console.log(chalk_1.default.cyan(" ├─────────────────────────────────────────────────────────────────────┤"));
643
+ console.log(` │ ${chalk_1.default.yellow("Entry Price:")} ${chalk_1.default.white.bold(formatPrice(setup.entryPrice))}`);
644
+ console.log(` │ ${chalk_1.default.red("Stop Loss:")} ${chalk_1.default.red.bold(formatPrice(setup.stopLoss))} ${chalk_1.default.dim(`(${((Math.abs(setup.entryPrice - setup.stopLoss) / setup.entryPrice) * 100).toFixed(2)}%)`)}`);
645
+ console.log(` │`);
646
+ console.log(` │ ${chalk_1.default.green("Take Profit 1:")} ${chalk_1.default.green(formatPrice(setup.takeProfit1))} ${chalk_1.default.dim("(1.5R)")}`);
647
+ console.log(` │ ${chalk_1.default.green("Take Profit 2:")} ${chalk_1.default.green(formatPrice(setup.takeProfit2))} ${chalk_1.default.dim("(2.5R)")}`);
648
+ console.log(` │ ${chalk_1.default.green("Take Profit 3:")} ${chalk_1.default.green(formatPrice(setup.takeProfit3))} ${chalk_1.default.dim("(4R)")}`);
649
+ console.log(chalk_1.default.cyan(" └─────────────────────────────────────────────────────────────────────┘"));
650
+ console.log("");
651
+ // Risk Management Box
652
+ console.log(chalk_1.default.magenta(" ┌─────────────────────────────────────────────────────────────────────┐"));
653
+ console.log(chalk_1.default.magenta(" │ ⚖️ RISK MANAGEMENT │"));
654
+ console.log(chalk_1.default.magenta(" ├─────────────────────────────────────────────────────────────────────┤"));
655
+ const leverageBadge = setup.recommendedLeverage <= 5
656
+ ? chalk_1.default.green
657
+ : setup.recommendedLeverage <= 10
658
+ ? chalk_1.default.yellow
659
+ : chalk_1.default.red;
660
+ console.log(` │ ${chalk_1.default.yellow("Recommended Leverage:")} ${leverageBadge.bold(`${setup.recommendedLeverage}x`)} ${chalk_1.default.dim(`(max: ${setup.maxLeverage}x)`)}`);
661
+ console.log(` │ ${chalk_1.default.yellow("Risk/Reward Ratio:")} ${chalk_1.default.cyan.bold(`1:${setup.riskRewardRatio.toFixed(1)}`)}`);
662
+ console.log(` │ ${chalk_1.default.yellow("Position Size:")} ${chalk_1.default.white(`${setup.positionSizePercent.toFixed(1)}% of account`)}`);
663
+ console.log(chalk_1.default.magenta(" └─────────────────────────────────────────────────────────────────────┘"));
664
+ console.log("");
665
+ // Trailing Stop Box
666
+ console.log(chalk_1.default.blue(" ┌─────────────────────────────────────────────────────────────────────┐"));
667
+ console.log(chalk_1.default.blue(" │ 📈 TRAILING STOP PLAN │"));
668
+ console.log(chalk_1.default.blue(" ├─────────────────────────────────────────────────────────────────────┤"));
669
+ for (let i = 0; i < setup.trailingStopLevels.length; i++) {
670
+ const ts = setup.trailingStopLevels[i];
671
+ console.log(` │ ${chalk_1.default.cyan(`Step ${i + 1}:`)} When price hits ${chalk_1.default.yellow(formatPrice(ts.trigger))}`);
672
+ console.log(` │ → Move stop to ${chalk_1.default.green(formatPrice(ts.newStop))}`);
673
+ console.log(` │ ${chalk_1.default.dim(ts.description)}`);
674
+ if (i < setup.trailingStopLevels.length - 1)
675
+ console.log(` │`);
676
+ }
677
+ console.log(chalk_1.default.blue(" └─────────────────────────────────────────────────────────────────────┘"));
678
+ console.log("");
679
+ }
680
+ // Multi-Timeframe Analysis
681
+ console.log(chalk_1.default.yellow(" ┌─────────────────────────────────────────────────────────────────────┐"));
682
+ console.log(chalk_1.default.yellow(" │ ⏰ MULTI-TIMEFRAME ANALYSIS │"));
683
+ console.log(chalk_1.default.yellow(" ├─────────────────────────────────────────────────────────────────────┤"));
684
+ const mtfIcon = (bias) => bias === "bullish" ? chalk_1.default.green("▲") : bias === "bearish" ? chalk_1.default.red("▼") : chalk_1.default.gray("●");
685
+ const mtfColor = (bias) => bias === "bullish" ? chalk_1.default.green : bias === "bearish" ? chalk_1.default.red : chalk_1.default.gray;
686
+ console.log(` │ ${chalk_1.default.dim("15min:")} ${mtfIcon(setup.mtfAlignment.m15)} ${mtfColor(setup.mtfAlignment.m15)(setup.mtfAlignment.m15.padEnd(10))} ${chalk_1.default.dim("1hour:")} ${mtfIcon(setup.mtfAlignment.h1)} ${mtfColor(setup.mtfAlignment.h1)(setup.mtfAlignment.h1.padEnd(10))} ${chalk_1.default.dim("4hour:")} ${mtfIcon(setup.mtfAlignment.h4)} ${mtfColor(setup.mtfAlignment.h4)(setup.mtfAlignment.h4)}`);
687
+ console.log(` │`);
688
+ console.log(` │ ${setup.mtfConfirmed ? chalk_1.default.green("✓ All timeframes aligned!") : chalk_1.default.yellow("⚠ Timeframes not fully aligned")}`);
689
+ console.log(chalk_1.default.yellow(" └─────────────────────────────────────────────────────────────────────┘"));
690
+ console.log("");
691
+ // Score Breakdown
692
+ console.log(chalk_1.default.gray(" ┌─────────────────────────────────────────────────────────────────────┐"));
693
+ console.log(chalk_1.default.gray(" │ 📊 SCORE BREAKDOWN │"));
694
+ console.log(chalk_1.default.gray(" ├─────────────────────────────────────────────────────────────────────┤"));
695
+ console.log(` │ ${chalk_1.default.dim("Technical (40%):")} ${getScoreBar(setup.technicalScore)} ${setup.technicalScore}%`);
696
+ console.log(` │ ${chalk_1.default.dim("SMC (25%):")} ${getScoreBar(setup.smcScore)} ${setup.smcScore}%`);
697
+ console.log(` │ ${chalk_1.default.dim("Volume (20%):")} ${getScoreBar(setup.volumeWhaleScore)} ${setup.volumeWhaleScore}%`);
698
+ console.log(` │ ${chalk_1.default.dim("News (15%):")} ${getScoreBar(setup.newsScore)} ${setup.newsScore}%`);
699
+ console.log(` │ ${"─".repeat(50)}`);
700
+ console.log(` │ ${chalk_1.default.white.bold("Overall:")} ${getScoreBar(setup.overallScore)} ${chalk_1.default.bold(setup.overallScore + "%")}`);
701
+ console.log(chalk_1.default.gray(" └─────────────────────────────────────────────────────────────────────┘"));
702
+ console.log("");
703
+ // Detailed Analysis
704
+ console.log(chalk_1.default.gray(" ┌─────────────────────────────────────────────────────────────────────┐"));
705
+ console.log(chalk_1.default.gray(" │ 🔍 DETAILED ANALYSIS │"));
706
+ console.log(chalk_1.default.gray(" ├─────────────────────────────────────────────────────────────────────┤"));
707
+ console.log(` │ ${chalk_1.default.cyan("•")} ${setup.analysis.emaStatus}`);
708
+ console.log(` │ ${chalk_1.default.cyan("•")} ${setup.analysis.rsiStatus}`);
709
+ console.log(` │ ${chalk_1.default.cyan("•")} ${setup.analysis.macdStatus}`);
710
+ console.log(` │ ${chalk_1.default.cyan("•")} ${setup.analysis.bbStatus}`);
711
+ console.log(` │ ${chalk_1.default.cyan("•")} ${setup.analysis.smcStatus}`);
712
+ console.log(` │ ${chalk_1.default.cyan("•")} ${setup.analysis.volumeStatus}`);
713
+ console.log(` │ ${chalk_1.default.cyan("•")} ${setup.analysis.newsStatus}`);
714
+ console.log(chalk_1.default.gray(" └─────────────────────────────────────────────────────────────────────┘"));
715
+ console.log("");
716
+ // Warnings
717
+ if (setup.warnings.length > 0) {
718
+ console.log(chalk_1.default.red(" ┌─────────────────────────────────────────────────────────────────────┐"));
719
+ console.log(chalk_1.default.red(" │ ⚠️ WARNINGS │"));
720
+ console.log(chalk_1.default.red(" ├─────────────────────────────────────────────────────────────────────┤"));
721
+ for (const warning of setup.warnings) {
722
+ console.log(` │ ${chalk_1.default.yellow("!")} ${chalk_1.default.yellow(warning)}`);
723
+ }
724
+ console.log(chalk_1.default.red(" └─────────────────────────────────────────────────────────────────────┘"));
725
+ console.log("");
726
+ }
727
+ // Disclaimer
728
+ console.log(chalk_1.default.dim.italic(" ⚠️ This is not financial advice. Always manage your risk and DYOR."));
729
+ console.log(chalk_1.default.dim.italic(" 💡 Tip: Wait for price to reach entry zone before executing trade."));
730
+ console.log("");
731
+ }
732
+ // Helper functions for display
733
+ function formatPrice(price) {
734
+ if (price < 0.001)
735
+ return `$${price.toFixed(8)}`;
736
+ if (price < 1)
737
+ return `$${price.toFixed(6)}`;
738
+ if (price < 100)
739
+ return `$${price.toFixed(4)}`;
740
+ return `$${price.toLocaleString(undefined, { maximumFractionDigits: 2 })}`;
741
+ }
742
+ function getScoreBar(score, width = 15) {
743
+ const filled = Math.round((score / 100) * width);
744
+ const empty = width - filled;
745
+ let color = chalk_1.default.green;
746
+ if (score < 40)
747
+ color = chalk_1.default.red;
748
+ else if (score < 60)
749
+ color = chalk_1.default.yellow;
750
+ return color("█".repeat(filled)) + chalk_1.default.gray("░".repeat(empty));
751
+ }
752
+ function getConfidenceBar(confidence) {
753
+ const width = 20;
754
+ const filled = Math.round((confidence / 100) * width);
755
+ const empty = width - filled;
756
+ let color = chalk_1.default.green;
757
+ if (confidence < 40)
758
+ color = chalk_1.default.red;
759
+ else if (confidence < 60)
760
+ color = chalk_1.default.yellow;
761
+ return "[" + color("█".repeat(filled)) + chalk_1.default.gray("░".repeat(empty)) + "]";
762
+ }
763
+ // ============================================
764
+ // MAIN RUNNER FUNCTION
765
+ // ============================================
766
+ async function runStrategy(symbol, config) {
767
+ const fullConfig = {
768
+ ...DEFAULT_CONFIG,
769
+ ...config,
770
+ };
771
+ try {
772
+ const setup = await generateTradeSetup(symbol, fullConfig);
773
+ displayTradeSetup(setup);
774
+ return setup;
775
+ }
776
+ catch (error) {
777
+ console.log("");
778
+ console.log(chalk_1.default.red(" ╔═══════════════════════════════════════════════════════════════════════╗"));
779
+ console.log(chalk_1.default.red(" ║ ❌ STRATEGY ERROR ║"));
780
+ console.log(chalk_1.default.red(" ╚═══════════════════════════════════════════════════════════════════════╝"));
781
+ console.log("");
782
+ if (error.message?.includes("Invalid symbol") || error.message?.includes("Failed to fetch")) {
783
+ console.log(chalk_1.default.yellow(` ⚠️ Symbol not found: "${symbol.toUpperCase()}"`));
784
+ console.log("");
785
+ // Get similar symbols from Binance
786
+ const suggestions = await (0, data_fetcher_1.findSimilarSymbols)(symbol, 8);
787
+ if (suggestions.length > 0) {
788
+ console.log(chalk_1.default.gray(" Did you mean one of these?"));
789
+ console.log("");
790
+ console.log(chalk_1.default.cyan(` ${suggestions.join(", ")}`));
791
+ console.log("");
792
+ }
793
+ console.log(chalk_1.default.gray(" This symbol is not available on Binance exchange."));
794
+ console.log(chalk_1.default.gray(" Note: This tool only supports Binance USDT trading pairs."));
795
+ console.log(chalk_1.default.gray(" The token might exist on other exchanges (KuCoin, Bybit, etc.)"));
796
+ }
797
+ else {
798
+ console.log(chalk_1.default.yellow(` ⚠️ ${error.message || "An unexpected error occurred"}`));
799
+ }
800
+ console.log("");
801
+ return null;
802
+ }
803
+ }