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.
package/dist/index.js CHANGED
@@ -144,6 +144,76 @@ program
144
144
  console.log("\nYou can also use any Binance trading pair (e.g., BTCUSDT, ETHBTC)");
145
145
  console.log("");
146
146
  });
147
+ // Whale activity tracker
148
+ program
149
+ .command("whale")
150
+ .description("Track whale activity (large BTC/ETH transactions)")
151
+ .option("-c, --coins <coins>", "Coins to track (comma-separated)", "BTC,ETH")
152
+ .action(async (options) => {
153
+ const coins = options.coins.split(",").map((c) => c.trim().toUpperCase());
154
+ await (0, crypto_1.runWhaleTracker)(coins);
155
+ });
156
+ // Market scanner for opportunities
157
+ program
158
+ .command("scan")
159
+ .description("Scan market for best trading opportunities")
160
+ .option("-l, --limit <number>", "Number of cryptos to scan (max 100)", "50")
161
+ .option("-m, --min-score <number>", "Minimum score to display", "50")
162
+ .action(async (options) => {
163
+ const limit = Math.min(parseInt(options.limit) || 50, 100);
164
+ const minScore = parseInt(options.minScore) || 50;
165
+ await (0, crypto_1.runMarketScanner)(limit, minScore);
166
+ });
167
+ // Crypto news fetcher
168
+ program
169
+ .command("news")
170
+ .description("Get latest cryptocurrency news and updates")
171
+ .option("-c, --coin <coin>", "Filter news by specific coin (e.g., btc, eth)")
172
+ .action(async (options) => {
173
+ await (0, crypto_1.runCryptoNews)(options.coin);
174
+ });
175
+ // Smart trading strategy
176
+ program
177
+ .command("strategy <crypto>")
178
+ .description("Generate smart trading strategy with entry, exit, and leverage")
179
+ .option("-s, --style <style>", "Trading style: conservative, moderate, aggressive", "moderate")
180
+ .option("-l, --leverage <number>", "Maximum leverage allowed", "20")
181
+ .option("-d, --direction <direction>", "Trade direction: both, long, short", "both")
182
+ .action(async (crypto, options) => {
183
+ const style = ["conservative", "moderate", "aggressive"].includes(options.style)
184
+ ? options.style
185
+ : "moderate";
186
+ const maxLeverage = Math.min(parseInt(options.leverage) || 20, 100);
187
+ const direction = ["both", "long", "short"].includes(options.direction)
188
+ ? options.direction
189
+ : "both";
190
+ await (0, crypto_1.runStrategy)(crypto, { style, maxLeverage, direction });
191
+ });
192
+ // Order Block trading strategy
193
+ program
194
+ .command("orderblock <crypto>")
195
+ .alias("ob")
196
+ .description("Order Block trading strategy with BUY/SELL signals based on OB zones")
197
+ .option("-i, --interval <interval>", "Time interval (15m, 1h, 4h, 1d)", "4h")
198
+ .action(async (crypto, options) => {
199
+ const validIntervals = ["15m", "1h", "4h", "1d"];
200
+ const interval = validIntervals.includes(options.interval)
201
+ ? options.interval
202
+ : "4h";
203
+ await (0, crypto_1.runOrderBlockStrategy)(crypto, interval);
204
+ });
205
+ // ICT (Inner Circle Trader) Strategy
206
+ program
207
+ .command("ict <crypto>")
208
+ .description("ICT trading strategy - Killzones, OTE, Breakers, Silver Bullet, AMD")
209
+ .option("-i, --interval <interval>", "Time interval (15m, 1h, 4h)", "1h")
210
+ .action(async (crypto, options) => {
211
+ const validIntervals = ["15m", "1h", "4h"];
212
+ const interval = validIntervals.includes(options.interval)
213
+ ? options.interval
214
+ : "1h";
215
+ await (0, crypto_1.runICTStrategy)(crypto, interval);
216
+ });
147
217
  // Model management commands
