prab-cli 1.2.0 → 1.2.4
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 +175 -4
- package/dist/lib/config.js +60 -1
- package/dist/lib/crypto/analyzer.js +275 -0
- package/dist/lib/crypto/chart-visual.js +548 -0
- package/dist/lib/crypto/data-fetcher.js +166 -0
- package/dist/lib/crypto/index.js +47 -0
- package/dist/lib/crypto/indicators.js +390 -0
- package/dist/lib/crypto/market-analyzer.js +497 -0
- package/dist/lib/crypto/signal-generator.js +559 -0
- package/dist/lib/crypto/smc-analyzer.js +418 -0
- package/dist/lib/crypto/smc-indicators.js +512 -0
- package/dist/lib/slash-commands.js +24 -0
- package/dist/lib/ui.js +70 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -53,6 +53,8 @@ const file_tools_1 = require("./lib/tools/file-tools");
|
|
|
53
53
|
const shell_tools_1 = require("./lib/tools/shell-tools");
|
|
54
54
|
const git_tools_1 = require("./lib/tools/git-tools");
|
|
55
55
|
const todo_tool_1 = require("./lib/tools/todo-tool");
|
|
56
|
+
// Import crypto signal system
|
|
57
|
+
const crypto_1 = require("./lib/crypto");
|
|
56
58
|
// Import model system
|
|
57
59
|
const groq_provider_1 = require("./lib/models/groq-provider");
|
|
58
60
|
const registry_1 = require("./lib/models/registry");
|
|
@@ -98,6 +100,50 @@ program
|
|
|
98
100
|
(0, config_1.clearApiKey)();
|
|
99
101
|
ui_1.log.success("API Key cleared!");
|
|
100
102
|
});
|
|
103
|
+
// SMC (Smart Money Concepts) analysis command
|
|
104
|
+
program
|
|
105
|
+
.command("smc <crypto>")
|
|
106
|
+
.description("Smart Money Concepts analysis (Order Blocks, FVG, Liquidity)")
|
|
107
|
+
.action(async (crypto) => {
|
|
108
|
+
await (0, crypto_1.runSMCAnalysis)(crypto);
|
|
109
|
+
});
|
|
110
|
+
// Comprehensive analysis command
|
|
111
|
+
program
|
|
112
|
+
.command("analyze <crypto>")
|
|
113
|
+
.description("Deep market analysis with multi-timeframe & all indicators")
|
|
114
|
+
.action(async (crypto) => {
|
|
115
|
+
await (0, crypto_1.comprehensiveAnalysis)(crypto);
|
|
116
|
+
});
|
|
117
|
+
// Quick trading signal command
|
|
118
|
+
program
|
|
119
|
+
.command("signal <crypto>")
|
|
120
|
+
.description("Quick trading signal for a cryptocurrency (e.g., prab-cli signal btc)")
|
|
121
|
+
.option("-i, --interval <interval>", "Time interval (1m, 5m, 15m, 1h, 4h, 1d, 1w)", "1h")
|
|
122
|
+
.action(async (crypto, options) => {
|
|
123
|
+
const validIntervals = ["1m", "5m", "15m", "1h", "4h", "1d", "1w"];
|
|
124
|
+
const interval = validIntervals.includes(options.interval)
|
|
125
|
+
? options.interval
|
|
126
|
+
: "1h";
|
|
127
|
+
await (0, crypto_1.fullSignal)(crypto, interval);
|
|
128
|
+
});
|
|
129
|
+
// List supported cryptocurrencies
|
|
130
|
+
program
|
|
131
|
+
.command("crypto-list")
|
|
132
|
+
.description("List supported cryptocurrency symbols")
|
|
133
|
+
.action(() => {
|
|
134
|
+
console.log("\nSupported Cryptocurrencies:\n");
|
|
135
|
+
const symbols = (0, crypto_1.getSupportedSymbols)();
|
|
136
|
+
const columns = 4;
|
|
137
|
+
for (let i = 0; i < symbols.length; i += columns) {
|
|
138
|
+
const row = symbols
|
|
139
|
+
.slice(i, i + columns)
|
|
140
|
+
.map((s) => s.padEnd(12))
|
|
141
|
+
.join("");
|
|
142
|
+
console.log(" " + row);
|
|
143
|
+
}
|
|
144
|
+
console.log("\nYou can also use any Binance trading pair (e.g., BTCUSDT, ETHBTC)");
|
|
145
|
+
console.log("");
|
|
146
|
+
});
|
|
101
147
|
// Model management commands
|
|
102
148
|
program
|
|
103
149
|
.command("model")
|
|
@@ -195,8 +241,9 @@ program.action(async () => {
|
|
|
195
241
|
tracker_1.tracker.modelInit(modelConfig.modelId, "groq", false, e.message);
|
|
196
242
|
process.exit(1);
|
|
197
243
|
}
|
|
198
|
-
// Display banner
|
|
199
|
-
(0,
|
|
244
|
+
// Display banner with customization
|
|
245
|
+
const customization = (0, config_1.getCustomization)();
|
|
246
|
+
(0, ui_1.banner)(modelConfig.modelId, toolRegistry.count(), customization);
|
|
200
247
|
// Context Gathering
|
|
201
248
|
const spinner = (0, ora_1.default)("Checking context...").start();
|
|
202
249
|
const isRepo = await (0, context_1.isGitRepo)();
|
|
@@ -238,6 +285,55 @@ program.action(async () => {
|
|
|
238
285
|
}
|
|
239
286
|
// Execute the selected command
|
|
240
287
|
switch (action) {
|
|
288
|
+
case "smc": {
|
|
289
|
+
// Prompt for crypto symbol
|
|
290
|
+
const { cryptoSymbol } = await inquirer_1.default.prompt([
|
|
291
|
+
{
|
|
292
|
+
type: "input",
|
|
293
|
+
name: "cryptoSymbol",
|
|
294
|
+
message: "Enter cryptocurrency symbol (e.g., btc, eth, sol):",
|
|
295
|
+
default: "btc",
|
|
296
|
+
},
|
|
297
|
+
]);
|
|
298
|
+
await (0, crypto_1.runSMCAnalysis)(cryptoSymbol);
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
case "analyze": {
|
|
302
|
+
// Prompt for crypto symbol
|
|
303
|
+
const { cryptoSymbol } = await inquirer_1.default.prompt([
|
|
304
|
+
{
|
|
305
|
+
type: "input",
|
|
306
|
+
name: "cryptoSymbol",
|
|
307
|
+
message: "Enter cryptocurrency symbol (e.g., btc, eth, sol):",
|
|
308
|
+
default: "btc",
|
|
309
|
+
},
|
|
310
|
+
]);
|
|
311
|
+
await (0, crypto_1.comprehensiveAnalysis)(cryptoSymbol);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
case "signal": {
|
|
315
|
+
// Prompt for crypto symbol
|
|
316
|
+
const { cryptoSymbol } = await inquirer_1.default.prompt([
|
|
317
|
+
{
|
|
318
|
+
type: "input",
|
|
319
|
+
name: "cryptoSymbol",
|
|
320
|
+
message: "Enter cryptocurrency symbol (e.g., btc, eth, sol):",
|
|
321
|
+
default: "btc",
|
|
322
|
+
},
|
|
323
|
+
]);
|
|
324
|
+
// Prompt for interval
|
|
325
|
+
const intervalChoice = await (0, select_1.default)({
|
|
326
|
+
message: "Select time interval:",
|
|
327
|
+
choices: [
|
|
328
|
+
{ name: "1 Hour (recommended)", value: "1h" },
|
|
329
|
+
{ name: "15 Minutes", value: "15m" },
|
|
330
|
+
{ name: "4 Hours", value: "4h" },
|
|
331
|
+
{ name: "1 Day", value: "1d" },
|
|
332
|
+
],
|
|
333
|
+
});
|
|
334
|
+
await (0, crypto_1.fullSignal)(cryptoSymbol, intervalChoice);
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
241
337
|
case "model": {
|
|
242
338
|
// Fetch models from Groq API if not cached
|
|
243
339
|
if (cachedModels.length === 0) {
|
|
@@ -278,7 +374,7 @@ program.action(async () => {
|
|
|
278
374
|
ui_1.log.info(`Already using ${selectedModel}`);
|
|
279
375
|
}
|
|
280
376
|
}
|
|
281
|
-
catch
|
|
377
|
+
catch {
|
|
282
378
|
// User cancelled with Ctrl+C
|
|
283
379
|
}
|
|
284
380
|
break;
|
|
@@ -364,6 +460,81 @@ program.action(async () => {
|
|
|
364
460
|
ui_1.log.success("API Key updated.");
|
|
365
461
|
break;
|
|
366
462
|
}
|
|
463
|
+
case "settings": {
|
|
464
|
+
console.log("\n┌─────────────────────────────────────┐");
|
|
465
|
+
console.log("│ CUSTOMIZATION │");
|
|
466
|
+
console.log("└─────────────────────────────────────┘\n");
|
|
467
|
+
const currentCustomization = (0, config_1.getCustomization)();
|
|
468
|
+
console.log(chalk_1.default.gray(` Current CLI Name: ${chalk_1.default.cyan(currentCustomization.cliName)}`));
|
|
469
|
+
console.log(chalk_1.default.gray(` Current User: ${chalk_1.default.cyan(currentCustomization.userName || "(not set)")}`));
|
|
470
|
+
console.log(chalk_1.default.gray(` Current Theme: ${chalk_1.default.cyan(currentCustomization.theme)}`));
|
|
471
|
+
console.log("");
|
|
472
|
+
try {
|
|
473
|
+
const settingChoice = await (0, select_1.default)({
|
|
474
|
+
message: "What would you like to customize?",
|
|
475
|
+
choices: [
|
|
476
|
+
{ name: "Change CLI Name (banner text)", value: "cli-name" },
|
|
477
|
+
{ name: "Set Your Name (greeting)", value: "user-name" },
|
|
478
|
+
{
|
|
479
|
+
name: "Change Theme (default, minimal, colorful)",
|
|
480
|
+
value: "theme",
|
|
481
|
+
},
|
|
482
|
+
{ name: "Reset to Defaults", value: "reset" },
|
|
483
|
+
{ name: "Cancel", value: "cancel" },
|
|
484
|
+
],
|
|
485
|
+
});
|
|
486
|
+
if (settingChoice === "cli-name") {
|
|
487
|
+
const { newName } = await inquirer_1.default.prompt([
|
|
488
|
+
{
|
|
489
|
+
type: "input",
|
|
490
|
+
name: "newName",
|
|
491
|
+
message: "Enter new CLI name (e.g., 'My CLI', 'Dev Tool'):",
|
|
492
|
+
default: currentCustomization.cliName,
|
|
493
|
+
},
|
|
494
|
+
]);
|
|
495
|
+
if (newName && newName.trim()) {
|
|
496
|
+
(0, config_1.setCliName)(newName.trim());
|
|
497
|
+
ui_1.log.success(`CLI name changed to: ${newName.trim()}`);
|
|
498
|
+
ui_1.log.info("Restart the CLI to see the new banner.");
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
else if (settingChoice === "user-name") {
|
|
502
|
+
const { newUserName } = await inquirer_1.default.prompt([
|
|
503
|
+
{
|
|
504
|
+
type: "input",
|
|
505
|
+
name: "newUserName",
|
|
506
|
+
message: "Enter your name (for greeting):",
|
|
507
|
+
default: currentCustomization.userName || "",
|
|
508
|
+
},
|
|
509
|
+
]);
|
|
510
|
+
if (newUserName && newUserName.trim()) {
|
|
511
|
+
(0, config_1.setUserName)(newUserName.trim());
|
|
512
|
+
ui_1.log.success(`Welcome message will now greet: ${newUserName.trim()}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
else if (settingChoice === "theme") {
|
|
516
|
+
const themeChoice = await (0, select_1.default)({
|
|
517
|
+
message: "Select a theme:",
|
|
518
|
+
choices: [
|
|
519
|
+
{ name: "Default (Cyan)", value: "default" },
|
|
520
|
+
{ name: "Minimal (White)", value: "minimal" },
|
|
521
|
+
{ name: "Colorful (Magenta)", value: "colorful" },
|
|
522
|
+
],
|
|
523
|
+
});
|
|
524
|
+
(0, config_1.setTheme)(themeChoice);
|
|
525
|
+
ui_1.log.success(`Theme changed to: ${themeChoice}`);
|
|
526
|
+
ui_1.log.info("Restart the CLI to see the new theme.");
|
|
527
|
+
}
|
|
528
|
+
else if (settingChoice === "reset") {
|
|
529
|
+
(0, config_1.resetCustomization)();
|
|
530
|
+
ui_1.log.success("Customization reset to defaults.");
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
catch {
|
|
534
|
+
// User cancelled
|
|
535
|
+
}
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
367
538
|
case "exit": {
|
|
368
539
|
process.exit(0);
|
|
369
540
|
}
|
|
@@ -450,7 +621,7 @@ program.action(async () => {
|
|
|
450
621
|
ui_1.log.info("Keeping current model. You can try again or switch models with /model");
|
|
451
622
|
}
|
|
452
623
|
}
|
|
453
|
-
catch
|
|
624
|
+
catch {
|
|
454
625
|
// User cancelled with Ctrl+C
|
|
455
626
|
ui_1.log.info("Model switch cancelled.");
|
|
456
627
|
}
|
package/dist/lib/config.js
CHANGED
|
@@ -3,11 +3,12 @@ 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.clearSessionData = exports.setSessionData = exports.getSessionData = exports.setPreference = exports.getPreferences = exports.setActiveModel = exports.getModelConfig = exports.clearApiKey = exports.setApiKey = exports.getApiKey = exports.getConfig = void 0;
|
|
6
|
+
exports.resetCustomization = exports.setTheme = exports.setUserName = exports.setCliName = exports.getCustomization = exports.clearSessionData = exports.setSessionData = exports.getSessionData = exports.setPreference = exports.getPreferences = exports.setActiveModel = exports.getModelConfig = exports.clearApiKey = exports.setApiKey = exports.getApiKey = exports.getConfig = void 0;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const os_1 = __importDefault(require("os"));
|
|
10
10
|
const registry_1 = require("./models/registry");
|
|
11
|
+
const DEFAULT_CLI_NAME = "Prab CLI";
|
|
11
12
|
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), ".config", "groq-cli-tool");
|
|
12
13
|
const CONFIG_FILE = path_1.default.join(CONFIG_DIR, "config.json");
|
|
13
14
|
const ensureConfigDir = () => {
|
|
@@ -50,6 +51,11 @@ const getConfig = () => {
|
|
|
50
51
|
safeMode: config.preferences?.safeMode ?? true,
|
|
51
52
|
maxTokens: config.preferences?.maxTokens,
|
|
52
53
|
},
|
|
54
|
+
customization: {
|
|
55
|
+
cliName: config.customization?.cliName || DEFAULT_CLI_NAME,
|
|
56
|
+
userName: config.customization?.userName,
|
|
57
|
+
theme: config.customization?.theme || "default",
|
|
58
|
+
},
|
|
53
59
|
session: config.session || { todos: [] },
|
|
54
60
|
};
|
|
55
61
|
};
|
|
@@ -154,3 +160,56 @@ const clearSessionData = () => {
|
|
|
154
160
|
writeConfig(config);
|
|
155
161
|
};
|
|
156
162
|
exports.clearSessionData = clearSessionData;
|
|
163
|
+
/**
|
|
164
|
+
* Get customization settings
|
|
165
|
+
*/
|
|
166
|
+
const getCustomization = () => {
|
|
167
|
+
const config = (0, exports.getConfig)();
|
|
168
|
+
return config.customization;
|
|
169
|
+
};
|
|
170
|
+
exports.getCustomization = getCustomization;
|
|
171
|
+
/**
|
|
172
|
+
* Set CLI name
|
|
173
|
+
*/
|
|
174
|
+
const setCliName = (name) => {
|
|
175
|
+
const config = readConfig();
|
|
176
|
+
if (!config.customization) {
|
|
177
|
+
config.customization = { cliName: DEFAULT_CLI_NAME };
|
|
178
|
+
}
|
|
179
|
+
config.customization.cliName = name;
|
|
180
|
+
writeConfig(config);
|
|
181
|
+
};
|
|
182
|
+
exports.setCliName = setCliName;
|
|
183
|
+
/**
|
|
184
|
+
* Set user name
|
|
185
|
+
*/
|
|
186
|
+
const setUserName = (name) => {
|
|
187
|
+
const config = readConfig();
|
|
188
|
+
if (!config.customization) {
|
|
189
|
+
config.customization = { cliName: DEFAULT_CLI_NAME };
|
|
190
|
+
}
|
|
191
|
+
config.customization.userName = name;
|
|
192
|
+
writeConfig(config);
|
|
193
|
+
};
|
|
194
|
+
exports.setUserName = setUserName;
|
|
195
|
+
/**
|
|
196
|
+
* Set theme
|
|
197
|
+
*/
|
|
198
|
+
const setTheme = (theme) => {
|
|
199
|
+
const config = readConfig();
|
|
200
|
+
if (!config.customization) {
|
|
201
|
+
config.customization = { cliName: DEFAULT_CLI_NAME };
|
|
202
|
+
}
|
|
203
|
+
config.customization.theme = theme;
|
|
204
|
+
writeConfig(config);
|
|
205
|
+
};
|
|
206
|
+
exports.setTheme = setTheme;
|
|
207
|
+
/**
|
|
208
|
+
* Reset customization to defaults
|
|
209
|
+
*/
|
|
210
|
+
const resetCustomization = () => {
|
|
211
|
+
const config = readConfig();
|
|
212
|
+
config.customization = { cliName: DEFAULT_CLI_NAME, theme: "default" };
|
|
213
|
+
writeConfig(config);
|
|
214
|
+
};
|
|
215
|
+
exports.resetCustomization = resetCustomization;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Technical Analysis Module
|
|
4
|
+
* Calculates EMA and generates trading signals
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.calculateEMA = calculateEMA;
|
|
8
|
+
exports.calculateAllEMAs = calculateAllEMAs;
|
|
9
|
+
exports.calculateIndicators = calculateIndicators;
|
|
10
|
+
exports.generateSignal = generateSignal;
|
|
11
|
+
exports.formatSignalSummary = formatSignalSummary;
|
|
12
|
+
/**
|
|
13
|
+
* Calculate Exponential Moving Average
|
|
14
|
+
*/
|
|
15
|
+
function calculateEMA(prices, period) {
|
|
16
|
+
if (prices.length < period) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
const ema = [];
|
|
20
|
+
const multiplier = 2 / (period + 1);
|
|
21
|
+
// First EMA value is SMA of first 'period' prices
|
|
22
|
+
let sum = 0;
|
|
23
|
+
for (let i = 0; i < period; i++) {
|
|
24
|
+
sum += prices[i];
|
|
25
|
+
}
|
|
26
|
+
ema.push(sum / period);
|
|
27
|
+
// Calculate EMA for remaining prices
|
|
28
|
+
for (let i = period; i < prices.length; i++) {
|
|
29
|
+
const currentEMA = (prices[i] - ema[ema.length - 1]) * multiplier + ema[ema.length - 1];
|
|
30
|
+
ema.push(currentEMA);
|
|
31
|
+
}
|
|
32
|
+
return ema;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Calculate all EMA values
|
|
36
|
+
*/
|
|
37
|
+
function calculateAllEMAs(candles) {
|
|
38
|
+
const closePrices = candles.map((c) => c.close);
|
|
39
|
+
return {
|
|
40
|
+
ema9: calculateEMA(closePrices, 9),
|
|
41
|
+
ema21: calculateEMA(closePrices, 21),
|
|
42
|
+
ema50: calculateEMA(closePrices, 50),
|
|
43
|
+
ema200: calculateEMA(closePrices, 200),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Detect EMA crossover patterns
|
|
48
|
+
*/
|
|
49
|
+
function detectCrossover(shortEMA, longEMA, lookback = 3) {
|
|
50
|
+
if (shortEMA.length < lookback + 1 || longEMA.length < lookback + 1) {
|
|
51
|
+
return "none";
|
|
52
|
+
}
|
|
53
|
+
const currentShort = shortEMA[shortEMA.length - 1];
|
|
54
|
+
const currentLong = longEMA[longEMA.length - 1];
|
|
55
|
+
const prevShort = shortEMA[shortEMA.length - lookback];
|
|
56
|
+
const prevLong = longEMA[longEMA.length - lookback];
|
|
57
|
+
// Golden cross: short EMA crosses above long EMA
|
|
58
|
+
if (prevShort <= prevLong && currentShort > currentLong) {
|
|
59
|
+
return "golden";
|
|
60
|
+
}
|
|
61
|
+
// Death cross: short EMA crosses below long EMA
|
|
62
|
+
if (prevShort >= prevLong && currentShort < currentLong) {
|
|
63
|
+
return "death";
|
|
64
|
+
}
|
|
65
|
+
return "none";
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Determine price position relative to EMAs
|
|
69
|
+
*/
|
|
70
|
+
function getPriceVsEMA(currentPrice, ema9, ema21, ema50) {
|
|
71
|
+
const aboveCount = [ema9, ema21, ema50].filter((ema) => currentPrice > ema).length;
|
|
72
|
+
if (aboveCount === 3)
|
|
73
|
+
return "above_all";
|
|
74
|
+
if (aboveCount === 0)
|
|
75
|
+
return "below_all";
|
|
76
|
+
return "mixed";
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Calculate trend strength based on EMA alignment
|
|
80
|
+
*/
|
|
81
|
+
function calculateTrendStrength(currentPrice, ema9, ema21, ema50, ema200) {
|
|
82
|
+
let strength = 0;
|
|
83
|
+
// Check EMA alignment (bullish: 9 > 21 > 50 > 200)
|
|
84
|
+
if (ema9 > ema21)
|
|
85
|
+
strength += 20;
|
|
86
|
+
if (ema21 > ema50)
|
|
87
|
+
strength += 20;
|
|
88
|
+
if (ema200 !== undefined && ema50 > ema200)
|
|
89
|
+
strength += 20;
|
|
90
|
+
// Check price vs EMAs
|
|
91
|
+
if (currentPrice > ema9)
|
|
92
|
+
strength += 10;
|
|
93
|
+
if (currentPrice > ema21)
|
|
94
|
+
strength += 10;
|
|
95
|
+
if (currentPrice > ema50)
|
|
96
|
+
strength += 10;
|
|
97
|
+
if (ema200 !== undefined && currentPrice > ema200)
|
|
98
|
+
strength += 10;
|
|
99
|
+
return Math.min(strength, 100);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Calculate technical indicators
|
|
103
|
+
*/
|
|
104
|
+
function calculateIndicators(data) {
|
|
105
|
+
const ema = calculateAllEMAs(data.candles);
|
|
106
|
+
const currentEMA9 = ema.ema9[ema.ema9.length - 1] || 0;
|
|
107
|
+
const currentEMA21 = ema.ema21[ema.ema21.length - 1] || 0;
|
|
108
|
+
const currentEMA50 = ema.ema50[ema.ema50.length - 1] || 0;
|
|
109
|
+
const currentEMA200 = ema.ema200.length > 0 ? ema.ema200[ema.ema200.length - 1] : undefined;
|
|
110
|
+
// Detect crossover between EMA9 and EMA21
|
|
111
|
+
const emaCrossover = detectCrossover(ema.ema9, ema.ema21);
|
|
112
|
+
// Price vs EMA position
|
|
113
|
+
const priceVsEMA = getPriceVsEMA(data.currentPrice, currentEMA9, currentEMA21, currentEMA50);
|
|
114
|
+
// Determine trend
|
|
115
|
+
let trend;
|
|
116
|
+
if (currentEMA9 > currentEMA21 && currentEMA21 > currentEMA50) {
|
|
117
|
+
trend = "bullish";
|
|
118
|
+
}
|
|
119
|
+
else if (currentEMA9 < currentEMA21 && currentEMA21 < currentEMA50) {
|
|
120
|
+
trend = "bearish";
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
trend = "neutral";
|
|
124
|
+
}
|
|
125
|
+
// Calculate trend strength
|
|
126
|
+
const trendStrength = calculateTrendStrength(data.currentPrice, currentEMA9, currentEMA21, currentEMA50, currentEMA200);
|
|
127
|
+
return {
|
|
128
|
+
ema,
|
|
129
|
+
currentEMA9,
|
|
130
|
+
currentEMA21,
|
|
131
|
+
currentEMA50,
|
|
132
|
+
currentEMA200: currentEMA200 || 0,
|
|
133
|
+
emaCrossover,
|
|
134
|
+
priceVsEMA,
|
|
135
|
+
trend,
|
|
136
|
+
trendStrength,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Generate trading signal based on EMA analysis
|
|
141
|
+
*/
|
|
142
|
+
function generateSignal(data) {
|
|
143
|
+
const indicators = calculateIndicators(data);
|
|
144
|
+
const reasoning = [];
|
|
145
|
+
let signal = "HOLD";
|
|
146
|
+
let confidence = 50;
|
|
147
|
+
let stopLoss = 3;
|
|
148
|
+
let takeProfit = 6;
|
|
149
|
+
// EMA Crossover signals
|
|
150
|
+
if (indicators.emaCrossover === "golden") {
|
|
151
|
+
signal = "BUY";
|
|
152
|
+
confidence += 25;
|
|
153
|
+
reasoning.push("Golden cross detected (EMA9 crossed above EMA21)");
|
|
154
|
+
}
|
|
155
|
+
else if (indicators.emaCrossover === "death") {
|
|
156
|
+
signal = "SELL";
|
|
157
|
+
confidence += 25;
|
|
158
|
+
reasoning.push("Death cross detected (EMA9 crossed below EMA21)");
|
|
159
|
+
}
|
|
160
|
+
// Price position relative to EMAs
|
|
161
|
+
if (indicators.priceVsEMA === "above_all") {
|
|
162
|
+
if (signal !== "SELL") {
|
|
163
|
+
signal = signal === "HOLD" ? "BUY" : signal;
|
|
164
|
+
confidence += 15;
|
|
165
|
+
}
|
|
166
|
+
reasoning.push("Price is above all major EMAs (9, 21, 50)");
|
|
167
|
+
}
|
|
168
|
+
else if (indicators.priceVsEMA === "below_all") {
|
|
169
|
+
if (signal !== "BUY") {
|
|
170
|
+
signal = signal === "HOLD" ? "SELL" : signal;
|
|
171
|
+
confidence += 15;
|
|
172
|
+
}
|
|
173
|
+
reasoning.push("Price is below all major EMAs (9, 21, 50)");
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
reasoning.push("Price is mixed relative to EMAs - consolidation phase");
|
|
177
|
+
}
|
|
178
|
+
// Trend analysis
|
|
179
|
+
if (indicators.trend === "bullish") {
|
|
180
|
+
if (signal !== "SELL") {
|
|
181
|
+
confidence += 10;
|
|
182
|
+
}
|
|
183
|
+
reasoning.push("EMAs are aligned bullishly (EMA9 > EMA21 > EMA50)");
|
|
184
|
+
}
|
|
185
|
+
else if (indicators.trend === "bearish") {
|
|
186
|
+
if (signal !== "BUY") {
|
|
187
|
+
confidence += 10;
|
|
188
|
+
}
|
|
189
|
+
reasoning.push("EMAs are aligned bearishly (EMA9 < EMA21 < EMA50)");
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
reasoning.push("EMAs show no clear trend alignment");
|
|
193
|
+
}
|
|
194
|
+
// EMA200 analysis (if available)
|
|
195
|
+
if (indicators.currentEMA200 > 0) {
|
|
196
|
+
if (data.currentPrice > indicators.currentEMA200) {
|
|
197
|
+
if (signal === "BUY")
|
|
198
|
+
confidence += 10;
|
|
199
|
+
reasoning.push("Price is above EMA200 (long-term bullish)");
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
if (signal === "SELL")
|
|
203
|
+
confidence += 10;
|
|
204
|
+
reasoning.push("Price is below EMA200 (long-term bearish)");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// 24h price change consideration
|
|
208
|
+
if (Math.abs(data.priceChangePercent24h) > 5) {
|
|
209
|
+
if (data.priceChangePercent24h > 5 && signal === "BUY") {
|
|
210
|
+
reasoning.push(`Strong momentum: +${data.priceChangePercent24h.toFixed(2)}% in 24h`);
|
|
211
|
+
confidence += 5;
|
|
212
|
+
}
|
|
213
|
+
else if (data.priceChangePercent24h < -5 && signal === "SELL") {
|
|
214
|
+
reasoning.push(`Strong downward momentum: ${data.priceChangePercent24h.toFixed(2)}% in 24h`);
|
|
215
|
+
confidence += 5;
|
|
216
|
+
}
|
|
217
|
+
else if (Math.abs(data.priceChangePercent24h) > 10) {
|
|
218
|
+
reasoning.push(`Caution: High volatility (${data.priceChangePercent24h.toFixed(2)}% in 24h)`);
|
|
219
|
+
confidence -= 5;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Adjust stop-loss and take-profit based on signal and confidence
|
|
223
|
+
if (signal === "BUY") {
|
|
224
|
+
// Set stop-loss below recent support (EMA21)
|
|
225
|
+
const distanceToEMA21 = ((data.currentPrice - indicators.currentEMA21) / data.currentPrice) * 100;
|
|
226
|
+
stopLoss = Math.max(2, Math.min(distanceToEMA21 + 1, 5));
|
|
227
|
+
takeProfit = stopLoss * 2; // 2:1 risk-reward ratio
|
|
228
|
+
}
|
|
229
|
+
else if (signal === "SELL") {
|
|
230
|
+
// For sell signals, stop-loss above resistance
|
|
231
|
+
const distanceToEMA21 = ((indicators.currentEMA21 - data.currentPrice) / data.currentPrice) * 100;
|
|
232
|
+
stopLoss = Math.max(2, Math.min(Math.abs(distanceToEMA21) + 1, 5));
|
|
233
|
+
takeProfit = stopLoss * 2;
|
|
234
|
+
}
|
|
235
|
+
// Cap confidence at 95%
|
|
236
|
+
confidence = Math.min(confidence, 95);
|
|
237
|
+
confidence = Math.max(confidence, 10);
|
|
238
|
+
// If no clear signals, suggest HOLD
|
|
239
|
+
if (reasoning.length <= 2 && indicators.emaCrossover === "none") {
|
|
240
|
+
signal = "HOLD";
|
|
241
|
+
reasoning.push("No clear trading signal - recommend waiting for better setup");
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
signal,
|
|
245
|
+
confidence,
|
|
246
|
+
stopLoss: Math.round(stopLoss * 10) / 10,
|
|
247
|
+
takeProfit: Math.round(takeProfit * 10) / 10,
|
|
248
|
+
indicators,
|
|
249
|
+
reasoning,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Format signal for display
|
|
254
|
+
*/
|
|
255
|
+
function formatSignalSummary(signal, symbol, price) {
|
|
256
|
+
const signalEmoji = signal.signal === "BUY" ? "\u{1F7E2}" : signal.signal === "SELL" ? "\u{1F534}" : "\u{1F7E1}";
|
|
257
|
+
const trendEmoji = signal.indicators.trend === "bullish"
|
|
258
|
+
? "\u{2197}\u{FE0F}"
|
|
259
|
+
: signal.indicators.trend === "bearish"
|
|
260
|
+
? "\u{2198}\u{FE0F}"
|
|
261
|
+
: "\u{27A1}\u{FE0F}";
|
|
262
|
+
return `
|
|
263
|
+
${signalEmoji} Signal: ${signal.signal}
|
|
264
|
+
Confidence: ${signal.confidence}%
|
|
265
|
+
Stop-Loss: ${signal.stopLoss}%
|
|
266
|
+
Take-Profit: ${signal.takeProfit}%
|
|
267
|
+
|
|
268
|
+
Current Price: $${price.toLocaleString()}
|
|
269
|
+
Trend: ${signal.indicators.trend} ${trendEmoji}
|
|
270
|
+
EMA9: $${signal.indicators.currentEMA9.toFixed(2)}
|
|
271
|
+
EMA21: $${signal.indicators.currentEMA21.toFixed(2)}
|
|
272
|
+
EMA50: $${signal.indicators.currentEMA50.toFixed(2)}
|
|
273
|
+
${signal.indicators.currentEMA200 > 0 ? `EMA200: $${signal.indicators.currentEMA200.toFixed(2)}` : ""}
|
|
274
|
+
`;
|
|
275
|
+
}
|