hedgequantx 1.7.6 → 1.7.8
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 -2
- package/src/app.js +54 -159
- package/src/menus/connect.js +54 -166
- package/src/menus/dashboard.js +49 -120
- package/src/pages/accounts.js +11 -22
- package/src/pages/algo/copy-trading.js +63 -210
- package/src/pages/algo/index.js +10 -20
- package/src/pages/algo/one-account.js +66 -172
- package/src/pages/orders.js +11 -32
- package/src/pages/positions.js +11 -32
- package/src/pages/stats.js +5 -14
- package/src/pages/user.js +8 -22
- package/src/utils/index.js +2 -1
- package/src/utils/prompts.js +195 -0
package/src/menus/dashboard.js
CHANGED
|
@@ -1,46 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dashboard Menu - Main menu after login
|
|
3
|
-
* Shows connected PropFirms and navigation options
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
const chalk = require('chalk');
|
|
7
|
-
const inquirer = require('inquirer');
|
|
8
6
|
const ora = require('ora');
|
|
9
7
|
const { execSync, spawn } = require('child_process');
|
|
10
8
|
|
|
11
9
|
const { connections } = require('../services');
|
|
12
10
|
const { getLogoWidth, centerText, prepareStdin } = require('../ui');
|
|
13
11
|
const { getCachedStats } = require('../services/stats-cache');
|
|
12
|
+
const { prompts } = require('../utils');
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Dashboard menu after login
|
|
17
|
-
* @param {Object} service - Connected service
|
|
18
16
|
*/
|
|
19
17
|
const dashboardMenu = async (service) => {
|
|
20
|
-
// Ensure stdin is ready for prompts
|
|
21
18
|
prepareStdin();
|
|
22
19
|
|
|
23
20
|
const boxWidth = getLogoWidth();
|
|
24
|
-
const W = boxWidth - 2;
|
|
21
|
+
const W = boxWidth - 2;
|
|
25
22
|
|
|
26
|
-
// Helper to create a line that fits exactly in the box
|
|
27
23
|
const makeLine = (content, align = 'left') => {
|
|
28
24
|
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
29
25
|
const padding = W - plainLen;
|
|
30
26
|
if (align === 'center') {
|
|
31
27
|
const leftPad = Math.floor(padding / 2);
|
|
32
|
-
|
|
33
|
-
return chalk.cyan('║') + ' '.repeat(leftPad) + content + ' '.repeat(rightPad) + chalk.cyan('║');
|
|
28
|
+
return chalk.cyan('║') + ' '.repeat(leftPad) + content + ' '.repeat(padding - leftPad) + chalk.cyan('║');
|
|
34
29
|
}
|
|
35
30
|
return chalk.cyan('║') + content + ' '.repeat(Math.max(0, padding)) + chalk.cyan('║');
|
|
36
31
|
};
|
|
37
32
|
|
|
38
|
-
//
|
|
39
|
-
console.log(chalk.cyan('
|
|
33
|
+
// Continue from banner (no top border)
|
|
34
|
+
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
40
35
|
console.log(makeLine(chalk.yellow.bold('Welcome, HQX Trader!'), 'center'));
|
|
41
36
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
42
37
|
|
|
43
|
-
// Show connected propfirms
|
|
38
|
+
// Show connected propfirms
|
|
44
39
|
const allConns = connections.getAll();
|
|
45
40
|
if (allConns.length > 0) {
|
|
46
41
|
const propfirms = allConns.slice(0, 3).map(c => c.propfirm || c.type || 'Connected');
|
|
@@ -48,38 +43,30 @@ const dashboardMenu = async (service) => {
|
|
|
48
43
|
console.log(makeLine(propfirmText, 'center'));
|
|
49
44
|
}
|
|
50
45
|
|
|
51
|
-
//
|
|
46
|
+
// Stats bar
|
|
52
47
|
const statsInfo = getCachedStats();
|
|
53
|
-
|
|
54
48
|
if (statsInfo) {
|
|
55
49
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
56
50
|
|
|
57
|
-
const
|
|
58
|
-
const accStr = `Accounts: ${statsInfo.accounts}`;
|
|
59
|
-
|
|
60
|
-
const balStr = statsInfo.balance !== null
|
|
61
|
-
? `$${statsInfo.balance.toLocaleString()}`
|
|
62
|
-
: '--';
|
|
51
|
+
const balStr = statsInfo.balance !== null ? `$${statsInfo.balance.toLocaleString()}` : '--';
|
|
63
52
|
const balColor = statsInfo.balance !== null ? chalk.green : chalk.gray;
|
|
64
53
|
|
|
65
54
|
let pnlDisplay, pnlColor;
|
|
66
55
|
if (statsInfo.pnl !== null) {
|
|
67
|
-
const pnlSign = statsInfo.pnl >= 0 ? '+' : '';
|
|
68
56
|
pnlColor = statsInfo.pnl >= 0 ? chalk.green : chalk.red;
|
|
69
|
-
pnlDisplay = `${
|
|
57
|
+
pnlDisplay = `${statsInfo.pnl >= 0 ? '+' : ''}$${Math.abs(statsInfo.pnl).toLocaleString()}`;
|
|
70
58
|
} else {
|
|
71
59
|
pnlColor = chalk.gray;
|
|
72
60
|
pnlDisplay = '--';
|
|
73
61
|
}
|
|
74
62
|
|
|
75
|
-
const
|
|
76
|
-
const statsPlain = `${connStr} ${accStr} Balance: ${balStr} P&L: ${pnlDisplay}`;
|
|
63
|
+
const statsPlain = `Connections: ${statsInfo.connections} Accounts: ${statsInfo.accounts} Balance: ${balStr} P&L: ${pnlDisplay}`;
|
|
77
64
|
const statsLeftPad = Math.floor((W - statsPlain.length) / 2);
|
|
78
65
|
const statsRightPad = W - statsPlain.length - statsLeftPad;
|
|
79
66
|
|
|
80
67
|
console.log(chalk.cyan('║') + ' '.repeat(statsLeftPad) +
|
|
81
|
-
chalk.white(
|
|
82
|
-
chalk.white(
|
|
68
|
+
chalk.white(`Connections: ${statsInfo.connections}`) + ' ' +
|
|
69
|
+
chalk.white(`Accounts: ${statsInfo.accounts}`) + ' ' +
|
|
83
70
|
chalk.white('Balance: ') + balColor(balStr) + ' ' +
|
|
84
71
|
chalk.white('P&L: ') + pnlColor(pnlDisplay) +
|
|
85
72
|
' '.repeat(Math.max(0, statsRightPad)) + chalk.cyan('║'));
|
|
@@ -87,9 +74,8 @@ const dashboardMenu = async (service) => {
|
|
|
87
74
|
|
|
88
75
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
89
76
|
|
|
90
|
-
// Menu
|
|
77
|
+
// Menu in 2 columns
|
|
91
78
|
const col1Width = Math.floor(W / 2);
|
|
92
|
-
|
|
93
79
|
const menuRow = (left, right) => {
|
|
94
80
|
const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '');
|
|
95
81
|
const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '');
|
|
@@ -98,153 +84,96 @@ const dashboardMenu = async (service) => {
|
|
|
98
84
|
console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
|
|
99
85
|
};
|
|
100
86
|
|
|
87
|
+
menuRow(chalk.cyan('[1] View Accounts'), chalk.cyan('[2] View Stats'));
|
|
88
|
+
menuRow(chalk.cyan('[+] Add Prop-Account'), chalk.magenta('[A] Algo-Trading'));
|
|
89
|
+
menuRow(chalk.yellow('[U] Update HQX'), chalk.red('[X] Disconnect'));
|
|
90
|
+
|
|
101
91
|
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
102
92
|
|
|
103
|
-
//
|
|
104
|
-
const
|
|
105
|
-
{
|
|
106
|
-
type: 'list',
|
|
107
|
-
name: 'action',
|
|
108
|
-
message: chalk.cyan('Select:'),
|
|
109
|
-
choices: [
|
|
110
|
-
{ name: chalk.cyan('[1] View Accounts'), value: 'accounts' },
|
|
111
|
-
{ name: chalk.cyan('[2] View Stats'), value: 'stats' },
|
|
112
|
-
{ name: chalk.cyan('[+] Add Prop-Account'), value: 'add_prop_account' },
|
|
113
|
-
{ name: chalk.magenta('[A] Algo-Trading'), value: 'algotrading' },
|
|
114
|
-
{ name: chalk.yellow('[U] Update HQX'), value: 'update' },
|
|
115
|
-
{ name: chalk.red('[X] Disconnect'), value: 'disconnect' }
|
|
116
|
-
],
|
|
117
|
-
loop: false
|
|
118
|
-
}
|
|
119
|
-
]);
|
|
93
|
+
// Simple input - no duplicate menu
|
|
94
|
+
const input = await prompts.textInput('Select (1/2/+/A/U/X)');
|
|
120
95
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
} catch (e) {
|
|
132
|
-
// Ignore prompt errors
|
|
133
|
-
}
|
|
96
|
+
const actionMap = {
|
|
97
|
+
'1': 'accounts',
|
|
98
|
+
'2': 'stats',
|
|
99
|
+
'+': 'add_prop_account',
|
|
100
|
+
'a': 'algotrading',
|
|
101
|
+
'u': 'update',
|
|
102
|
+
'x': 'disconnect'
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return actionMap[(input || '').toLowerCase()] || null;
|
|
134
106
|
};
|
|
135
107
|
|
|
136
108
|
/**
|
|
137
|
-
*
|
|
138
|
-
* Robust version that handles all edge cases
|
|
109
|
+
* Handle update process
|
|
139
110
|
*/
|
|
140
111
|
const handleUpdate = async () => {
|
|
141
112
|
prepareStdin();
|
|
142
113
|
|
|
143
114
|
let spinner = null;
|
|
144
115
|
let currentVersion = 'unknown';
|
|
145
|
-
let latestVersion = null;
|
|
146
116
|
|
|
147
117
|
try {
|
|
148
|
-
// Get current version safely
|
|
149
118
|
try {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
} catch (e) {
|
|
153
|
-
currentVersion = 'unknown';
|
|
154
|
-
}
|
|
119
|
+
currentVersion = require('../../package.json').version || 'unknown';
|
|
120
|
+
} catch (e) {}
|
|
155
121
|
|
|
156
122
|
spinner = ora({ text: 'Checking for updates...', color: 'yellow' }).start();
|
|
157
123
|
|
|
158
|
-
|
|
159
|
-
spinner.text = 'Checking npm registry...';
|
|
124
|
+
let latestVersion;
|
|
160
125
|
try {
|
|
161
|
-
|
|
162
|
-
stdio: 'pipe',
|
|
163
|
-
|
|
164
|
-
encoding: 'utf8'
|
|
165
|
-
});
|
|
166
|
-
latestVersion = (result || '').toString().trim();
|
|
126
|
+
latestVersion = execSync('npm view hedgequantx version 2>/dev/null', {
|
|
127
|
+
stdio: 'pipe', timeout: 15000, encoding: 'utf8'
|
|
128
|
+
}).trim();
|
|
167
129
|
|
|
168
|
-
// Validate version format (x.y.z)
|
|
169
130
|
if (!latestVersion || !/^\d+\.\d+\.\d+/.test(latestVersion)) {
|
|
170
|
-
throw new Error('Invalid version
|
|
131
|
+
throw new Error('Invalid version');
|
|
171
132
|
}
|
|
172
133
|
} catch (e) {
|
|
173
134
|
spinner.fail('Cannot reach npm registry');
|
|
174
|
-
|
|
175
|
-
console.log();
|
|
176
|
-
await waitForEnter();
|
|
135
|
+
await prompts.waitForEnter();
|
|
177
136
|
return;
|
|
178
137
|
}
|
|
179
138
|
|
|
180
|
-
// Compare versions
|
|
181
139
|
if (currentVersion === latestVersion) {
|
|
182
140
|
spinner.succeed(`Already up to date! (v${currentVersion})`);
|
|
183
|
-
console.log();
|
|
184
141
|
await new Promise(r => setTimeout(r, 2000));
|
|
185
142
|
return;
|
|
186
143
|
}
|
|
187
144
|
|
|
188
|
-
// Show version info and update automatically
|
|
189
145
|
spinner.text = `Updating v${currentVersion} → v${latestVersion}...`;
|
|
190
146
|
|
|
191
147
|
try {
|
|
192
148
|
execSync('npm install -g hedgequantx@latest 2>/dev/null', {
|
|
193
|
-
stdio: 'pipe',
|
|
194
|
-
timeout: 120000,
|
|
195
|
-
encoding: 'utf8'
|
|
149
|
+
stdio: 'pipe', timeout: 120000, encoding: 'utf8'
|
|
196
150
|
});
|
|
197
151
|
} catch (e) {
|
|
198
152
|
spinner.fail('Update failed');
|
|
199
|
-
console.log();
|
|
200
|
-
|
|
201
|
-
console.log(chalk.white(' npm install -g hedgequantx@latest'));
|
|
202
|
-
console.log();
|
|
203
|
-
await waitForEnter();
|
|
153
|
+
console.log(chalk.yellow(' Try: npm install -g hedgequantx@latest'));
|
|
154
|
+
await prompts.waitForEnter();
|
|
204
155
|
return;
|
|
205
156
|
}
|
|
206
157
|
|
|
207
158
|
spinner.succeed(`Updated: v${currentVersion} → v${latestVersion}`);
|
|
208
|
-
console.log();
|
|
209
|
-
console.log(chalk.cyan(' Restarting HedgeQuantX CLI...'));
|
|
210
|
-
console.log();
|
|
159
|
+
console.log(chalk.cyan(' Restarting...'));
|
|
211
160
|
|
|
212
|
-
// Auto restart after 2 seconds
|
|
213
161
|
await new Promise(r => setTimeout(r, 2000));
|
|
214
162
|
|
|
215
|
-
// Restart the CLI
|
|
216
163
|
try {
|
|
217
|
-
const child = spawn('hedgequantx', [], {
|
|
218
|
-
stdio: 'inherit',
|
|
219
|
-
detached: true,
|
|
220
|
-
shell: true
|
|
221
|
-
});
|
|
164
|
+
const child = spawn('hedgequantx', [], { stdio: 'inherit', detached: true, shell: true });
|
|
222
165
|
child.unref();
|
|
223
166
|
process.exit(0);
|
|
224
167
|
} catch (e) {
|
|
225
|
-
console.log(chalk.yellow('
|
|
226
|
-
|
|
227
|
-
await waitForEnter();
|
|
168
|
+
console.log(chalk.yellow(' Please run: hedgequantx'));
|
|
169
|
+
await prompts.waitForEnter();
|
|
228
170
|
}
|
|
229
171
|
|
|
230
172
|
} catch (error) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
console.log();
|
|
236
|
-
console.log(chalk.red(' An error occurred during update'));
|
|
237
|
-
if (error && error.message) {
|
|
238
|
-
console.log(chalk.gray(` ${error.message.substring(0, 100)}`));
|
|
239
|
-
}
|
|
240
|
-
console.log();
|
|
241
|
-
console.log(chalk.yellow(' Try manually: npm install -g hedgequantx@latest'));
|
|
242
|
-
console.log();
|
|
243
|
-
await waitForEnter();
|
|
173
|
+
if (spinner) spinner.fail('Update error');
|
|
174
|
+
console.log(chalk.yellow(' Try: npm install -g hedgequantx@latest'));
|
|
175
|
+
await prompts.waitForEnter();
|
|
244
176
|
}
|
|
245
177
|
};
|
|
246
178
|
|
|
247
|
-
module.exports = {
|
|
248
|
-
dashboardMenu,
|
|
249
|
-
handleUpdate
|
|
250
|
-
};
|
|
179
|
+
module.exports = { dashboardMenu, handleUpdate };
|
package/src/pages/accounts.js
CHANGED
|
@@ -1,35 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @module pages/accounts
|
|
2
|
+
* Accounts page
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
const chalk = require('chalk');
|
|
7
6
|
const ora = require('ora');
|
|
8
|
-
const inquirer = require('inquirer');
|
|
9
7
|
|
|
10
8
|
const { connections } = require('../services');
|
|
11
9
|
const { ACCOUNT_STATUS, ACCOUNT_TYPE } = require('../config');
|
|
12
|
-
const { getLogoWidth, getColWidths, drawBoxHeader, drawBoxFooter, draw2ColHeader, visibleLength
|
|
10
|
+
const { getLogoWidth, getColWidths, drawBoxHeader, drawBoxFooter, draw2ColHeader, visibleLength } = require('../ui');
|
|
11
|
+
const { prompts } = require('../utils');
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
|
-
*
|
|
16
|
-
* @param {Object} service - Current service
|
|
14
|
+
* Show all accounts
|
|
17
15
|
*/
|
|
18
16
|
const showAccounts = async (service) => {
|
|
19
17
|
const spinner = ora({ text: 'Fetching accounts...', color: 'yellow' }).start();
|
|
20
18
|
const boxWidth = getLogoWidth();
|
|
21
19
|
const { col1, col2 } = getColWidths(boxWidth);
|
|
22
20
|
|
|
23
|
-
// Helper for row formatting
|
|
24
21
|
const fmtRow = (label, value, colW) => {
|
|
25
22
|
const labelStr = ' ' + label.padEnd(12);
|
|
26
23
|
const valueVisible = visibleLength(value || '');
|
|
27
|
-
const
|
|
28
|
-
const padding = Math.max(0, colW - totalVisible);
|
|
24
|
+
const padding = Math.max(0, colW - labelStr.length - valueVisible);
|
|
29
25
|
return chalk.white(labelStr) + value + ' '.repeat(padding);
|
|
30
26
|
};
|
|
31
27
|
|
|
32
|
-
// Get accounts from all connections
|
|
33
28
|
let allAccounts = [];
|
|
34
29
|
|
|
35
30
|
if (connections.count() > 0) {
|
|
@@ -38,14 +33,10 @@ const showAccounts = async (service) => {
|
|
|
38
33
|
const result = await conn.service.getTradingAccounts();
|
|
39
34
|
if (result.success && result.accounts) {
|
|
40
35
|
result.accounts.forEach(account => {
|
|
41
|
-
allAccounts.push({
|
|
42
|
-
...account,
|
|
43
|
-
propfirm: conn.propfirm || conn.type,
|
|
44
|
-
service: conn.service
|
|
45
|
-
});
|
|
36
|
+
allAccounts.push({ ...account, propfirm: conn.propfirm || conn.type, service: conn.service });
|
|
46
37
|
});
|
|
47
38
|
}
|
|
48
|
-
} catch (e) {
|
|
39
|
+
} catch (e) {}
|
|
49
40
|
}
|
|
50
41
|
} else if (service) {
|
|
51
42
|
const result = await service.getTradingAccounts();
|
|
@@ -56,7 +47,7 @@ const showAccounts = async (service) => {
|
|
|
56
47
|
|
|
57
48
|
if (allAccounts.length === 0) {
|
|
58
49
|
spinner.fail('No accounts found');
|
|
59
|
-
await
|
|
50
|
+
await prompts.waitForEnter();
|
|
60
51
|
return;
|
|
61
52
|
}
|
|
62
53
|
|
|
@@ -65,7 +56,6 @@ const showAccounts = async (service) => {
|
|
|
65
56
|
|
|
66
57
|
drawBoxHeader('TRADING ACCOUNTS', boxWidth);
|
|
67
58
|
|
|
68
|
-
// Display accounts 2 per row
|
|
69
59
|
for (let i = 0; i < allAccounts.length; i += 2) {
|
|
70
60
|
const acc1 = allAccounts[i];
|
|
71
61
|
const acc2 = allAccounts[i + 1];
|
|
@@ -80,7 +70,7 @@ const showAccounts = async (service) => {
|
|
|
80
70
|
const pf2 = acc2 ? chalk.magenta(acc2.propfirm || 'Unknown') : '';
|
|
81
71
|
console.log(chalk.cyan('║') + fmtRow('PropFirm:', pf1, col1) + chalk.cyan('│') + (acc2 ? fmtRow('PropFirm:', pf2, col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
82
72
|
|
|
83
|
-
// Balance
|
|
73
|
+
// Balance
|
|
84
74
|
const bal1 = acc1.balance;
|
|
85
75
|
const bal2 = acc2 ? acc2.balance : null;
|
|
86
76
|
const balStr1 = bal1 !== null && bal1 !== undefined ? '$' + bal1.toLocaleString() : '--';
|
|
@@ -99,10 +89,9 @@ const showAccounts = async (service) => {
|
|
|
99
89
|
const type2 = acc2 ? (ACCOUNT_TYPE[acc2.type] || { text: 'Unknown', color: 'white' }) : null;
|
|
100
90
|
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('║'));
|
|
101
91
|
|
|
102
|
-
//
|
|
92
|
+
// ID
|
|
103
93
|
console.log(chalk.cyan('║') + fmtRow('ID:', chalk.gray(acc1.accountId), col1) + chalk.cyan('│') + (acc2 ? fmtRow('ID:', chalk.gray(acc2.accountId), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
104
94
|
|
|
105
|
-
// Separator between pairs
|
|
106
95
|
if (i + 2 < allAccounts.length) {
|
|
107
96
|
console.log(chalk.cyan('╠') + chalk.cyan('═'.repeat(col1)) + chalk.cyan('╪') + chalk.cyan('═'.repeat(col2)) + chalk.cyan('╣'));
|
|
108
97
|
}
|
|
@@ -111,7 +100,7 @@ const showAccounts = async (service) => {
|
|
|
111
100
|
drawBoxFooter(boxWidth);
|
|
112
101
|
console.log();
|
|
113
102
|
|
|
114
|
-
await
|
|
103
|
+
await prompts.waitForEnter();
|
|
115
104
|
};
|
|
116
105
|
|
|
117
106
|
module.exports = { showAccounts };
|