hedgequantx 2.6.162 → 2.7.0
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 +15 -88
- package/bin/cli.js +0 -11
- package/dist/lib/api.jsc +0 -0
- package/dist/lib/api2.jsc +0 -0
- package/dist/lib/core.jsc +0 -0
- package/dist/lib/core2.jsc +0 -0
- package/dist/lib/data.js +1 -1
- package/dist/lib/data.jsc +0 -0
- package/dist/lib/data2.jsc +0 -0
- package/dist/lib/decoder.jsc +0 -0
- package/dist/lib/m/mod1.jsc +0 -0
- package/dist/lib/m/mod2.jsc +0 -0
- package/dist/lib/n/r1.jsc +0 -0
- package/dist/lib/n/r2.jsc +0 -0
- package/dist/lib/n/r3.jsc +0 -0
- package/dist/lib/n/r4.jsc +0 -0
- package/dist/lib/n/r5.jsc +0 -0
- package/dist/lib/n/r6.jsc +0 -0
- package/dist/lib/n/r7.jsc +0 -0
- package/dist/lib/o/util1.jsc +0 -0
- package/dist/lib/o/util2.jsc +0 -0
- package/package.json +6 -3
- package/src/app.js +40 -162
- package/src/config/constants.js +31 -33
- package/src/config/propfirms.js +13 -217
- package/src/config/settings.js +0 -43
- package/src/lib/api.js +198 -0
- package/src/lib/api2.js +353 -0
- package/src/lib/core.js +539 -0
- package/src/lib/core2.js +341 -0
- package/src/lib/data.js +555 -0
- package/src/lib/data2.js +492 -0
- package/src/lib/decoder.js +599 -0
- package/src/lib/m/s1.js +804 -0
- package/src/lib/m/s2.js +34 -0
- package/src/lib/n/r1.js +454 -0
- package/src/lib/n/r2.js +514 -0
- package/src/lib/n/r3.js +631 -0
- package/src/lib/n/r4.js +401 -0
- package/src/lib/n/r5.js +335 -0
- package/src/lib/n/r6.js +425 -0
- package/src/lib/n/r7.js +530 -0
- package/src/lib/o/l1.js +44 -0
- package/src/lib/o/l2.js +427 -0
- package/src/lib/python-bridge.js +206 -0
- package/src/menus/connect.js +14 -176
- package/src/menus/dashboard.js +65 -110
- package/src/pages/accounts.js +18 -18
- package/src/pages/algo/copy-trading.js +210 -240
- package/src/pages/algo/index.js +41 -104
- package/src/pages/algo/one-account.js +386 -33
- package/src/pages/algo/ui.js +312 -151
- package/src/pages/orders.js +3 -3
- package/src/pages/positions.js +3 -3
- package/src/pages/stats/chart.js +74 -0
- package/src/pages/stats/display.js +228 -0
- package/src/pages/stats/index.js +236 -0
- package/src/pages/stats/metrics.js +213 -0
- package/src/pages/user.js +6 -6
- package/src/services/hqx-server/constants.js +55 -0
- package/src/services/hqx-server/index.js +401 -0
- package/src/services/hqx-server/latency.js +81 -0
- package/src/services/index.js +12 -3
- package/src/services/rithmic/accounts.js +7 -32
- package/src/services/rithmic/connection.js +1 -204
- package/src/services/rithmic/contracts.js +235 -0
- package/src/services/rithmic/handlers.js +21 -196
- package/src/services/rithmic/index.js +60 -291
- package/src/services/rithmic/market.js +31 -0
- package/src/services/rithmic/orders.js +5 -361
- package/src/services/rithmic/protobuf.js +5 -195
- package/src/services/session.js +22 -173
- package/src/ui/box.js +10 -18
- package/src/ui/index.js +1 -3
- package/src/ui/menu.js +1 -1
- package/src/utils/prompts.js +2 -2
- package/dist/lib/m/s1.js +0 -1
- package/src/menus/ai-agent-connect.js +0 -181
- package/src/menus/ai-agent-models.js +0 -219
- package/src/menus/ai-agent-oauth.js +0 -292
- package/src/menus/ai-agent-ui.js +0 -141
- package/src/menus/ai-agent.js +0 -484
- package/src/pages/algo/algo-config.js +0 -195
- package/src/pages/algo/algo-multi.js +0 -801
- package/src/pages/algo/algo-utils.js +0 -58
- package/src/pages/algo/copy-engine.js +0 -449
- package/src/pages/algo/custom-strategy.js +0 -459
- package/src/pages/algo/logger.js +0 -245
- package/src/pages/algo/smart-logs-data.js +0 -218
- package/src/pages/algo/smart-logs.js +0 -387
- package/src/pages/algo/ui-constants.js +0 -144
- package/src/pages/algo/ui-summary.js +0 -184
- package/src/pages/stats-calculations.js +0 -191
- package/src/pages/stats-ui.js +0 -381
- package/src/pages/stats.js +0 -339
- package/src/services/ai/client-analysis.js +0 -194
- package/src/services/ai/client-models.js +0 -333
- package/src/services/ai/client.js +0 -343
- package/src/services/ai/index.js +0 -384
- package/src/services/ai/oauth-anthropic.js +0 -265
- package/src/services/ai/oauth-gemini.js +0 -223
- package/src/services/ai/oauth-iflow.js +0 -269
- package/src/services/ai/oauth-openai.js +0 -233
- package/src/services/ai/oauth-qwen.js +0 -279
- package/src/services/ai/providers/index.js +0 -526
- package/src/services/ai/proxy-install.js +0 -249
- package/src/services/ai/proxy-manager.js +0 -494
- package/src/services/ai/proxy-remote.js +0 -161
- package/src/services/ai/strategy-supervisor.js +0 -1312
- package/src/services/ai/supervisor-data.js +0 -195
- package/src/services/ai/supervisor-optimize.js +0 -215
- package/src/services/ai/supervisor-sync.js +0 -178
- package/src/services/ai/supervisor-utils.js +0 -158
- package/src/services/ai/supervisor.js +0 -484
- package/src/services/ai/validation.js +0 -250
- package/src/services/hqx-server-events.js +0 -110
- package/src/services/hqx-server-handlers.js +0 -217
- package/src/services/hqx-server-latency.js +0 -136
- package/src/services/hqx-server.js +0 -403
- package/src/services/position-constants.js +0 -28
- package/src/services/position-manager.js +0 -528
- package/src/services/position-momentum.js +0 -206
- package/src/services/projectx/accounts.js +0 -142
- package/src/services/projectx/index.js +0 -443
- package/src/services/projectx/market.js +0 -172
- package/src/services/projectx/stats.js +0 -110
- package/src/services/projectx/trading.js +0 -180
- package/src/services/rithmic/latency-tracker.js +0 -182
- package/src/services/rithmic/market-data.js +0 -549
- package/src/services/rithmic/specs.js +0 -146
- package/src/services/rithmic/trade-history.js +0 -254
- package/src/services/session-history.js +0 -475
- package/src/services/strategy/hft-tick.js +0 -507
- package/src/services/strategy/recovery-math.js +0 -402
- package/src/services/tradovate/constants.js +0 -109
- package/src/services/tradovate/index.js +0 -505
- package/src/services/tradovate/market.js +0 -47
- package/src/services/tradovate/websocket.js +0 -97
package/src/menus/connect.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Connection Menus -
|
|
2
|
+
* Connection Menus - Rithmic Only
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const chalk = require('chalk');
|
|
6
6
|
const ora = require('ora');
|
|
7
7
|
|
|
8
|
-
const {
|
|
8
|
+
const { connections } = require('../services');
|
|
9
9
|
const { RithmicService } = require('../services/rithmic');
|
|
10
|
-
const {
|
|
11
|
-
const { getPropFirmsByPlatform } = require('../config');
|
|
10
|
+
const { PROPFIRM_CHOICES } = require('../config');
|
|
12
11
|
const { getLogoWidth, centerText, prepareStdin } = require('../ui');
|
|
13
12
|
const { validateUsername, validatePassword } = require('../security');
|
|
14
13
|
const { prompts } = require('../utils');
|
|
@@ -22,12 +21,12 @@ const loginPrompt = async (propfirmName) => {
|
|
|
22
21
|
console.log(chalk.cyan(`Connecting to ${propfirmName}...`));
|
|
23
22
|
console.log();
|
|
24
23
|
|
|
25
|
-
const username = await prompts.textInput('
|
|
24
|
+
const username = await prompts.textInput('Username:', '', (input) => {
|
|
26
25
|
try { validateUsername(input); return undefined; } catch (e) { return e.message; }
|
|
27
26
|
});
|
|
28
27
|
if (!username) return null;
|
|
29
28
|
|
|
30
|
-
const pwd = await prompts.passwordInput('
|
|
29
|
+
const pwd = await prompts.passwordInput('Password:', (input) => {
|
|
31
30
|
try { validatePassword(input); return undefined; } catch (e) { return e.message; }
|
|
32
31
|
});
|
|
33
32
|
if (!pwd) return null;
|
|
@@ -36,87 +35,20 @@ const loginPrompt = async (propfirmName) => {
|
|
|
36
35
|
};
|
|
37
36
|
|
|
38
37
|
/**
|
|
39
|
-
*
|
|
40
|
-
*/
|
|
41
|
-
const projectXMenu = async () => {
|
|
42
|
-
const propfirms = getPropFirmsByPlatform('ProjectX');
|
|
43
|
-
const boxWidth = getLogoWidth();
|
|
44
|
-
const W = boxWidth - 2;
|
|
45
|
-
const col1Width = Math.floor(W / 2);
|
|
46
|
-
|
|
47
|
-
const numbered = propfirms.map((pf, i) => ({ num: i + 1, key: pf.key, name: pf.displayName }));
|
|
48
|
-
|
|
49
|
-
console.log();
|
|
50
|
-
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
51
|
-
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM (ProjectX)', W)) + chalk.cyan('║'));
|
|
52
|
-
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
53
|
-
|
|
54
|
-
const menuRow = (left, right) => {
|
|
55
|
-
const leftPlain = left ? left.replace(/\x1b\[[0-9;]*m/g, '') : '';
|
|
56
|
-
const rightPlain = right ? right.replace(/\x1b\[[0-9;]*m/g, '') : '';
|
|
57
|
-
const leftPadded = ' ' + (left || '') + ' '.repeat(Math.max(0, col1Width - leftPlain.length - 2));
|
|
58
|
-
const rightPadded = (right || '') + ' '.repeat(Math.max(0, W - col1Width - rightPlain.length));
|
|
59
|
-
console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
for (let i = 0; i < numbered.length; i += 2) {
|
|
63
|
-
const left = numbered[i];
|
|
64
|
-
const right = numbered[i + 1];
|
|
65
|
-
const leftText = chalk.cyan(`[${left.num.toString().padStart(2, ' ')}]`) + ' ' + chalk.white(left.name);
|
|
66
|
-
const rightText = right ? chalk.cyan(`[${right.num.toString().padStart(2, ' ')}]`) + ' ' + chalk.white(right.name) : '';
|
|
67
|
-
menuRow(leftText, rightText);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
71
|
-
console.log(chalk.cyan('║') + ' ' + chalk.red('[X] BACK') + ' '.repeat(W - 10) + chalk.cyan('║'));
|
|
72
|
-
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
73
|
-
|
|
74
|
-
const input = await prompts.textInput(chalk.cyan('SELECT NUMBER (OR X):'));
|
|
75
|
-
if (!input || input.toLowerCase() === 'x') return null;
|
|
76
|
-
|
|
77
|
-
const action = parseInt(input);
|
|
78
|
-
if (isNaN(action) || action < 1 || action > numbered.length) return null;
|
|
79
|
-
|
|
80
|
-
const selectedPropfirm = numbered[action - 1];
|
|
81
|
-
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
82
|
-
if (!credentials) return null;
|
|
83
|
-
|
|
84
|
-
const spinner = ora({ text: 'AUTHENTICATING...', color: 'yellow' }).start();
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
const service = new ProjectXService(selectedPropfirm.key);
|
|
88
|
-
const result = await service.login(credentials.username, credentials.password);
|
|
89
|
-
|
|
90
|
-
if (result.success) {
|
|
91
|
-
await service.getUser();
|
|
92
|
-
connections.add('projectx', service, service.propfirm.name);
|
|
93
|
-
spinner.succeed(`Connected to ${service.propfirm.name}`);
|
|
94
|
-
return service;
|
|
95
|
-
} else {
|
|
96
|
-
spinner.fail(result.error || 'AUTHENTICATION FAILED');
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
} catch (error) {
|
|
100
|
-
spinner.fail(error.message);
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Rithmic menu
|
|
38
|
+
* Rithmic menu - Main connection menu
|
|
107
39
|
*/
|
|
108
40
|
const rithmicMenu = async () => {
|
|
109
|
-
const propfirms =
|
|
41
|
+
const propfirms = PROPFIRM_CHOICES;
|
|
110
42
|
const boxWidth = getLogoWidth();
|
|
111
43
|
const innerWidth = boxWidth - 2;
|
|
112
44
|
const numCols = 3;
|
|
113
45
|
const colWidth = Math.floor(innerWidth / numCols);
|
|
114
46
|
|
|
115
|
-
const numbered = propfirms.map((pf, i) => ({ num: i + 1, key: pf.
|
|
47
|
+
const numbered = propfirms.map((pf, i) => ({ num: i + 1, key: pf.value, name: pf.name }));
|
|
116
48
|
|
|
117
49
|
console.log();
|
|
118
50
|
console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
|
|
119
|
-
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM
|
|
51
|
+
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM', innerWidth)) + chalk.cyan('║'));
|
|
120
52
|
console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
|
|
121
53
|
|
|
122
54
|
const rows = Math.ceil(numbered.length / numCols);
|
|
@@ -139,10 +71,10 @@ const rithmicMenu = async () => {
|
|
|
139
71
|
}
|
|
140
72
|
|
|
141
73
|
console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
|
|
142
|
-
console.log(chalk.cyan('║') + ' ' + chalk.red('[X]
|
|
74
|
+
console.log(chalk.cyan('║') + ' ' + chalk.red('[X] Back') + ' '.repeat(innerWidth - 10) + chalk.cyan('║'));
|
|
143
75
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
144
76
|
|
|
145
|
-
const input = await prompts.textInput(chalk.cyan('
|
|
77
|
+
const input = await prompts.textInput(chalk.cyan('Select number (or X):'));
|
|
146
78
|
if (!input || input.toLowerCase() === 'x') return null;
|
|
147
79
|
|
|
148
80
|
const action = parseInt(input);
|
|
@@ -152,7 +84,7 @@ const rithmicMenu = async () => {
|
|
|
152
84
|
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
153
85
|
if (!credentials) return null;
|
|
154
86
|
|
|
155
|
-
const spinner = ora({ text: '
|
|
87
|
+
const spinner = ora({ text: 'Connecting to Rithmic...', color: 'yellow' }).start();
|
|
156
88
|
|
|
157
89
|
try {
|
|
158
90
|
const service = new RithmicService(selectedPropfirm.key);
|
|
@@ -166,7 +98,7 @@ const rithmicMenu = async () => {
|
|
|
166
98
|
await new Promise(r => setTimeout(r, 1500));
|
|
167
99
|
return service;
|
|
168
100
|
} else {
|
|
169
|
-
spinner.fail(result.error || '
|
|
101
|
+
spinner.fail(result.error || 'Authentication failed');
|
|
170
102
|
await new Promise(r => setTimeout(r, 2000));
|
|
171
103
|
return null;
|
|
172
104
|
}
|
|
@@ -177,98 +109,4 @@ const rithmicMenu = async () => {
|
|
|
177
109
|
}
|
|
178
110
|
};
|
|
179
111
|
|
|
180
|
-
|
|
181
|
-
* Tradovate menu
|
|
182
|
-
*/
|
|
183
|
-
const tradovateMenu = async () => {
|
|
184
|
-
const propfirms = getPropFirmsByPlatform('Tradovate');
|
|
185
|
-
const boxWidth = getLogoWidth();
|
|
186
|
-
const innerWidth = boxWidth - 2;
|
|
187
|
-
|
|
188
|
-
const numbered = propfirms.map((pf, i) => ({ num: i + 1, key: pf.key, name: pf.displayName }));
|
|
189
|
-
|
|
190
|
-
console.log();
|
|
191
|
-
console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
|
|
192
|
-
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM (TRADOVATE)', innerWidth)) + chalk.cyan('║'));
|
|
193
|
-
console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
|
|
194
|
-
|
|
195
|
-
for (const item of numbered) {
|
|
196
|
-
const numStr = item.num.toString().padStart(2, ' ');
|
|
197
|
-
const text = ' ' + chalk.cyan(`[${numStr}]`) + ' ' + chalk.white(item.name);
|
|
198
|
-
const textLen = 4 + 1 + item.name.length + 2;
|
|
199
|
-
console.log(chalk.cyan('║') + text + ' '.repeat(innerWidth - textLen) + chalk.cyan('║'));
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
|
|
203
|
-
console.log(chalk.cyan('║') + ' ' + chalk.red('[X] BACK') + ' '.repeat(innerWidth - 10) + chalk.cyan('║'));
|
|
204
|
-
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
205
|
-
|
|
206
|
-
const input = await prompts.textInput(chalk.cyan('SELECT NUMBER (OR X):'));
|
|
207
|
-
if (!input || input.toLowerCase() === 'x') return null;
|
|
208
|
-
|
|
209
|
-
const action = parseInt(input);
|
|
210
|
-
if (isNaN(action) || action < 1 || action > numbered.length) return null;
|
|
211
|
-
|
|
212
|
-
const selectedPropfirm = numbered[action - 1];
|
|
213
|
-
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
214
|
-
if (!credentials) return null;
|
|
215
|
-
|
|
216
|
-
const spinner = ora({ text: 'CONNECTING TO TRADOVATE...', color: 'yellow' }).start();
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
const service = new TradovateService(selectedPropfirm.key);
|
|
220
|
-
const result = await service.login(credentials.username, credentials.password);
|
|
221
|
-
|
|
222
|
-
if (result.success) {
|
|
223
|
-
spinner.text = 'Fetching accounts...';
|
|
224
|
-
await service.getTradingAccounts();
|
|
225
|
-
connections.add('tradovate', service, service.propfirm.name);
|
|
226
|
-
spinner.succeed(`Connected to ${service.propfirm.name}`);
|
|
227
|
-
return service;
|
|
228
|
-
} else {
|
|
229
|
-
spinner.fail(result.error || 'AUTHENTICATION FAILED');
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
} catch (error) {
|
|
233
|
-
spinner.fail(error.message);
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Add Prop Account menu
|
|
240
|
-
*/
|
|
241
|
-
const addPropAccountMenu = async () => {
|
|
242
|
-
const boxWidth = getLogoWidth();
|
|
243
|
-
const W = boxWidth - 2;
|
|
244
|
-
const col1Width = Math.floor(W / 2);
|
|
245
|
-
|
|
246
|
-
console.log();
|
|
247
|
-
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
248
|
-
console.log(chalk.cyan('║') + chalk.white.bold(centerText('ADD PROP ACCOUNT', W)) + chalk.cyan('║'));
|
|
249
|
-
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
250
|
-
|
|
251
|
-
const menuRow = (left, right) => {
|
|
252
|
-
const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '');
|
|
253
|
-
const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '');
|
|
254
|
-
const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain.length - 2));
|
|
255
|
-
const rightPadded = right + ' '.repeat(Math.max(0, W - col1Width - rightPlain.length));
|
|
256
|
-
console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
menuRow(chalk.cyan('[1] ProjectX'), chalk.cyan('[2] Rithmic'));
|
|
260
|
-
menuRow(chalk.cyan('[3] Tradovate'), chalk.red('[X] BACK'));
|
|
261
|
-
|
|
262
|
-
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
263
|
-
|
|
264
|
-
const input = await prompts.textInput(chalk.cyan('SELECT (1/2/3/X):'));
|
|
265
|
-
if (!input || input.toLowerCase() === 'x') return null;
|
|
266
|
-
|
|
267
|
-
const num = parseInt(input);
|
|
268
|
-
if (num === 1) return 'projectx';
|
|
269
|
-
if (num === 2) return 'rithmic';
|
|
270
|
-
if (num === 3) return 'tradovate';
|
|
271
|
-
return null;
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
module.exports = { loginPrompt, projectXMenu, rithmicMenu, tradovateMenu, addPropAccountMenu };
|
|
112
|
+
module.exports = { loginPrompt, rithmicMenu };
|
package/src/menus/dashboard.js
CHANGED
|
@@ -10,8 +10,6 @@ const { connections } = require('../services');
|
|
|
10
10
|
const { getLogoWidth, centerText, prepareStdin } = require('../ui');
|
|
11
11
|
const { getCachedStats } = require('../services/stats-cache');
|
|
12
12
|
const { prompts } = require('../utils');
|
|
13
|
-
const aiService = require('../services/ai');
|
|
14
|
-
|
|
15
13
|
|
|
16
14
|
/**
|
|
17
15
|
* Dashboard menu after login
|
|
@@ -22,10 +20,6 @@ const dashboardMenu = async (service) => {
|
|
|
22
20
|
const boxWidth = getLogoWidth();
|
|
23
21
|
const W = boxWidth - 2;
|
|
24
22
|
|
|
25
|
-
// Check AI connection status
|
|
26
|
-
const aiConnected = aiService.isConnected();
|
|
27
|
-
const aiAgentCount = aiService.getAgentCount();
|
|
28
|
-
|
|
29
23
|
const makeLine = (content, align = 'left') => {
|
|
30
24
|
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
31
25
|
const padding = W - plainLen;
|
|
@@ -38,111 +32,74 @@ const dashboardMenu = async (service) => {
|
|
|
38
32
|
|
|
39
33
|
// Continue from banner (use ╠ not ╔)
|
|
40
34
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
41
|
-
console.log(makeLine(chalk.
|
|
35
|
+
console.log(makeLine(chalk.yellow.bold('Welcome, HQX Trader!'), 'center'));
|
|
42
36
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
43
37
|
|
|
44
38
|
// Show connected propfirms
|
|
45
39
|
const allConns = connections.getAll();
|
|
46
40
|
if (allConns.length > 0) {
|
|
47
|
-
const propfirms = allConns.slice(0, 3).map(c =>
|
|
41
|
+
const propfirms = allConns.slice(0, 3).map(c => c.propfirm || c.type || 'Connected');
|
|
48
42
|
const propfirmText = propfirms.map(p => chalk.green('● ') + chalk.white(p)).join(' ');
|
|
49
43
|
console.log(makeLine(propfirmText, 'center'));
|
|
50
44
|
}
|
|
51
45
|
|
|
52
|
-
// Stats bar with icons
|
|
46
|
+
// Stats bar with yellow icons
|
|
53
47
|
const statsInfo = getCachedStats();
|
|
54
48
|
if (statsInfo) {
|
|
55
49
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
56
50
|
|
|
57
|
-
const balStr = statsInfo.balance !== null ? `$${statsInfo.balance.toLocaleString()}` : '--';
|
|
51
|
+
const balStr = statsInfo.balance !== null ? `$${statsInfo.balance.toLocaleString()}` : '--';
|
|
58
52
|
const balColor = statsInfo.balance !== null ? chalk.green : chalk.gray;
|
|
59
53
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const statsRightPad = Math.max(0, W - statsLen - statsLeftPad);
|
|
54
|
+
let pnlDisplay, pnlColor;
|
|
55
|
+
if (statsInfo.pnl !== null) {
|
|
56
|
+
pnlColor = statsInfo.pnl >= 0 ? chalk.green : chalk.red;
|
|
57
|
+
pnlDisplay = `${statsInfo.pnl >= 0 ? '+' : ''}$${Math.abs(statsInfo.pnl).toLocaleString()}`;
|
|
58
|
+
} else {
|
|
59
|
+
pnlColor = chalk.gray;
|
|
60
|
+
pnlDisplay = '--';
|
|
61
|
+
}
|
|
69
62
|
|
|
70
|
-
//
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
: chalk.gray('NONE');
|
|
63
|
+
// Yellow icons: ✔ for each stat
|
|
64
|
+
const icon = chalk.yellow('✔ ');
|
|
65
|
+
const statsPlain = `✔ Connections: ${statsInfo.connections} ✔ Accounts: ${statsInfo.accounts} ✔ Balance: ${balStr} ✔ P&L: ${pnlDisplay}`;
|
|
66
|
+
const statsLeftPad = Math.floor((W - statsPlain.length) / 2);
|
|
67
|
+
const statsRightPad = W - statsPlain.length - statsLeftPad;
|
|
76
68
|
|
|
77
69
|
console.log(chalk.cyan('║') + ' '.repeat(statsLeftPad) +
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
' '.repeat(statsRightPad) + chalk.cyan('║'));
|
|
70
|
+
icon + chalk.white(`Connections: ${statsInfo.connections}`) + ' ' +
|
|
71
|
+
icon + chalk.white(`Accounts: ${statsInfo.accounts}`) + ' ' +
|
|
72
|
+
icon + chalk.white('Balance: ') + balColor(balStr) + ' ' +
|
|
73
|
+
icon + chalk.white('P&L: ') + pnlColor(pnlDisplay) +
|
|
74
|
+
' '.repeat(Math.max(0, statsRightPad)) + chalk.cyan('║'));
|
|
83
75
|
}
|
|
84
76
|
|
|
85
77
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
86
78
|
|
|
87
|
-
// Menu in
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
// Center each item within its fixed-width column
|
|
96
|
-
const pad1Left = Math.floor((colWidth - c1Plain.length) / 2);
|
|
97
|
-
const pad1Right = colWidth - c1Plain.length - pad1Left;
|
|
98
|
-
|
|
99
|
-
const pad2Left = Math.floor((colWidth - c2Plain.length) / 2);
|
|
100
|
-
const pad2Right = colWidth - c2Plain.length - pad2Left;
|
|
101
|
-
|
|
102
|
-
// Third column gets remaining width
|
|
103
|
-
const col3Width = W - (colWidth * 2);
|
|
104
|
-
const pad3Left = Math.floor((col3Width - c3Plain.length) / 2);
|
|
105
|
-
const pad3Right = col3Width - c3Plain.length - pad3Left;
|
|
106
|
-
|
|
107
|
-
const line =
|
|
108
|
-
' '.repeat(pad1Left) + col1 + ' '.repeat(pad1Right) +
|
|
109
|
-
' '.repeat(pad2Left) + col2 + ' '.repeat(pad2Right) +
|
|
110
|
-
' '.repeat(pad3Left) + col3 + ' '.repeat(pad3Right);
|
|
111
|
-
|
|
112
|
-
console.log(chalk.cyan('║') + line + chalk.cyan('║'));
|
|
79
|
+
// Menu in 2 columns
|
|
80
|
+
const col1Width = Math.floor(W / 2);
|
|
81
|
+
const menuRow = (left, right) => {
|
|
82
|
+
const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '');
|
|
83
|
+
const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '');
|
|
84
|
+
const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain.length - 2));
|
|
85
|
+
const rightPadded = right + ' '.repeat(Math.max(0, W - col1Width - rightPlain.length));
|
|
86
|
+
console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
|
|
113
87
|
};
|
|
114
88
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const leftPad = Math.floor(padding / 2);
|
|
119
|
-
console.log(chalk.cyan('║') + ' '.repeat(leftPad) + content + ' '.repeat(padding - leftPad) + chalk.cyan('║'));
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
// Fixed-width menu items for perfect alignment
|
|
123
|
-
const menuItem = (key, label, color) => {
|
|
124
|
-
const text = `[${key}] ${label.padEnd(14)}`;
|
|
125
|
-
return color(text);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
menuRow3(menuItem('1', 'VIEW ACCOUNTS', chalk.cyan), menuItem('2', 'VIEW STATS', chalk.cyan), menuItem('+', 'ADD ACCOUNT', chalk.cyan));
|
|
129
|
-
menuRow3(menuItem('A', 'ALGO TRADING', chalk.magenta), menuItem('I', 'AI AGENT', chalk.magenta), menuItem('U', 'UPDATE HQX', chalk.yellow));
|
|
130
|
-
|
|
131
|
-
// Separator and disconnect button centered
|
|
132
|
-
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
133
|
-
centerLine(chalk.red('[X] DISCONNECT'));
|
|
89
|
+
menuRow(chalk.cyan('[1] View Accounts'), chalk.cyan('[2] View Stats'));
|
|
90
|
+
menuRow(chalk.cyan('[+] Add Prop-Account'), chalk.magenta('[A] Algo-Trading'));
|
|
91
|
+
menuRow(chalk.yellow('[U] Update HQX'), chalk.red('[X] Disconnect'));
|
|
134
92
|
|
|
135
93
|
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
136
94
|
|
|
137
95
|
// Simple input - no duplicate menu
|
|
138
|
-
const input = await prompts.textInput(chalk.cyan('
|
|
96
|
+
const input = await prompts.textInput(chalk.cyan('Select (1/2/+/A/U/X)'));
|
|
139
97
|
|
|
140
98
|
const actionMap = {
|
|
141
99
|
'1': 'accounts',
|
|
142
100
|
'2': 'stats',
|
|
143
101
|
'+': 'add_prop_account',
|
|
144
102
|
'a': 'algotrading',
|
|
145
|
-
'i': 'ai_agent',
|
|
146
103
|
'u': 'update',
|
|
147
104
|
'x': 'disconnect'
|
|
148
105
|
};
|
|
@@ -164,8 +121,8 @@ const handleUpdate = async () => {
|
|
|
164
121
|
currentVersion = require('../../package.json').version || 'unknown';
|
|
165
122
|
} catch (e) {}
|
|
166
123
|
|
|
167
|
-
console.log(chalk.cyan(`\n
|
|
168
|
-
spinner = ora({ text: '
|
|
124
|
+
console.log(chalk.cyan(`\n Current version: v${currentVersion}`));
|
|
125
|
+
spinner = ora({ text: 'Checking for updates...', color: 'yellow' }).start();
|
|
169
126
|
|
|
170
127
|
let latestVersion;
|
|
171
128
|
try {
|
|
@@ -179,23 +136,23 @@ const handleUpdate = async () => {
|
|
|
179
136
|
throw new Error('Invalid version format');
|
|
180
137
|
}
|
|
181
138
|
} catch (e) {
|
|
182
|
-
spinner.fail('
|
|
183
|
-
console.log(chalk.gray(`
|
|
184
|
-
console.log(chalk.yellow('
|
|
139
|
+
spinner.fail('Cannot reach npm registry');
|
|
140
|
+
console.log(chalk.gray(` Error: ${e.message}`));
|
|
141
|
+
console.log(chalk.yellow(' Try manually: npm install -g hedgequantx@latest'));
|
|
185
142
|
await prompts.waitForEnter();
|
|
186
143
|
return;
|
|
187
144
|
}
|
|
188
145
|
|
|
189
|
-
spinner.succeed(`
|
|
146
|
+
spinner.succeed(`Latest version: v${latestVersion}`);
|
|
190
147
|
|
|
191
148
|
if (currentVersion === latestVersion) {
|
|
192
|
-
console.log(chalk.green('
|
|
149
|
+
console.log(chalk.green(' Already up to date!'));
|
|
193
150
|
await prompts.waitForEnter();
|
|
194
151
|
return;
|
|
195
152
|
}
|
|
196
153
|
|
|
197
|
-
console.log(chalk.yellow(`
|
|
198
|
-
spinner = ora({ text: '
|
|
154
|
+
console.log(chalk.yellow(` Update available: v${currentVersion} → v${latestVersion}`));
|
|
155
|
+
spinner = ora({ text: 'Installing update...', color: 'yellow' }).start();
|
|
199
156
|
|
|
200
157
|
try {
|
|
201
158
|
// Try with sudo first on Unix systems
|
|
@@ -210,39 +167,37 @@ const handleUpdate = async () => {
|
|
|
210
167
|
encoding: 'utf8'
|
|
211
168
|
});
|
|
212
169
|
} catch (e) {
|
|
213
|
-
spinner.fail('
|
|
214
|
-
console.log(chalk.gray(`
|
|
215
|
-
console.log(chalk.yellow('
|
|
170
|
+
spinner.fail('Update failed - permission denied?');
|
|
171
|
+
console.log(chalk.gray(` Error: ${e.message}`));
|
|
172
|
+
console.log(chalk.yellow(' Try manually with sudo:'));
|
|
216
173
|
console.log(chalk.white(' sudo npm install -g hedgequantx@latest'));
|
|
217
174
|
await prompts.waitForEnter();
|
|
218
175
|
return;
|
|
219
176
|
}
|
|
220
177
|
|
|
221
|
-
spinner.succeed(`
|
|
222
|
-
console.log(chalk.cyan('
|
|
223
|
-
|
|
224
|
-
// Clean terminal state before restart
|
|
225
|
-
if (process.stdin.isTTY) {
|
|
226
|
-
process.stdin.setRawMode(false);
|
|
227
|
-
}
|
|
228
|
-
process.stdin.pause();
|
|
229
|
-
|
|
230
|
-
// Small delay then restart with clean stdio
|
|
231
|
-
await new Promise(r => setTimeout(r, 500));
|
|
178
|
+
spinner.succeed(`Updated to v${latestVersion}!`);
|
|
179
|
+
console.log(chalk.cyan(' Restarting HQX...'));
|
|
232
180
|
|
|
233
|
-
|
|
234
|
-
const { spawnSync } = require('child_process');
|
|
235
|
-
spawnSync('hqx', [], {
|
|
236
|
-
stdio: 'inherit',
|
|
237
|
-
shell: true
|
|
238
|
-
});
|
|
181
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
239
182
|
|
|
240
|
-
|
|
183
|
+
try {
|
|
184
|
+
const child = spawn('hqx', [], {
|
|
185
|
+
stdio: 'inherit',
|
|
186
|
+
detached: true,
|
|
187
|
+
shell: true
|
|
188
|
+
});
|
|
189
|
+
child.unref();
|
|
190
|
+
process.exit(0);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
console.log(chalk.yellow('\n Please restart HQX manually:'));
|
|
193
|
+
console.log(chalk.white(' hqx'));
|
|
194
|
+
await prompts.waitForEnter();
|
|
195
|
+
}
|
|
241
196
|
|
|
242
197
|
} catch (error) {
|
|
243
|
-
if (spinner) spinner.fail('
|
|
244
|
-
console.log(chalk.gray(`
|
|
245
|
-
console.log(chalk.yellow('
|
|
198
|
+
if (spinner) spinner.fail('Update error');
|
|
199
|
+
console.log(chalk.gray(` Error: ${error.message}`));
|
|
200
|
+
console.log(chalk.yellow(' Try manually: npm install -g hedgequantx@latest'));
|
|
246
201
|
await prompts.waitForEnter();
|
|
247
202
|
}
|
|
248
203
|
};
|
package/src/pages/accounts.js
CHANGED
|
@@ -7,7 +7,7 @@ const ora = require('ora');
|
|
|
7
7
|
|
|
8
8
|
const { connections } = require('../services');
|
|
9
9
|
const { ACCOUNT_STATUS, ACCOUNT_TYPE } = require('../config');
|
|
10
|
-
const { getLogoWidth, getColWidths, drawBoxHeader, drawBoxFooter, draw2ColHeader,
|
|
10
|
+
const { getLogoWidth, getColWidths, drawBoxHeader, drawBoxFooter, draw2ColHeader, visibleLength } = require('../ui');
|
|
11
11
|
const { prompts } = require('../utils');
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -29,19 +29,19 @@ const showAccounts = async (service) => {
|
|
|
29
29
|
|
|
30
30
|
try {
|
|
31
31
|
// Single spinner for loading (appears below the dashboard header)
|
|
32
|
-
spinner = ora({ text: '
|
|
32
|
+
spinner = ora({ text: 'Loading accounts...', color: 'yellow' }).start();
|
|
33
33
|
|
|
34
|
-
const allConns = connections.count() > 0 ? connections.getAll() : (service ? [{ service, propfirm: service.propfirm?.name || '
|
|
34
|
+
const allConns = connections.count() > 0 ? connections.getAll() : (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
|
|
35
35
|
|
|
36
36
|
if (allConns.length === 0) {
|
|
37
|
-
spinner.fail('
|
|
37
|
+
spinner.fail('No connections found');
|
|
38
38
|
await prompts.waitForEnter();
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// Fetch accounts from each connection
|
|
43
43
|
for (const conn of allConns) {
|
|
44
|
-
const propfirmName = conn.propfirm || conn.type || '
|
|
44
|
+
const propfirmName = conn.propfirm || conn.type || 'Unknown';
|
|
45
45
|
|
|
46
46
|
try {
|
|
47
47
|
const result = await conn.service.getTradingAccounts();
|
|
@@ -60,7 +60,7 @@ const showAccounts = async (service) => {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
if (allAccounts.length === 0) {
|
|
63
|
-
spinner.fail('
|
|
63
|
+
spinner.fail('No accounts found');
|
|
64
64
|
await prompts.waitForEnter();
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
@@ -78,7 +78,7 @@ const showAccounts = async (service) => {
|
|
|
78
78
|
} catch (e) {}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
spinner.succeed('
|
|
81
|
+
spinner.succeed('Accounts loaded');
|
|
82
82
|
console.log();
|
|
83
83
|
|
|
84
84
|
// Display accounts
|
|
@@ -94,9 +94,9 @@ const showAccounts = async (service) => {
|
|
|
94
94
|
draw2ColHeader(name1.substring(0, col1 - 4), name2 ? name2.substring(0, col2 - 4) : '', boxWidth);
|
|
95
95
|
|
|
96
96
|
// PropFirm
|
|
97
|
-
const pf1 = chalk.magenta(acc1.propfirm || '
|
|
98
|
-
const pf2 = acc2 ? chalk.magenta(acc2.propfirm || '
|
|
99
|
-
console.log(chalk.cyan('║') + fmtRow('
|
|
97
|
+
const pf1 = chalk.magenta(acc1.propfirm || 'Unknown');
|
|
98
|
+
const pf2 = acc2 ? chalk.magenta(acc2.propfirm || 'Unknown') : '';
|
|
99
|
+
console.log(chalk.cyan('║') + fmtRow('PropFirm:', pf1, col1) + chalk.cyan('│') + (acc2 ? fmtRow('PropFirm:', pf2, col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
100
100
|
|
|
101
101
|
// Balance
|
|
102
102
|
const bal1 = acc1.balance;
|
|
@@ -105,7 +105,7 @@ const showAccounts = async (service) => {
|
|
|
105
105
|
const balStr2 = bal2 !== null && bal2 !== undefined ? '$' + Number(bal2).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '--';
|
|
106
106
|
const balColor1 = bal1 === null || bal1 === undefined ? chalk.gray : (bal1 >= 0 ? chalk.green : chalk.red);
|
|
107
107
|
const balColor2 = bal2 === null || bal2 === undefined ? chalk.gray : (bal2 >= 0 ? chalk.green : chalk.red);
|
|
108
|
-
console.log(chalk.cyan('║') + fmtRow('
|
|
108
|
+
console.log(chalk.cyan('║') + fmtRow('Balance:', balColor1(balStr1), col1) + chalk.cyan('│') + (acc2 ? fmtRow('Balance:', balColor2(balStr2), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
109
109
|
|
|
110
110
|
// P&L
|
|
111
111
|
const pnl1 = acc1.profitAndLoss;
|
|
@@ -117,17 +117,17 @@ const showAccounts = async (service) => {
|
|
|
117
117
|
console.log(chalk.cyan('║') + fmtRow('P&L:', pnlColor1(pnlStr1), col1) + chalk.cyan('│') + (acc2 ? fmtRow('P&L:', pnlColor2(pnlStr2), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
118
118
|
|
|
119
119
|
// Status
|
|
120
|
-
const status1 = ACCOUNT_STATUS[acc1.status] || { text: '
|
|
121
|
-
const status2 = acc2 ? (ACCOUNT_STATUS[acc2.status] || { text: '
|
|
122
|
-
console.log(chalk.cyan('║') + fmtRow('
|
|
120
|
+
const status1 = ACCOUNT_STATUS[acc1.status] || { text: 'Unknown', color: 'gray' };
|
|
121
|
+
const status2 = acc2 ? (ACCOUNT_STATUS[acc2.status] || { text: 'Unknown', color: 'gray' }) : null;
|
|
122
|
+
console.log(chalk.cyan('║') + fmtRow('Status:', chalk[status1.color](status1.text), col1) + chalk.cyan('│') + (acc2 ? fmtRow('Status:', chalk[status2.color](status2.text), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
123
123
|
|
|
124
124
|
// Type
|
|
125
|
-
const type1 = ACCOUNT_TYPE[acc1.type] || { text: '
|
|
126
|
-
const type2 = acc2 ? (ACCOUNT_TYPE[acc2.type] || { text: '
|
|
127
|
-
console.log(chalk.cyan('║') + fmtRow('
|
|
125
|
+
const type1 = ACCOUNT_TYPE[acc1.type] || { text: 'Unknown', color: 'white' };
|
|
126
|
+
const type2 = acc2 ? (ACCOUNT_TYPE[acc2.type] || { text: 'Unknown', color: 'white' }) : null;
|
|
127
|
+
console.log(chalk.cyan('║') + fmtRow('Type:', chalk[type1.color](type1.text), col1) + chalk.cyan('│') + (acc2 ? fmtRow('Type:', chalk[type2.color](type2.text), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
128
128
|
|
|
129
129
|
if (i + 2 < allAccounts.length) {
|
|
130
|
-
|
|
130
|
+
console.log(chalk.cyan('╠') + chalk.cyan('═'.repeat(col1)) + chalk.cyan('╪') + chalk.cyan('═'.repeat(col2)) + chalk.cyan('╣'));
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|