hedgequantx 1.1.1 → 1.2.32
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/README.md +128 -136
- package/bin/cli.js +28 -2076
- package/package.json +3 -3
- package/src/app.js +550 -0
- package/src/config/index.js +16 -2
- package/src/config/propfirms.js +324 -12
- package/src/pages/accounts.js +115 -0
- package/src/pages/algo.js +538 -0
- package/src/pages/index.js +13 -2
- package/src/pages/orders.js +114 -0
- package/src/pages/positions.js +115 -0
- package/src/pages/stats.js +212 -3
- package/src/pages/user.js +92 -0
- package/src/security/encryption.js +168 -0
- package/src/security/index.js +61 -0
- package/src/security/rateLimit.js +155 -0
- package/src/security/validation.js +253 -0
- package/src/services/hqx-server.js +34 -17
- package/src/services/index.js +2 -1
- package/src/services/projectx.js +383 -35
- package/src/services/session.js +150 -38
- package/src/ui/index.js +4 -1
- package/src/ui/menu.js +154 -0
- package/src/services/local-storage.js +0 -309
package/bin/cli.js
CHANGED
|
@@ -1,2096 +1,48 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
const figlet = require('figlet');
|
|
5
|
-
const inquirer = require('inquirer');
|
|
6
|
-
const ora = require('ora');
|
|
7
|
-
const asciichart = require('asciichart');
|
|
8
|
-
const { execSync } = require('child_process');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const os = require('os');
|
|
12
|
-
const { program } = require('commander');
|
|
13
|
-
|
|
14
|
-
// Import modular services
|
|
15
|
-
const { ProjectXService, connections: connMgr, storage: sessionStorage } = require('../src/services');
|
|
16
|
-
const { HQXServerService } = require('../src/services/hqx-server');
|
|
17
|
-
const { PROPFIRMS, PROPFIRM_CHOICES, ACCOUNT_STATUS, ACCOUNT_TYPE, ORDER_STATUS, ORDER_TYPE, ORDER_SIDE, FUTURES_SYMBOLS } = require('../src/config');
|
|
18
|
-
const { getDevice, getSeparator, getLogoWidth, visibleLength, centerText, padText, drawBoxHeader, drawBoxFooter, drawBoxRow, drawBoxSeparator, getColWidths, draw2ColHeader, draw2ColRow, draw2ColRowRaw, draw2ColSeparator, fmtRow, printLogo } = require('../src/ui');
|
|
19
|
-
const { showStats } = require('../src/pages');
|
|
20
|
-
|
|
21
|
-
// Alias for connections module
|
|
22
|
-
const connections = connMgr;
|
|
23
|
-
|
|
24
|
-
// Session courante (pour compatibilité)
|
|
25
|
-
let currentService = null;
|
|
26
|
-
|
|
27
3
|
/**
|
|
28
|
-
*
|
|
4
|
+
* @fileoverview HedgeQuantX CLI - Entry Point
|
|
5
|
+
* @module cli
|
|
6
|
+
* @description Prop Futures Algo Trading CLI
|
|
7
|
+
* @version 1.2.0
|
|
29
8
|
*/
|
|
30
|
-
const formatForDevice = (text, indent = 2) => {
|
|
31
|
-
const device = getDevice();
|
|
32
|
-
const maxWidth = device.maxContentWidth - indent;
|
|
33
|
-
|
|
34
|
-
if (text.length <= maxWidth) {
|
|
35
|
-
return ' '.repeat(indent) + text;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Word wrap for mobile
|
|
39
|
-
const words = text.split(' ');
|
|
40
|
-
const lines = [];
|
|
41
|
-
let currentLine = '';
|
|
42
|
-
|
|
43
|
-
for (const word of words) {
|
|
44
|
-
if ((currentLine + ' ' + word).trim().length <= maxWidth) {
|
|
45
|
-
currentLine = (currentLine + ' ' + word).trim();
|
|
46
|
-
} else {
|
|
47
|
-
if (currentLine) lines.push(' '.repeat(indent) + currentLine);
|
|
48
|
-
currentLine = word;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (currentLine) lines.push(' '.repeat(indent) + currentLine);
|
|
52
|
-
|
|
53
|
-
return lines.join('\n');
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Show device info (for debugging)
|
|
58
|
-
*/
|
|
59
|
-
const showDeviceInfo = () => {
|
|
60
|
-
const device = getDevice();
|
|
61
|
-
console.log();
|
|
62
|
-
console.log(chalk.gray(getSeparator()));
|
|
63
|
-
console.log(chalk.white(` ${device.deviceIcon} Device: ${chalk.cyan(device.deviceType.toUpperCase())}`));
|
|
64
|
-
console.log(chalk.white(` 📐 Size: ${chalk.cyan(device.width + 'x' + device.height)}`));
|
|
65
|
-
console.log(chalk.white(` [>] Platform: ${chalk.cyan(device.platform)}`));
|
|
66
|
-
if (device.isRemote) {
|
|
67
|
-
console.log(chalk.white(` 🌐 Remote: ${chalk.yellow('SSH Connection')}`));
|
|
68
|
-
}
|
|
69
|
-
console.log(chalk.gray(getSeparator()));
|
|
70
|
-
console.log();
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// Liste des PropFirms ProjectX
|
|
74
|
-
const projectXPropfirms = [
|
|
75
|
-
{ name: 'Topstep', value: 'topstep' },
|
|
76
|
-
{ name: 'Alpha Futures', value: 'alpha_futures' },
|
|
77
|
-
{ name: 'TickTickTrader', value: 'tickticktrader' },
|
|
78
|
-
{ name: 'Bulenox', value: 'bulenox' },
|
|
79
|
-
{ name: 'TradeDay', value: 'tradeday' },
|
|
80
|
-
{ name: 'Blusky', value: 'blusky' },
|
|
81
|
-
{ name: 'Goat Futures', value: 'goat_futures' },
|
|
82
|
-
{ name: 'The Futures Desk', value: 'futures_desk' },
|
|
83
|
-
{ name: 'DayTraders', value: 'daytraders' },
|
|
84
|
-
{ name: 'E8 Futures', value: 'e8_futures' },
|
|
85
|
-
{ name: 'Blue Guardian Futures', value: 'blue_guardian' },
|
|
86
|
-
{ name: 'FuturesElite', value: 'futures_elite' },
|
|
87
|
-
{ name: 'FXIFY', value: 'fxify' },
|
|
88
|
-
{ name: 'Hola Prime', value: 'hola_prime' },
|
|
89
|
-
{ name: 'Top One Futures', value: 'top_one_futures' },
|
|
90
|
-
{ name: 'Funding Futures', value: 'funding_futures' },
|
|
91
|
-
{ name: 'TX3 Funding', value: 'tx3_funding' },
|
|
92
|
-
{ name: 'Lucid Trading', value: 'lucid_trading' },
|
|
93
|
-
{ name: 'Tradeify', value: 'tradeify' },
|
|
94
|
-
{ name: 'Earn2Trade (Coming Soon!)', value: 'earn2trade', disabled: 'Coming Soon' },
|
|
95
|
-
];
|
|
96
|
-
|
|
97
|
-
// Banner - Responsive for all devices
|
|
98
|
-
const banner = async () => {
|
|
99
|
-
// Clear screen properly with ANSI escape codes
|
|
100
|
-
process.stdout.write('\x1b[2J\x1b[0f');
|
|
101
|
-
const device = getDevice();
|
|
102
|
-
|
|
103
|
-
// Get stats if connected
|
|
104
|
-
let statsInfo = null;
|
|
105
|
-
if (connections.count() > 0) {
|
|
106
|
-
try {
|
|
107
|
-
const allAccounts = await connections.getAllAccounts();
|
|
108
|
-
let totalBalance = 0;
|
|
109
|
-
let totalStartingBalance = 0;
|
|
110
|
-
let totalPnl = 0;
|
|
111
|
-
|
|
112
|
-
allAccounts.forEach(account => {
|
|
113
|
-
totalBalance += account.balance || 0;
|
|
114
|
-
totalStartingBalance += account.startingBalance || 0;
|
|
115
|
-
totalPnl += account.profitAndLoss || 0;
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// Use API P&L if available, otherwise calculate
|
|
119
|
-
const pnl = totalPnl !== 0 ? totalPnl : (totalBalance - totalStartingBalance);
|
|
120
|
-
const pnlPercent = totalStartingBalance > 0 ? ((pnl / totalStartingBalance) * 100).toFixed(1) : '0.0';
|
|
121
|
-
|
|
122
|
-
statsInfo = {
|
|
123
|
-
connections: connections.count(),
|
|
124
|
-
accounts: allAccounts.length,
|
|
125
|
-
balance: totalBalance,
|
|
126
|
-
pnl: pnl,
|
|
127
|
-
pnlPercent: pnlPercent
|
|
128
|
-
};
|
|
129
|
-
} catch (e) {
|
|
130
|
-
// Ignore errors
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (device.isMobile) {
|
|
135
|
-
// 📱 MOBILE: Adaptive to screen width
|
|
136
|
-
const width = device.width - 2; // Leave margin
|
|
137
|
-
const innerWidth = width - 4; // Account for borders and spaces
|
|
138
|
-
|
|
139
|
-
const centerText = (text, w) => {
|
|
140
|
-
const padding = Math.max(0, w - text.length);
|
|
141
|
-
const left = Math.floor(padding / 2);
|
|
142
|
-
const right = padding - left;
|
|
143
|
-
return ' '.repeat(left) + text + ' '.repeat(right);
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
console.log();
|
|
147
|
-
console.log(chalk.cyan.bold(' ┌' + '─'.repeat(innerWidth + 2) + '┐'));
|
|
148
|
-
console.log(chalk.cyan.bold(' │' + centerText('HEDGEQUANTX', innerWidth + 2) + '│'));
|
|
149
|
-
console.log(chalk.cyan.bold(' │' + centerText('═'.repeat(Math.min(11, innerWidth)), innerWidth + 2) + '│'));
|
|
150
|
-
|
|
151
|
-
if (statsInfo) {
|
|
152
|
-
const pnlColor = statsInfo.pnl >= 0 ? chalk.green : chalk.red;
|
|
153
|
-
const balStr = `$${(statsInfo.balance/1000).toFixed(0)}K`;
|
|
154
|
-
const accStr = `${statsInfo.accounts} acc`;
|
|
155
|
-
const pnlStr = `${statsInfo.pnl >= 0 ? '+' : ''}${statsInfo.pnlPercent}%`;
|
|
156
|
-
const infoText = `${balStr} | ${accStr} | ${pnlStr}`;
|
|
157
|
-
console.log(chalk.cyan.bold(' │') + centerText(infoText, innerWidth + 2) + chalk.cyan.bold('│'));
|
|
158
|
-
} else {
|
|
159
|
-
console.log(chalk.cyan.bold(' │') + chalk.yellow.bold(centerText('Algo Trading', innerWidth + 2)) + chalk.cyan.bold('│'));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
console.log(chalk.cyan.bold(' └' + '─'.repeat(innerWidth + 2) + '┘'));
|
|
163
|
-
console.log();
|
|
164
|
-
|
|
165
|
-
} else if (device.isTablet) {
|
|
166
|
-
// 📲 TABLET: Medium compact
|
|
167
|
-
const pkg = require('../package.json');
|
|
168
|
-
console.log();
|
|
169
|
-
console.log(
|
|
170
|
-
chalk.cyan(
|
|
171
|
-
figlet.textSync('HQX', {
|
|
172
|
-
font: 'Small',
|
|
173
|
-
horizontalLayout: 'fitted'
|
|
174
|
-
})
|
|
175
|
-
)
|
|
176
|
-
);
|
|
177
|
-
console.log(chalk.gray(getSeparator()));
|
|
178
|
-
if (statsInfo) {
|
|
179
|
-
const pnlColor = statsInfo.pnl >= 0 ? chalk.green : chalk.red;
|
|
180
|
-
console.log(
|
|
181
|
-
chalk.white(` Conn: ${chalk.cyan(statsInfo.connections)}`) +
|
|
182
|
-
chalk.gray(' | ') +
|
|
183
|
-
chalk.white(`Acc: ${chalk.cyan(statsInfo.accounts)}`) +
|
|
184
|
-
chalk.gray(' | ') +
|
|
185
|
-
chalk.white(`Bal: ${chalk.green('$' + statsInfo.balance.toLocaleString())}`) +
|
|
186
|
-
chalk.gray(' | ') +
|
|
187
|
-
chalk.white(`P&L: ${pnlColor((statsInfo.pnl >= 0 ? '+' : '') + '$' + statsInfo.pnl.toLocaleString())}`)
|
|
188
|
-
);
|
|
189
|
-
} else {
|
|
190
|
-
console.log(chalk.yellow.bold(' Prop Futures Algo Trading') + chalk.gray(` v${pkg.version}`));
|
|
191
|
-
}
|
|
192
|
-
console.log(chalk.gray(getSeparator()));
|
|
193
|
-
console.log();
|
|
194
|
-
|
|
195
|
-
} else {
|
|
196
|
-
// 💻 DESKTOP & LARGE DESKTOP
|
|
197
|
-
const logoText = figlet.textSync('HEDGEQUANTX', {
|
|
198
|
-
font: 'ANSI Shadow',
|
|
199
|
-
horizontalLayout: 'default',
|
|
200
|
-
verticalLayout: 'default'
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// Remove trailing empty lines from logo
|
|
204
|
-
const logoLines = logoText.split('\n').filter(line => line.trim().length > 0);
|
|
205
|
-
|
|
206
|
-
// Get max width of all logo lines
|
|
207
|
-
const maxLogoWidth = Math.max(...logoLines.map(line => line.length));
|
|
208
|
-
|
|
209
|
-
// Box width = logo width + 2 for borders
|
|
210
|
-
const boxWidth = maxLogoWidth + 2;
|
|
211
|
-
|
|
212
|
-
// Draw top border
|
|
213
|
-
console.log(chalk.cyan('╔' + '═'.repeat(maxLogoWidth) + '╗'));
|
|
214
|
-
|
|
215
|
-
// Draw logo lines inside box - pad each line to max width
|
|
216
|
-
logoLines.forEach(line => {
|
|
217
|
-
const paddedLine = line.padEnd(maxLogoWidth);
|
|
218
|
-
console.log(chalk.cyan('║') + chalk.cyan(paddedLine) + chalk.cyan('║'));
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
// Inner width (content area between ║ and ║)
|
|
222
|
-
const innerWidth = maxLogoWidth;
|
|
223
|
-
|
|
224
|
-
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
225
|
-
|
|
226
|
-
// Always show tagline centered
|
|
227
|
-
const tagline = 'Prop Futures Algo Trading';
|
|
228
|
-
const pkg = require('../package.json');
|
|
229
|
-
const version = 'v' + pkg.version;
|
|
230
|
-
const taglineText = chalk.yellow.bold(tagline) + ' ' + chalk.gray(version);
|
|
231
|
-
const taglineLen = tagline.length + 2 + version.length;
|
|
232
|
-
const taglineLeftPad = Math.floor((innerWidth - taglineLen) / 2);
|
|
233
|
-
const taglineRightPad = innerWidth - taglineLen - taglineLeftPad;
|
|
234
|
-
console.log(chalk.cyan('║') + ' '.repeat(taglineLeftPad) + taglineText + ' '.repeat(taglineRightPad) + chalk.cyan('║'));
|
|
235
|
-
|
|
236
|
-
// Show stats if connected
|
|
237
|
-
if (statsInfo) {
|
|
238
|
-
// Separator between tagline and stats
|
|
239
|
-
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
240
|
-
|
|
241
|
-
const pnlColor = statsInfo.pnl >= 0 ? chalk.green : chalk.red;
|
|
242
|
-
const pnlSign = statsInfo.pnl >= 0 ? '+' : '';
|
|
243
|
-
|
|
244
|
-
// Build info line
|
|
245
|
-
const connStr = `Connections: ${statsInfo.connections}`;
|
|
246
|
-
const accStr = `Accounts: ${statsInfo.accounts}`;
|
|
247
|
-
const balStr = `Balance: $${statsInfo.balance.toLocaleString()}`;
|
|
248
|
-
const pnlStr = `P&L: ${pnlSign}$${statsInfo.pnl.toLocaleString()} (${statsInfo.pnlPercent}%)`;
|
|
249
|
-
|
|
250
|
-
const statsLen = connStr.length + 4 + accStr.length + 4 + balStr.length + 4 + pnlStr.length;
|
|
251
|
-
const statsLeftPad = Math.floor((innerWidth - statsLen) / 2);
|
|
252
|
-
const statsRightPad = innerWidth - statsLen - statsLeftPad;
|
|
253
|
-
|
|
254
|
-
console.log(chalk.cyan('║') + ' '.repeat(statsLeftPad) +
|
|
255
|
-
chalk.white(connStr) + ' ' +
|
|
256
|
-
chalk.white(accStr) + ' ' +
|
|
257
|
-
chalk.green(balStr) + ' ' +
|
|
258
|
-
pnlColor(pnlStr) + ' '.repeat(statsRightPad) + chalk.cyan('║')
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
263
|
-
console.log();
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
// Menu principal - Choix de connexion (Responsive)
|
|
268
|
-
const mainMenu = async () => {
|
|
269
|
-
const device = getDevice();
|
|
270
|
-
|
|
271
|
-
const { connection } = await inquirer.prompt([
|
|
272
|
-
{
|
|
273
|
-
type: 'list',
|
|
274
|
-
name: 'connection',
|
|
275
|
-
message: device.isMobile
|
|
276
|
-
? chalk.white.bold('Connection:')
|
|
277
|
-
: chalk.white.bold('Choose Your Connection:'),
|
|
278
|
-
choices: [
|
|
279
|
-
{ name: chalk.cyan('ProjectX'), value: 'projectx' },
|
|
280
|
-
{ name: chalk.gray('Rithmic (Soon)'), value: 'rithmic', disabled: device.isMobile ? '' : 'Coming Soon' },
|
|
281
|
-
{ name: chalk.gray('Tradovate (Soon)'), value: 'tradovate', disabled: device.isMobile ? '' : 'Coming Soon' },
|
|
282
|
-
new inquirer.Separator(),
|
|
283
|
-
{ name: chalk.red('Exit'), value: 'exit' }
|
|
284
|
-
],
|
|
285
|
-
pageSize: device.menuPageSize,
|
|
286
|
-
loop: false
|
|
287
|
-
}
|
|
288
|
-
]);
|
|
289
|
-
|
|
290
|
-
return connection;
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
// Menu PropFirm pour ProjectX (Responsive)
|
|
294
|
-
const projectXMenu = async () => {
|
|
295
|
-
const device = getDevice();
|
|
296
|
-
console.log();
|
|
297
|
-
|
|
298
|
-
// Sur mobile, afficher des noms plus courts
|
|
299
|
-
const formatPropfirmName = (name) => {
|
|
300
|
-
if (device.isMobile && name.length > 15) {
|
|
301
|
-
// Raccourcir les noms longs sur mobile
|
|
302
|
-
const shortNames = {
|
|
303
|
-
'TickTickTrader': 'TickTick',
|
|
304
|
-
'Blue Guardian Futures': 'BlueGuardian',
|
|
305
|
-
'The Futures Desk': 'FuturesDesk',
|
|
306
|
-
'Top One Futures': 'TopOne',
|
|
307
|
-
'Funding Futures': 'FundingFut',
|
|
308
|
-
'Lucid Trading': 'Lucid',
|
|
309
|
-
'Earn2Trade (Coming Soon!)': 'Earn2Trade'
|
|
310
|
-
};
|
|
311
|
-
return shortNames[name] || name.substring(0, 12);
|
|
312
|
-
}
|
|
313
|
-
return name;
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
const { propfirm } = await inquirer.prompt([
|
|
317
|
-
{
|
|
318
|
-
type: 'list',
|
|
319
|
-
name: 'propfirm',
|
|
320
|
-
message: device.isMobile
|
|
321
|
-
? chalk.white.bold('Propfirm:')
|
|
322
|
-
: chalk.white.bold('Choose Your Propfirm:'),
|
|
323
|
-
choices: [
|
|
324
|
-
...projectXPropfirms.map(pf => ({
|
|
325
|
-
name: pf.disabled
|
|
326
|
-
? chalk.gray(formatPropfirmName(pf.name))
|
|
327
|
-
: chalk.cyan(formatPropfirmName(pf.name)),
|
|
328
|
-
value: pf.value,
|
|
329
|
-
disabled: pf.disabled
|
|
330
|
-
})),
|
|
331
|
-
new inquirer.Separator(),
|
|
332
|
-
{ name: chalk.yellow('< Back'), value: 'back' }
|
|
333
|
-
],
|
|
334
|
-
pageSize: device.menuPageSize,
|
|
335
|
-
loop: false
|
|
336
|
-
}
|
|
337
|
-
]);
|
|
338
|
-
|
|
339
|
-
return propfirm;
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
// Login prompt (Responsive)
|
|
343
|
-
const loginPrompt = async (propfirmName) => {
|
|
344
|
-
const device = getDevice();
|
|
345
|
-
console.log();
|
|
346
|
-
|
|
347
|
-
if (device.isMobile) {
|
|
348
|
-
console.log(chalk.cyan(`→ ${propfirmName}`));
|
|
349
|
-
} else {
|
|
350
|
-
console.log(chalk.cyan(`Connecting to ${propfirmName}...`));
|
|
351
|
-
}
|
|
352
|
-
console.log();
|
|
353
|
-
|
|
354
|
-
const credentials = await inquirer.prompt([
|
|
355
|
-
{
|
|
356
|
-
type: 'input',
|
|
357
|
-
name: 'username',
|
|
358
|
-
message: device.isMobile ? chalk.white.bold('Username:') : chalk.white.bold('Enter Your Username:'),
|
|
359
|
-
validate: (input) => input.length > 0 || 'Required'
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
type: 'password',
|
|
363
|
-
name: 'password',
|
|
364
|
-
message: device.isMobile ? chalk.white.bold('Password:') : chalk.white.bold('Enter Your Password:'),
|
|
365
|
-
mask: '*',
|
|
366
|
-
validate: (input) => input.length > 0 || 'Required'
|
|
367
|
-
}
|
|
368
|
-
]);
|
|
369
|
-
|
|
370
|
-
return credentials;
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
// Menu après connexion (Responsive)
|
|
374
|
-
const dashboardMenu = async (service) => {
|
|
375
|
-
const device = getDevice();
|
|
376
|
-
const user = service.user;
|
|
377
|
-
const propfirmName = service.getPropfirmName();
|
|
378
|
-
|
|
379
|
-
console.log();
|
|
380
|
-
console.log(chalk.gray(getSeparator()));
|
|
381
|
-
|
|
382
|
-
if (device.isMobile) {
|
|
383
|
-
console.log(chalk.green.bold(` ✓ ${propfirmName}`));
|
|
384
|
-
if (user) {
|
|
385
|
-
console.log(chalk.white(` ${user.userName.toUpperCase()}`));
|
|
386
|
-
}
|
|
387
|
-
} else {
|
|
388
|
-
console.log(chalk.green.bold(` Connected to ${propfirmName}`));
|
|
389
|
-
if (user) {
|
|
390
|
-
console.log(chalk.white(` Welcome, ${user.userName.toUpperCase()}!`));
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
console.log(chalk.gray(getSeparator()));
|
|
394
|
-
console.log();
|
|
395
|
-
|
|
396
|
-
// Choix adaptatifs selon le device
|
|
397
|
-
let choices;
|
|
398
|
-
if (device.isMobile) {
|
|
399
|
-
choices = [
|
|
400
|
-
{ name: chalk.cyan('Accounts'), value: 'accounts' },
|
|
401
|
-
{ name: chalk.cyan('Positions'), value: 'positions' },
|
|
402
|
-
{ name: chalk.cyan('Orders'), value: 'orders' },
|
|
403
|
-
{ name: chalk.cyan('Stats'), value: 'stats' },
|
|
404
|
-
{ name: chalk.cyan('Add Prop-Account'), value: 'add_prop_account' },
|
|
405
|
-
new inquirer.Separator(),
|
|
406
|
-
{ name: chalk.cyan('Algo'), value: 'algotrading' },
|
|
407
|
-
new inquirer.Separator(),
|
|
408
|
-
{ name: chalk.yellow('Update HQX'), value: 'refresh' },
|
|
409
|
-
{ name: chalk.red('Disconnect'), value: 'disconnect' }
|
|
410
|
-
];
|
|
411
|
-
} else {
|
|
412
|
-
choices = [
|
|
413
|
-
{ name: chalk.cyan('View Accounts'), value: 'accounts' },
|
|
414
|
-
{ name: chalk.cyan('View Positions'), value: 'positions' },
|
|
415
|
-
{ name: chalk.cyan('View Orders'), value: 'orders' },
|
|
416
|
-
{ name: chalk.cyan('View Stats'), value: 'stats' },
|
|
417
|
-
{ name: chalk.cyan('Add Prop-Account'), value: 'add_prop_account' },
|
|
418
|
-
{ name: chalk.cyan('User Info'), value: 'userinfo' },
|
|
419
|
-
new inquirer.Separator(),
|
|
420
|
-
{ name: chalk.cyan('Algo-Trading'), value: 'algotrading' },
|
|
421
|
-
new inquirer.Separator(),
|
|
422
|
-
{ name: chalk.yellow('Update HQX'), value: 'refresh' },
|
|
423
|
-
{ name: chalk.red('Disconnect'), value: 'disconnect' }
|
|
424
|
-
];
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
const { action } = await inquirer.prompt([
|
|
428
|
-
{
|
|
429
|
-
type: 'list',
|
|
430
|
-
name: 'action',
|
|
431
|
-
message: device.isMobile ? chalk.white.bold('Menu:') : chalk.white.bold('What would you like to do?'),
|
|
432
|
-
choices,
|
|
433
|
-
pageSize: device.menuPageSize,
|
|
434
|
-
loop: false
|
|
435
|
-
}
|
|
436
|
-
]);
|
|
437
|
-
|
|
438
|
-
return action;
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
// Afficher les comptes (toutes connexions)
|
|
442
|
-
const showAccounts = async (service) => {
|
|
443
|
-
const spinner = ora('Fetching accounts...').start();
|
|
444
|
-
const boxWidth = getLogoWidth();
|
|
445
|
-
const { col1, col2 } = getColWidths(boxWidth);
|
|
446
|
-
|
|
447
|
-
// Helper to format a row with label and value
|
|
448
|
-
const fmtRow = (label, value, colW) => {
|
|
449
|
-
const labelStr = ' ' + label.padEnd(12);
|
|
450
|
-
const valueVisible = (value || '').toString().replace(/\x1b\[[0-9;]*m/g, '');
|
|
451
|
-
const totalVisible = labelStr.length + valueVisible.length;
|
|
452
|
-
const padding = Math.max(0, colW - totalVisible);
|
|
453
|
-
return chalk.white(labelStr) + value + ' '.repeat(padding);
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
// Collecter les comptes de TOUTES les connexions
|
|
457
|
-
let allAccountsData = [];
|
|
458
|
-
|
|
459
|
-
if (connections.count() > 0) {
|
|
460
|
-
for (const conn of connections.getAll()) {
|
|
461
|
-
try {
|
|
462
|
-
const result = await conn.service.getTradingAccounts();
|
|
463
|
-
if (result.success && result.accounts) {
|
|
464
|
-
result.accounts.forEach(account => {
|
|
465
|
-
allAccountsData.push({
|
|
466
|
-
...account,
|
|
467
|
-
propfirm: conn.propfirm || conn.type,
|
|
468
|
-
service: conn.service
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
} catch (e) {}
|
|
473
|
-
}
|
|
474
|
-
} else if (service) {
|
|
475
|
-
const result = await service.getTradingAccounts();
|
|
476
|
-
if (result.success && result.accounts) {
|
|
477
|
-
allAccountsData = result.accounts.map(a => ({ ...a, propfirm: service.getPropfirmName(), service }));
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
spinner.succeed('Accounts loaded');
|
|
482
|
-
console.log();
|
|
483
|
-
|
|
484
|
-
if (allAccountsData.length === 0) {
|
|
485
|
-
drawBoxHeader('ACCOUNTS', boxWidth);
|
|
486
|
-
draw2ColRowRaw(chalk.yellow(' No accounts found.'), '', boxWidth);
|
|
487
|
-
drawBoxFooter(boxWidth);
|
|
488
|
-
} else {
|
|
489
|
-
const totalConns = connections.count() || 1;
|
|
490
|
-
drawBoxHeader(`ACCOUNTS (${allAccountsData.length} accounts, ${totalConns} connection${totalConns > 1 ? 's' : ''})`, boxWidth);
|
|
491
|
-
|
|
492
|
-
// Display 2 accounts per row
|
|
493
|
-
for (let i = 0; i < allAccountsData.length; i += 2) {
|
|
494
|
-
const acc1 = allAccountsData[i];
|
|
495
|
-
const acc2 = allAccountsData[i + 1];
|
|
496
|
-
|
|
497
|
-
const name1 = acc1.accountName || acc1.name || `Account #${acc1.accountId}`;
|
|
498
|
-
const name2 = acc2 ? (acc2.accountName || acc2.name || `Account #${acc2.accountId}`) : '';
|
|
499
|
-
|
|
500
|
-
// Account name header
|
|
501
|
-
draw2ColHeader(name1.substring(0, col1 - 4), name2.substring(0, col2 - 4), boxWidth);
|
|
502
|
-
|
|
503
|
-
// PropFirm
|
|
504
|
-
const st1 = ACCOUNT_STATUS[acc1.status] || { text: 'Unknown', color: 'gray' };
|
|
505
|
-
const st2 = acc2 ? (ACCOUNT_STATUS[acc2.status] || { text: 'Unknown', color: 'gray' }) : null;
|
|
506
|
-
const tp1 = ACCOUNT_TYPE[acc1.type] || { text: 'Unknown', color: 'white' };
|
|
507
|
-
const tp2 = acc2 ? (ACCOUNT_TYPE[acc2.type] || { text: 'Unknown', color: 'white' }) : null;
|
|
508
|
-
|
|
509
|
-
console.log(chalk.cyan('║') + fmtRow('PropFirm:', chalk.magenta(acc1.propfirm), col1) + chalk.cyan('│') + (acc2 ? fmtRow('PropFirm:', chalk.magenta(acc2.propfirm), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
510
|
-
console.log(chalk.cyan('║') + fmtRow('Balance:', acc1.balance >= 0 ? chalk.green('$' + acc1.balance.toLocaleString()) : chalk.red('$' + acc1.balance.toLocaleString()), col1) + chalk.cyan('│') + (acc2 ? fmtRow('Balance:', acc2.balance >= 0 ? chalk.green('$' + acc2.balance.toLocaleString()) : chalk.red('$' + acc2.balance.toLocaleString()), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
511
|
-
console.log(chalk.cyan('║') + fmtRow('Status:', chalk[st1.color](st1.text), col1) + chalk.cyan('│') + (acc2 ? fmtRow('Status:', chalk[st2.color](st2.text), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
512
|
-
console.log(chalk.cyan('║') + fmtRow('Type:', chalk[tp1.color](tp1.text), col1) + chalk.cyan('│') + (acc2 ? fmtRow('Type:', chalk[tp2.color](tp2.text), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
513
|
-
|
|
514
|
-
if (i + 2 < allAccountsData.length) {
|
|
515
|
-
draw2ColSeparator(boxWidth);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
drawBoxFooter(boxWidth);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
console.log();
|
|
523
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
524
|
-
};
|
|
525
|
-
|
|
526
|
-
// Afficher les infos utilisateur
|
|
527
|
-
const showUserInfo = async (service) => {
|
|
528
|
-
const boxWidth = getLogoWidth();
|
|
529
|
-
const spinner = ora('Fetching user info...').start();
|
|
530
|
-
|
|
531
|
-
// Collecter les infos de TOUTES les connexions
|
|
532
|
-
let allUsers = [];
|
|
533
|
-
|
|
534
|
-
if (connections.count() > 0) {
|
|
535
|
-
for (const conn of connections.getAll()) {
|
|
536
|
-
try {
|
|
537
|
-
const result = await conn.service.getUser();
|
|
538
|
-
if (result.success && result.user) {
|
|
539
|
-
allUsers.push({
|
|
540
|
-
...result.user,
|
|
541
|
-
propfirm: conn.propfirm || conn.type
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
} catch (e) {}
|
|
545
|
-
}
|
|
546
|
-
} else if (service) {
|
|
547
|
-
const result = await service.getUser();
|
|
548
|
-
if (result.success && result.user) {
|
|
549
|
-
allUsers.push({
|
|
550
|
-
...result.user,
|
|
551
|
-
propfirm: service.getPropfirmName()
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
spinner.succeed('User info loaded');
|
|
557
|
-
console.log();
|
|
558
|
-
|
|
559
|
-
// Inner width for content
|
|
560
|
-
const innerWidth = boxWidth - 2;
|
|
561
|
-
|
|
562
|
-
// Helper to format row
|
|
563
|
-
const fmtRow = (label, value, totalW) => {
|
|
564
|
-
const labelStr = ' ' + label.padEnd(14);
|
|
565
|
-
const valueVisible = (value || '').toString().replace(/\x1b\[[0-9;]*m/g, '');
|
|
566
|
-
const totalVisible = labelStr.length + valueVisible.length;
|
|
567
|
-
const padding = Math.max(0, totalW - totalVisible - 1);
|
|
568
|
-
return chalk.white(labelStr) + value + ' '.repeat(padding);
|
|
569
|
-
};
|
|
570
|
-
|
|
571
|
-
if (allUsers.length === 0) {
|
|
572
|
-
drawBoxHeader('USER INFO', boxWidth);
|
|
573
|
-
console.log(chalk.cyan('║') + padText(chalk.yellow(' No user information available.'), innerWidth) + chalk.cyan('║'));
|
|
574
|
-
drawBoxFooter(boxWidth);
|
|
575
|
-
} else {
|
|
576
|
-
drawBoxHeader(`USER INFO (${allUsers.length} connection${allUsers.length > 1 ? 's' : ''})`, boxWidth);
|
|
577
|
-
|
|
578
|
-
allUsers.forEach((user, index) => {
|
|
579
|
-
// PropFirm header
|
|
580
|
-
const pfHeader = `── ${user.propfirm} ──`;
|
|
581
|
-
console.log(chalk.cyan('║') + chalk.magenta.bold(centerText(pfHeader, innerWidth)) + chalk.cyan('║'));
|
|
582
|
-
|
|
583
|
-
// Username
|
|
584
|
-
console.log(chalk.cyan('║') + fmtRow('Username:', chalk.cyan(user.userName || 'N/A'), innerWidth) + chalk.cyan('║'));
|
|
585
|
-
|
|
586
|
-
// Full Name
|
|
587
|
-
const fullName = `${user.firstName || ''} ${user.lastName || ''}`.trim() || 'N/A';
|
|
588
|
-
console.log(chalk.cyan('║') + fmtRow('Name:', chalk.white(fullName), innerWidth) + chalk.cyan('║'));
|
|
589
|
-
|
|
590
|
-
// Email
|
|
591
|
-
console.log(chalk.cyan('║') + fmtRow('Email:', chalk.white(user.email || 'N/A'), innerWidth) + chalk.cyan('║'));
|
|
592
|
-
|
|
593
|
-
// User ID
|
|
594
|
-
console.log(chalk.cyan('║') + fmtRow('User ID:', chalk.gray(user.userId || 'N/A'), innerWidth) + chalk.cyan('║'));
|
|
595
|
-
|
|
596
|
-
// Separator between users if there are more
|
|
597
|
-
if (index < allUsers.length - 1) {
|
|
598
|
-
console.log(chalk.cyan('╠' + '─'.repeat(innerWidth) + '╣'));
|
|
599
|
-
}
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
drawBoxFooter(boxWidth);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
console.log();
|
|
606
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
// Afficher les positions
|
|
610
|
-
const showPositions = async (service) => {
|
|
611
|
-
const spinner = ora('Fetching positions...').start();
|
|
612
|
-
const boxWidth = getLogoWidth();
|
|
613
|
-
const { col1, col2 } = getColWidths(boxWidth);
|
|
614
|
-
|
|
615
|
-
// Collecter les positions de TOUTES les connexions
|
|
616
|
-
let allPositions = [];
|
|
617
|
-
|
|
618
|
-
if (connections.count() > 0) {
|
|
619
|
-
for (const conn of connections.getAll()) {
|
|
620
|
-
try {
|
|
621
|
-
const accountsResult = await conn.service.getTradingAccounts();
|
|
622
|
-
if (accountsResult.success && accountsResult.accounts) {
|
|
623
|
-
for (const account of accountsResult.accounts) {
|
|
624
|
-
const result = await conn.service.getPositions(account.accountId);
|
|
625
|
-
if (result.success && result.positions) {
|
|
626
|
-
allPositions = allPositions.concat(result.positions.map(p => ({
|
|
627
|
-
...p,
|
|
628
|
-
accountName: account.accountName || account.name || `Account #${account.accountId}`,
|
|
629
|
-
propfirm: conn.propfirm || conn.type
|
|
630
|
-
})));
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
} catch (e) {}
|
|
635
|
-
}
|
|
636
|
-
} else if (service) {
|
|
637
|
-
const accountsResult = await service.getTradingAccounts();
|
|
638
|
-
if (accountsResult.success && accountsResult.accounts) {
|
|
639
|
-
for (const account of accountsResult.accounts) {
|
|
640
|
-
const result = await service.getPositions(account.accountId);
|
|
641
|
-
if (result.success && result.positions) {
|
|
642
|
-
allPositions = allPositions.concat(result.positions.map(p => ({
|
|
643
|
-
...p,
|
|
644
|
-
accountName: account.accountName || account.name || `Account #${account.accountId}`,
|
|
645
|
-
propfirm: service.getPropfirmName()
|
|
646
|
-
})));
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
spinner.succeed('Positions loaded');
|
|
653
|
-
console.log();
|
|
654
|
-
|
|
655
|
-
// Helper to format row for positions
|
|
656
|
-
const fmtRow = (label, value, colW) => {
|
|
657
|
-
const labelStr = ' ' + label.padEnd(12);
|
|
658
|
-
const valueVisible = (value || '').toString().replace(/\x1b\[[0-9;]*m/g, '');
|
|
659
|
-
const totalVisible = labelStr.length + valueVisible.length;
|
|
660
|
-
const padding = Math.max(0, colW - totalVisible);
|
|
661
|
-
return chalk.white(labelStr) + value + ' '.repeat(padding);
|
|
662
|
-
};
|
|
663
|
-
|
|
664
|
-
if (allPositions.length === 0) {
|
|
665
|
-
drawBoxHeader('OPEN POSITIONS', boxWidth);
|
|
666
|
-
draw2ColRowRaw(chalk.yellow(' No open positions.'), '', boxWidth);
|
|
667
|
-
drawBoxFooter(boxWidth);
|
|
668
|
-
} else {
|
|
669
|
-
drawBoxHeader(`OPEN POSITIONS (${allPositions.length})`, boxWidth);
|
|
670
|
-
|
|
671
|
-
// Display 2 positions per row
|
|
672
|
-
for (let i = 0; i < allPositions.length; i += 2) {
|
|
673
|
-
const pos1 = allPositions[i];
|
|
674
|
-
const pos2 = allPositions[i + 1];
|
|
675
|
-
|
|
676
|
-
const symbol1 = pos1.symbolId || pos1.contractId || 'Unknown';
|
|
677
|
-
const symbol2 = pos2 ? (pos2.symbolId || pos2.contractId || 'Unknown') : '';
|
|
678
|
-
|
|
679
|
-
// Symbol header
|
|
680
|
-
draw2ColHeader(symbol1.substring(0, col1 - 4), symbol2.substring(0, col2 - 4), boxWidth);
|
|
681
|
-
|
|
682
|
-
// Position details
|
|
683
|
-
const size1 = pos1.positionSize || pos1.size || 0;
|
|
684
|
-
const size2 = pos2 ? (pos2.positionSize || pos2.size || 0) : 0;
|
|
685
|
-
const sizeColor1 = size1 > 0 ? chalk.green : (size1 < 0 ? chalk.red : chalk.white);
|
|
686
|
-
const sizeColor2 = size2 > 0 ? chalk.green : (size2 < 0 ? chalk.red : chalk.white);
|
|
687
|
-
const price1 = pos1.averagePrice ? '$' + pos1.averagePrice.toFixed(2) : 'N/A';
|
|
688
|
-
const price2 = pos2 && pos2.averagePrice ? '$' + pos2.averagePrice.toFixed(2) : 'N/A';
|
|
689
|
-
const pnl1 = pos1.profitAndLoss || 0;
|
|
690
|
-
const pnl2 = pos2 ? (pos2.profitAndLoss || 0) : 0;
|
|
691
|
-
const pnlColor1 = pnl1 >= 0 ? chalk.green : chalk.red;
|
|
692
|
-
const pnlColor2 = pnl2 >= 0 ? chalk.green : chalk.red;
|
|
693
|
-
|
|
694
|
-
console.log(chalk.cyan('║') + fmtRow('Account:', chalk.cyan(pos1.accountName.substring(0, 15)), col1) + chalk.cyan('│') + (pos2 ? fmtRow('Account:', chalk.cyan(pos2.accountName.substring(0, 15)), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
695
|
-
console.log(chalk.cyan('║') + fmtRow('PropFirm:', chalk.magenta(pos1.propfirm), col1) + chalk.cyan('│') + (pos2 ? fmtRow('PropFirm:', chalk.magenta(pos2.propfirm), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
696
|
-
console.log(chalk.cyan('║') + fmtRow('Size:', sizeColor1(size1.toString()), col1) + chalk.cyan('│') + (pos2 ? fmtRow('Size:', sizeColor2(size2.toString()), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
697
|
-
console.log(chalk.cyan('║') + fmtRow('Avg Price:', chalk.white(price1), col1) + chalk.cyan('│') + (pos2 ? fmtRow('Avg Price:', chalk.white(price2), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
698
|
-
console.log(chalk.cyan('║') + fmtRow('P&L:', pnlColor1((pnl1 >= 0 ? '+' : '') + '$' + pnl1.toFixed(2)), col1) + chalk.cyan('│') + (pos2 ? fmtRow('P&L:', pnlColor2((pnl2 >= 0 ? '+' : '') + '$' + pnl2.toFixed(2)), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
699
|
-
|
|
700
|
-
if (i + 2 < allPositions.length) {
|
|
701
|
-
draw2ColSeparator(boxWidth);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
drawBoxFooter(boxWidth);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
console.log();
|
|
709
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
710
|
-
};
|
|
711
9
|
|
|
712
|
-
|
|
713
|
-
const showOrders = async (service) => {
|
|
714
|
-
const spinner = ora('Fetching orders...').start();
|
|
715
|
-
const boxWidth = getLogoWidth();
|
|
716
|
-
const { col1, col2 } = getColWidths(boxWidth);
|
|
717
|
-
|
|
718
|
-
// Order status mapping
|
|
719
|
-
const ORDER_STATUS = {
|
|
720
|
-
0: { text: 'Pending', color: 'gray' },
|
|
721
|
-
1: { text: 'Open', color: 'yellow' },
|
|
722
|
-
2: { text: 'Filled', color: 'green' },
|
|
723
|
-
3: { text: 'Cancelled', color: 'red' }
|
|
724
|
-
};
|
|
725
|
-
|
|
726
|
-
// Order side mapping
|
|
727
|
-
const ORDER_SIDE = {
|
|
728
|
-
1: { text: 'BUY', color: 'green' },
|
|
729
|
-
2: { text: 'SELL', color: 'red' }
|
|
730
|
-
};
|
|
731
|
-
|
|
732
|
-
// Collecter les ordres de TOUTES les connexions
|
|
733
|
-
let allOrders = [];
|
|
734
|
-
|
|
735
|
-
if (connections.count() > 0) {
|
|
736
|
-
for (const conn of connections.getAll()) {
|
|
737
|
-
try {
|
|
738
|
-
const accountsResult = await conn.service.getTradingAccounts();
|
|
739
|
-
if (accountsResult.success && accountsResult.accounts) {
|
|
740
|
-
for (const account of accountsResult.accounts) {
|
|
741
|
-
const result = await conn.service.getOrders(account.accountId);
|
|
742
|
-
if (result.success && result.orders) {
|
|
743
|
-
allOrders = allOrders.concat(result.orders.map(o => ({
|
|
744
|
-
...o,
|
|
745
|
-
accountName: account.accountName || account.name || `Account #${account.accountId}`,
|
|
746
|
-
propfirm: conn.propfirm || conn.type
|
|
747
|
-
})));
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
} catch (e) {}
|
|
752
|
-
}
|
|
753
|
-
} else if (service) {
|
|
754
|
-
const accountsResult = await service.getTradingAccounts();
|
|
755
|
-
if (accountsResult.success && accountsResult.accounts) {
|
|
756
|
-
for (const account of accountsResult.accounts) {
|
|
757
|
-
const result = await service.getOrders(account.accountId);
|
|
758
|
-
if (result.success && result.orders) {
|
|
759
|
-
allOrders = allOrders.concat(result.orders.map(o => ({
|
|
760
|
-
...o,
|
|
761
|
-
accountName: account.accountName || account.name || `Account #${account.accountId}`,
|
|
762
|
-
propfirm: service.getPropfirmName()
|
|
763
|
-
})));
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
}
|
|
10
|
+
'use strict';
|
|
768
11
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
// Limit to recent 20 orders
|
|
773
|
-
const recentOrders = allOrders.slice(0, 20);
|
|
774
|
-
|
|
775
|
-
// Helper to format row for orders
|
|
776
|
-
const fmtRow = (label, value, colW) => {
|
|
777
|
-
const labelStr = ' ' + label.padEnd(12);
|
|
778
|
-
const valueVisible = (value || '').toString().replace(/\x1b\[[0-9;]*m/g, '');
|
|
779
|
-
const totalVisible = labelStr.length + valueVisible.length;
|
|
780
|
-
const padding = Math.max(0, colW - totalVisible);
|
|
781
|
-
return chalk.white(labelStr) + value + ' '.repeat(padding);
|
|
782
|
-
};
|
|
783
|
-
|
|
784
|
-
if (recentOrders.length === 0) {
|
|
785
|
-
drawBoxHeader('ORDERS', boxWidth);
|
|
786
|
-
draw2ColRowRaw(chalk.yellow(' No recent orders.'), '', boxWidth);
|
|
787
|
-
drawBoxFooter(boxWidth);
|
|
788
|
-
} else {
|
|
789
|
-
drawBoxHeader(`ORDERS (${recentOrders.length} of ${allOrders.length})`, boxWidth);
|
|
790
|
-
|
|
791
|
-
// Display 2 orders per row
|
|
792
|
-
for (let i = 0; i < recentOrders.length; i += 2) {
|
|
793
|
-
const ord1 = recentOrders[i];
|
|
794
|
-
const ord2 = recentOrders[i + 1];
|
|
795
|
-
|
|
796
|
-
const symbol1 = ord1.symbolId || 'Unknown';
|
|
797
|
-
const symbol2 = ord2 ? (ord2.symbolId || 'Unknown') : '';
|
|
798
|
-
|
|
799
|
-
// Symbol header
|
|
800
|
-
draw2ColHeader(symbol1.substring(0, col1 - 4), symbol2.substring(0, col2 - 4), boxWidth);
|
|
801
|
-
|
|
802
|
-
// Order details
|
|
803
|
-
const side1 = ORDER_SIDE[ord1.side || ord1.orderSide] || { text: 'N/A', color: 'white' };
|
|
804
|
-
const side2 = ord2 ? (ORDER_SIDE[ord2.side || ord2.orderSide] || { text: 'N/A', color: 'white' }) : null;
|
|
805
|
-
const st1 = ORDER_STATUS[ord1.status] || { text: 'Unknown', color: 'gray' };
|
|
806
|
-
const st2 = ord2 ? (ORDER_STATUS[ord2.status] || { text: 'Unknown', color: 'gray' }) : null;
|
|
807
|
-
const size1 = ord1.positionSize || ord1.size || ord1.quantity || 0;
|
|
808
|
-
const size2 = ord2 ? (ord2.positionSize || ord2.size || ord2.quantity || 0) : 0;
|
|
809
|
-
const price1 = ord1.price ? '$' + ord1.price.toFixed(2) : 'Market';
|
|
810
|
-
const price2 = ord2 && ord2.price ? '$' + ord2.price.toFixed(2) : (ord2 ? 'Market' : '');
|
|
811
|
-
|
|
812
|
-
console.log(chalk.cyan('║') + fmtRow('Account:', chalk.cyan(ord1.accountName.substring(0, 15)), col1) + chalk.cyan('│') + (ord2 ? fmtRow('Account:', chalk.cyan(ord2.accountName.substring(0, 15)), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
813
|
-
console.log(chalk.cyan('║') + fmtRow('Side:', chalk[side1.color](side1.text), col1) + chalk.cyan('│') + (ord2 ? fmtRow('Side:', chalk[side2.color](side2.text), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
814
|
-
console.log(chalk.cyan('║') + fmtRow('Size:', chalk.white(size1.toString()), col1) + chalk.cyan('│') + (ord2 ? fmtRow('Size:', chalk.white(size2.toString()), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
815
|
-
console.log(chalk.cyan('║') + fmtRow('Price:', chalk.white(price1), col1) + chalk.cyan('│') + (ord2 ? fmtRow('Price:', chalk.white(price2), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
816
|
-
console.log(chalk.cyan('║') + fmtRow('Status:', chalk[st1.color](st1.text), col1) + chalk.cyan('│') + (ord2 ? fmtRow('Status:', chalk[st2.color](st2.text), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
817
|
-
|
|
818
|
-
if (i + 2 < recentOrders.length) {
|
|
819
|
-
draw2ColSeparator(boxWidth);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
drawBoxFooter(boxWidth);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
console.log();
|
|
827
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
828
|
-
};
|
|
829
|
-
|
|
830
|
-
// Ajouter un compte PropFirm supplémentaire
|
|
831
|
-
const addPropAccount = async () => {
|
|
832
|
-
const device = getDevice();
|
|
833
|
-
|
|
834
|
-
// Afficher les connexions actives
|
|
835
|
-
if (connections.count() > 0) {
|
|
836
|
-
console.log();
|
|
837
|
-
console.log(chalk.cyan.bold(' Active Connections:'));
|
|
838
|
-
connections.getAll().forEach((conn, i) => {
|
|
839
|
-
console.log(chalk.green(` ${i + 1}. ${conn.propfirm || conn.type}`));
|
|
840
|
-
});
|
|
841
|
-
console.log();
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
// Menu pour choisir le type de connexion
|
|
845
|
-
const { connectionType } = await inquirer.prompt([
|
|
846
|
-
{
|
|
847
|
-
type: 'list',
|
|
848
|
-
name: 'connectionType',
|
|
849
|
-
message: chalk.white.bold('Add Connection:'),
|
|
850
|
-
choices: [
|
|
851
|
-
{ name: chalk.cyan('ProjectX (19 PropFirms)'), value: 'projectx' },
|
|
852
|
-
{ name: chalk.gray('Rithmic (Coming Soon)'), value: 'rithmic', disabled: 'Coming Soon' },
|
|
853
|
-
{ name: chalk.gray('Tradovate (Coming Soon)'), value: 'tradovate', disabled: 'Coming Soon' },
|
|
854
|
-
new inquirer.Separator(),
|
|
855
|
-
{ name: chalk.yellow('< Back'), value: 'back' }
|
|
856
|
-
],
|
|
857
|
-
pageSize: device.menuPageSize,
|
|
858
|
-
loop: false
|
|
859
|
-
}
|
|
860
|
-
]);
|
|
861
|
-
|
|
862
|
-
if (connectionType === 'back') {
|
|
863
|
-
return;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
if (connectionType === 'projectx') {
|
|
867
|
-
// Sélection de la PropFirm
|
|
868
|
-
const propfirm = await projectXMenu();
|
|
869
|
-
if (propfirm === 'back') {
|
|
870
|
-
return;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
// Demander les credentials
|
|
874
|
-
console.log();
|
|
875
|
-
const credentials = await inquirer.prompt([
|
|
876
|
-
{
|
|
877
|
-
type: 'input',
|
|
878
|
-
name: 'username',
|
|
879
|
-
message: chalk.white('Username/Email:'),
|
|
880
|
-
validate: (input) => input.length > 0 || 'Username is required'
|
|
881
|
-
},
|
|
882
|
-
{
|
|
883
|
-
type: 'password',
|
|
884
|
-
name: 'password',
|
|
885
|
-
message: chalk.white('Password:'),
|
|
886
|
-
mask: '*',
|
|
887
|
-
validate: (input) => input.length > 0 || 'Password is required'
|
|
888
|
-
}
|
|
889
|
-
]);
|
|
890
|
-
|
|
891
|
-
// Tenter la connexion
|
|
892
|
-
const spinner = ora('Connecting to PropFirm...').start();
|
|
893
|
-
|
|
894
|
-
try {
|
|
895
|
-
const newService = new ProjectXService(propfirm);
|
|
896
|
-
const loginResult = await newService.login(credentials.username, credentials.password);
|
|
897
|
-
|
|
898
|
-
if (loginResult.success) {
|
|
899
|
-
await newService.getUser();
|
|
900
|
-
const accountsResult = await newService.getTradingAccounts();
|
|
901
|
-
|
|
902
|
-
// Ajouter au connection manager
|
|
903
|
-
connections.add('projectx', newService, newService.getPropfirmName());
|
|
904
|
-
|
|
905
|
-
spinner.succeed('Connection added!');
|
|
906
|
-
console.log();
|
|
907
|
-
console.log(chalk.green(` Connected to ${newService.getPropfirmName()}`));
|
|
908
|
-
|
|
909
|
-
if (accountsResult.success && accountsResult.accounts) {
|
|
910
|
-
console.log(chalk.cyan(` Found ${accountsResult.accounts.length} trading account(s)`));
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
console.log(chalk.white(` Total connections: ${connections.count()}`));
|
|
914
|
-
} else {
|
|
915
|
-
spinner.fail('Connection failed');
|
|
916
|
-
console.log(chalk.red(` Error: ${loginResult.error}`));
|
|
917
|
-
}
|
|
918
|
-
} catch (error) {
|
|
919
|
-
spinner.fail('Connection failed');
|
|
920
|
-
console.log(chalk.red(` Error: ${error.message}`));
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
console.log();
|
|
925
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
926
|
-
};
|
|
927
|
-
|
|
928
|
-
// Session de trading active
|
|
929
|
-
let activeAlgoSession = null;
|
|
930
|
-
|
|
931
|
-
// Menu Algo-Trading principal
|
|
932
|
-
const algoTradingMenu = async (service) => {
|
|
933
|
-
const device = getDevice();
|
|
934
|
-
console.log();
|
|
935
|
-
console.log(chalk.gray(getSeparator()));
|
|
936
|
-
console.log(chalk.magenta.bold(' Algo-Trading'));
|
|
937
|
-
console.log(chalk.gray(getSeparator()));
|
|
938
|
-
console.log();
|
|
939
|
-
|
|
940
|
-
const { action } = await inquirer.prompt([
|
|
941
|
-
{
|
|
942
|
-
type: 'list',
|
|
943
|
-
name: 'action',
|
|
944
|
-
message: chalk.white.bold('Select Mode:'),
|
|
945
|
-
choices: [
|
|
946
|
-
{ name: chalk.cyan('One Account'), value: 'one_account' },
|
|
947
|
-
{ name: chalk.gray('Copy Trading (Coming Soon)'), value: 'copy_trading', disabled: 'Coming Soon' },
|
|
948
|
-
new inquirer.Separator(),
|
|
949
|
-
{ name: chalk.yellow('< Back'), value: 'back' }
|
|
950
|
-
],
|
|
951
|
-
pageSize: device.menuPageSize,
|
|
952
|
-
loop: false
|
|
953
|
-
}
|
|
954
|
-
]);
|
|
955
|
-
|
|
956
|
-
switch (action) {
|
|
957
|
-
case 'one_account':
|
|
958
|
-
await oneAccountMenu(service);
|
|
959
|
-
break;
|
|
960
|
-
case 'copy_trading':
|
|
961
|
-
// Disabled - Coming Soon
|
|
962
|
-
break;
|
|
963
|
-
case 'back':
|
|
964
|
-
return 'back';
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
return action;
|
|
968
|
-
};
|
|
969
|
-
|
|
970
|
-
// Menu One Account - Sélection du compte actif
|
|
971
|
-
const oneAccountMenu = async (service) => {
|
|
972
|
-
const spinner = ora('Fetching active accounts...').start();
|
|
973
|
-
|
|
974
|
-
// Récupérer les comptes via getTradingAccounts (plus fiable)
|
|
975
|
-
const result = await service.getTradingAccounts();
|
|
976
|
-
|
|
977
|
-
if (!result.success || !result.accounts || result.accounts.length === 0) {
|
|
978
|
-
spinner.fail('No active accounts found');
|
|
979
|
-
console.log(chalk.yellow(' You need at least one active trading account.'));
|
|
980
|
-
console.log();
|
|
981
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
982
|
-
return;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
// Filtrer seulement les comptes actifs (status === 0 = Active)
|
|
986
|
-
const activeAccounts = result.accounts.filter(acc => acc.status === 0);
|
|
987
|
-
|
|
988
|
-
if (activeAccounts.length === 0) {
|
|
989
|
-
spinner.fail('No active accounts found');
|
|
990
|
-
console.log(chalk.yellow(' You need at least one active trading account (status: Active).'));
|
|
991
|
-
console.log();
|
|
992
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
spinner.succeed(`Found ${activeAccounts.length} active account(s)`);
|
|
997
|
-
console.log();
|
|
998
|
-
|
|
999
|
-
// Afficher seulement les comptes actifs
|
|
1000
|
-
const accountChoices = activeAccounts.map(account => ({
|
|
1001
|
-
name: chalk.cyan(`${account.accountName || account.name || 'Account #' + account.accountId} - Balance: $${account.balance.toLocaleString()}`),
|
|
1002
|
-
value: account
|
|
1003
|
-
}));
|
|
1004
|
-
|
|
1005
|
-
accountChoices.push(new inquirer.Separator());
|
|
1006
|
-
accountChoices.push({ name: chalk.yellow('< Back'), value: 'back' });
|
|
1007
|
-
|
|
1008
|
-
const { selectedAccount } = await inquirer.prompt([
|
|
1009
|
-
{
|
|
1010
|
-
type: 'list',
|
|
1011
|
-
name: 'selectedAccount',
|
|
1012
|
-
message: chalk.white.bold('Select Account:'),
|
|
1013
|
-
choices: accountChoices,
|
|
1014
|
-
pageSize: 15,
|
|
1015
|
-
loop: false
|
|
1016
|
-
}
|
|
1017
|
-
]);
|
|
1018
|
-
|
|
1019
|
-
if (selectedAccount === 'back') {
|
|
1020
|
-
return;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
// Vérifier si le marché est ouvert
|
|
1024
|
-
const accountName = selectedAccount.accountName || selectedAccount.name || 'Account #' + selectedAccount.accountId;
|
|
1025
|
-
console.log();
|
|
1026
|
-
const marketSpinner = ora('Checking market status...').start();
|
|
1027
|
-
|
|
1028
|
-
// Vérifier les heures de marché
|
|
1029
|
-
const marketHours = service.checkMarketHours();
|
|
1030
|
-
|
|
1031
|
-
// Vérifier aussi via l'API si le compte peut trader
|
|
1032
|
-
const marketStatus = await service.getMarketStatus(selectedAccount.accountId);
|
|
1033
|
-
|
|
1034
|
-
if (!marketHours.isOpen) {
|
|
1035
|
-
marketSpinner.fail('Market is CLOSED');
|
|
1036
|
-
console.log();
|
|
1037
|
-
console.log(chalk.red.bold(' [X] ' + marketHours.message));
|
|
1038
|
-
console.log();
|
|
1039
|
-
console.log(chalk.gray(' Futures markets (CME) trading hours:'));
|
|
1040
|
-
console.log(chalk.gray(' Sunday 5:00 PM CT - Friday 4:00 PM CT'));
|
|
1041
|
-
console.log(chalk.gray(' Daily maintenance: 4:00 PM - 5:00 PM CT'));
|
|
1042
|
-
console.log();
|
|
1043
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
if (marketStatus.success && !marketStatus.isOpen) {
|
|
1048
|
-
marketSpinner.fail('Cannot trade on this account');
|
|
1049
|
-
console.log();
|
|
1050
|
-
console.log(chalk.red.bold(' [X] ' + marketStatus.message));
|
|
1051
|
-
console.log();
|
|
1052
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
1053
|
-
return;
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
marketSpinner.succeed('Market is OPEN - Ready to trade!');
|
|
1057
|
-
|
|
1058
|
-
// Passer à la sélection du symbole
|
|
1059
|
-
await selectSymbolMenu(service, selectedAccount);
|
|
1060
|
-
};
|
|
1061
|
-
|
|
1062
|
-
// Menu de sélection du symbole futures
|
|
1063
|
-
const selectSymbolMenu = async (service, account) => {
|
|
1064
|
-
const device = getDevice();
|
|
1065
|
-
const accountName = account.accountName || account.name || 'Account #' + account.accountId;
|
|
1066
|
-
|
|
1067
|
-
console.log();
|
|
1068
|
-
console.log(chalk.gray(getSeparator()));
|
|
1069
|
-
console.log(chalk.cyan.bold(` Account: ${accountName}`));
|
|
1070
|
-
console.log(chalk.gray(getSeparator()));
|
|
1071
|
-
console.log();
|
|
1072
|
-
|
|
1073
|
-
const symbolChoices = FUTURES_SYMBOLS.map(symbol => ({
|
|
1074
|
-
name: chalk.cyan(device.isMobile ? symbol.value : symbol.name),
|
|
1075
|
-
value: symbol
|
|
1076
|
-
}));
|
|
1077
|
-
|
|
1078
|
-
symbolChoices.push(new inquirer.Separator());
|
|
1079
|
-
symbolChoices.push({ name: chalk.yellow('< Back'), value: 'back' });
|
|
1080
|
-
|
|
1081
|
-
const { selectedSymbol } = await inquirer.prompt([
|
|
1082
|
-
{
|
|
1083
|
-
type: 'list',
|
|
1084
|
-
name: 'selectedSymbol',
|
|
1085
|
-
message: chalk.white.bold('Select Symbol:'),
|
|
1086
|
-
choices: symbolChoices,
|
|
1087
|
-
pageSize: device.menuPageSize,
|
|
1088
|
-
loop: false
|
|
1089
|
-
}
|
|
1090
|
-
]);
|
|
1091
|
-
|
|
1092
|
-
if (selectedSymbol === 'back') {
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
// Rechercher le contrat via Gateway API
|
|
1097
|
-
const spinner = ora(`Searching for ${selectedSymbol.value} contract...`).start();
|
|
1098
|
-
const contractResult = await service.searchContracts(selectedSymbol.searchText, false);
|
|
1099
|
-
|
|
1100
|
-
let contract = null;
|
|
1101
|
-
if (contractResult.success && contractResult.contracts && contractResult.contracts.length > 0) {
|
|
1102
|
-
// Trouver le contrat actif ou prendre le premier
|
|
1103
|
-
contract = contractResult.contracts.find(c => c.activeContract) || contractResult.contracts[0];
|
|
1104
|
-
spinner.succeed(`Found: ${contract.name || selectedSymbol.value}`);
|
|
1105
|
-
if (contract.tickSize && contract.tickValue) {
|
|
1106
|
-
console.log(chalk.gray(` Tick Size: ${contract.tickSize} | Tick Value: $${contract.tickValue}`));
|
|
1107
|
-
}
|
|
1108
|
-
} else {
|
|
1109
|
-
// Fallback: utiliser le symbole directement si l'API ne retourne rien
|
|
1110
|
-
spinner.warn(`Using ${selectedSymbol.value} (contract details unavailable)`);
|
|
1111
|
-
contract = {
|
|
1112
|
-
id: selectedSymbol.value,
|
|
1113
|
-
name: selectedSymbol.name,
|
|
1114
|
-
symbol: selectedSymbol.value
|
|
1115
|
-
};
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
console.log();
|
|
1119
|
-
|
|
1120
|
-
// Demander le nombre de contrats
|
|
1121
|
-
const { contracts } = await inquirer.prompt([
|
|
1122
|
-
{
|
|
1123
|
-
type: 'input',
|
|
1124
|
-
name: 'contracts',
|
|
1125
|
-
message: chalk.white.bold('Number of Contracts:'),
|
|
1126
|
-
default: '1',
|
|
1127
|
-
validate: (input) => {
|
|
1128
|
-
const num = parseInt(input);
|
|
1129
|
-
if (isNaN(num) || num <= 0 || num > 100) {
|
|
1130
|
-
return 'Please enter a valid number between 1 and 100';
|
|
1131
|
-
}
|
|
1132
|
-
return true;
|
|
1133
|
-
},
|
|
1134
|
-
filter: (input) => parseInt(input)
|
|
1135
|
-
}
|
|
1136
|
-
]);
|
|
1137
|
-
|
|
1138
|
-
// Confirmation et lancement
|
|
1139
|
-
console.log();
|
|
1140
|
-
console.log(chalk.gray(getSeparator()));
|
|
1141
|
-
console.log(chalk.white.bold(' Algo Configuration:'));
|
|
1142
|
-
console.log(chalk.gray(getSeparator()));
|
|
1143
|
-
console.log(chalk.white(` Account: ${chalk.cyan(accountName)}`));
|
|
1144
|
-
console.log(chalk.white(` Symbol: ${chalk.cyan(contract.name || selectedSymbol.value)}`));
|
|
1145
|
-
console.log(chalk.white(` Contracts: ${chalk.cyan(contracts)}`));
|
|
1146
|
-
console.log(chalk.gray(getSeparator()));
|
|
1147
|
-
console.log();
|
|
1148
|
-
|
|
1149
|
-
const { launch } = await inquirer.prompt([
|
|
1150
|
-
{
|
|
1151
|
-
type: 'list',
|
|
1152
|
-
name: 'launch',
|
|
1153
|
-
message: chalk.white.bold('Ready to launch?'),
|
|
1154
|
-
choices: [
|
|
1155
|
-
{ name: chalk.green.bold('[>] Launch Algo'), value: 'launch' },
|
|
1156
|
-
{ name: chalk.yellow('< Back'), value: 'back' }
|
|
1157
|
-
],
|
|
1158
|
-
loop: false
|
|
1159
|
-
}
|
|
1160
|
-
]);
|
|
1161
|
-
|
|
1162
|
-
if (launch === 'back') {
|
|
1163
|
-
return;
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
// Lancer l'algo
|
|
1167
|
-
await launchAlgo(service, account, contract, contracts);
|
|
1168
|
-
};
|
|
1169
|
-
|
|
1170
|
-
// Lancer l'algo
|
|
1171
|
-
const launchAlgo = async (service, account, contract, numContracts) => {
|
|
1172
|
-
const accountName = account.accountName || account.name || 'Account #' + account.accountId;
|
|
1173
|
-
const symbolName = contract.name || contract.symbol || contract.id;
|
|
1174
|
-
|
|
1175
|
-
console.log();
|
|
1176
|
-
console.log(chalk.green.bold(' [>] Launching HQX Algo...'));
|
|
1177
|
-
console.log();
|
|
1178
|
-
|
|
1179
|
-
const spinner = ora('Connecting to HQX Server...').start();
|
|
1180
|
-
|
|
1181
|
-
// Essayer de se connecter au serveur HQX
|
|
1182
|
-
let hqxConnected = false;
|
|
1183
|
-
try {
|
|
1184
|
-
if (hqxServer) {
|
|
1185
|
-
await hqxServer.connect();
|
|
1186
|
-
hqxConnected = hqxServer.isConnected();
|
|
1187
|
-
}
|
|
1188
|
-
} catch (e) {
|
|
1189
|
-
// Ignore connection errors
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
if (hqxConnected) {
|
|
1193
|
-
spinner.succeed('Connected to HQX Server');
|
|
1194
|
-
} else {
|
|
1195
|
-
spinner.warn('HQX Server unavailable - Running in Demo Mode');
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
console.log();
|
|
1199
|
-
console.log(chalk.gray(getSeparator()));
|
|
1200
|
-
console.log(chalk.cyan.bold(' HQX Ultra-Scalping Algo'));
|
|
1201
|
-
console.log(chalk.gray(getSeparator()));
|
|
1202
|
-
console.log(chalk.white(` Status: ${chalk.green('RUNNING')}`));
|
|
1203
|
-
console.log(chalk.white(` Account: ${chalk.cyan(accountName)}`));
|
|
1204
|
-
console.log(chalk.white(` Symbol: ${chalk.cyan(symbolName)}`));
|
|
1205
|
-
console.log(chalk.white(` Contracts: ${chalk.cyan(numContracts)}`));
|
|
1206
|
-
console.log(chalk.white(` Mode: ${hqxConnected ? chalk.green('LIVE') : chalk.yellow('DEMO')}`));
|
|
1207
|
-
console.log(chalk.gray(getSeparator()));
|
|
1208
|
-
console.log();
|
|
1209
|
-
|
|
1210
|
-
// Afficher les logs en temps réel
|
|
1211
|
-
let running = true;
|
|
1212
|
-
let logs = [];
|
|
1213
|
-
|
|
1214
|
-
const addLog = (type, message) => {
|
|
1215
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
1216
|
-
const typeColors = {
|
|
1217
|
-
info: chalk.cyan,
|
|
1218
|
-
signal: chalk.yellow,
|
|
1219
|
-
trade: chalk.green,
|
|
1220
|
-
error: chalk.red,
|
|
1221
|
-
warning: chalk.yellow
|
|
1222
|
-
};
|
|
1223
|
-
const color = typeColors[type] || chalk.white;
|
|
1224
|
-
logs.push({ timestamp, type, message, color });
|
|
1225
|
-
if (logs.length > 10) logs.shift();
|
|
1226
|
-
return logs;
|
|
1227
|
-
};
|
|
1228
|
-
|
|
1229
|
-
const displayLogs = () => {
|
|
1230
|
-
console.log(chalk.gray(' Recent Activity:'));
|
|
1231
|
-
logs.forEach(log => {
|
|
1232
|
-
console.log(chalk.gray(` [${log.timestamp}]`) + ' ' + log.color(log.message));
|
|
1233
|
-
});
|
|
1234
|
-
};
|
|
1235
|
-
|
|
1236
|
-
// Simulation de l'algo en mode demo
|
|
1237
|
-
addLog('info', 'Algo initialized');
|
|
1238
|
-
addLog('info', `Monitoring ${symbolName}...`);
|
|
1239
|
-
displayLogs();
|
|
1240
|
-
|
|
1241
|
-
if (!hqxConnected) {
|
|
1242
|
-
// Mode demo - simulation
|
|
1243
|
-
console.log();
|
|
1244
|
-
console.log(chalk.yellow(' Demo mode: No real trades will be executed.'));
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
console.log();
|
|
1248
|
-
|
|
1249
|
-
// Menu pour arrêter l'algo
|
|
1250
|
-
const { stopAction } = await inquirer.prompt([
|
|
1251
|
-
{
|
|
1252
|
-
type: 'list',
|
|
1253
|
-
name: 'stopAction',
|
|
1254
|
-
message: chalk.red.bold(''),
|
|
1255
|
-
choices: [
|
|
1256
|
-
{ name: chalk.red.bold('[X] Stop Algo'), value: 'stop' }
|
|
1257
|
-
],
|
|
1258
|
-
pageSize: 1,
|
|
1259
|
-
loop: false
|
|
1260
|
-
}
|
|
1261
|
-
]);
|
|
1262
|
-
|
|
1263
|
-
if (stopAction === 'stop') {
|
|
1264
|
-
running = false;
|
|
1265
|
-
console.log();
|
|
1266
|
-
console.log(chalk.yellow(' Stopping algo...'));
|
|
1267
|
-
|
|
1268
|
-
if (hqxConnected && hqxServer) {
|
|
1269
|
-
hqxServer.stopAlgo();
|
|
1270
|
-
hqxServer.disconnect();
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
console.log(chalk.green(' [OK] Algo stopped successfully'));
|
|
1274
|
-
console.log();
|
|
1275
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
1276
|
-
}
|
|
1277
|
-
};
|
|
1278
|
-
|
|
1279
|
-
// Menu des paramètres de trading (legacy - peut être supprimé)
|
|
1280
|
-
const tradingSettingsMenu = async (service, account, contract) => {
|
|
1281
|
-
console.log(chalk.gray('─'.repeat(60)));
|
|
1282
|
-
console.log(chalk.white.bold(' Trading Settings'));
|
|
1283
|
-
console.log(chalk.gray('─'.repeat(60)));
|
|
1284
|
-
console.log();
|
|
1285
|
-
|
|
1286
|
-
const settings = await inquirer.prompt([
|
|
1287
|
-
{
|
|
1288
|
-
type: 'input',
|
|
1289
|
-
name: 'dailyTarget',
|
|
1290
|
-
message: chalk.white.bold('Daily Target ($):'),
|
|
1291
|
-
default: '500',
|
|
1292
|
-
validate: (input) => {
|
|
1293
|
-
const num = parseFloat(input);
|
|
1294
|
-
if (isNaN(num) || num <= 0) {
|
|
1295
|
-
return 'Please enter a valid positive number';
|
|
1296
|
-
}
|
|
1297
|
-
return true;
|
|
1298
|
-
},
|
|
1299
|
-
filter: (input) => parseFloat(input)
|
|
1300
|
-
},
|
|
1301
|
-
{
|
|
1302
|
-
type: 'input',
|
|
1303
|
-
name: 'maxRisk',
|
|
1304
|
-
message: chalk.white.bold('Max Risk ($):'),
|
|
1305
|
-
default: '250',
|
|
1306
|
-
validate: (input) => {
|
|
1307
|
-
const num = parseFloat(input);
|
|
1308
|
-
if (isNaN(num) || num <= 0) {
|
|
1309
|
-
return 'Please enter a valid positive number';
|
|
1310
|
-
}
|
|
1311
|
-
return true;
|
|
1312
|
-
},
|
|
1313
|
-
filter: (input) => parseFloat(input)
|
|
1314
|
-
},
|
|
1315
|
-
{
|
|
1316
|
-
type: 'input',
|
|
1317
|
-
name: 'contracts',
|
|
1318
|
-
message: chalk.white.bold('Number of Contracts:'),
|
|
1319
|
-
default: '1',
|
|
1320
|
-
validate: (input) => {
|
|
1321
|
-
const num = parseInt(input);
|
|
1322
|
-
if (isNaN(num) || num <= 0 || num > 100) {
|
|
1323
|
-
return 'Please enter a valid number between 1 and 100';
|
|
1324
|
-
}
|
|
1325
|
-
return true;
|
|
1326
|
-
},
|
|
1327
|
-
filter: (input) => parseInt(input)
|
|
1328
|
-
}
|
|
1329
|
-
]);
|
|
1330
|
-
|
|
1331
|
-
// Afficher le résumé
|
|
1332
|
-
const device = getDevice();
|
|
1333
|
-
console.log();
|
|
1334
|
-
console.log(chalk.gray(getSeparator()));
|
|
1335
|
-
console.log(chalk.green.bold(' Trading Session Summary'));
|
|
1336
|
-
console.log(chalk.gray(getSeparator()));
|
|
1337
|
-
console.log();
|
|
1338
|
-
|
|
1339
|
-
if (device.isMobile) {
|
|
1340
|
-
console.log(chalk.white(` ${chalk.cyan(account.name)}`));
|
|
1341
|
-
console.log(chalk.white(` ${chalk.cyan(contract.name)}`));
|
|
1342
|
-
console.log(chalk.white(` Target: ${chalk.green('$' + settings.dailyTarget)} | Risk: ${chalk.red('$' + settings.maxRisk)}`));
|
|
1343
|
-
console.log(chalk.white(` Contracts: ${chalk.yellow(settings.contracts)}`));
|
|
1344
|
-
} else {
|
|
1345
|
-
console.log(chalk.white(` Account: ${chalk.cyan(account.name)}`));
|
|
1346
|
-
console.log(chalk.white(` Symbol: ${chalk.cyan(contract.name)} (${contract.description})`));
|
|
1347
|
-
console.log(chalk.white(` Daily Target: ${chalk.green('$' + settings.dailyTarget.toLocaleString())}`));
|
|
1348
|
-
console.log(chalk.white(` Max Risk: ${chalk.red('$' + settings.maxRisk.toLocaleString())}`));
|
|
1349
|
-
console.log(chalk.white(` Contracts: ${chalk.yellow(settings.contracts)}`));
|
|
1350
|
-
console.log(chalk.white(` Tick Value: ${chalk.gray('$' + contract.tickValue)}`));
|
|
1351
|
-
}
|
|
1352
|
-
console.log();
|
|
1353
|
-
|
|
1354
|
-
// Menu d'action
|
|
1355
|
-
const { action } = await inquirer.prompt([
|
|
1356
|
-
{
|
|
1357
|
-
type: 'list',
|
|
1358
|
-
name: 'action',
|
|
1359
|
-
message: chalk.white.bold('Action:'),
|
|
1360
|
-
choices: [
|
|
1361
|
-
{ name: chalk.cyan.bold('Launch Algo'), value: 'launch' },
|
|
1362
|
-
{ name: chalk.yellow('< Back'), value: 'back' }
|
|
1363
|
-
],
|
|
1364
|
-
pageSize: 5,
|
|
1365
|
-
loop: false
|
|
1366
|
-
}
|
|
1367
|
-
]);
|
|
1368
|
-
|
|
1369
|
-
if (action === 'launch') {
|
|
1370
|
-
// Sauvegarder la session active
|
|
1371
|
-
activeAlgoSession = {
|
|
1372
|
-
account,
|
|
1373
|
-
contract,
|
|
1374
|
-
settings,
|
|
1375
|
-
startTime: new Date(),
|
|
1376
|
-
status: 'active',
|
|
1377
|
-
pnl: 0,
|
|
1378
|
-
trades: 0,
|
|
1379
|
-
wins: 0,
|
|
1380
|
-
losses: 0
|
|
1381
|
-
};
|
|
1382
|
-
|
|
1383
|
-
// Lancer l'écran de logs
|
|
1384
|
-
await algoLogsScreen(service);
|
|
1385
|
-
}
|
|
1386
|
-
};
|
|
1387
|
-
|
|
1388
|
-
// Fonction pour formater le timestamp (Responsive)
|
|
1389
|
-
const formatTimestamp = () => {
|
|
1390
|
-
const device = getDevice();
|
|
1391
|
-
const now = new Date();
|
|
1392
|
-
|
|
1393
|
-
if (device.isMobile) {
|
|
1394
|
-
// Format court pour mobile: HH:MM
|
|
1395
|
-
return chalk.gray(`[${now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}]`);
|
|
1396
|
-
}
|
|
1397
|
-
return chalk.gray(`[${now.toLocaleTimeString()}]`);
|
|
1398
|
-
};
|
|
1399
|
-
|
|
1400
|
-
// Fonction pour ajouter un log (Responsive)
|
|
1401
|
-
const addLog = (logs, type, message) => {
|
|
1402
|
-
const device = getDevice();
|
|
1403
|
-
const timestamp = formatTimestamp();
|
|
1404
|
-
let coloredMessage;
|
|
1405
|
-
|
|
1406
|
-
// Labels courts pour mobile
|
|
1407
|
-
const labels = device.isMobile ? {
|
|
1408
|
-
info: 'i',
|
|
1409
|
-
success: '[+]',
|
|
1410
|
-
warning: '!',
|
|
1411
|
-
error: '✗',
|
|
1412
|
-
trade: '$',
|
|
1413
|
-
signal: '[>]'
|
|
1414
|
-
} : {
|
|
1415
|
-
info: 'INFO',
|
|
1416
|
-
success: 'SUCCESS',
|
|
1417
|
-
warning: 'WARNING',
|
|
1418
|
-
error: 'ERROR',
|
|
1419
|
-
trade: 'TRADE',
|
|
1420
|
-
signal: 'SIGNAL'
|
|
1421
|
-
};
|
|
1422
|
-
|
|
1423
|
-
switch (type) {
|
|
1424
|
-
case 'info':
|
|
1425
|
-
coloredMessage = chalk.blue(`[${labels.info}] ${message}`);
|
|
1426
|
-
break;
|
|
1427
|
-
case 'success':
|
|
1428
|
-
coloredMessage = chalk.green(`[${labels.success}] ${message}`);
|
|
1429
|
-
break;
|
|
1430
|
-
case 'warning':
|
|
1431
|
-
coloredMessage = chalk.yellow(`[${labels.warning}] ${message}`);
|
|
1432
|
-
break;
|
|
1433
|
-
case 'error':
|
|
1434
|
-
coloredMessage = chalk.red(`[${labels.error}] ${message}`);
|
|
1435
|
-
break;
|
|
1436
|
-
case 'trade':
|
|
1437
|
-
coloredMessage = chalk.magenta(`[${labels.trade}] ${message}`);
|
|
1438
|
-
break;
|
|
1439
|
-
case 'signal':
|
|
1440
|
-
coloredMessage = chalk.cyan(`[${labels.signal}] ${message}`);
|
|
1441
|
-
break;
|
|
1442
|
-
default:
|
|
1443
|
-
coloredMessage = chalk.white(message);
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
logs.push(`${timestamp} ${coloredMessage}`);
|
|
1447
|
-
return logs;
|
|
1448
|
-
};
|
|
1449
|
-
|
|
1450
|
-
// Écran des logs de l'algo (Responsive) - Connected to HQX-Algo Server
|
|
1451
|
-
const algoLogsScreen = async (service) => {
|
|
1452
|
-
let logs = [];
|
|
1453
|
-
let running = true;
|
|
1454
|
-
const device = getDevice();
|
|
1455
|
-
let hqxServer = null;
|
|
1456
|
-
let refreshInterval = null;
|
|
1457
|
-
|
|
1458
|
-
// Header (Responsive)
|
|
1459
|
-
const showHeader = () => {
|
|
1460
|
-
console.clear();
|
|
1461
|
-
const sep = getSeparator('═');
|
|
1462
|
-
const sepLight = getSeparator('─');
|
|
1463
|
-
|
|
1464
|
-
if (device.isMobile) {
|
|
1465
|
-
// 📱 MOBILE: Compact header
|
|
1466
|
-
console.log(chalk.gray(sep));
|
|
1467
|
-
console.log(chalk.magenta.bold(' HQX ULTRA-SCALPING'));
|
|
1468
|
-
console.log(chalk.green.bold(' [*] LIVE'));
|
|
1469
|
-
console.log(chalk.gray(sep));
|
|
1470
|
-
console.log(chalk.cyan(` ${activeAlgoSession.contract.name}`) + chalk.gray(` x${activeAlgoSession.settings.contracts}`));
|
|
1471
|
-
|
|
1472
|
-
// Stats compactes sur une ligne
|
|
1473
|
-
const pnlColor = activeAlgoSession.pnl >= 0 ? chalk.green : chalk.red;
|
|
1474
|
-
console.log(pnlColor(`$${activeAlgoSession.pnl.toFixed(0)}`) +
|
|
1475
|
-
chalk.gray(` W:${activeAlgoSession.wins} L:${activeAlgoSession.losses}`));
|
|
1476
|
-
console.log(chalk.gray(sepLight));
|
|
1477
|
-
|
|
1478
|
-
} else if (device.isTablet) {
|
|
1479
|
-
// 📲 TABLET: Medium header
|
|
1480
|
-
console.log(chalk.gray(sep));
|
|
1481
|
-
console.log(chalk.magenta.bold(' HQX ULTRA-SCALPING') + chalk.green.bold(' - LIVE'));
|
|
1482
|
-
console.log(chalk.gray(sep));
|
|
1483
|
-
console.log();
|
|
1484
|
-
console.log(chalk.white(` ${chalk.cyan(activeAlgoSession.contract.name)} | ${chalk.yellow(activeAlgoSession.settings.contracts + ' contracts')}`));
|
|
1485
|
-
console.log(chalk.white(` Target: ${chalk.green('$' + activeAlgoSession.settings.dailyTarget)} | Risk: ${chalk.red('$' + activeAlgoSession.settings.maxRisk)}`));
|
|
1486
|
-
console.log();
|
|
1487
|
-
|
|
1488
|
-
const pnlColor = activeAlgoSession.pnl >= 0 ? chalk.green : chalk.red;
|
|
1489
|
-
console.log(chalk.gray(sepLight));
|
|
1490
|
-
console.log(chalk.white(` P&L: ${pnlColor('$' + activeAlgoSession.pnl.toFixed(2))} | T:${activeAlgoSession.trades} W:${chalk.green(activeAlgoSession.wins)} L:${chalk.red(activeAlgoSession.losses)}`));
|
|
1491
|
-
console.log(chalk.gray(sepLight));
|
|
1492
|
-
console.log();
|
|
1493
|
-
|
|
1494
|
-
} else {
|
|
1495
|
-
// 💻 DESKTOP: Full header
|
|
1496
|
-
console.log(chalk.gray(sep));
|
|
1497
|
-
console.log(chalk.magenta.bold(' HQX ULTRA-SCALPING') + chalk.green.bold(' - LIVE'));
|
|
1498
|
-
console.log(chalk.gray(sep));
|
|
1499
|
-
console.log();
|
|
1500
|
-
console.log(chalk.white(` Account: ${chalk.cyan(activeAlgoSession.account.name)}`));
|
|
1501
|
-
console.log(chalk.white(` Symbol: ${chalk.cyan(activeAlgoSession.contract.name)}`));
|
|
1502
|
-
console.log(chalk.white(` Contracts: ${chalk.yellow(activeAlgoSession.settings.contracts)}`));
|
|
1503
|
-
console.log(chalk.white(` Target: ${chalk.green('$' + activeAlgoSession.settings.dailyTarget)}`));
|
|
1504
|
-
console.log(chalk.white(` Max Risk: ${chalk.red('$' + activeAlgoSession.settings.maxRisk)}`));
|
|
1505
|
-
console.log();
|
|
1506
|
-
|
|
1507
|
-
const pnlColor = activeAlgoSession.pnl >= 0 ? chalk.green : chalk.red;
|
|
1508
|
-
console.log(chalk.gray(sepLight));
|
|
1509
|
-
console.log(chalk.white(` P&L: ${pnlColor('$' + activeAlgoSession.pnl.toFixed(2))} | Trades: ${chalk.white(activeAlgoSession.trades)} | Wins: ${chalk.green(activeAlgoSession.wins)} | Losses: ${chalk.red(activeAlgoSession.losses)}`));
|
|
1510
|
-
console.log(chalk.gray(sepLight));
|
|
1511
|
-
console.log();
|
|
1512
|
-
}
|
|
1513
|
-
};
|
|
1514
|
-
|
|
1515
|
-
// Afficher les logs (Responsive)
|
|
1516
|
-
const showLogs = () => {
|
|
1517
|
-
const maxLogs = device.isMobile ? 6 : (device.isTablet ? 10 : 15);
|
|
1518
|
-
|
|
1519
|
-
if (!device.isMobile) {
|
|
1520
|
-
console.log(chalk.white.bold(' Logs:'));
|
|
1521
|
-
console.log();
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
const recentLogs = logs.slice(-maxLogs);
|
|
1525
|
-
recentLogs.forEach(log => {
|
|
1526
|
-
console.log(device.isMobile ? ` ${log}` : ` ${log}`);
|
|
1527
|
-
});
|
|
1528
|
-
|
|
1529
|
-
// Remplir les lignes vides
|
|
1530
|
-
for (let i = recentLogs.length; i < maxLogs; i++) {
|
|
1531
|
-
console.log();
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
console.log();
|
|
1535
|
-
console.log(chalk.gray(getSeparator()));
|
|
1536
|
-
console.log(chalk.yellow(device.isMobile ? ' CTRL+C to stop' : ' Press CTRL+C or select Stop to exit'));
|
|
1537
|
-
console.log(chalk.gray(getSeparator()));
|
|
1538
|
-
};
|
|
1539
|
-
|
|
1540
|
-
// Refresh display
|
|
1541
|
-
const refreshDisplay = () => {
|
|
1542
|
-
if (running) {
|
|
1543
|
-
showHeader();
|
|
1544
|
-
showLogs();
|
|
1545
|
-
}
|
|
1546
|
-
};
|
|
1547
|
-
|
|
1548
|
-
// Initialize HQX Server connection
|
|
1549
|
-
const initHQXServer = async () => {
|
|
1550
|
-
hqxServer = new HQXServerService();
|
|
1551
|
-
|
|
1552
|
-
// Setup event listeners
|
|
1553
|
-
hqxServer.on('connected', () => {
|
|
1554
|
-
logs = addLog(logs, 'success', 'Connected to HQX Server');
|
|
1555
|
-
refreshDisplay();
|
|
1556
|
-
});
|
|
1557
|
-
|
|
1558
|
-
hqxServer.on('disconnected', () => {
|
|
1559
|
-
logs = addLog(logs, 'warning', 'Disconnected from HQX Server');
|
|
1560
|
-
refreshDisplay();
|
|
1561
|
-
});
|
|
1562
|
-
|
|
1563
|
-
hqxServer.on('signal', (data) => {
|
|
1564
|
-
const direction = data.direction === 'long' ? 'BUY' : 'SELL';
|
|
1565
|
-
logs = addLog(logs, 'signal', `${direction} @ ${data.price}`);
|
|
1566
|
-
refreshDisplay();
|
|
1567
|
-
});
|
|
1568
|
-
|
|
1569
|
-
hqxServer.on('trade', (data) => {
|
|
1570
|
-
const pnlStr = data.pnl >= 0 ? `+$${data.pnl.toFixed(2)}` : `-$${Math.abs(data.pnl).toFixed(2)}`;
|
|
1571
|
-
logs = addLog(logs, 'trade', `${data.type.toUpperCase()} | P&L: ${pnlStr}`);
|
|
1572
|
-
|
|
1573
|
-
// Update session stats
|
|
1574
|
-
activeAlgoSession.trades++;
|
|
1575
|
-
activeAlgoSession.pnl += data.pnl;
|
|
1576
|
-
if (data.pnl > 0) {
|
|
1577
|
-
activeAlgoSession.wins++;
|
|
1578
|
-
} else {
|
|
1579
|
-
activeAlgoSession.losses++;
|
|
1580
|
-
}
|
|
1581
|
-
refreshDisplay();
|
|
1582
|
-
});
|
|
1583
|
-
|
|
1584
|
-
hqxServer.on('log', (data) => {
|
|
1585
|
-
logs = addLog(logs, data.type || 'info', data.message);
|
|
1586
|
-
refreshDisplay();
|
|
1587
|
-
});
|
|
1588
|
-
|
|
1589
|
-
hqxServer.on('stats', (data) => {
|
|
1590
|
-
if (data.pnl !== undefined) activeAlgoSession.pnl = data.pnl;
|
|
1591
|
-
if (data.trades !== undefined) activeAlgoSession.trades = data.trades;
|
|
1592
|
-
if (data.wins !== undefined) activeAlgoSession.wins = data.wins;
|
|
1593
|
-
if (data.losses !== undefined) activeAlgoSession.losses = data.losses;
|
|
1594
|
-
refreshDisplay();
|
|
1595
|
-
});
|
|
1596
|
-
|
|
1597
|
-
hqxServer.on('error', (data) => {
|
|
1598
|
-
logs = addLog(logs, 'error', data.message || 'Unknown error');
|
|
1599
|
-
refreshDisplay();
|
|
1600
|
-
});
|
|
1601
|
-
|
|
1602
|
-
return hqxServer;
|
|
1603
|
-
};
|
|
1604
|
-
|
|
1605
|
-
// Logs initiaux
|
|
1606
|
-
logs = addLog(logs, 'info', 'Initializing HQX Ultra-Scalping...');
|
|
1607
|
-
logs = addLog(logs, 'info', `Connecting to ${service.getPropfirmName()}...`);
|
|
1608
|
-
|
|
1609
|
-
// Afficher l'écran initial
|
|
1610
|
-
showHeader();
|
|
1611
|
-
showLogs();
|
|
1612
|
-
|
|
1613
|
-
// Try to connect to HQX Server
|
|
1614
|
-
try {
|
|
1615
|
-
await initHQXServer();
|
|
1616
|
-
|
|
1617
|
-
// Get PropFirm token for market data
|
|
1618
|
-
const propfirmToken = service.getToken();
|
|
1619
|
-
|
|
1620
|
-
// Authenticate with HQX Server (using propfirm token as API key for now)
|
|
1621
|
-
logs = addLog(logs, 'info', 'Authenticating with HQX Server...');
|
|
1622
|
-
refreshDisplay();
|
|
1623
|
-
|
|
1624
|
-
const authResult = await hqxServer.authenticate(propfirmToken).catch(() => null);
|
|
1625
|
-
|
|
1626
|
-
if (authResult && authResult.success) {
|
|
1627
|
-
logs = addLog(logs, 'success', 'Authenticated');
|
|
1628
|
-
|
|
1629
|
-
// Connect WebSocket
|
|
1630
|
-
await hqxServer.connect().catch(() => null);
|
|
1631
|
-
|
|
1632
|
-
// Start algo
|
|
1633
|
-
hqxServer.startAlgo({
|
|
1634
|
-
accountId: activeAlgoSession.account.id,
|
|
1635
|
-
contractId: activeAlgoSession.contract.id,
|
|
1636
|
-
symbol: activeAlgoSession.contract.name,
|
|
1637
|
-
contracts: activeAlgoSession.settings.contracts,
|
|
1638
|
-
dailyTarget: activeAlgoSession.settings.dailyTarget,
|
|
1639
|
-
maxRisk: activeAlgoSession.settings.maxRisk,
|
|
1640
|
-
propfirm: service.propfirm,
|
|
1641
|
-
propfirmToken: propfirmToken
|
|
1642
|
-
});
|
|
1643
|
-
|
|
1644
|
-
logs = addLog(logs, 'success', 'Algo started');
|
|
1645
|
-
} else {
|
|
1646
|
-
// Fallback to simulation mode if HQX Server not available
|
|
1647
|
-
logs = addLog(logs, 'warning', 'HQX Server unavailable - Simulation mode');
|
|
1648
|
-
logs = addLog(logs, 'info', 'Running in demo mode');
|
|
1649
|
-
|
|
1650
|
-
// Start simulation
|
|
1651
|
-
startSimulation();
|
|
1652
|
-
}
|
|
1653
|
-
} catch (error) {
|
|
1654
|
-
// Fallback to simulation
|
|
1655
|
-
logs = addLog(logs, 'warning', 'HQX Server unavailable - Simulation mode');
|
|
1656
|
-
startSimulation();
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
refreshDisplay();
|
|
1660
|
-
|
|
1661
|
-
// Simulation mode (when HQX Server not available)
|
|
1662
|
-
function startSimulation() {
|
|
1663
|
-
logs = addLog(logs, 'success', 'Engine started (Simulation)');
|
|
1664
|
-
refreshDisplay();
|
|
1665
|
-
|
|
1666
|
-
refreshInterval = setInterval(() => {
|
|
1667
|
-
if (!running) {
|
|
1668
|
-
clearInterval(refreshInterval);
|
|
1669
|
-
return;
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
const randomMsgs = [
|
|
1673
|
-
{ type: 'info', msg: 'Monitoring market...' },
|
|
1674
|
-
{ type: 'signal', msg: 'Scanning for entry...' },
|
|
1675
|
-
{ type: 'info', msg: 'No positions' },
|
|
1676
|
-
{ type: 'info', msg: 'Risk: OK' },
|
|
1677
|
-
{ type: 'signal', msg: 'Analyzing order flow...' },
|
|
1678
|
-
{ type: 'info', msg: 'Volatility: Normal' }
|
|
1679
|
-
];
|
|
1680
|
-
const randomMsg = randomMsgs[Math.floor(Math.random() * randomMsgs.length)];
|
|
1681
|
-
logs = addLog(logs, randomMsg.type, randomMsg.msg);
|
|
1682
|
-
refreshDisplay();
|
|
1683
|
-
}, 3000);
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
// Wait for user to stop
|
|
1687
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1688
|
-
|
|
1689
|
-
const { stopAction } = await inquirer.prompt([
|
|
1690
|
-
{
|
|
1691
|
-
type: 'list',
|
|
1692
|
-
name: 'stopAction',
|
|
1693
|
-
message: chalk.red.bold(''),
|
|
1694
|
-
choices: [
|
|
1695
|
-
{ name: chalk.red.bold('Stop Algo'), value: 'stop' }
|
|
1696
|
-
],
|
|
1697
|
-
pageSize: 1,
|
|
1698
|
-
loop: false
|
|
1699
|
-
}
|
|
1700
|
-
]);
|
|
1701
|
-
|
|
1702
|
-
if (stopAction === 'stop') {
|
|
1703
|
-
running = false;
|
|
1704
|
-
activeAlgoSession.status = 'stopped';
|
|
1705
|
-
|
|
1706
|
-
// Clear simulation interval if running
|
|
1707
|
-
if (refreshInterval) {
|
|
1708
|
-
clearInterval(refreshInterval);
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
logs = addLog(logs, 'warning', 'Stop signal received');
|
|
1712
|
-
logs = addLog(logs, 'info', 'Closing all positions...');
|
|
1713
|
-
|
|
1714
|
-
// Stop algo on HQX Server
|
|
1715
|
-
if (hqxServer && hqxServer.isConnected()) {
|
|
1716
|
-
hqxServer.stopAlgo();
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1720
|
-
logs = addLog(logs, 'success', 'All positions closed');
|
|
1721
|
-
logs = addLog(logs, 'info', 'Disconnecting...');
|
|
1722
|
-
|
|
1723
|
-
// Disconnect from HQX Server
|
|
1724
|
-
if (hqxServer) {
|
|
1725
|
-
hqxServer.disconnect();
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1729
|
-
logs = addLog(logs, 'success', 'Algo stopped successfully');
|
|
1730
|
-
|
|
1731
|
-
showHeader();
|
|
1732
|
-
showLogs();
|
|
1733
|
-
|
|
1734
|
-
console.log();
|
|
1735
|
-
console.log(chalk.yellow.bold(' Algo stopped.'));
|
|
1736
|
-
console.log();
|
|
1737
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
1738
|
-
}
|
|
1739
|
-
};
|
|
1740
|
-
|
|
1741
|
-
// Fonction pour gérer les mises à jour
|
|
1742
|
-
const handleUpdate = async () => {
|
|
1743
|
-
const pkg = require('../package.json');
|
|
1744
|
-
const currentVersion = pkg.version;
|
|
1745
|
-
|
|
1746
|
-
const spinnerRefresh = ora('Checking for updates...').start();
|
|
1747
|
-
try {
|
|
1748
|
-
const cliDir = path.resolve(__dirname, '..');
|
|
1749
|
-
|
|
1750
|
-
// Check if git repo exists
|
|
1751
|
-
try {
|
|
1752
|
-
execSync('git status', { cwd: cliDir, stdio: 'pipe' });
|
|
1753
|
-
} catch (e) {
|
|
1754
|
-
throw new Error('Not a git repository');
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
// Check if remote exists
|
|
1758
|
-
let hasRemote = false;
|
|
1759
|
-
try {
|
|
1760
|
-
const gitRemoteUrl = execSync('git remote get-url origin', { cwd: cliDir, stdio: 'pipe' }).toString().trim();
|
|
1761
|
-
hasRemote = gitRemoteUrl.length > 0;
|
|
1762
|
-
} catch (e) {
|
|
1763
|
-
hasRemote = false;
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
if (hasRemote) {
|
|
1767
|
-
// Get current commit before pull
|
|
1768
|
-
const beforeCommit = execSync('git rev-parse --short HEAD', { cwd: cliDir, stdio: 'pipe' }).toString().trim();
|
|
1769
|
-
|
|
1770
|
-
// Fetch first to check if updates available
|
|
1771
|
-
execSync('git fetch origin main', { cwd: cliDir, stdio: 'pipe' });
|
|
1772
|
-
|
|
1773
|
-
// Check if we're behind
|
|
1774
|
-
const behindCount = execSync('git rev-list HEAD..origin/main --count', { cwd: cliDir, stdio: 'pipe' }).toString().trim();
|
|
1775
|
-
|
|
1776
|
-
if (parseInt(behindCount) > 0) {
|
|
1777
|
-
// Check for local changes that might block pull
|
|
1778
|
-
let hasLocalChanges = false;
|
|
1779
|
-
try {
|
|
1780
|
-
const statusOutput = execSync('git status --porcelain', { cwd: cliDir, stdio: 'pipe' }).toString().trim();
|
|
1781
|
-
hasLocalChanges = statusOutput.length > 0;
|
|
1782
|
-
} catch (e) {
|
|
1783
|
-
hasLocalChanges = false;
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
// If there are local changes, stash them or reset
|
|
1787
|
-
if (hasLocalChanges) {
|
|
1788
|
-
spinnerRefresh.text = 'Stashing local changes...';
|
|
1789
|
-
try {
|
|
1790
|
-
// Try to stash changes first
|
|
1791
|
-
execSync('git stash --include-untracked', { cwd: cliDir, stdio: 'pipe' });
|
|
1792
|
-
} catch (e) {
|
|
1793
|
-
// If stash fails, do a hard reset (for generated files like package-lock.json)
|
|
1794
|
-
spinnerRefresh.text = 'Resetting local changes...';
|
|
1795
|
-
execSync('git checkout -- .', { cwd: cliDir, stdio: 'pipe' });
|
|
1796
|
-
execSync('git clean -fd', { cwd: cliDir, stdio: 'pipe' });
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
spinnerRefresh.text = 'Downloading updates...';
|
|
1801
|
-
|
|
1802
|
-
// Pull from remote
|
|
1803
|
-
execSync('git pull origin main', { cwd: cliDir, stdio: 'pipe' });
|
|
1804
|
-
const afterCommit = execSync('git rev-parse --short HEAD', { cwd: cliDir, stdio: 'pipe' }).toString().trim();
|
|
1805
|
-
|
|
1806
|
-
// Reinstall dependencies if package.json changed
|
|
1807
|
-
spinnerRefresh.text = 'Installing dependencies...';
|
|
1808
|
-
try {
|
|
1809
|
-
execSync('npm install --silent', { cwd: cliDir, stdio: 'pipe' });
|
|
1810
|
-
} catch (e) {
|
|
1811
|
-
// Ignore npm install errors
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
// Re-read package.json to get new version
|
|
1815
|
-
delete require.cache[require.resolve('../package.json')];
|
|
1816
|
-
const newPkg = require('../package.json');
|
|
1817
|
-
const newVersion = newPkg.version;
|
|
1818
|
-
|
|
1819
|
-
spinnerRefresh.succeed('CLI updated!');
|
|
1820
|
-
console.log();
|
|
1821
|
-
console.log(chalk.green(` Version: v${currentVersion} -> v${newVersion}`));
|
|
1822
|
-
console.log(chalk.gray(` Commits: ${beforeCommit} -> ${afterCommit} (${behindCount} new)`));
|
|
1823
|
-
console.log();
|
|
1824
|
-
|
|
1825
|
-
// Ask user if they want to restart
|
|
1826
|
-
const { restart } = await inquirer.prompt([
|
|
1827
|
-
{
|
|
1828
|
-
type: 'confirm',
|
|
1829
|
-
name: 'restart',
|
|
1830
|
-
message: chalk.yellow('Restart CLI to apply changes?'),
|
|
1831
|
-
default: true
|
|
1832
|
-
}
|
|
1833
|
-
]);
|
|
1834
|
-
|
|
1835
|
-
if (restart) {
|
|
1836
|
-
console.log(chalk.cyan(' Restarting...'));
|
|
1837
|
-
console.log();
|
|
1838
|
-
|
|
1839
|
-
// Clear require cache to reload modules
|
|
1840
|
-
Object.keys(require.cache).forEach(key => {
|
|
1841
|
-
delete require.cache[key];
|
|
1842
|
-
});
|
|
1843
|
-
|
|
1844
|
-
// Restart by spawning a new process and replacing current one
|
|
1845
|
-
const { spawn } = require('child_process');
|
|
1846
|
-
const child = spawn(process.argv[0], [path.join(cliDir, 'bin', 'cli.js')], {
|
|
1847
|
-
cwd: cliDir,
|
|
1848
|
-
stdio: 'inherit',
|
|
1849
|
-
shell: true
|
|
1850
|
-
});
|
|
1851
|
-
|
|
1852
|
-
child.on('exit', (code) => {
|
|
1853
|
-
process.exit(code);
|
|
1854
|
-
});
|
|
1855
|
-
|
|
1856
|
-
// Prevent current process from continuing
|
|
1857
|
-
return;
|
|
1858
|
-
}
|
|
1859
|
-
} else {
|
|
1860
|
-
spinnerRefresh.succeed('Already up to date!');
|
|
1861
|
-
console.log(chalk.cyan(` Version: v${currentVersion}`));
|
|
1862
|
-
console.log(chalk.gray(` Commit: ${beforeCommit}`));
|
|
1863
|
-
}
|
|
1864
|
-
} else {
|
|
1865
|
-
spinnerRefresh.succeed('Data refreshed');
|
|
1866
|
-
console.log(chalk.cyan(` Version: v${currentVersion} (local dev mode)`));
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
// Refresh user data
|
|
1870
|
-
if (currentService) {
|
|
1871
|
-
await currentService.getUser();
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
} catch (err) {
|
|
1875
|
-
spinnerRefresh.fail('Update failed');
|
|
1876
|
-
console.log(chalk.red(` Error: ${err.message}`));
|
|
1877
|
-
console.log(chalk.gray(' Your session is still active.'));
|
|
1878
|
-
}
|
|
1879
|
-
console.log();
|
|
1880
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
1881
|
-
};
|
|
1882
|
-
|
|
1883
|
-
// Fonction principale
|
|
1884
|
-
const main = async () => {
|
|
1885
|
-
await banner();
|
|
1886
|
-
|
|
1887
|
-
// Essayer de restaurer les sessions précédentes
|
|
1888
|
-
const spinner = ora('Restoring session...').start();
|
|
1889
|
-
const restored = await connections.restoreFromStorage();
|
|
1890
|
-
|
|
1891
|
-
if (restored) {
|
|
1892
|
-
spinner.succeed('Session restored!');
|
|
1893
|
-
currentService = connections.services[0].service;
|
|
1894
|
-
|
|
1895
|
-
// Aller directement au dashboard
|
|
1896
|
-
let connected = true;
|
|
1897
|
-
while (connected) {
|
|
1898
|
-
await banner();
|
|
1899
|
-
const action = await dashboardMenu(currentService);
|
|
1900
|
-
|
|
1901
|
-
switch (action) {
|
|
1902
|
-
case 'accounts':
|
|
1903
|
-
await showAccounts(currentService);
|
|
1904
|
-
break;
|
|
1905
|
-
case 'positions':
|
|
1906
|
-
await showPositions(currentService);
|
|
1907
|
-
break;
|
|
1908
|
-
case 'orders':
|
|
1909
|
-
await showOrders(currentService);
|
|
1910
|
-
break;
|
|
1911
|
-
case 'stats':
|
|
1912
|
-
await showStats(currentService);
|
|
1913
|
-
break;
|
|
1914
|
-
case 'userinfo':
|
|
1915
|
-
await showUserInfo(currentService);
|
|
1916
|
-
break;
|
|
1917
|
-
case 'add_prop_account':
|
|
1918
|
-
await addPropAccount();
|
|
1919
|
-
break;
|
|
1920
|
-
case 'algotrading':
|
|
1921
|
-
let algoRunning = true;
|
|
1922
|
-
while (algoRunning) {
|
|
1923
|
-
await banner();
|
|
1924
|
-
const algoResult = await algoTradingMenu(currentService);
|
|
1925
|
-
if (algoResult === 'back') {
|
|
1926
|
-
algoRunning = false;
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
break;
|
|
1930
|
-
case 'refresh':
|
|
1931
|
-
await handleUpdate();
|
|
1932
|
-
break;
|
|
1933
|
-
case 'disconnect':
|
|
1934
|
-
connections.disconnectAll();
|
|
1935
|
-
currentService = null;
|
|
1936
|
-
connected = false;
|
|
1937
|
-
await banner();
|
|
1938
|
-
console.log(chalk.yellow(' All connections disconnected.'));
|
|
1939
|
-
console.log();
|
|
1940
|
-
break;
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
} else {
|
|
1944
|
-
spinner.stop();
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
let running = true;
|
|
1948
|
-
|
|
1949
|
-
while (running) {
|
|
1950
|
-
const connection = await mainMenu();
|
|
1951
|
-
|
|
1952
|
-
switch (connection) {
|
|
1953
|
-
case 'projectx':
|
|
1954
|
-
const propfirm = await projectXMenu();
|
|
1955
|
-
if (propfirm === 'back') {
|
|
1956
|
-
await banner();
|
|
1957
|
-
continue;
|
|
1958
|
-
}
|
|
1959
|
-
|
|
1960
|
-
// Créer le service
|
|
1961
|
-
currentService = new ProjectXService(propfirm);
|
|
1962
|
-
|
|
1963
|
-
// Login
|
|
1964
|
-
const credentials = await loginPrompt(currentService.getPropfirmName());
|
|
1965
|
-
|
|
1966
|
-
const spinner = ora('Authenticating...').start();
|
|
1967
|
-
const loginResult = await currentService.login(credentials.username, credentials.password);
|
|
1968
|
-
|
|
1969
|
-
if (loginResult.success) {
|
|
1970
|
-
// Récupérer les infos utilisateur
|
|
1971
|
-
await currentService.getUser();
|
|
1972
|
-
|
|
1973
|
-
// Ajouter au connection manager
|
|
1974
|
-
connections.add('projectx', currentService, currentService.getPropfirmName());
|
|
1975
|
-
|
|
1976
|
-
spinner.succeed('Connected successfully!');
|
|
1977
|
-
|
|
1978
|
-
// Dashboard loop
|
|
1979
|
-
let connected = true;
|
|
1980
|
-
while (connected) {
|
|
1981
|
-
await banner();
|
|
1982
|
-
const action = await dashboardMenu(currentService);
|
|
1983
|
-
|
|
1984
|
-
switch (action) {
|
|
1985
|
-
case 'accounts':
|
|
1986
|
-
await showAccounts(currentService);
|
|
1987
|
-
break;
|
|
1988
|
-
case 'positions':
|
|
1989
|
-
await showPositions(currentService);
|
|
1990
|
-
break;
|
|
1991
|
-
case 'orders':
|
|
1992
|
-
await showOrders(currentService);
|
|
1993
|
-
break;
|
|
1994
|
-
case 'stats':
|
|
1995
|
-
await showStats(currentService);
|
|
1996
|
-
break;
|
|
1997
|
-
case 'userinfo':
|
|
1998
|
-
await showUserInfo(currentService);
|
|
1999
|
-
break;
|
|
2000
|
-
case 'algotrading':
|
|
2001
|
-
let algoRunning = true;
|
|
2002
|
-
while (algoRunning) {
|
|
2003
|
-
await banner();
|
|
2004
|
-
const algoResult = await algoTradingMenu(currentService);
|
|
2005
|
-
if (algoResult === 'back') {
|
|
2006
|
-
algoRunning = false;
|
|
2007
|
-
}
|
|
2008
|
-
}
|
|
2009
|
-
break;
|
|
2010
|
-
case 'add_prop_account':
|
|
2011
|
-
await addPropAccount();
|
|
2012
|
-
break;
|
|
2013
|
-
case 'refresh':
|
|
2014
|
-
await handleUpdate();
|
|
2015
|
-
break;
|
|
2016
|
-
case 'disconnect':
|
|
2017
|
-
// Déconnecter toutes les connexions
|
|
2018
|
-
connections.disconnectAll();
|
|
2019
|
-
currentService = null;
|
|
2020
|
-
connected = false;
|
|
2021
|
-
await banner();
|
|
2022
|
-
console.log(chalk.yellow(' All connections disconnected.'));
|
|
2023
|
-
console.log();
|
|
2024
|
-
break;
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
2027
|
-
} else {
|
|
2028
|
-
spinner.fail('Authentication failed');
|
|
2029
|
-
console.log(chalk.red(` Error: ${loginResult.error}`));
|
|
2030
|
-
console.log();
|
|
2031
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
2032
|
-
await banner();
|
|
2033
|
-
}
|
|
2034
|
-
break;
|
|
2035
|
-
|
|
2036
|
-
case 'rithmic':
|
|
2037
|
-
console.log();
|
|
2038
|
-
console.log(chalk.cyan('Rithmic connection...'));
|
|
2039
|
-
console.log(chalk.gray('Feature coming soon!'));
|
|
2040
|
-
console.log();
|
|
2041
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
2042
|
-
await banner();
|
|
2043
|
-
break;
|
|
12
|
+
const { program } = require('commander');
|
|
13
|
+
const pkg = require('../package.json');
|
|
2044
14
|
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
2051
|
-
await banner();
|
|
2052
|
-
break;
|
|
15
|
+
// Handle uncaught errors gracefully
|
|
16
|
+
process.on('uncaughtException', (err) => {
|
|
17
|
+
console.error('Fatal error:', err.message);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
});
|
|
2053
20
|
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
break;
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
2061
|
-
};
|
|
21
|
+
process.on('unhandledRejection', (err) => {
|
|
22
|
+
console.error('Unhandled rejection:', err.message);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
2062
25
|
|
|
2063
|
-
//
|
|
2064
|
-
const packageInfo = require('../package.json');
|
|
26
|
+
// CLI configuration
|
|
2065
27
|
program
|
|
2066
28
|
.name('hedgequantx')
|
|
2067
29
|
.description('Prop Futures Algo Trading CLI')
|
|
2068
|
-
.version(
|
|
2069
|
-
|
|
2070
|
-
program
|
|
2071
|
-
.command('status')
|
|
2072
|
-
.description('Show system status')
|
|
2073
|
-
.action(() => {
|
|
2074
|
-
console.log(chalk.green('System Status: Online'));
|
|
2075
|
-
});
|
|
30
|
+
.version(pkg.version);
|
|
2076
31
|
|
|
2077
32
|
program
|
|
2078
|
-
.command('start')
|
|
2079
|
-
.description('Start
|
|
2080
|
-
.action(() => {
|
|
2081
|
-
|
|
33
|
+
.command('start', { isDefault: true })
|
|
34
|
+
.description('Start the interactive CLI')
|
|
35
|
+
.action(async () => {
|
|
36
|
+
const { run } = require('../src/app');
|
|
37
|
+
await run();
|
|
2082
38
|
});
|
|
2083
39
|
|
|
2084
40
|
program
|
|
2085
|
-
.command('
|
|
2086
|
-
.description('
|
|
41
|
+
.command('version')
|
|
42
|
+
.description('Show version')
|
|
2087
43
|
.action(() => {
|
|
2088
|
-
console.log(
|
|
44
|
+
console.log(`HedgeQuantX CLI v${pkg.version}`);
|
|
2089
45
|
});
|
|
2090
46
|
|
|
2091
|
-
//
|
|
2092
|
-
|
|
2093
|
-
main().catch(console.error);
|
|
2094
|
-
} else {
|
|
2095
|
-
program.parse();
|
|
2096
|
-
}
|
|
47
|
+
// Parse and run
|
|
48
|
+
program.parse(process.argv);
|