prab-cli 1.2.4 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +84 -0
- package/dist/lib/chat-handler.js +39 -5
- package/dist/lib/crypto/index.js +11 -1
- package/dist/lib/crypto/market-scanner.js +569 -0
- package/dist/lib/crypto/news-fetcher.js +394 -0
- package/dist/lib/crypto/signal-generator.js +76 -10
- package/dist/lib/crypto/smc-analyzer.js +39 -7
- package/dist/lib/crypto/whale-tracker.js +508 -0
- package/dist/lib/slash-commands.js +18 -0
- package/dist/lib/ui.js +45 -1
- package/package.json +1 -1
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Market Scanner - Find Best Trading Opportunities
|
|
4
|
+
* Scans top cryptocurrencies and scores them based on multiple criteria
|
|
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.displayScanResults = displayScanResults;
|
|
11
|
+
exports.runMarketScanner = runMarketScanner;
|
|
12
|
+
/* global fetch */
|
|
13
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
14
|
+
const ora_1 = __importDefault(require("ora"));
|
|
15
|
+
const data_fetcher_1 = require("./data-fetcher");
|
|
16
|
+
const indicators_1 = require("./indicators");
|
|
17
|
+
// ============================================
|
|
18
|
+
// TOP CRYPTOCURRENCIES
|
|
19
|
+
// ============================================
|
|
20
|
+
// Top 100 by market cap (symbols for Binance)
|
|
21
|
+
const TOP_CRYPTOS = [
|
|
22
|
+
"BTC",
|
|
23
|
+
"ETH",
|
|
24
|
+
"BNB",
|
|
25
|
+
"SOL",
|
|
26
|
+
"XRP",
|
|
27
|
+
"ADA",
|
|
28
|
+
"AVAX",
|
|
29
|
+
"DOGE",
|
|
30
|
+
"DOT",
|
|
31
|
+
"LINK",
|
|
32
|
+
"MATIC",
|
|
33
|
+
"SHIB",
|
|
34
|
+
"LTC",
|
|
35
|
+
"ATOM",
|
|
36
|
+
"UNI",
|
|
37
|
+
"XLM",
|
|
38
|
+
"ETC",
|
|
39
|
+
"FIL",
|
|
40
|
+
"NEAR",
|
|
41
|
+
"APT",
|
|
42
|
+
"ARB",
|
|
43
|
+
"OP",
|
|
44
|
+
"INJ",
|
|
45
|
+
"IMX",
|
|
46
|
+
"VET",
|
|
47
|
+
"ALGO",
|
|
48
|
+
"FTM",
|
|
49
|
+
"SAND",
|
|
50
|
+
"MANA",
|
|
51
|
+
"AXS",
|
|
52
|
+
"GALA",
|
|
53
|
+
"THETA",
|
|
54
|
+
"EGLD",
|
|
55
|
+
"EOS",
|
|
56
|
+
"AAVE",
|
|
57
|
+
"MKR",
|
|
58
|
+
"GRT",
|
|
59
|
+
"SNX",
|
|
60
|
+
"CRV",
|
|
61
|
+
"LDO",
|
|
62
|
+
"RUNE",
|
|
63
|
+
"KAVA",
|
|
64
|
+
"ZEC",
|
|
65
|
+
"DASH",
|
|
66
|
+
"NEO",
|
|
67
|
+
"XTZ",
|
|
68
|
+
"IOTA",
|
|
69
|
+
"WAVES",
|
|
70
|
+
"CHZ",
|
|
71
|
+
"ENJ",
|
|
72
|
+
"BAT",
|
|
73
|
+
"1INCH",
|
|
74
|
+
"COMP",
|
|
75
|
+
"YFI",
|
|
76
|
+
"SUSHI",
|
|
77
|
+
"ZRX",
|
|
78
|
+
"ANKR",
|
|
79
|
+
"STORJ",
|
|
80
|
+
"SKL",
|
|
81
|
+
"CELO",
|
|
82
|
+
"ICX",
|
|
83
|
+
"ONT",
|
|
84
|
+
"QTUM",
|
|
85
|
+
"ZIL",
|
|
86
|
+
"IOST",
|
|
87
|
+
"SXP",
|
|
88
|
+
"RSR",
|
|
89
|
+
"REN",
|
|
90
|
+
"BAND",
|
|
91
|
+
"ALPHA",
|
|
92
|
+
"DENT",
|
|
93
|
+
"CELR",
|
|
94
|
+
"OGN",
|
|
95
|
+
"NKN",
|
|
96
|
+
"ARPA",
|
|
97
|
+
"CTSI",
|
|
98
|
+
"SLP",
|
|
99
|
+
"TLM",
|
|
100
|
+
"REEF",
|
|
101
|
+
"DODO",
|
|
102
|
+
"LINA",
|
|
103
|
+
"SUPER",
|
|
104
|
+
"TVK",
|
|
105
|
+
"BADGER",
|
|
106
|
+
"AUCTION",
|
|
107
|
+
"MASK",
|
|
108
|
+
"FRONT",
|
|
109
|
+
"AKRO",
|
|
110
|
+
"SUI",
|
|
111
|
+
"SEI",
|
|
112
|
+
"UNFI",
|
|
113
|
+
"WING",
|
|
114
|
+
"SFP",
|
|
115
|
+
"TKO",
|
|
116
|
+
"BURGER",
|
|
117
|
+
"PEPE",
|
|
118
|
+
"WIF",
|
|
119
|
+
"BONK",
|
|
120
|
+
"FLOKI",
|
|
121
|
+
"WLD",
|
|
122
|
+
];
|
|
123
|
+
/**
|
|
124
|
+
* Format symbol for display (BTCUSDT -> BTC/USD)
|
|
125
|
+
*/
|
|
126
|
+
function formatSymbolDisplay(binanceSymbol) {
|
|
127
|
+
// Remove USDT suffix and add /USD for cleaner display
|
|
128
|
+
if (binanceSymbol.endsWith("USDT")) {
|
|
129
|
+
return binanceSymbol.slice(0, -4) + "/USD";
|
|
130
|
+
}
|
|
131
|
+
if (binanceSymbol.endsWith("BTC")) {
|
|
132
|
+
return binanceSymbol.slice(0, -3) + "/BTC";
|
|
133
|
+
}
|
|
134
|
+
if (binanceSymbol.endsWith("ETH")) {
|
|
135
|
+
return binanceSymbol.slice(0, -3) + "/ETH";
|
|
136
|
+
}
|
|
137
|
+
return binanceSymbol;
|
|
138
|
+
}
|
|
139
|
+
// ============================================
|
|
140
|
+
// WICK ANALYSIS
|
|
141
|
+
// ============================================
|
|
142
|
+
/**
|
|
143
|
+
* Calculate wick ratio for candles
|
|
144
|
+
* Lower ratio = cleaner candles = more reliable price action
|
|
145
|
+
*/
|
|
146
|
+
function calculateWickRatio(candles) {
|
|
147
|
+
let totalRatio = 0;
|
|
148
|
+
let cleanCount = 0;
|
|
149
|
+
for (const candle of candles) {
|
|
150
|
+
const body = Math.abs(candle.close - candle.open);
|
|
151
|
+
const totalRange = candle.high - candle.low;
|
|
152
|
+
if (totalRange === 0)
|
|
153
|
+
continue;
|
|
154
|
+
const wickRatio = 1 - body / totalRange; // 0 = all body, 1 = all wick
|
|
155
|
+
totalRatio += wickRatio;
|
|
156
|
+
// Clean candle = wick is less than 30% of total range
|
|
157
|
+
if (wickRatio < 0.3)
|
|
158
|
+
cleanCount++;
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
avgRatio: totalRatio / candles.length,
|
|
162
|
+
cleanCandles: cleanCount,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Calculate upper and lower wick sizes
|
|
167
|
+
*/
|
|
168
|
+
function analyzeWicks(candles) {
|
|
169
|
+
let totalUpperWick = 0;
|
|
170
|
+
let totalLowerWick = 0;
|
|
171
|
+
for (const candle of candles) {
|
|
172
|
+
const body = Math.abs(candle.close - candle.open);
|
|
173
|
+
const totalRange = candle.high - candle.low;
|
|
174
|
+
if (totalRange === 0)
|
|
175
|
+
continue;
|
|
176
|
+
const isGreen = candle.close >= candle.open;
|
|
177
|
+
const bodyTop = isGreen ? candle.close : candle.open;
|
|
178
|
+
const bodyBottom = isGreen ? candle.open : candle.close;
|
|
179
|
+
const upperWick = candle.high - bodyTop;
|
|
180
|
+
const lowerWick = bodyBottom - candle.low;
|
|
181
|
+
totalUpperWick += upperWick / totalRange;
|
|
182
|
+
totalLowerWick += lowerWick / totalRange;
|
|
183
|
+
}
|
|
184
|
+
const avgUpperWick = totalUpperWick / candles.length;
|
|
185
|
+
const avgLowerWick = totalLowerWick / candles.length;
|
|
186
|
+
// Positive = more upper wicks (bearish rejection), Negative = more lower wicks (bullish rejection)
|
|
187
|
+
const wickImbalance = avgUpperWick - avgLowerWick;
|
|
188
|
+
return { avgUpperWick, avgLowerWick, wickImbalance };
|
|
189
|
+
}
|
|
190
|
+
// ============================================
|
|
191
|
+
// SCORING FUNCTIONS
|
|
192
|
+
// ============================================
|
|
193
|
+
/**
|
|
194
|
+
* Calculate risk/reward score
|
|
195
|
+
*/
|
|
196
|
+
function calculateRiskRewardScore(price, support, resistance) {
|
|
197
|
+
const distToSupport = ((price - support) / price) * 100;
|
|
198
|
+
const distToResist = ((resistance - price) / price) * 100;
|
|
199
|
+
// Risk = distance to support (stop loss area)
|
|
200
|
+
// Reward = distance to resistance (take profit area)
|
|
201
|
+
const ratio = distToResist / Math.max(distToSupport, 0.1);
|
|
202
|
+
// Score: higher ratio = better
|
|
203
|
+
let score = 0;
|
|
204
|
+
if (ratio >= 3)
|
|
205
|
+
score = 100;
|
|
206
|
+
else if (ratio >= 2.5)
|
|
207
|
+
score = 90;
|
|
208
|
+
else if (ratio >= 2)
|
|
209
|
+
score = 80;
|
|
210
|
+
else if (ratio >= 1.5)
|
|
211
|
+
score = 70;
|
|
212
|
+
else if (ratio >= 1)
|
|
213
|
+
score = 50;
|
|
214
|
+
else
|
|
215
|
+
score = 30;
|
|
216
|
+
return { score, ratio, distToSupport, distToResist };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Calculate signal strength score based on indicator confluence
|
|
220
|
+
*/
|
|
221
|
+
function calculateSignalStrengthScore(rsi, macdCrossover, trend, priceVsEMA) {
|
|
222
|
+
let score = 50; // Base score
|
|
223
|
+
// RSI contribution
|
|
224
|
+
if (rsi < 30)
|
|
225
|
+
score += 20; // Oversold = bullish
|
|
226
|
+
else if (rsi < 40)
|
|
227
|
+
score += 10;
|
|
228
|
+
else if (rsi > 70)
|
|
229
|
+
score -= 20; // Overbought = bearish
|
|
230
|
+
else if (rsi > 60)
|
|
231
|
+
score -= 10;
|
|
232
|
+
// MACD contribution
|
|
233
|
+
if (macdCrossover === "bullish")
|
|
234
|
+
score += 15;
|
|
235
|
+
else if (macdCrossover === "bearish")
|
|
236
|
+
score -= 15;
|
|
237
|
+
// Trend contribution
|
|
238
|
+
if (trend === "bullish")
|
|
239
|
+
score += 15;
|
|
240
|
+
else if (trend === "bearish")
|
|
241
|
+
score -= 15;
|
|
242
|
+
// Price vs EMA contribution
|
|
243
|
+
if (priceVsEMA === "above_all")
|
|
244
|
+
score += 10;
|
|
245
|
+
else if (priceVsEMA === "below_all")
|
|
246
|
+
score -= 10;
|
|
247
|
+
return Math.max(0, Math.min(100, score));
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Calculate momentum score
|
|
251
|
+
*/
|
|
252
|
+
function calculateMomentumScore(change24h, volumeRatio, trendStrength) {
|
|
253
|
+
let score = 50;
|
|
254
|
+
// Price change contribution (positive change = bullish momentum)
|
|
255
|
+
if (change24h > 10)
|
|
256
|
+
score += 25;
|
|
257
|
+
else if (change24h > 5)
|
|
258
|
+
score += 15;
|
|
259
|
+
else if (change24h > 2)
|
|
260
|
+
score += 10;
|
|
261
|
+
else if (change24h < -10)
|
|
262
|
+
score -= 25;
|
|
263
|
+
else if (change24h < -5)
|
|
264
|
+
score -= 15;
|
|
265
|
+
else if (change24h < -2)
|
|
266
|
+
score -= 10;
|
|
267
|
+
// Volume contribution
|
|
268
|
+
if (volumeRatio > 2)
|
|
269
|
+
score += 20;
|
|
270
|
+
else if (volumeRatio > 1.5)
|
|
271
|
+
score += 10;
|
|
272
|
+
else if (volumeRatio < 0.5)
|
|
273
|
+
score -= 15;
|
|
274
|
+
// Trend strength
|
|
275
|
+
score += (trendStrength - 50) * 0.3;
|
|
276
|
+
return Math.max(0, Math.min(100, score));
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Calculate wick score (higher = cleaner candles)
|
|
280
|
+
*/
|
|
281
|
+
function calculateWickScore(avgWickRatio) {
|
|
282
|
+
// avgWickRatio: 0 = perfect (no wicks), 1 = all wicks
|
|
283
|
+
// We want to reward LOW wick ratios
|
|
284
|
+
const score = (1 - avgWickRatio) * 100;
|
|
285
|
+
return Math.max(0, Math.min(100, score));
|
|
286
|
+
}
|
|
287
|
+
// ============================================
|
|
288
|
+
// ANALYSIS FUNCTIONS
|
|
289
|
+
// ============================================
|
|
290
|
+
/**
|
|
291
|
+
* Analyze a single cryptocurrency
|
|
292
|
+
*/
|
|
293
|
+
async function analyzeCrypto(symbol) {
|
|
294
|
+
try {
|
|
295
|
+
const data = await (0, data_fetcher_1.fetchCryptoData)(symbol, "4h", 100);
|
|
296
|
+
if (!data || data.candles.length < 50)
|
|
297
|
+
return null;
|
|
298
|
+
const closes = data.candles.map((c) => c.close);
|
|
299
|
+
const volumes = data.candles.map((c) => c.volume);
|
|
300
|
+
// Calculate indicators
|
|
301
|
+
const rsiResult = (0, indicators_1.calculateRSI)(closes, 14);
|
|
302
|
+
const currentRSI = rsiResult.current;
|
|
303
|
+
const macd = (0, indicators_1.calculateMACD)(closes);
|
|
304
|
+
const macdCrossover = macd.crossover;
|
|
305
|
+
const trend = (0, indicators_1.analyzeTrend)(data.candles);
|
|
306
|
+
const supportResistance = (0, indicators_1.calculateSupportResistance)(data.candles);
|
|
307
|
+
// Volume analysis
|
|
308
|
+
const recentVolume = volumes.slice(-5).reduce((a, b) => a + b, 0) / 5;
|
|
309
|
+
const avgVolume = volumes.slice(-20).reduce((a, b) => a + b, 0) / 20;
|
|
310
|
+
const volumeRatio = avgVolume > 0 ? recentVolume / avgVolume : 1;
|
|
311
|
+
// Wick analysis
|
|
312
|
+
const recentCandles = data.candles.slice(-20);
|
|
313
|
+
const wickData = calculateWickRatio(recentCandles);
|
|
314
|
+
const wickAnalysis = analyzeWicks(recentCandles);
|
|
315
|
+
// Calculate scores
|
|
316
|
+
const rrData = calculateRiskRewardScore(data.currentPrice, supportResistance.nearestSupport, supportResistance.nearestResistance);
|
|
317
|
+
// Determine price position relative to trend
|
|
318
|
+
const pricePosition = trend.direction === "bullish"
|
|
319
|
+
? "above_all"
|
|
320
|
+
: trend.direction === "bearish"
|
|
321
|
+
? "below_all"
|
|
322
|
+
: "neutral";
|
|
323
|
+
const signalStrengthScore = calculateSignalStrengthScore(currentRSI, macdCrossover, trend.direction, pricePosition);
|
|
324
|
+
const momentumScore = calculateMomentumScore(data.priceChangePercent24h, volumeRatio, trend.strength);
|
|
325
|
+
const wickScore = calculateWickScore(wickData.avgRatio);
|
|
326
|
+
// Calculate overall score (weighted average)
|
|
327
|
+
const overallScore = Math.round(rrData.score * 0.25 + signalStrengthScore * 0.3 + momentumScore * 0.25 + wickScore * 0.2);
|
|
328
|
+
// Determine signal
|
|
329
|
+
let signal = "HOLD";
|
|
330
|
+
if (overallScore >= 80 && signalStrengthScore >= 70)
|
|
331
|
+
signal = "STRONG_BUY";
|
|
332
|
+
else if (overallScore >= 65 && signalStrengthScore >= 55)
|
|
333
|
+
signal = "BUY";
|
|
334
|
+
else if (overallScore <= 30 && signalStrengthScore <= 35)
|
|
335
|
+
signal = "STRONG_SELL";
|
|
336
|
+
else if (overallScore <= 45 && signalStrengthScore <= 45)
|
|
337
|
+
signal = "SELL";
|
|
338
|
+
// Generate reasoning
|
|
339
|
+
const reasoning = [];
|
|
340
|
+
if (currentRSI < 35)
|
|
341
|
+
reasoning.push(`RSI oversold (${currentRSI.toFixed(0)})`);
|
|
342
|
+
if (currentRSI > 65)
|
|
343
|
+
reasoning.push(`RSI overbought (${currentRSI.toFixed(0)})`);
|
|
344
|
+
if (macdCrossover === "bullish")
|
|
345
|
+
reasoning.push("Bullish MACD crossover");
|
|
346
|
+
if (macdCrossover === "bearish")
|
|
347
|
+
reasoning.push("Bearish MACD crossover");
|
|
348
|
+
if (rrData.ratio >= 2)
|
|
349
|
+
reasoning.push(`Good R:R ratio (${rrData.ratio.toFixed(1)}:1)`);
|
|
350
|
+
if (wickScore >= 70)
|
|
351
|
+
reasoning.push("Clean price action (low wicks)");
|
|
352
|
+
if (wickScore < 40)
|
|
353
|
+
reasoning.push("Choppy price action (high wicks)");
|
|
354
|
+
if (volumeRatio > 1.5)
|
|
355
|
+
reasoning.push(`High volume (${volumeRatio.toFixed(1)}x avg)`);
|
|
356
|
+
return {
|
|
357
|
+
symbol: data.symbol,
|
|
358
|
+
price: data.currentPrice,
|
|
359
|
+
change24h: data.priceChangePercent24h,
|
|
360
|
+
overallScore,
|
|
361
|
+
riskRewardScore: rrData.score,
|
|
362
|
+
signalStrengthScore,
|
|
363
|
+
momentumScore,
|
|
364
|
+
wickScore,
|
|
365
|
+
rsi: currentRSI,
|
|
366
|
+
trend: trend.direction,
|
|
367
|
+
macdSignal: macdCrossover,
|
|
368
|
+
volumeRatio,
|
|
369
|
+
distanceToSupport: rrData.distToSupport,
|
|
370
|
+
distanceToResistance: rrData.distToResist,
|
|
371
|
+
riskRewardRatio: rrData.ratio,
|
|
372
|
+
avgWickRatio: wickData.avgRatio,
|
|
373
|
+
signal,
|
|
374
|
+
confidence: overallScore,
|
|
375
|
+
reasoning,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// ============================================
|
|
383
|
+
// DISPLAY FUNCTIONS
|
|
384
|
+
// ============================================
|
|
385
|
+
function formatPrice(price) {
|
|
386
|
+
if (price < 0.001)
|
|
387
|
+
return `$${price.toFixed(6)}`;
|
|
388
|
+
if (price < 1)
|
|
389
|
+
return `$${price.toFixed(4)}`;
|
|
390
|
+
if (price < 100)
|
|
391
|
+
return `$${price.toFixed(2)}`;
|
|
392
|
+
return `$${price.toLocaleString(undefined, { maximumFractionDigits: 2 })}`;
|
|
393
|
+
}
|
|
394
|
+
function getScoreBar(score, width = 10) {
|
|
395
|
+
const filled = Math.round((score / 100) * width);
|
|
396
|
+
const empty = width - filled;
|
|
397
|
+
let color = chalk_1.default.green;
|
|
398
|
+
if (score < 40)
|
|
399
|
+
color = chalk_1.default.red;
|
|
400
|
+
else if (score < 60)
|
|
401
|
+
color = chalk_1.default.yellow;
|
|
402
|
+
return color("█".repeat(filled)) + chalk_1.default.gray("░".repeat(empty));
|
|
403
|
+
}
|
|
404
|
+
function getSignalColor(signal) {
|
|
405
|
+
switch (signal) {
|
|
406
|
+
case "STRONG_BUY":
|
|
407
|
+
return chalk_1.default.green.bold;
|
|
408
|
+
case "BUY":
|
|
409
|
+
return chalk_1.default.green;
|
|
410
|
+
case "SELL":
|
|
411
|
+
return chalk_1.default.red;
|
|
412
|
+
case "STRONG_SELL":
|
|
413
|
+
return chalk_1.default.red.bold;
|
|
414
|
+
default:
|
|
415
|
+
return chalk_1.default.yellow;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function displayScanResults(result) {
|
|
419
|
+
console.log("");
|
|
420
|
+
console.log(chalk_1.default.bold.cyan(" ═══════════════════════════════════════════════════════════════════"));
|
|
421
|
+
console.log(chalk_1.default.bold.cyan(" 🔍 MARKET SCANNER - TRADING OPPORTUNITIES "));
|
|
422
|
+
console.log(chalk_1.default.bold.cyan(" ═══════════════════════════════════════════════════════════════════"));
|
|
423
|
+
console.log("");
|
|
424
|
+
console.log(chalk_1.default.gray(` Scanned ${result.totalScanned} cryptocurrencies | ${new Date(result.timestamp).toLocaleString()}`));
|
|
425
|
+
console.log(chalk_1.default.gray(` Found ${result.opportunities.length} valid results`));
|
|
426
|
+
console.log("");
|
|
427
|
+
// Show all opportunities sorted by score
|
|
428
|
+
console.log(chalk_1.default.bold.yellow(" ┌─────────────────────────────────────────────────────────────────┐"));
|
|
429
|
+
console.log(chalk_1.default.bold.yellow(" │ 📊 ALL OPPORTUNITIES (Sorted) │"));
|
|
430
|
+
console.log(chalk_1.default.bold.yellow(" └─────────────────────────────────────────────────────────────────┘"));
|
|
431
|
+
console.log("");
|
|
432
|
+
console.log(chalk_1.default.gray(" Symbol Price 24h Score R:R Wick Signal"));
|
|
433
|
+
console.log(chalk_1.default.gray(" " + "─".repeat(65)));
|
|
434
|
+
// Show top 15 results
|
|
435
|
+
for (const opp of result.opportunities.slice(0, 15)) {
|
|
436
|
+
const changeColor = opp.change24h >= 0 ? chalk_1.default.green : chalk_1.default.red;
|
|
437
|
+
const changeStr = `${opp.change24h >= 0 ? "+" : ""}${opp.change24h.toFixed(1)}%`;
|
|
438
|
+
const displaySymbol = formatSymbolDisplay(opp.symbol);
|
|
439
|
+
console.log(chalk_1.default.white(` ${displaySymbol.padEnd(12)}`) +
|
|
440
|
+
chalk_1.default.yellow(`${formatPrice(opp.price).padEnd(14)}`) +
|
|
441
|
+
changeColor(`${changeStr.padEnd(8)}`) +
|
|
442
|
+
`${getScoreBar(opp.overallScore, 6)} ` +
|
|
443
|
+
chalk_1.default.cyan(`${opp.riskRewardRatio.toFixed(1)}:1`.padEnd(6)) +
|
|
444
|
+
`${getScoreBar(opp.wickScore, 4)} ` +
|
|
445
|
+
getSignalColor(opp.signal)(opp.signal));
|
|
446
|
+
if (opp.reasoning.length > 0) {
|
|
447
|
+
console.log(chalk_1.default.gray(` ${opp.reasoning.slice(0, 2).join(" | ")}`));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
console.log("");
|
|
451
|
+
// Top Buy Opportunities
|
|
452
|
+
if (result.topBuys.length > 0) {
|
|
453
|
+
console.log(chalk_1.default.bold.green(" ┌─────────────────────────────────────────────────────────────────┐"));
|
|
454
|
+
console.log(chalk_1.default.bold.green(" │ 🟢 TOP BUY OPPORTUNITIES │"));
|
|
455
|
+
console.log(chalk_1.default.bold.green(" └─────────────────────────────────────────────────────────────────┘"));
|
|
456
|
+
console.log("");
|
|
457
|
+
console.log(chalk_1.default.gray(" Symbol Price 24h Score R:R Wick Signal"));
|
|
458
|
+
console.log(chalk_1.default.gray(" " + "─".repeat(65)));
|
|
459
|
+
}
|
|
460
|
+
for (const opp of result.topBuys.slice(0, 10)) {
|
|
461
|
+
const changeColor = opp.change24h >= 0 ? chalk_1.default.green : chalk_1.default.red;
|
|
462
|
+
const changeStr = `${opp.change24h >= 0 ? "+" : ""}${opp.change24h.toFixed(1)}%`;
|
|
463
|
+
const displaySymbol = formatSymbolDisplay(opp.symbol);
|
|
464
|
+
console.log(chalk_1.default.white(` ${displaySymbol.padEnd(12)}`) +
|
|
465
|
+
chalk_1.default.yellow(`${formatPrice(opp.price).padEnd(14)}`) +
|
|
466
|
+
changeColor(`${changeStr.padEnd(8)}`) +
|
|
467
|
+
`${getScoreBar(opp.overallScore, 6)} ` +
|
|
468
|
+
chalk_1.default.cyan(`${opp.riskRewardRatio.toFixed(1)}:1`.padEnd(6)) +
|
|
469
|
+
`${getScoreBar(opp.wickScore, 4)} ` +
|
|
470
|
+
getSignalColor(opp.signal)(opp.signal));
|
|
471
|
+
if (opp.reasoning.length > 0) {
|
|
472
|
+
console.log(chalk_1.default.gray(` ${opp.reasoning.slice(0, 2).join(" | ")}`));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
console.log("");
|
|
476
|
+
// Top Sell Opportunities
|
|
477
|
+
if (result.topSells.length > 0) {
|
|
478
|
+
console.log(chalk_1.default.bold.red(" ┌─────────────────────────────────────────────────────────────────┐"));
|
|
479
|
+
console.log(chalk_1.default.bold.red(" │ 🔴 TOP SELL SIGNALS │"));
|
|
480
|
+
console.log(chalk_1.default.bold.red(" └─────────────────────────────────────────────────────────────────┘"));
|
|
481
|
+
console.log("");
|
|
482
|
+
for (const opp of result.topSells.slice(0, 5)) {
|
|
483
|
+
const changeColor = opp.change24h >= 0 ? chalk_1.default.green : chalk_1.default.red;
|
|
484
|
+
const changeStr = `${opp.change24h >= 0 ? "+" : ""}${opp.change24h.toFixed(1)}%`;
|
|
485
|
+
const displaySymbol = formatSymbolDisplay(opp.symbol);
|
|
486
|
+
console.log(chalk_1.default.white(` ${displaySymbol.padEnd(12)}`) +
|
|
487
|
+
chalk_1.default.yellow(`${formatPrice(opp.price).padEnd(14)}`) +
|
|
488
|
+
changeColor(`${changeStr.padEnd(8)}`) +
|
|
489
|
+
`${getScoreBar(opp.overallScore, 6)} ` +
|
|
490
|
+
getSignalColor(opp.signal)(opp.signal));
|
|
491
|
+
if (opp.reasoning.length > 0) {
|
|
492
|
+
console.log(chalk_1.default.gray(` ${opp.reasoning.slice(0, 2).join(" | ")}`));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
console.log("");
|
|
496
|
+
}
|
|
497
|
+
// Cleanest Charts (Best for trading - low manipulation)
|
|
498
|
+
console.log(chalk_1.default.bold.cyan(" ┌─────────────────────────────────────────────────────────────────┐"));
|
|
499
|
+
console.log(chalk_1.default.bold.cyan(" │ 📊 CLEANEST CHARTS (Low Wick = Reliable) │"));
|
|
500
|
+
console.log(chalk_1.default.bold.cyan(" └─────────────────────────────────────────────────────────────────┘"));
|
|
501
|
+
console.log("");
|
|
502
|
+
console.log(chalk_1.default.gray(" Lower wick ratio = cleaner price action = more reliable trades"));
|
|
503
|
+
console.log("");
|
|
504
|
+
for (const opp of result.cleanestCharts.slice(0, 5)) {
|
|
505
|
+
const wickPct = ((1 - opp.avgWickRatio) * 100).toFixed(0);
|
|
506
|
+
const displaySymbol = formatSymbolDisplay(opp.symbol);
|
|
507
|
+
console.log(chalk_1.default.white(` ${displaySymbol.padEnd(12)}`) +
|
|
508
|
+
chalk_1.default.cyan(`Wick Score: ${opp.wickScore.toFixed(0)}/100`.padEnd(20)) +
|
|
509
|
+
chalk_1.default.green(`${wickPct}% body (clean)`.padEnd(18)) +
|
|
510
|
+
getSignalColor(opp.signal)(opp.signal));
|
|
511
|
+
}
|
|
512
|
+
console.log("");
|
|
513
|
+
// Legend
|
|
514
|
+
console.log(chalk_1.default.gray(" ─".repeat(33)));
|
|
515
|
+
console.log(chalk_1.default.gray(" Score: ") +
|
|
516
|
+
chalk_1.default.green("███") +
|
|
517
|
+
chalk_1.default.gray(" High (>60) ") +
|
|
518
|
+
chalk_1.default.yellow("███") +
|
|
519
|
+
chalk_1.default.gray(" Medium (40-60) ") +
|
|
520
|
+
chalk_1.default.red("███") +
|
|
521
|
+
chalk_1.default.gray(" Low (<40)"));
|
|
522
|
+
console.log(chalk_1.default.gray(" R:R = Risk/Reward Ratio | Wick = Price Action Cleanliness"));
|
|
523
|
+
console.log("");
|
|
524
|
+
console.log(chalk_1.default.gray.italic(" ⚠️ This is not financial advice. Always do your own research."));
|
|
525
|
+
console.log("");
|
|
526
|
+
}
|
|
527
|
+
// ============================================
|
|
528
|
+
// MAIN FUNCTION
|
|
529
|
+
// ============================================
|
|
530
|
+
async function runMarketScanner(limit = 50, minScore = 50) {
|
|
531
|
+
const spinner = (0, ora_1.default)("Scanning market for opportunities...").start();
|
|
532
|
+
const opportunities = [];
|
|
533
|
+
const cryptosToScan = TOP_CRYPTOS.slice(0, limit);
|
|
534
|
+
let scanned = 0;
|
|
535
|
+
const batchSize = 5; // Scan in batches to avoid rate limiting
|
|
536
|
+
for (let i = 0; i < cryptosToScan.length; i += batchSize) {
|
|
537
|
+
const batch = cryptosToScan.slice(i, i + batchSize);
|
|
538
|
+
spinner.text = `Scanning ${scanned + batch.length}/${cryptosToScan.length} cryptocurrencies...`;
|
|
539
|
+
const results = await Promise.all(batch.map((symbol) => analyzeCrypto(symbol)));
|
|
540
|
+
for (const result of results) {
|
|
541
|
+
if (result) {
|
|
542
|
+
opportunities.push(result);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
scanned += batch.length;
|
|
546
|
+
// Small delay between batches to avoid rate limiting
|
|
547
|
+
if (i + batchSize < cryptosToScan.length) {
|
|
548
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
spinner.succeed(`Scanned ${scanned} cryptocurrencies`);
|
|
552
|
+
// Sort and categorize
|
|
553
|
+
const sortedByScore = [...opportunities].sort((a, b) => b.overallScore - a.overallScore);
|
|
554
|
+
const topBuys = sortedByScore.filter((o) => o.signal === "STRONG_BUY" || o.signal === "BUY");
|
|
555
|
+
const topSells = sortedByScore.filter((o) => o.signal === "STRONG_SELL" || o.signal === "SELL");
|
|
556
|
+
const cleanestCharts = [...opportunities]
|
|
557
|
+
.sort((a, b) => b.wickScore - a.wickScore)
|
|
558
|
+
.filter((o) => o.wickScore >= 60);
|
|
559
|
+
const result = {
|
|
560
|
+
timestamp: Date.now(),
|
|
561
|
+
totalScanned: scanned,
|
|
562
|
+
opportunities: sortedByScore,
|
|
563
|
+
topBuys,
|
|
564
|
+
topSells,
|
|
565
|
+
cleanestCharts,
|
|
566
|
+
};
|
|
567
|
+
displayScanResults(result);
|
|
568
|
+
return result;
|
|
569
|
+
}
|