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,508 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Whale Activity Tracker
|
|
4
|
+
* Monitors large cryptocurrency transactions and exchange flows
|
|
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.displayWhaleActivity = displayWhaleActivity;
|
|
11
|
+
exports.runWhaleTracker = runWhaleTracker;
|
|
12
|
+
/* global fetch */
|
|
13
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
14
|
+
const ora_1 = __importDefault(require("ora"));
|
|
15
|
+
// Known exchange addresses (simplified list)
|
|
16
|
+
const EXCHANGE_ADDRESSES = {
|
|
17
|
+
BTC: [
|
|
18
|
+
"bc1qgdjqv0av3q56jvd82tkdjpy7gdp9ut8tlqmgrpmv24sq90ecnvqqjwvw97", // Binance
|
|
19
|
+
"3FupZp77ySr7jwoLYEJ9mwzJpvoNBXsBnE", // Binance
|
|
20
|
+
"1NDyJtNTjmwk5xPNhjgAMu4HDHigtobu1s", // Binance
|
|
21
|
+
"bc1qm34lsc65zpw79lxes69zkqmk6ee3ewf0j77s3h", // Binance
|
|
22
|
+
"3Kzh9qAqVWQhEsfQz7zEQL1EuSx5tyNLNS", // Coinbase
|
|
23
|
+
"3JZq4atUahhuA9rLhXLMhhTo133J9rF97j", // Coinbase
|
|
24
|
+
"1Pzaqw98PeRfyHypfqyEgg5yycJRsENrE7", // Bitfinex
|
|
25
|
+
],
|
|
26
|
+
ETH: [
|
|
27
|
+
"0x28c6c06298d514db089934071355e5743bf21d60", // Binance
|
|
28
|
+
"0x21a31ee1afc51d94c2efccaa2092ad1028285549", // Binance
|
|
29
|
+
"0xdfd5293d8e347dfe59e90efd55b2956a1343963d", // Binance
|
|
30
|
+
"0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", // Coinbase
|
|
31
|
+
"0x503828976d22510aad0201ac7ec88293211d23da", // Coinbase
|
|
32
|
+
"0x77134cbc06cb00b66f4c7e623d5fdbf6777635ec", // Kraken
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
// ============================================
|
|
36
|
+
// BLOCKCHAIN DATA FETCHING
|
|
37
|
+
// ============================================
|
|
38
|
+
/**
|
|
39
|
+
* Fetch recent large BTC transactions from Blockchain.info
|
|
40
|
+
*/
|
|
41
|
+
async function fetchBTCWhaleTransactions(minAmount = 100) {
|
|
42
|
+
const transactions = [];
|
|
43
|
+
try {
|
|
44
|
+
// Fetch latest unconfirmed transactions (more reliable than blocks)
|
|
45
|
+
const response = await fetch("https://blockchain.info/unconfirmed-transactions?format=json");
|
|
46
|
+
const data = await response.json();
|
|
47
|
+
if (!data || !data.txs || !Array.isArray(data.txs)) {
|
|
48
|
+
// Fallback: try fetching latest block
|
|
49
|
+
const blocksRes = await fetch("https://blockchain.info/latestblock");
|
|
50
|
+
const latestBlock = await blocksRes.json();
|
|
51
|
+
if (latestBlock && latestBlock.hash) {
|
|
52
|
+
const blockRes = await fetch(`https://blockchain.info/rawblock/${latestBlock.hash}?format=json`);
|
|
53
|
+
const blockData = await blockRes.json();
|
|
54
|
+
if (blockData && blockData.tx) {
|
|
55
|
+
for (const tx of blockData.tx.slice(0, 30)) {
|
|
56
|
+
processTransaction(tx, latestBlock.height, minAmount, transactions);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return transactions;
|
|
61
|
+
}
|
|
62
|
+
// Process unconfirmed transactions
|
|
63
|
+
for (const tx of data.txs.slice(0, 50)) {
|
|
64
|
+
processTransaction(tx, 0, minAmount, transactions);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
// Silently handle errors - whale data is supplementary
|
|
69
|
+
}
|
|
70
|
+
return transactions;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Process a single transaction and add to list if it meets criteria
|
|
74
|
+
*/
|
|
75
|
+
function processTransaction(tx, blockHeight, minAmount, transactions) {
|
|
76
|
+
try {
|
|
77
|
+
if (!tx || !tx.out)
|
|
78
|
+
return;
|
|
79
|
+
const totalOutput = tx.out.reduce((sum, out) => sum + (out.value || 0), 0);
|
|
80
|
+
const btcAmount = totalOutput / 100000000; // Convert satoshis to BTC
|
|
81
|
+
if (btcAmount >= minAmount) {
|
|
82
|
+
const fromAddr = tx.inputs?.[0]?.prev_out?.addr || "unknown";
|
|
83
|
+
const toAddr = tx.out?.[0]?.addr || "unknown";
|
|
84
|
+
const fromType = isExchangeAddress(fromAddr, "BTC") ? "exchange" : "wallet";
|
|
85
|
+
const toType = isExchangeAddress(toAddr, "BTC") ? "exchange" : "wallet";
|
|
86
|
+
let flowType = "unknown";
|
|
87
|
+
if (fromType === "wallet" && toType === "exchange")
|
|
88
|
+
flowType = "exchange_inflow";
|
|
89
|
+
else if (fromType === "exchange" && toType === "wallet")
|
|
90
|
+
flowType = "exchange_outflow";
|
|
91
|
+
else if (fromType === "wallet" && toType === "wallet")
|
|
92
|
+
flowType = "wallet_transfer";
|
|
93
|
+
transactions.push({
|
|
94
|
+
hash: tx.hash || "unknown",
|
|
95
|
+
coin: "BTC",
|
|
96
|
+
amount: btcAmount,
|
|
97
|
+
amountUSD: 0,
|
|
98
|
+
from: fromAddr,
|
|
99
|
+
to: toAddr,
|
|
100
|
+
fromType,
|
|
101
|
+
toType,
|
|
102
|
+
flowType,
|
|
103
|
+
timestamp: (tx.time || Date.now() / 1000) * 1000,
|
|
104
|
+
blockHeight,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Skip invalid transactions
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Fetch recent large ETH transactions from Etherscan (public API - limited)
|
|
114
|
+
*/
|
|
115
|
+
async function fetchETHWhaleTransactions(minAmount = 1000) {
|
|
116
|
+
const transactions = [];
|
|
117
|
+
try {
|
|
118
|
+
// Use public Etherscan API (limited but no key needed for basic queries)
|
|
119
|
+
// Fetch internal transactions of known whale addresses or latest blocks
|
|
120
|
+
const response = await fetch(`https://api.etherscan.io/api?module=account&action=txlist&address=0x28c6c06298d514db089934071355e5743bf21d60&startblock=0&endblock=99999999&page=1&offset=20&sort=desc`);
|
|
121
|
+
const data = await response.json();
|
|
122
|
+
if (data.status !== "1" || !data.result)
|
|
123
|
+
return transactions;
|
|
124
|
+
for (const tx of data.result) {
|
|
125
|
+
const ethAmount = parseFloat(tx.value) / 1e18;
|
|
126
|
+
if (ethAmount >= minAmount) {
|
|
127
|
+
const fromType = isExchangeAddress(tx.from, "ETH") ? "exchange" : "wallet";
|
|
128
|
+
const toType = isExchangeAddress(tx.to, "ETH") ? "exchange" : "wallet";
|
|
129
|
+
let flowType = "unknown";
|
|
130
|
+
if (fromType === "wallet" && toType === "exchange")
|
|
131
|
+
flowType = "exchange_inflow";
|
|
132
|
+
else if (fromType === "exchange" && toType === "wallet")
|
|
133
|
+
flowType = "exchange_outflow";
|
|
134
|
+
else if (fromType === "wallet" && toType === "wallet")
|
|
135
|
+
flowType = "wallet_transfer";
|
|
136
|
+
transactions.push({
|
|
137
|
+
hash: tx.hash,
|
|
138
|
+
coin: "ETH",
|
|
139
|
+
amount: ethAmount,
|
|
140
|
+
amountUSD: 0,
|
|
141
|
+
from: tx.from,
|
|
142
|
+
to: tx.to,
|
|
143
|
+
fromType,
|
|
144
|
+
toType,
|
|
145
|
+
flowType,
|
|
146
|
+
timestamp: parseInt(tx.timeStamp) * 1000,
|
|
147
|
+
blockHeight: parseInt(tx.blockNumber),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error("Error fetching ETH whale transactions:", error);
|
|
154
|
+
}
|
|
155
|
+
return transactions;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Check if address is a known exchange address
|
|
159
|
+
*/
|
|
160
|
+
function isExchangeAddress(address, coin) {
|
|
161
|
+
const addresses = EXCHANGE_ADDRESSES[coin] || [];
|
|
162
|
+
return addresses.some((a) => a.toLowerCase() === address.toLowerCase());
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Fetch current prices for USD conversion
|
|
166
|
+
*/
|
|
167
|
+
async function fetchPrices() {
|
|
168
|
+
try {
|
|
169
|
+
const response = await fetch("https://api.binance.com/api/v3/ticker/price?symbols=[%22BTCUSDT%22,%22ETHUSDT%22]");
|
|
170
|
+
const data = await response.json();
|
|
171
|
+
const prices = {};
|
|
172
|
+
for (const item of data) {
|
|
173
|
+
const symbol = item.symbol.replace("USDT", "");
|
|
174
|
+
prices[symbol] = parseFloat(item.price);
|
|
175
|
+
}
|
|
176
|
+
return prices;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return { BTC: 85000, ETH: 2800 }; // Fallback prices
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// ============================================
|
|
183
|
+
// WHALE ACTIVITY ANALYSIS
|
|
184
|
+
// ============================================
|
|
185
|
+
/**
|
|
186
|
+
* Analyze whale activity and generate summary
|
|
187
|
+
*/
|
|
188
|
+
function analyzeWhaleActivity(transactions, coin) {
|
|
189
|
+
const coinTxs = transactions.filter((tx) => tx.coin === coin);
|
|
190
|
+
let totalInflow = 0;
|
|
191
|
+
let totalOutflow = 0;
|
|
192
|
+
for (const tx of coinTxs) {
|
|
193
|
+
if (tx.flowType === "exchange_inflow") {
|
|
194
|
+
totalInflow += tx.amountUSD;
|
|
195
|
+
}
|
|
196
|
+
else if (tx.flowType === "exchange_outflow") {
|
|
197
|
+
totalOutflow += tx.amountUSD;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const netFlow = totalOutflow - totalInflow;
|
|
201
|
+
// Determine sentiment based on net flow
|
|
202
|
+
// Outflow > Inflow = Bullish (coins leaving exchanges = less sell pressure)
|
|
203
|
+
// Inflow > Outflow = Bearish (coins entering exchanges = potential sell pressure)
|
|
204
|
+
let sentiment = "neutral";
|
|
205
|
+
if (netFlow > 1000000)
|
|
206
|
+
sentiment = "bullish";
|
|
207
|
+
else if (netFlow < -1000000)
|
|
208
|
+
sentiment = "bearish";
|
|
209
|
+
// Determine activity level
|
|
210
|
+
let whaleActivity = "low";
|
|
211
|
+
if (coinTxs.length >= 10)
|
|
212
|
+
whaleActivity = "high";
|
|
213
|
+
else if (coinTxs.length >= 5)
|
|
214
|
+
whaleActivity = "medium";
|
|
215
|
+
return {
|
|
216
|
+
coin,
|
|
217
|
+
totalInflow,
|
|
218
|
+
totalOutflow,
|
|
219
|
+
netFlow,
|
|
220
|
+
largeTransactions: coinTxs.length,
|
|
221
|
+
sentiment,
|
|
222
|
+
whaleActivity,
|
|
223
|
+
recentTransactions: coinTxs.slice(0, 5),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// ============================================
|
|
227
|
+
// DISPLAY FUNCTIONS
|
|
228
|
+
// ============================================
|
|
229
|
+
function formatAmount(amount, coin) {
|
|
230
|
+
if (amount >= 1000) {
|
|
231
|
+
return `${(amount / 1000).toFixed(1)}K ${coin}`;
|
|
232
|
+
}
|
|
233
|
+
return `${amount.toFixed(2)} ${coin}`;
|
|
234
|
+
}
|
|
235
|
+
function formatUSD(amount) {
|
|
236
|
+
if (amount >= 1000000000) {
|
|
237
|
+
return `$${(amount / 1000000000).toFixed(2)}B`;
|
|
238
|
+
}
|
|
239
|
+
if (amount >= 1000000) {
|
|
240
|
+
return `$${(amount / 1000000).toFixed(2)}M`;
|
|
241
|
+
}
|
|
242
|
+
if (amount >= 1000) {
|
|
243
|
+
return `$${(amount / 1000).toFixed(1)}K`;
|
|
244
|
+
}
|
|
245
|
+
return `$${amount.toFixed(0)}`;
|
|
246
|
+
}
|
|
247
|
+
function truncateAddress(addr) {
|
|
248
|
+
if (addr.length <= 16)
|
|
249
|
+
return addr;
|
|
250
|
+
return `${addr.slice(0, 8)}...${addr.slice(-6)}`;
|
|
251
|
+
}
|
|
252
|
+
function formatTimeAgo(timestamp) {
|
|
253
|
+
const diff = Date.now() - timestamp;
|
|
254
|
+
const minutes = Math.floor(diff / 60000);
|
|
255
|
+
const hours = Math.floor(diff / 3600000);
|
|
256
|
+
if (hours > 0)
|
|
257
|
+
return `${hours}h ago`;
|
|
258
|
+
if (minutes > 0)
|
|
259
|
+
return `${minutes}m ago`;
|
|
260
|
+
return "just now";
|
|
261
|
+
}
|
|
262
|
+
function displayWhaleActivity(summaries) {
|
|
263
|
+
console.log("");
|
|
264
|
+
console.log(chalk_1.default.bold.cyan(" ═══════════════════════════════════════════════════════"));
|
|
265
|
+
console.log(chalk_1.default.bold.cyan(" 🐋 WHALE ACTIVITY MONITOR "));
|
|
266
|
+
console.log(chalk_1.default.bold.cyan(" ═══════════════════════════════════════════════════════"));
|
|
267
|
+
console.log("");
|
|
268
|
+
for (const summary of summaries) {
|
|
269
|
+
const sentimentColor = summary.sentiment === "bullish"
|
|
270
|
+
? chalk_1.default.green
|
|
271
|
+
: summary.sentiment === "bearish"
|
|
272
|
+
? chalk_1.default.red
|
|
273
|
+
: chalk_1.default.yellow;
|
|
274
|
+
const activityColor = summary.whaleActivity === "high"
|
|
275
|
+
? chalk_1.default.red
|
|
276
|
+
: summary.whaleActivity === "medium"
|
|
277
|
+
? chalk_1.default.yellow
|
|
278
|
+
: chalk_1.default.gray;
|
|
279
|
+
console.log(chalk_1.default.bold.white(` ┌─────────────────────────────────────────────────┐`));
|
|
280
|
+
console.log(chalk_1.default.bold.white(` │`) +
|
|
281
|
+
chalk_1.default.bold.yellow(` 💰 ${summary.coin} WHALE ACTIVITY`.padEnd(48)) +
|
|
282
|
+
chalk_1.default.bold.white(`│`));
|
|
283
|
+
console.log(chalk_1.default.bold.white(` └─────────────────────────────────────────────────┘`));
|
|
284
|
+
console.log("");
|
|
285
|
+
// Activity Level
|
|
286
|
+
console.log(chalk_1.default.gray(" Activity Level: ") +
|
|
287
|
+
activityColor(`${summary.whaleActivity.toUpperCase()} (${summary.largeTransactions} large txs)`));
|
|
288
|
+
// Sentiment
|
|
289
|
+
const sentimentIcon = summary.sentiment === "bullish" ? "🟢" : summary.sentiment === "bearish" ? "🔴" : "🟡";
|
|
290
|
+
console.log(chalk_1.default.gray(" Market Sentiment: ") +
|
|
291
|
+
sentimentColor(`${sentimentIcon} ${summary.sentiment.toUpperCase()}`));
|
|
292
|
+
console.log("");
|
|
293
|
+
// Flow Summary
|
|
294
|
+
console.log(chalk_1.default.bold.white(" 📊 EXCHANGE FLOW SUMMARY"));
|
|
295
|
+
console.log(chalk_1.default.gray(" ┌─────────────────────────────────────────────────┐"));
|
|
296
|
+
console.log(chalk_1.default.gray(" │") +
|
|
297
|
+
chalk_1.default.red(` 📥 Inflow (Sell Pressure): ${formatUSD(summary.totalInflow).padEnd(15)}`) +
|
|
298
|
+
chalk_1.default.gray("│"));
|
|
299
|
+
console.log(chalk_1.default.gray(" │") +
|
|
300
|
+
chalk_1.default.green(` 📤 Outflow (Accumulation): ${formatUSD(summary.totalOutflow).padEnd(15)}`) +
|
|
301
|
+
chalk_1.default.gray("│"));
|
|
302
|
+
console.log(chalk_1.default.gray(" │") +
|
|
303
|
+
chalk_1.default.gray(" ─────────────────────────────────────────────") +
|
|
304
|
+
chalk_1.default.gray("│"));
|
|
305
|
+
const netFlowColor = summary.netFlow >= 0 ? chalk_1.default.green : chalk_1.default.red;
|
|
306
|
+
const netFlowIcon = summary.netFlow >= 0 ? "↑" : "↓";
|
|
307
|
+
console.log(chalk_1.default.gray(" │") +
|
|
308
|
+
netFlowColor(` ${netFlowIcon} Net Flow: ${formatUSD(Math.abs(summary.netFlow))} ${summary.netFlow >= 0 ? "(Bullish)" : "(Bearish)"}`.padEnd(46)) +
|
|
309
|
+
chalk_1.default.gray("│"));
|
|
310
|
+
console.log(chalk_1.default.gray(" └─────────────────────────────────────────────────┘"));
|
|
311
|
+
console.log("");
|
|
312
|
+
// Recent Transactions
|
|
313
|
+
if (summary.recentTransactions.length > 0) {
|
|
314
|
+
console.log(chalk_1.default.bold.white(" 📋 RECENT LARGE TRANSACTIONS"));
|
|
315
|
+
console.log("");
|
|
316
|
+
for (const tx of summary.recentTransactions.slice(0, 5)) {
|
|
317
|
+
const flowIcon = tx.flowType === "exchange_inflow"
|
|
318
|
+
? chalk_1.default.red("📥 IN")
|
|
319
|
+
: tx.flowType === "exchange_outflow"
|
|
320
|
+
? chalk_1.default.green("📤 OUT")
|
|
321
|
+
: chalk_1.default.gray("↔️ TFR");
|
|
322
|
+
const typeLabel = tx.flowType === "exchange_inflow"
|
|
323
|
+
? chalk_1.default.red("→ Exchange")
|
|
324
|
+
: tx.flowType === "exchange_outflow"
|
|
325
|
+
? chalk_1.default.green("← Exchange")
|
|
326
|
+
: chalk_1.default.gray("Wallet→Wallet");
|
|
327
|
+
console.log(chalk_1.default.gray(" ") +
|
|
328
|
+
flowIcon +
|
|
329
|
+
chalk_1.default.white(` ${formatAmount(tx.amount, tx.coin).padEnd(12)}`) +
|
|
330
|
+
chalk_1.default.gray(`(${formatUSD(tx.amountUSD)})`.padEnd(12)) +
|
|
331
|
+
typeLabel);
|
|
332
|
+
console.log(chalk_1.default.gray(` ${truncateAddress(tx.from)} → ${truncateAddress(tx.to)}`));
|
|
333
|
+
console.log(chalk_1.default.gray(` ${formatTimeAgo(tx.timestamp)}`));
|
|
334
|
+
console.log("");
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Interpretation
|
|
338
|
+
console.log(chalk_1.default.bold.white(" 💡 INTERPRETATION"));
|
|
339
|
+
if (summary.sentiment === "bullish") {
|
|
340
|
+
console.log(chalk_1.default.green(" Whales are moving coins OFF exchanges → Less sell pressure"));
|
|
341
|
+
console.log(chalk_1.default.green(" This typically indicates accumulation phase"));
|
|
342
|
+
}
|
|
343
|
+
else if (summary.sentiment === "bearish") {
|
|
344
|
+
console.log(chalk_1.default.red(" Whales are moving coins TO exchanges → Potential selling"));
|
|
345
|
+
console.log(chalk_1.default.red(" This may indicate distribution phase"));
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
console.log(chalk_1.default.yellow(" Mixed whale activity → No clear directional bias"));
|
|
349
|
+
console.log(chalk_1.default.yellow(" Wait for clearer signals before trading"));
|
|
350
|
+
}
|
|
351
|
+
console.log("");
|
|
352
|
+
console.log(chalk_1.default.gray(" ─".repeat(25)));
|
|
353
|
+
console.log("");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Detect whale activity based on volume analysis from Binance
|
|
358
|
+
*/
|
|
359
|
+
async function detectWhaleActivityFromVolume(symbol) {
|
|
360
|
+
try {
|
|
361
|
+
const pair = symbol.toUpperCase() + "USDT";
|
|
362
|
+
// Fetch recent trades
|
|
363
|
+
const tradesRes = await fetch(`https://api.binance.com/api/v3/trades?symbol=${pair}&limit=500`);
|
|
364
|
+
const trades = await tradesRes.json();
|
|
365
|
+
if (!Array.isArray(trades) || trades.length === 0)
|
|
366
|
+
return null;
|
|
367
|
+
// Fetch 24h ticker for context
|
|
368
|
+
const tickerRes = await fetch(`https://api.binance.com/api/v3/ticker/24hr?symbol=${pair}`);
|
|
369
|
+
const ticker = await tickerRes.json();
|
|
370
|
+
// Calculate average trade size
|
|
371
|
+
const tradeSizes = trades.map((t) => parseFloat(t.quoteQty));
|
|
372
|
+
const avgTradeSize = tradeSizes.reduce((a, b) => a + b, 0) / tradeSizes.length;
|
|
373
|
+
// Count "large" trades (>5x average)
|
|
374
|
+
const largeTradeThreshold = avgTradeSize * 5;
|
|
375
|
+
const largeTrades = trades.filter((t) => parseFloat(t.quoteQty) > largeTradeThreshold);
|
|
376
|
+
// Calculate buy/sell pressure from large trades
|
|
377
|
+
let buyVolume = 0;
|
|
378
|
+
let sellVolume = 0;
|
|
379
|
+
for (const trade of largeTrades) {
|
|
380
|
+
const qty = parseFloat(trade.quoteQty);
|
|
381
|
+
if (trade.isBuyerMaker) {
|
|
382
|
+
sellVolume += qty; // Taker sold
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
buyVolume += qty; // Taker bought
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const totalLargeVolume = buyVolume + sellVolume;
|
|
389
|
+
const buyPressure = totalLargeVolume > 0 ? (buyVolume / totalLargeVolume) * 100 : 50;
|
|
390
|
+
const sellPressure = totalLargeVolume > 0 ? (sellVolume / totalLargeVolume) * 100 : 50;
|
|
391
|
+
// Calculate volume ratio (current vs 24h average)
|
|
392
|
+
const currentVolume = tradeSizes.reduce((a, b) => a + b, 0);
|
|
393
|
+
const avgVolume24h = parseFloat(ticker.quoteVolume) / 24; // Hourly average
|
|
394
|
+
const volumeRatio = avgVolume24h > 0 ? (currentVolume / avgVolume24h) * 60 : 1; // Normalize to ~1 hour
|
|
395
|
+
// Determine sentiment
|
|
396
|
+
let sentiment = "neutral";
|
|
397
|
+
if (buyPressure > 60)
|
|
398
|
+
sentiment = "bullish";
|
|
399
|
+
else if (sellPressure > 60)
|
|
400
|
+
sentiment = "bearish";
|
|
401
|
+
// Determine activity level
|
|
402
|
+
let whaleActivity = "low";
|
|
403
|
+
if (largeTrades.length > 20 || volumeRatio > 2)
|
|
404
|
+
whaleActivity = "high";
|
|
405
|
+
else if (largeTrades.length > 10 || volumeRatio > 1.5)
|
|
406
|
+
whaleActivity = "medium";
|
|
407
|
+
return {
|
|
408
|
+
coin: symbol,
|
|
409
|
+
unusualVolumeRatio: volumeRatio,
|
|
410
|
+
largeTradeCount: largeTrades.length,
|
|
411
|
+
buyPressure,
|
|
412
|
+
sellPressure,
|
|
413
|
+
sentiment,
|
|
414
|
+
whaleActivity,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Display volume-based whale activity
|
|
423
|
+
*/
|
|
424
|
+
function displayVolumeWhaleData(data) {
|
|
425
|
+
console.log("");
|
|
426
|
+
console.log(chalk_1.default.bold.magenta(" ┌─────────────────────────────────────────────────┐"));
|
|
427
|
+
console.log(chalk_1.default.bold.magenta(" │ 📈 VOLUME-BASED WHALE DETECTION │"));
|
|
428
|
+
console.log(chalk_1.default.bold.magenta(" └─────────────────────────────────────────────────┘"));
|
|
429
|
+
console.log("");
|
|
430
|
+
console.log(chalk_1.default.gray(" Based on Binance large trade analysis (500 recent trades)"));
|
|
431
|
+
console.log("");
|
|
432
|
+
for (const item of data) {
|
|
433
|
+
const sentimentColor = item.sentiment === "bullish"
|
|
434
|
+
? chalk_1.default.green
|
|
435
|
+
: item.sentiment === "bearish"
|
|
436
|
+
? chalk_1.default.red
|
|
437
|
+
: chalk_1.default.yellow;
|
|
438
|
+
const activityColor = item.whaleActivity === "high"
|
|
439
|
+
? chalk_1.default.red
|
|
440
|
+
: item.whaleActivity === "medium"
|
|
441
|
+
? chalk_1.default.yellow
|
|
442
|
+
: chalk_1.default.gray;
|
|
443
|
+
const sentimentIcon = item.sentiment === "bullish" ? "🟢" : item.sentiment === "bearish" ? "🔴" : "🟡";
|
|
444
|
+
console.log(chalk_1.default.bold.white(` ${item.coin}USDT`));
|
|
445
|
+
console.log(chalk_1.default.gray(" Activity Level: ") +
|
|
446
|
+
activityColor(`${item.whaleActivity.toUpperCase()} (${item.largeTradeCount} large trades)`));
|
|
447
|
+
console.log(chalk_1.default.gray(" Volume Ratio: ") +
|
|
448
|
+
(item.unusualVolumeRatio > 1.5 ? chalk_1.default.yellow : chalk_1.default.gray)(`${item.unusualVolumeRatio.toFixed(1)}x normal`));
|
|
449
|
+
console.log(chalk_1.default.gray(" Whale Pressure: ") +
|
|
450
|
+
chalk_1.default.green(`Buy ${item.buyPressure.toFixed(0)}%`) +
|
|
451
|
+
chalk_1.default.gray(" | ") +
|
|
452
|
+
chalk_1.default.red(`Sell ${item.sellPressure.toFixed(0)}%`));
|
|
453
|
+
console.log(chalk_1.default.gray(" Sentiment: ") +
|
|
454
|
+
sentimentColor(`${sentimentIcon} ${item.sentiment.toUpperCase()}`));
|
|
455
|
+
// Visual pressure bar
|
|
456
|
+
const buyBar = Math.round(item.buyPressure / 5);
|
|
457
|
+
const sellBar = Math.round(item.sellPressure / 5);
|
|
458
|
+
console.log(chalk_1.default.gray(" ") +
|
|
459
|
+
chalk_1.default.green("█".repeat(buyBar)) +
|
|
460
|
+
chalk_1.default.gray("░".repeat(20 - buyBar - sellBar)) +
|
|
461
|
+
chalk_1.default.red("█".repeat(sellBar)));
|
|
462
|
+
console.log("");
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// ============================================
|
|
466
|
+
// MAIN FUNCTION
|
|
467
|
+
// ============================================
|
|
468
|
+
async function runWhaleTracker(coins = ["BTC", "ETH"]) {
|
|
469
|
+
const spinner = (0, ora_1.default)("Tracking whale activity...").start();
|
|
470
|
+
try {
|
|
471
|
+
spinner.text = "Fetching current prices...";
|
|
472
|
+
const prices = await fetchPrices();
|
|
473
|
+
spinner.text = "Scanning BTC blockchain for large transactions...";
|
|
474
|
+
const btcTxs = await fetchBTCWhaleTransactions(50); // Lower threshold
|
|
475
|
+
spinner.text = "Scanning ETH blockchain for large transactions...";
|
|
476
|
+
const ethTxs = await fetchETHWhaleTransactions(200); // Lower threshold
|
|
477
|
+
// Add USD values
|
|
478
|
+
const allTransactions = [...btcTxs, ...ethTxs].map((tx) => ({
|
|
479
|
+
...tx,
|
|
480
|
+
amountUSD: tx.amount * (prices[tx.coin] || 0),
|
|
481
|
+
}));
|
|
482
|
+
spinner.text = "Analyzing whale activity patterns...";
|
|
483
|
+
const summaries = [];
|
|
484
|
+
for (const coin of coins) {
|
|
485
|
+
if (coin === "BTC" || coin === "ETH") {
|
|
486
|
+
summaries.push(analyzeWhaleActivity(allTransactions, coin));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// Also get volume-based whale detection
|
|
490
|
+
spinner.text = "Analyzing large trade patterns on Binance...";
|
|
491
|
+
const volumeData = [];
|
|
492
|
+
for (const coin of coins) {
|
|
493
|
+
const data = await detectWhaleActivityFromVolume(coin);
|
|
494
|
+
if (data)
|
|
495
|
+
volumeData.push(data);
|
|
496
|
+
}
|
|
497
|
+
spinner.succeed("Whale activity analysis complete");
|
|
498
|
+
displayWhaleActivity(summaries);
|
|
499
|
+
// Show volume-based analysis
|
|
500
|
+
if (volumeData.length > 0) {
|
|
501
|
+
displayVolumeWhaleData(volumeData);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
spinner.fail("Failed to track whale activity");
|
|
506
|
+
console.log(chalk_1.default.red(`\nError: ${error.message}`));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
@@ -26,6 +26,24 @@ exports.SLASH_COMMANDS = [
|
|
|
26
26
|
shortcut: "sig",
|
|
27
27
|
action: "signal",
|
|
28
28
|
},
|
|
29
|
+
{
|
|
30
|
+
name: "/whale",
|
|
31
|
+
description: "Track whale activity (large BTC/ETH transactions)",
|
|
32
|
+
shortcut: "w",
|
|
33
|
+
action: "whale",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "/scan",
|
|
37
|
+
description: "Scan market for best trading opportunities",
|
|
38
|
+
shortcut: "sc",
|
|
39
|
+
action: "scan",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "/news",
|
|
43
|
+
description: "Get latest cryptocurrency news and updates",
|
|
44
|
+
shortcut: "n",
|
|
45
|
+
action: "news",
|
|
46
|
+
},
|
|
29
47
|
{
|
|
30
48
|
name: "/model",
|
|
31
49
|
description: "Switch between available AI models",
|
package/dist/lib/ui.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.StreamFormatter = exports.formatMarkdown = exports.showToolProgress = exports.showTodoList = exports.showDiff = exports.banner = exports.log = void 0;
|
|
6
|
+
exports.StreamFormatter = exports.formatMarkdown = exports.showToolProgress = exports.showTodoList = exports.showDiff = exports.banner = exports.showTokenUsageCompact = exports.showTokenUsage = exports.log = void 0;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const diff_1 = require("diff");
|
|
9
9
|
// Simple ASCII art letters for custom banners
|
|
@@ -500,6 +500,50 @@ exports.log = {
|
|
|
500
500
|
console.log(formatted);
|
|
501
501
|
},
|
|
502
502
|
};
|
|
503
|
+
/**
|
|
504
|
+
* Display token usage statistics with visual bar
|
|
505
|
+
*/
|
|
506
|
+
const showTokenUsage = (promptTokens, completionTokens, totalTokens, modelName) => {
|
|
507
|
+
const maxBarWidth = 30;
|
|
508
|
+
const maxTokensEstimate = 8000; // Rough estimate for visualization
|
|
509
|
+
// Calculate bar widths
|
|
510
|
+
const promptWidth = Math.min(Math.round((promptTokens / maxTokensEstimate) * maxBarWidth), maxBarWidth);
|
|
511
|
+
const completionWidth = Math.min(Math.round((completionTokens / maxTokensEstimate) * maxBarWidth), maxBarWidth);
|
|
512
|
+
// Create visual bars
|
|
513
|
+
const promptBar = chalk_1.default.cyan("█".repeat(promptWidth) + "░".repeat(Math.max(0, 15 - promptWidth)));
|
|
514
|
+
const completionBar = chalk_1.default.green("█".repeat(completionWidth) + "░".repeat(Math.max(0, 15 - completionWidth)));
|
|
515
|
+
// Format numbers with commas
|
|
516
|
+
const formatNum = (n) => n.toLocaleString();
|
|
517
|
+
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
518
|
+
console.log(chalk_1.default.gray("📊 ") + chalk_1.default.bold.white("Token Usage"));
|
|
519
|
+
if (modelName) {
|
|
520
|
+
console.log(chalk_1.default.gray(" Model: ") + chalk_1.default.cyan(modelName));
|
|
521
|
+
}
|
|
522
|
+
console.log(chalk_1.default.gray(" Prompt: ") +
|
|
523
|
+
promptBar +
|
|
524
|
+
chalk_1.default.gray(" ") +
|
|
525
|
+
chalk_1.default.cyan(formatNum(promptTokens).padStart(6)));
|
|
526
|
+
console.log(chalk_1.default.gray(" Completion: ") +
|
|
527
|
+
completionBar +
|
|
528
|
+
chalk_1.default.gray(" ") +
|
|
529
|
+
chalk_1.default.green(formatNum(completionTokens).padStart(6)));
|
|
530
|
+
console.log(chalk_1.default.gray(" ─────────────────────────────────"));
|
|
531
|
+
console.log(chalk_1.default.gray(" Total: ") + chalk_1.default.bold.yellow(formatNum(totalTokens).padStart(21)));
|
|
532
|
+
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
533
|
+
};
|
|
534
|
+
exports.showTokenUsage = showTokenUsage;
|
|
535
|
+
/**
|
|
536
|
+
* Display compact token usage (single line)
|
|
537
|
+
*/
|
|
538
|
+
const showTokenUsageCompact = (promptTokens, completionTokens, totalTokens) => {
|
|
539
|
+
console.log(chalk_1.default.gray(" 📊 Tokens: ") +
|
|
540
|
+
chalk_1.default.cyan(`↑${promptTokens.toLocaleString()}`) +
|
|
541
|
+
chalk_1.default.gray(" + ") +
|
|
542
|
+
chalk_1.default.green(`↓${completionTokens.toLocaleString()}`) +
|
|
543
|
+
chalk_1.default.gray(" = ") +
|
|
544
|
+
chalk_1.default.yellow.bold(totalTokens.toLocaleString()));
|
|
545
|
+
};
|
|
546
|
+
exports.showTokenUsageCompact = showTokenUsageCompact;
|
|
503
547
|
const banner = (modelName, toolCount, customization) => {
|
|
504
548
|
const cliName = customization?.cliName || "Prab CLI";
|
|
505
549
|
const userName = customization?.userName;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prab-cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.5",
|
|
4
4
|
"description": "AI-powered coding assistant for your terminal. Built with Groq's lightning-fast LLMs, featuring autonomous tool execution, syntax-highlighted output, and git integration.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|