hedgequantx 2.9.20 → 2.9.22
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/package.json +1 -1
- package/src/app.js +64 -42
- package/src/menus/connect.js +17 -14
- package/src/menus/dashboard.js +76 -58
- package/src/pages/accounts.js +49 -38
- package/src/pages/ai-agents-ui.js +388 -0
- package/src/pages/ai-agents.js +494 -0
- package/src/pages/ai-models.js +389 -0
- package/src/pages/algo/algo-executor.js +307 -0
- package/src/pages/algo/copy-executor.js +331 -0
- package/src/pages/algo/copy-trading.js +178 -546
- package/src/pages/algo/custom-strategy.js +313 -0
- package/src/pages/algo/index.js +75 -18
- package/src/pages/algo/one-account.js +57 -322
- package/src/pages/algo/ui.js +15 -15
- package/src/pages/orders.js +22 -19
- package/src/pages/positions.js +22 -19
- package/src/pages/stats/index.js +16 -15
- package/src/pages/user.js +11 -7
- package/src/services/ai-supervision/consensus.js +284 -0
- package/src/services/ai-supervision/context.js +275 -0
- package/src/services/ai-supervision/directive.js +167 -0
- package/src/services/ai-supervision/health.js +47 -35
- package/src/services/ai-supervision/index.js +359 -0
- package/src/services/ai-supervision/parser.js +278 -0
- package/src/services/ai-supervision/symbols.js +259 -0
- package/src/services/cliproxy/index.js +256 -0
- package/src/services/cliproxy/installer.js +111 -0
- package/src/services/cliproxy/manager.js +387 -0
- package/src/services/index.js +9 -1
- package/src/services/llmproxy/index.js +166 -0
- package/src/services/llmproxy/manager.js +411 -0
- package/src/services/rithmic/accounts.js +6 -8
- package/src/ui/box.js +5 -9
- package/src/ui/index.js +18 -5
- package/src/ui/menu.js +4 -4
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -7,7 +7,7 @@ const chalk = require('chalk');
|
|
|
7
7
|
const ora = require('ora');
|
|
8
8
|
|
|
9
9
|
const { connections } = require('./services');
|
|
10
|
-
const { getLogoWidth, centerText, prepareStdin } = require('./ui');
|
|
10
|
+
const { getLogoWidth, centerText, prepareStdin, clearScreen } = require('./ui');
|
|
11
11
|
const { logger, prompts } = require('./utils');
|
|
12
12
|
const { setCachedStats, clearCachedStats } = require('./services/stats-cache');
|
|
13
13
|
|
|
@@ -17,6 +17,7 @@ const log = logger.scope('App');
|
|
|
17
17
|
const { showStats } = require('./pages/stats');
|
|
18
18
|
const { showAccounts } = require('./pages/accounts');
|
|
19
19
|
const { algoTradingMenu } = require('./pages/algo');
|
|
20
|
+
const { aiAgentsMenu, getActiveAgentCount } = require('./pages/ai-agents');
|
|
20
21
|
|
|
21
22
|
// Menus
|
|
22
23
|
const { rithmicMenu, dashboardMenu, handleUpdate } = require('./menus');
|
|
@@ -29,7 +30,7 @@ let currentService = null;
|
|
|
29
30
|
|
|
30
31
|
const restoreTerminal = () => {
|
|
31
32
|
try {
|
|
32
|
-
|
|
33
|
+
// Show cursor
|
|
33
34
|
process.stdout.write('\x1B[?25h');
|
|
34
35
|
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
35
36
|
process.stdin.setRawMode(false);
|
|
@@ -41,7 +42,15 @@ const restoreTerminal = () => {
|
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
process.on('exit', restoreTerminal);
|
|
44
|
-
process.on('SIGINT', () => {
|
|
45
|
+
process.on('SIGINT', () => {
|
|
46
|
+
restoreTerminal();
|
|
47
|
+
// Draw bottom border before exit
|
|
48
|
+
const termWidth = process.stdout.columns || 100;
|
|
49
|
+
const boxWidth = termWidth < 60 ? Math.max(termWidth - 2, 40) : Math.max(getLogoWidth(), 98);
|
|
50
|
+
console.log(chalk.cyan('\n╚' + '═'.repeat(boxWidth - 2) + '╝'));
|
|
51
|
+
console.log(chalk.gray('GOODBYE!'));
|
|
52
|
+
process.exit(0);
|
|
53
|
+
});
|
|
45
54
|
process.on('SIGTERM', () => { restoreTerminal(); process.exit(0); });
|
|
46
55
|
|
|
47
56
|
process.on('uncaughtException', (err) => {
|
|
@@ -92,6 +101,7 @@ const refreshStats = async () => {
|
|
|
92
101
|
balance: hasBalanceData ? totalBalance : null,
|
|
93
102
|
pnl: hasPnlData ? totalPnl : null,
|
|
94
103
|
pnlPercent: null,
|
|
104
|
+
agents: getActiveAgentCount(),
|
|
95
105
|
});
|
|
96
106
|
} catch (err) {
|
|
97
107
|
log.warn('Failed to refresh stats', { error: err.message });
|
|
@@ -101,7 +111,7 @@ const refreshStats = async () => {
|
|
|
101
111
|
// ==================== BANNER ====================
|
|
102
112
|
|
|
103
113
|
const banner = async () => {
|
|
104
|
-
|
|
114
|
+
clearScreen();
|
|
105
115
|
|
|
106
116
|
const termWidth = process.stdout.columns || 100;
|
|
107
117
|
const isMobile = termWidth < 60;
|
|
@@ -123,8 +133,11 @@ const banner = async () => {
|
|
|
123
133
|
|
|
124
134
|
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
125
135
|
|
|
126
|
-
const tagline = isMobile ? `HQX
|
|
127
|
-
console.log(chalk.cyan('║') + chalk.
|
|
136
|
+
const tagline = isMobile ? `HQX V${version}` : `PROP FUTURES ALGO TRADING V${version}`;
|
|
137
|
+
console.log(chalk.cyan('║') + chalk.yellow(centerText(tagline, innerWidth)) + chalk.cyan('║'));
|
|
138
|
+
|
|
139
|
+
// ALWAYS close the banner
|
|
140
|
+
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
128
141
|
};
|
|
129
142
|
|
|
130
143
|
const getFullLogo = () => [
|
|
@@ -145,39 +158,37 @@ const getMobileLogo = () => [
|
|
|
145
158
|
['╚═╝ ╚═╝ ╚══▀▀═╝ ', '╚═╝ ╚═╝'],
|
|
146
159
|
];
|
|
147
160
|
|
|
148
|
-
const bannerClosed = async () => {
|
|
149
|
-
await banner();
|
|
150
|
-
const termWidth = process.stdout.columns || 100;
|
|
151
|
-
const boxWidth = termWidth < 60 ? Math.max(termWidth - 2, 40) : Math.max(getLogoWidth(), 98);
|
|
152
|
-
console.log(chalk.cyan('╚' + '═'.repeat(boxWidth - 2) + '╝'));
|
|
153
|
-
};
|
|
154
|
-
|
|
155
161
|
// ==================== MAIN LOOP ====================
|
|
156
162
|
|
|
157
163
|
const run = async () => {
|
|
158
164
|
try {
|
|
159
165
|
log.info('Starting HQX CLI');
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
166
|
+
|
|
167
|
+
// First launch - show banner then try restore session
|
|
168
|
+
await banner();
|
|
169
|
+
|
|
170
|
+
const spinner = ora({ text: 'LOADING DASHBOARD...', color: 'yellow' }).start();
|
|
171
|
+
|
|
164
172
|
const restored = await connections.restoreFromStorage();
|
|
165
173
|
|
|
166
174
|
if (restored) {
|
|
167
|
-
spinner.succeed('Session restored');
|
|
168
175
|
currentService = connections.getAll()[0].service;
|
|
169
176
|
await refreshStats();
|
|
177
|
+
// Store spinner globally - dashboard will stop it when ready to display
|
|
178
|
+
global.__hqxSpinner = spinner;
|
|
170
179
|
} else {
|
|
171
|
-
spinner.
|
|
180
|
+
spinner.stop(); // Stop spinner - no session to restore
|
|
181
|
+
global.__hqxSpinner = null;
|
|
172
182
|
}
|
|
173
183
|
|
|
174
184
|
// Main loop
|
|
175
185
|
while (true) {
|
|
176
186
|
try {
|
|
177
187
|
prepareStdin();
|
|
178
|
-
await banner();
|
|
179
188
|
|
|
180
189
|
if (!connections.isConnected()) {
|
|
190
|
+
// Not connected - show banner + propfirm selection
|
|
191
|
+
await banner();
|
|
181
192
|
// Not connected - show propfirm selection directly
|
|
182
193
|
const boxWidth = getLogoWidth();
|
|
183
194
|
const innerWidth = boxWidth - 2;
|
|
@@ -188,13 +199,14 @@ const run = async () => {
|
|
|
188
199
|
|
|
189
200
|
// Find max name length for alignment
|
|
190
201
|
const maxNameLen = Math.max(...numbered.map(n => n.name.length));
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
const
|
|
202
|
+
const itemWidth = 4 + 1 + maxNameLen; // [##] + space + name
|
|
203
|
+
const gap = 3; // gap between columns
|
|
204
|
+
const totalContentWidth = (itemWidth * numCols) + (gap * (numCols - 1));
|
|
194
205
|
|
|
195
|
-
|
|
206
|
+
// New rectangle (banner is always closed)
|
|
207
|
+
console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
|
|
196
208
|
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM', innerWidth)) + chalk.cyan('║'));
|
|
197
|
-
console.log(chalk.cyan('
|
|
209
|
+
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
198
210
|
|
|
199
211
|
const rows = Math.ceil(numbered.length / numCols);
|
|
200
212
|
for (let row = 0; row < rows; row++) {
|
|
@@ -211,29 +223,32 @@ const run = async () => {
|
|
|
211
223
|
}
|
|
212
224
|
}
|
|
213
225
|
|
|
214
|
-
|
|
226
|
+
// Build line content
|
|
227
|
+
let content = '';
|
|
215
228
|
for (let i = 0; i < lineParts.length; i++) {
|
|
216
229
|
if (lineParts[i]) {
|
|
217
|
-
|
|
230
|
+
content += chalk.cyan(lineParts[i].num) + ' ' + chalk.white(lineParts[i].name);
|
|
218
231
|
} else {
|
|
219
|
-
|
|
232
|
+
content += ' '.repeat(itemWidth);
|
|
220
233
|
}
|
|
221
|
-
if (i < lineParts.length - 1)
|
|
234
|
+
if (i < lineParts.length - 1) content += ' '.repeat(gap);
|
|
222
235
|
}
|
|
223
236
|
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
237
|
+
// Center the content
|
|
238
|
+
const contentLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
239
|
+
const leftPad = Math.floor((innerWidth - contentLen) / 2);
|
|
240
|
+
const rightPad = innerWidth - contentLen - leftPad;
|
|
241
|
+
console.log(chalk.cyan('║') + ' '.repeat(leftPad) + content + ' '.repeat(rightPad) + chalk.cyan('║'));
|
|
227
242
|
}
|
|
228
243
|
|
|
229
244
|
console.log(chalk.cyan('╠' + '─'.repeat(innerWidth) + '╣'));
|
|
230
|
-
console.log(chalk.cyan('║') + chalk.red(centerText('[X]
|
|
245
|
+
console.log(chalk.cyan('║') + chalk.red(centerText('[X] EXIT', innerWidth)) + chalk.cyan('║'));
|
|
231
246
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
232
247
|
|
|
233
|
-
const input = await prompts.textInput(chalk.cyan('
|
|
248
|
+
const input = await prompts.textInput(chalk.cyan('SELECT (1-' + numbered.length + '/X): '));
|
|
234
249
|
|
|
235
250
|
if (!input || input.toLowerCase() === 'x') {
|
|
236
|
-
console.log(chalk.gray('
|
|
251
|
+
console.log(chalk.gray('GOODBYE!'));
|
|
237
252
|
process.exit(0);
|
|
238
253
|
}
|
|
239
254
|
|
|
@@ -244,26 +259,26 @@ const run = async () => {
|
|
|
244
259
|
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
245
260
|
|
|
246
261
|
if (credentials) {
|
|
247
|
-
const spinner = ora({ text: '
|
|
262
|
+
const spinner = ora({ text: 'CONNECTING TO RITHMIC...', color: 'yellow' }).start();
|
|
248
263
|
try {
|
|
249
264
|
const { RithmicService } = require('./services/rithmic');
|
|
250
265
|
const service = new RithmicService(selectedPropfirm.key);
|
|
251
266
|
const result = await service.login(credentials.username, credentials.password);
|
|
252
267
|
|
|
253
268
|
if (result.success) {
|
|
254
|
-
spinner.text = '
|
|
269
|
+
spinner.text = 'FETCHING ACCOUNTS...';
|
|
255
270
|
const accResult = await service.getTradingAccounts();
|
|
256
271
|
connections.add('rithmic', service, service.propfirm.name);
|
|
257
|
-
spinner.succeed(`
|
|
272
|
+
spinner.succeed(`CONNECTED TO ${service.propfirm.name.toUpperCase()} (${accResult.accounts?.length || 0} ACCOUNTS)`);
|
|
258
273
|
currentService = service;
|
|
259
274
|
await refreshStats();
|
|
260
275
|
await new Promise(r => setTimeout(r, 1500));
|
|
261
276
|
} else {
|
|
262
|
-
spinner.fail(result.error || '
|
|
277
|
+
spinner.fail((result.error || 'AUTHENTICATION FAILED').toUpperCase());
|
|
263
278
|
await new Promise(r => setTimeout(r, 2000));
|
|
264
279
|
}
|
|
265
280
|
} catch (error) {
|
|
266
|
-
spinner.fail(`
|
|
281
|
+
spinner.fail(`CONNECTION ERROR: ${error.message.toUpperCase()}`);
|
|
267
282
|
await new Promise(r => setTimeout(r, 2000));
|
|
268
283
|
}
|
|
269
284
|
}
|
|
@@ -296,13 +311,20 @@ const run = async () => {
|
|
|
296
311
|
try {
|
|
297
312
|
await algoTradingMenu(currentService);
|
|
298
313
|
} catch (err) {
|
|
299
|
-
console.log(chalk.red(`
|
|
314
|
+
console.log(chalk.red(` ALGO ERROR: ${err.message.toUpperCase()}`));
|
|
300
315
|
prepareStdin();
|
|
301
316
|
}
|
|
302
317
|
break;
|
|
303
318
|
|
|
319
|
+
case 'aiagents':
|
|
320
|
+
await aiAgentsMenu();
|
|
321
|
+
break;
|
|
322
|
+
|
|
304
323
|
case 'update':
|
|
305
|
-
await handleUpdate();
|
|
324
|
+
const updateResult = await handleUpdate();
|
|
325
|
+
if (updateResult === 'exit') {
|
|
326
|
+
running = false;
|
|
327
|
+
}
|
|
306
328
|
break;
|
|
307
329
|
|
|
308
330
|
case 'disconnect':
|
package/src/menus/connect.js
CHANGED
|
@@ -8,7 +8,7 @@ const ora = require('ora');
|
|
|
8
8
|
const { connections } = require('../services');
|
|
9
9
|
const { RithmicService } = require('../services/rithmic');
|
|
10
10
|
const { PROPFIRM_CHOICES } = require('../config');
|
|
11
|
-
const { getLogoWidth, centerText, prepareStdin } = require('../ui');
|
|
11
|
+
const { getLogoWidth, centerText, prepareStdin, displayBanner , clearScreen } = require('../ui');
|
|
12
12
|
const { validateUsername, validatePassword } = require('../security');
|
|
13
13
|
const { prompts } = require('../utils');
|
|
14
14
|
|
|
@@ -18,16 +18,16 @@ const { prompts } = require('../utils');
|
|
|
18
18
|
const loginPrompt = async (propfirmName) => {
|
|
19
19
|
prepareStdin();
|
|
20
20
|
console.log();
|
|
21
|
-
console.log(chalk.cyan(`
|
|
21
|
+
console.log(chalk.cyan(`CONNECTING TO ${propfirmName.toUpperCase()}...`));
|
|
22
22
|
console.log();
|
|
23
23
|
|
|
24
|
-
const username = await prompts.textInput('
|
|
25
|
-
try { validateUsername(input); return undefined; } catch (e) { return e.message; }
|
|
24
|
+
const username = await prompts.textInput('USERNAME:', '', (input) => {
|
|
25
|
+
try { validateUsername(input); return undefined; } catch (e) { return e.message.toUpperCase(); }
|
|
26
26
|
});
|
|
27
27
|
if (!username) return null;
|
|
28
28
|
|
|
29
|
-
const pwd = await prompts.passwordInput('
|
|
30
|
-
try { validatePassword(input); return undefined; } catch (e) { return e.message; }
|
|
29
|
+
const pwd = await prompts.passwordInput('PASSWORD:', (input) => {
|
|
30
|
+
try { validatePassword(input); return undefined; } catch (e) { return e.message.toUpperCase(); }
|
|
31
31
|
});
|
|
32
32
|
if (!pwd) return null;
|
|
33
33
|
|
|
@@ -38,6 +38,10 @@ const loginPrompt = async (propfirmName) => {
|
|
|
38
38
|
* Rithmic menu - Main connection menu
|
|
39
39
|
*/
|
|
40
40
|
const rithmicMenu = async () => {
|
|
41
|
+
// Clear screen and show banner
|
|
42
|
+
clearScreen();
|
|
43
|
+
displayBanner();
|
|
44
|
+
|
|
41
45
|
const propfirms = PROPFIRM_CHOICES;
|
|
42
46
|
const boxWidth = getLogoWidth();
|
|
43
47
|
const innerWidth = boxWidth - 2;
|
|
@@ -46,7 +50,6 @@ const rithmicMenu = async () => {
|
|
|
46
50
|
|
|
47
51
|
const numbered = propfirms.map((pf, i) => ({ num: i + 1, key: pf.value, name: pf.name }));
|
|
48
52
|
|
|
49
|
-
console.log();
|
|
50
53
|
console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
|
|
51
54
|
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM', innerWidth)) + chalk.cyan('║'));
|
|
52
55
|
console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
|
|
@@ -71,10 +74,10 @@ const rithmicMenu = async () => {
|
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
console.log(chalk.cyan('╠' + '─'.repeat(innerWidth) + '╣'));
|
|
74
|
-
console.log(chalk.cyan('║') + chalk.red(centerText('[X]
|
|
77
|
+
console.log(chalk.cyan('║') + chalk.red(centerText('[X] EXIT', innerWidth)) + chalk.cyan('║'));
|
|
75
78
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
76
79
|
|
|
77
|
-
const input = await prompts.textInput(chalk.cyan('
|
|
80
|
+
const input = await prompts.textInput(chalk.cyan('SELECT NUMBER (OR X):'));
|
|
78
81
|
if (!input || input.toLowerCase() === 'x') return null;
|
|
79
82
|
|
|
80
83
|
const action = parseInt(input);
|
|
@@ -84,26 +87,26 @@ const rithmicMenu = async () => {
|
|
|
84
87
|
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
85
88
|
if (!credentials) return null;
|
|
86
89
|
|
|
87
|
-
const spinner = ora({ text: '
|
|
90
|
+
const spinner = ora({ text: 'CONNECTING TO RITHMIC...', color: 'yellow' }).start();
|
|
88
91
|
|
|
89
92
|
try {
|
|
90
93
|
const service = new RithmicService(selectedPropfirm.key);
|
|
91
94
|
const result = await service.login(credentials.username, credentials.password);
|
|
92
95
|
|
|
93
96
|
if (result.success) {
|
|
94
|
-
spinner.text = '
|
|
97
|
+
spinner.text = 'FETCHING ACCOUNTS...';
|
|
95
98
|
const accResult = await service.getTradingAccounts();
|
|
96
99
|
connections.add('rithmic', service, service.propfirm.name);
|
|
97
|
-
spinner.succeed(`
|
|
100
|
+
spinner.succeed(`CONNECTED TO ${service.propfirm.name.toUpperCase()} (${accResult.accounts?.length || 0} ACCOUNTS)`);
|
|
98
101
|
await new Promise(r => setTimeout(r, 1500));
|
|
99
102
|
return service;
|
|
100
103
|
} else {
|
|
101
|
-
spinner.fail(result.error || '
|
|
104
|
+
spinner.fail((result.error || 'AUTHENTICATION FAILED').toUpperCase());
|
|
102
105
|
await new Promise(r => setTimeout(r, 2000));
|
|
103
106
|
return null;
|
|
104
107
|
}
|
|
105
108
|
} catch (error) {
|
|
106
|
-
spinner.fail(`
|
|
109
|
+
spinner.fail(`CONNECTION ERROR: ${error.message.toUpperCase()}`);
|
|
107
110
|
await new Promise(r => setTimeout(r, 2000));
|
|
108
111
|
return null;
|
|
109
112
|
}
|
package/src/menus/dashboard.js
CHANGED
|
@@ -7,9 +7,10 @@ const ora = require('ora');
|
|
|
7
7
|
const { execSync, spawn } = require('child_process');
|
|
8
8
|
|
|
9
9
|
const { connections } = require('../services');
|
|
10
|
-
const { getLogoWidth, centerText, prepareStdin } = require('../ui');
|
|
10
|
+
const { getLogoWidth, centerText, prepareStdin, displayBanner, clearScreen } = require('../ui');
|
|
11
11
|
const { getCachedStats } = require('../services/stats-cache');
|
|
12
12
|
const { prompts } = require('../utils');
|
|
13
|
+
const { getActiveAgentCount } = require('../pages/ai-agents');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Dashboard menu after login
|
|
@@ -17,6 +18,16 @@ const { prompts } = require('../utils');
|
|
|
17
18
|
const dashboardMenu = async (service) => {
|
|
18
19
|
prepareStdin();
|
|
19
20
|
|
|
21
|
+
// Stop any global spinner before clearing
|
|
22
|
+
if (global.__hqxSpinner) {
|
|
23
|
+
global.__hqxSpinner.stop();
|
|
24
|
+
global.__hqxSpinner = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Clear screen and show banner (always closed)
|
|
28
|
+
clearScreen();
|
|
29
|
+
displayBanner();
|
|
30
|
+
|
|
20
31
|
const boxWidth = getLogoWidth();
|
|
21
32
|
const W = boxWidth - 2;
|
|
22
33
|
|
|
@@ -30,9 +41,9 @@ const dashboardMenu = async (service) => {
|
|
|
30
41
|
return chalk.cyan('║') + content + ' '.repeat(Math.max(0, padding)) + chalk.cyan('║');
|
|
31
42
|
};
|
|
32
43
|
|
|
33
|
-
//
|
|
34
|
-
console.log(chalk.cyan('
|
|
35
|
-
console.log(makeLine(chalk.yellow.bold('
|
|
44
|
+
// New rectangle (banner is always closed)
|
|
45
|
+
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
46
|
+
console.log(makeLine(chalk.yellow.bold('WELCOME, HQX TRADER!'), 'center'));
|
|
36
47
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
37
48
|
|
|
38
49
|
// Show connected propfirms
|
|
@@ -51,8 +62,8 @@ const dashboardMenu = async (service) => {
|
|
|
51
62
|
const balStr = statsInfo.balance !== null ? `$${statsInfo.balance.toLocaleString()}` : '--';
|
|
52
63
|
const balColor = statsInfo.balance !== null ? chalk.green : chalk.gray;
|
|
53
64
|
|
|
54
|
-
// AI Agents status
|
|
55
|
-
const agentCount =
|
|
65
|
+
// AI Agents status - get fresh count, not from cache
|
|
66
|
+
const agentCount = getActiveAgentCount();
|
|
56
67
|
const agentDisplay = agentCount > 0 ? 'ON' : 'OFF';
|
|
57
68
|
const agentColor = agentCount > 0 ? chalk.green : chalk.red;
|
|
58
69
|
|
|
@@ -87,9 +98,9 @@ const dashboardMenu = async (service) => {
|
|
|
87
98
|
|
|
88
99
|
// Find max width for alignment
|
|
89
100
|
const menuItems = [
|
|
90
|
-
{ left: '[1]
|
|
91
|
-
{ left: '[+]
|
|
92
|
-
{ left: '[
|
|
101
|
+
{ left: '[1] VIEW ACCOUNTS', right: '[2] VIEW STATS' },
|
|
102
|
+
{ left: '[+] ADD PROP-ACCOUNT', right: '[A] ALGO-TRADING' },
|
|
103
|
+
{ left: '[I] AI AGENTS', right: '[U] UPDATE HQX' },
|
|
93
104
|
];
|
|
94
105
|
|
|
95
106
|
const maxLeftLen = Math.max(...menuItems.map(m => m.left.length));
|
|
@@ -119,20 +130,25 @@ const dashboardMenu = async (service) => {
|
|
|
119
130
|
);
|
|
120
131
|
};
|
|
121
132
|
|
|
122
|
-
menuRow('[1]
|
|
123
|
-
menuRow('[+]
|
|
124
|
-
menuRow('[
|
|
133
|
+
menuRow('[1] VIEW ACCOUNTS', '[2] VIEW STATS', chalk.cyan, chalk.cyan);
|
|
134
|
+
menuRow('[+] ADD PROP-ACCOUNT', '[A] ALGO-TRADING', chalk.cyan, chalk.magenta);
|
|
135
|
+
menuRow('[I] AI AGENTS', '[U] UPDATE HQX', chalk.green, chalk.yellow);
|
|
136
|
+
|
|
137
|
+
// Separator and centered Disconnect button
|
|
138
|
+
console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
|
|
139
|
+
console.log(makeLine(chalk.red('[X] DISCONNECT'), 'center'));
|
|
125
140
|
|
|
126
141
|
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
127
142
|
|
|
128
143
|
// Simple input - no duplicate menu
|
|
129
|
-
const input = await prompts.textInput(chalk.cyan('
|
|
144
|
+
const input = await prompts.textInput(chalk.cyan('SELECT (1/2/+/A/I/U/X):'));
|
|
130
145
|
|
|
131
146
|
const actionMap = {
|
|
132
147
|
'1': 'accounts',
|
|
133
148
|
'2': 'stats',
|
|
134
149
|
'+': 'add_prop_account',
|
|
135
150
|
'a': 'algotrading',
|
|
151
|
+
'i': 'aiagents',
|
|
136
152
|
'u': 'update',
|
|
137
153
|
'x': 'disconnect'
|
|
138
154
|
};
|
|
@@ -144,93 +160,95 @@ const dashboardMenu = async (service) => {
|
|
|
144
160
|
* Handle update process
|
|
145
161
|
*/
|
|
146
162
|
const handleUpdate = async () => {
|
|
163
|
+
clearScreen();
|
|
164
|
+
displayBanner();
|
|
147
165
|
prepareStdin();
|
|
148
166
|
|
|
167
|
+
const boxWidth = getLogoWidth();
|
|
168
|
+
const W = boxWidth - 2;
|
|
169
|
+
|
|
170
|
+
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
171
|
+
console.log(chalk.cyan('║') + chalk.yellow.bold(centerText('UPDATE HQX', W)) + chalk.cyan('║'));
|
|
172
|
+
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
173
|
+
|
|
149
174
|
let spinner = null;
|
|
150
175
|
let currentVersion = 'unknown';
|
|
151
176
|
|
|
152
177
|
try {
|
|
178
|
+
// Get current version
|
|
153
179
|
try {
|
|
154
180
|
currentVersion = require('../../package.json').version || 'unknown';
|
|
155
181
|
} catch (e) {}
|
|
156
182
|
|
|
157
|
-
console.log(chalk.cyan(`\n
|
|
158
|
-
spinner = ora({ text: '
|
|
183
|
+
console.log(chalk.cyan(`\n CURRENT VERSION: V${currentVersion.toUpperCase()}`));
|
|
184
|
+
spinner = ora({ text: 'CHECKING FOR UPDATES...', color: 'yellow' }).start();
|
|
159
185
|
|
|
186
|
+
// Check latest version from npm
|
|
160
187
|
let latestVersion;
|
|
161
188
|
try {
|
|
162
|
-
latestVersion = execSync('npm view hedgequantx version', {
|
|
189
|
+
latestVersion = execSync('npm view hedgequantx version 2>/dev/null', {
|
|
163
190
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
164
191
|
timeout: 30000,
|
|
165
192
|
encoding: 'utf8'
|
|
166
193
|
}).trim();
|
|
167
194
|
|
|
168
195
|
if (!latestVersion || !/^\d+\.\d+\.\d+/.test(latestVersion)) {
|
|
169
|
-
throw new Error('
|
|
196
|
+
throw new Error('INVALID VERSION FORMAT');
|
|
170
197
|
}
|
|
171
198
|
} catch (e) {
|
|
172
|
-
spinner.fail('
|
|
173
|
-
console.log(chalk.
|
|
174
|
-
console.log(chalk.yellow(' Try manually: npm install -g hedgequantx@latest'));
|
|
199
|
+
spinner.fail('CANNOT REACH NPM REGISTRY');
|
|
200
|
+
console.log(chalk.yellow('\n TRY MANUALLY: npm update -g hedgequantx'));
|
|
175
201
|
await prompts.waitForEnter();
|
|
176
202
|
return;
|
|
177
203
|
}
|
|
178
204
|
|
|
179
|
-
spinner.succeed(`
|
|
205
|
+
spinner.succeed(`LATEST VERSION: V${latestVersion.toUpperCase()}`);
|
|
180
206
|
|
|
207
|
+
// Already up to date
|
|
181
208
|
if (currentVersion === latestVersion) {
|
|
182
|
-
console.log(chalk.green('
|
|
209
|
+
console.log(chalk.green('\n ✓ ALREADY UP TO DATE!'));
|
|
183
210
|
await prompts.waitForEnter();
|
|
184
211
|
return;
|
|
185
212
|
}
|
|
186
213
|
|
|
187
|
-
|
|
188
|
-
|
|
214
|
+
// Update available
|
|
215
|
+
console.log(chalk.yellow(`\n UPDATE AVAILABLE: V${currentVersion} → V${latestVersion}`));
|
|
216
|
+
spinner = ora({ text: 'INSTALLING UPDATE...', color: 'yellow' }).start();
|
|
189
217
|
|
|
218
|
+
// Try to install update
|
|
190
219
|
try {
|
|
191
|
-
|
|
192
|
-
const isWindows = process.platform === 'win32';
|
|
193
|
-
const cmd = isWindows
|
|
194
|
-
? 'npm install -g hedgequantx@latest'
|
|
195
|
-
: 'npm install -g hedgequantx@latest';
|
|
196
|
-
|
|
197
|
-
execSync(cmd, {
|
|
220
|
+
execSync('npm update -g hedgequantx 2>/dev/null', {
|
|
198
221
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
199
222
|
timeout: 180000,
|
|
200
223
|
encoding: 'utf8'
|
|
201
224
|
});
|
|
202
225
|
} catch (e) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
226
|
+
// Try without redirecting stderr
|
|
227
|
+
try {
|
|
228
|
+
execSync('npm update -g hedgequantx', {
|
|
229
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
230
|
+
timeout: 180000,
|
|
231
|
+
encoding: 'utf8'
|
|
232
|
+
});
|
|
233
|
+
} catch (e2) {
|
|
234
|
+
spinner.fail('UPDATE FAILED');
|
|
235
|
+
console.log(chalk.yellow('\n TRY MANUALLY:'));
|
|
236
|
+
console.log(chalk.white(' npm update -g hedgequantx'));
|
|
237
|
+
console.log(chalk.gray(' OR WITH SUDO:'));
|
|
238
|
+
console.log(chalk.white(' sudo npm update -g hedgequantx'));
|
|
239
|
+
await prompts.waitForEnter();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
209
242
|
}
|
|
210
243
|
|
|
211
|
-
spinner.succeed(`
|
|
212
|
-
console.log(chalk.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
const child = spawn('hqx', [], {
|
|
218
|
-
stdio: 'inherit',
|
|
219
|
-
detached: true,
|
|
220
|
-
shell: true
|
|
221
|
-
});
|
|
222
|
-
child.unref();
|
|
223
|
-
process.exit(0);
|
|
224
|
-
} catch (e) {
|
|
225
|
-
console.log(chalk.yellow('\n Please restart HQX manually:'));
|
|
226
|
-
console.log(chalk.white(' hqx'));
|
|
227
|
-
await prompts.waitForEnter();
|
|
228
|
-
}
|
|
244
|
+
spinner.succeed(`UPDATED TO V${latestVersion}!`);
|
|
245
|
+
console.log(chalk.green('\n ✓ UPDATE SUCCESSFUL!'));
|
|
246
|
+
console.log(chalk.yellow('\n Run ') + chalk.cyan('hqx') + chalk.yellow(' to start the new version.\n'));
|
|
247
|
+
process.exit(0);
|
|
229
248
|
|
|
230
249
|
} catch (error) {
|
|
231
|
-
if (spinner) spinner.fail('
|
|
232
|
-
console.log(chalk.
|
|
233
|
-
console.log(chalk.yellow(' Try manually: npm install -g hedgequantx@latest'));
|
|
250
|
+
if (spinner) spinner.fail('UPDATE ERROR');
|
|
251
|
+
console.log(chalk.yellow('\n TRY MANUALLY: npm update -g hedgequantx'));
|
|
234
252
|
await prompts.waitForEnter();
|
|
235
253
|
}
|
|
236
254
|
};
|