148
218
  program
149
219
  .command("model")
@@ -308,7 +378,12 @@ program.action(async () => {
308
378
  default: "btc",
309
379
  },
310
380
  ]);
311
- await (0, crypto_1.comprehensiveAnalysis)(cryptoSymbol);
381
+ try {
382
+ await (0, crypto_1.comprehensiveAnalysis)(cryptoSymbol);
383
+ }
384
+ catch (err) {
385
+ // Error already handled in comprehensiveAnalysis
386
+ }
312
387
  break;
313
388
  }
314
389
  case "signal": {
@@ -331,7 +406,148 @@ program.action(async () => {
331
406
  { name: "1 Day", value: "1d" },
332
407
  ],
333
408
  });
334
- await (0, crypto_1.fullSignal)(cryptoSymbol, intervalChoice);
409
+ try {
410
+ await (0, crypto_1.fullSignal)(cryptoSymbol, intervalChoice);
411
+ }
412
+ catch (err) {
413
+ // Error already handled in fullSignal
414
+ }
415
+ break;
416
+ }
417
+ case "whale": {
418
+ // Prompt for coins to track
419
+ const { coins } = await inquirer_1.default.prompt([
420
+ {
421
+ type: "input",
422
+ name: "coins",
423
+ message: "Enter coins to track (comma-separated):",
424
+ default: "BTC,ETH",
425
+ },
426
+ ]);
427
+ const coinList = coins.split(",").map((c) => c.trim().toUpperCase());
428
+ await (0, crypto_1.runWhaleTracker)(coinList);
429
+ break;
430
+ }
431
+ case "scan": {
432
+ // Prompt for scan options
433
+ const scanLimit = await (0, select_1.default)({
434
+ message: "How many cryptocurrencies to scan?",
435
+ choices: [
436
+ { name: "Top 20 (Quick)", value: 20 },
437
+ { name: "Top 50 (Recommended)", value: 50 },
438
+ { name: "Top 100 (Comprehensive)", value: 100 },
439
+ ],
440
+ });
441
+ await (0, crypto_1.runMarketScanner)(scanLimit, 50);
442
+ break;
443
+ }
444
+ case "news": {
445
+ // Prompt for optional coin filter
446
+ const newsFilterChoice = await (0, select_1.default)({
447
+ message: "Filter news by coin?",
448
+ choices: [
449
+ { name: "All Crypto News", value: "" },
450
+ { name: "Bitcoin (BTC)", value: "BTC" },
451
+ { name: "Ethereum (ETH)", value: "ETH" },
452
+ { name: "Solana (SOL)", value: "SOL" },
453
+ { name: "Other (specify)", value: "__other__" },
454
+ ],
455
+ });
456
+ let coinFilter;
457
+ if (newsFilterChoice === "__other__") {
458
+ const { customCoin } = await inquirer_1.default.prompt([
459
+ {
460
+ type: "input",
461
+ name: "customCoin",
462
+ message: "Enter coin symbol (e.g., XRP, ADA, DOGE):",
463
+ },
464
+ ]);
465
+ coinFilter = customCoin.trim().toUpperCase() || undefined;
466
+ }
467
+ else if (newsFilterChoice) {
468
+ coinFilter = newsFilterChoice;
469
+ }
470
+ await (0, crypto_1.runCryptoNews)(coinFilter);
471
+ break;
472
+ }
473
+ case "strategy": {
474
+ // Prompt for crypto symbol
475
+ const { strategySymbol } = await inquirer_1.default.prompt([
476
+ {
477
+ type: "input",
478
+ name: "strategySymbol",
479
+ message: "Enter cryptocurrency symbol (e.g., btc, eth, sol):",
480
+ default: "btc",
481
+ },
482
+ ]);
483
+ // Prompt for trading style
484
+ const styleChoice = await (0, select_1.default)({
485
+ message: "Select your trading style:",
486
+ choices: [
487
+ { name: "Conservative (5-10x leverage, wider stops)", value: "conservative" },
488
+ { name: "Moderate (10-20x leverage, balanced) - Recommended", value: "moderate" },
489
+ { name: "Aggressive (20-50x leverage, tighter stops)", value: "aggressive" },
490
+ ],
491
+ });
492
+ // Prompt for direction
493
+ const directionChoice = await (0, select_1.default)({
494
+ message: "Trade direction:",
495
+ choices: [
496
+ { name: "Both Long & Short", value: "both" },
497
+ { name: "Long Only", value: "long" },
498
+ { name: "Short Only", value: "short" },
499
+ ],
500
+ });
501
+ await (0, crypto_1.runStrategy)(strategySymbol, {
502
+ style: styleChoice,
503
+ direction: directionChoice,
504
+ maxLeverage: styleChoice === "conservative" ? 10 : styleChoice === "moderate" ? 20 : 50,
505
+ });
506
+ break;
507
+ }
508
+ case "orderblock": {
509
+ // Prompt for crypto symbol
510
+ const { obSymbol } = await inquirer_1.default.prompt([
511
+ {
512
+ type: "input",
513
+ name: "obSymbol",
514
+ message: "Enter cryptocurrency symbol (e.g., btc, eth, sol):",
515
+ default: "btc",
516
+ },
517
+ ]);
518
+ // Prompt for timeframe
519
+ const obIntervalChoice = await (0, select_1.default)({
520
+ message: "Select timeframe for Order Block analysis:",
521
+ choices: [
522
+ { name: "4 Hours (Recommended for swing trades)", value: "4h" },
523
+ { name: "1 Hour (Intraday trades)", value: "1h" },
524
+ { name: "15 Minutes (Scalping)", value: "15m" },
525
+ { name: "1 Day (Position trades)", value: "1d" },
526
+ ],
527
+ });
528
+ await (0, crypto_1.runOrderBlockStrategy)(obSymbol, obIntervalChoice);
529
+ break;
530
+ }
531
+ case "ict": {
532
+ // Prompt for crypto symbol
533
+ const { ictSymbol } = await inquirer_1.default.prompt([
534
+ {
535
+ type: "input",
536
+ name: "ictSymbol",
537
+ message: "Enter cryptocurrency symbol (e.g., btc, eth, sol):",
538
+ default: "btc",
539
+ },
540
+ ]);
541
+ // Prompt for timeframe
542
+ const ictIntervalChoice = await (0, select_1.default)({
543
+ message: "Select timeframe for ICT analysis:",
544
+ choices: [
545
+ { name: "1 Hour (Recommended for ICT)", value: "1h" },
546
+ { name: "15 Minutes (Scalping with Silver Bullet)", value: "15m" },
547
+ { name: "4 Hours (Higher timeframe bias)", value: "4h" },
548
+ ],
549
+ });
550
+ await (0, crypto_1.runICTStrategy)(ictSymbol, ictIntervalChoice);
335
551
  break;
336
552
  }
337
553
  case "model": {
@@ -82,6 +82,10 @@ When you need to perform file operations, use the appropriate tools rather than
82
82
  let fullResponse = "";
83
83
  let toolCalls = [];
84
84
  const formatter = new ui_1.StreamFormatter();
85
+ // Track tokens for this specific request
86
+ let requestPromptTokens = 0;
87
+ let requestCompletionTokens = 0;
88
+ let requestTotalTokens = 0;
85
89
  process.stdout.write("\n");
86
90
  try {
87
91
  for await (const chunk of stream) {
@@ -98,11 +102,26 @@ When you need to perform file operations, use the appropriate tools rather than
98
102
  if (chunk.tool_calls && chunk.tool_calls.length > 0) {
99
103
  toolCalls = chunk.tool_calls;
100
104
  }
101
- // Capture usage metadata from the chunk
105
+ // Capture usage metadata from the chunk (LangChain uses different field names)
102
106
  if (chunk.usage_metadata) {
103
- this.usage.promptTokens += chunk.usage_metadata.input_tokens || 0;
104
- this.usage.completionTokens += chunk.usage_metadata.output_tokens || 0;
105
- this.usage.totalTokens += chunk.usage_metadata.total_tokens || 0;
107
+ requestPromptTokens =
108
+ chunk.usage_metadata.input_tokens || chunk.usage_metadata.prompt_tokens || 0;
109
+ requestCompletionTokens =
110
+ chunk.usage_metadata.output_tokens || chunk.usage_metadata.completion_tokens || 0;
111
+ requestTotalTokens =
112
+ chunk.usage_metadata.total_tokens || requestPromptTokens + requestCompletionTokens;
113
+ // Also update cumulative stats
114
+ this.usage.promptTokens += requestPromptTokens;
115
+ this.usage.completionTokens += requestCompletionTokens;
116
+ this.usage.totalTokens += requestTotalTokens;
117
+ }
118
+ // Also check response_metadata (alternative LangChain format)
119
+ if (chunk.response_metadata?.usage) {
120
+ const usage = chunk.response_metadata.usage;
121
+ requestPromptTokens = usage.prompt_tokens || usage.input_tokens || 0;
122
+ requestCompletionTokens = usage.completion_tokens || usage.output_tokens || 0;
123
+ requestTotalTokens =
124
+ usage.total_tokens || requestPromptTokens + requestCompletionTokens;
106
125
  }
107
126
  }
108
127
  // Increment request count
@@ -120,7 +139,22 @@ When you need to perform file operations, use the appropriate tools rather than
120
139
  tracker_1.tracker.apiError(apiError.message, { stack: apiError.stack });
121
140
  throw apiError;
122
141
  }
123
- process.stdout.write("\n\n");
142
+ process.stdout.write("\n");
143
+ // Estimate tokens if not provided by API (rough estimate: ~4 chars per token)
144
+ if (requestTotalTokens === 0 && fullResponse.length > 0) {
145
+ // Estimate based on message content length
146
+ const inputText = this.messages
147
+ .map((m) => (typeof m.content === "string" ? m.content : ""))
148
+ .join(" ");
149
+ requestPromptTokens = Math.ceil(inputText.length / 4);
150
+ requestCompletionTokens = Math.ceil(fullResponse.length / 4);
151
+ requestTotalTokens = requestPromptTokens + requestCompletionTokens;
152
+ }
153
+ // Show token usage for this request
154
+ if (requestTotalTokens > 0) {
155
+ (0, ui_1.showTokenUsageCompact)(requestPromptTokens, requestCompletionTokens, requestTotalTokens);
156
+ }
157
+ process.stdout.write("\n");
124
158
  // Log AI response if there's content
125
159
  if (fullResponse.length > 0) {
126
160
  tracker_1.tracker.aiResponse(fullResponse);
@@ -5,12 +5,15 @@
5
5
  * Fetches OHLCV data from Binance public API (no API key required)
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.fetchAllSymbols = fetchAllSymbols;
9
+ exports.findSimilarSymbols = findSimilarSymbols;
8
10
  exports.normalizeSymbol = normalizeSymbol;
9
11
  exports.fetchOHLCV = fetchOHLCV;
10
12
  exports.fetch24hTicker = fetch24hTicker;
11
13
  exports.fetchCryptoData = fetchCryptoData;
12
14
  exports.getSupportedSymbols = getSupportedSymbols;
13
15
  exports.isValidSymbol = isValidSymbol;
16
+ exports.validateSymbol = validateSymbol;
14
17
  // Common crypto symbols mapping (user-friendly -> Binance format)
15
18
  const SYMBOL_MAP = {
16
19
  btc: "BTCUSDT",
@@ -57,6 +60,63 @@ const SYMBOL_MAP = {
57
60
  wif: "WIFUSDT",
58
61
  };
59
62
  const BINANCE_API_BASE = "https://api.binance.com/api/v3";
63
+ // Cache for valid Binance symbols
64
+ let cachedSymbols = null;
65
+ let cacheTimestamp = 0;
66
+ const CACHE_DURATION = 1000 * 60 * 60; // 1 hour cache
67
+ /**
68
+ * Fetch all valid USDT trading pairs from Binance
69
+ */
70
+ async function fetchAllSymbols() {
71
+ // Return cached symbols if still valid
72
+ if (cachedSymbols && Date.now() - cacheTimestamp < CACHE_DURATION) {
73
+ return cachedSymbols;
74
+ }
75
+ try {
76
+ const response = await fetch(`${BINANCE_API_BASE}/exchangeInfo`);
77
+ if (!response.ok) {
78
+ throw new Error("Failed to fetch exchange info");
79
+ }
80
+ const data = await response.json();
81
+ const symbols = new Set();
82
+ for (const symbol of data.symbols) {
83
+ // Only include USDT pairs that are trading
84
+ if (symbol.status === "TRADING" && symbol.quoteAsset === "USDT") {
85
+ symbols.add(symbol.symbol);
86
+ // Also add the base asset for easy lookup
87
+ symbols.add(symbol.baseAsset.toLowerCase());
88
+ }
89
+ }
90
+ cachedSymbols = symbols;
91
+ cacheTimestamp = Date.now();
92
+ return symbols;
93
+ }
94
+ catch (error) {
95
+ // Return empty set on error, will fall back to direct API check
96
+ return new Set();
97
+ }
98
+ }
99
+ /**
100
+ * Get similar symbols for suggestions
101
+ */
102
+ async function findSimilarSymbols(input, limit = 5) {
103
+ const symbols = await fetchAllSymbols();
104
+ const inputUpper = input.toUpperCase();
105
+ const similar = [];
106
+ for (const symbol of symbols) {
107
+ // Only show USDT pairs, not base assets
108
+ if (!symbol.endsWith("USDT"))
109
+ continue;
110
+ const baseAsset = symbol.replace("USDT", "");
111
+ // Check if base asset starts with or contains the input
112
+ if (baseAsset.startsWith(inputUpper) || baseAsset.includes(inputUpper)) {
113
+ similar.push(baseAsset);
114
+ if (similar.length >= limit)
115
+ break;
116
+ }
117
+ }
118
+ return similar;
119
+ }
60
120
  /**
61
121
  * Normalize symbol to Binance format
62
122
  */
@@ -156,6 +216,12 @@ function getSupportedSymbols() {
156
216
  async function isValidSymbol(symbol) {
157
217
  try {
158
218
  const normalizedSymbol = normalizeSymbol(symbol);
219
+ // First check cache
220
+ const symbols = await fetchAllSymbols();
221
+ if (symbols.has(normalizedSymbol)) {
222
+ return true;
223
+ }
224
+ // Fall back to direct API check
159
225
  const url = `${BINANCE_API_BASE}/ticker/price?symbol=${normalizedSymbol}`;
160
226
  const response = await fetch(url);
161
227
  return response.ok;
@@ -164,3 +230,23 @@ async function isValidSymbol(symbol) {
164
230
  return false;
165
231
  }
166
232
  }
233
+ /**
234
+ * Validate symbol and get suggestions if invalid
235
+ */
236
+ async function validateSymbol(symbol) {
237
+ const normalized = normalizeSymbol(symbol);
238
+ try {
239
+ const url = `${BINANCE_API_BASE}/ticker/price?symbol=${normalized}`;
240
+ const response = await fetch(url);
241
+ if (response.ok) {
242
+ return { valid: true, normalized, suggestions: [] };
243
+ }
244
+ // If invalid, find similar symbols
245
+ const suggestions = await findSimilarSymbols(symbol, 5);
246
+ return { valid: false, normalized, suggestions };
247
+ }
248
+ catch {
249
+ const suggestions = await findSimilarSymbols(symbol, 5);
250
+ return { valid: false, normalized, suggestions };
251
+ }
252
+ }