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/pages/user.js
CHANGED
|
@@ -1,55 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @module pages/user
|
|
2
|
+
* User info 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 { getLogoWidth, getColWidths, drawBoxHeader, drawBoxFooter, draw2ColHeader, visibleLength, padText } = require('../ui');
|
|
10
|
+
const { prompts } = require('../utils');
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
15
|
-
* @param {Object} service - Current service
|
|
13
|
+
* Show user info
|
|
16
14
|
*/
|
|
17
15
|
const showUserInfo = async (service) => {
|
|
18
16
|
const spinner = ora({ text: 'Fetching user info...', color: 'yellow' }).start();
|
|
19
17
|
const boxWidth = getLogoWidth();
|
|
20
18
|
const { col1, col2 } = getColWidths(boxWidth);
|
|
21
19
|
|
|
22
|
-
// Helper for row formatting
|
|
23
20
|
const fmtRow = (label, value, colW) => {
|
|
24
21
|
const labelStr = ' ' + label.padEnd(14);
|
|
25
22
|
const valueVisible = visibleLength(value || '');
|
|
26
|
-
const
|
|
27
|
-
const padding = Math.max(0, colW - totalVisible);
|
|
23
|
+
const padding = Math.max(0, colW - labelStr.length - valueVisible);
|
|
28
24
|
return chalk.white(labelStr) + value + ' '.repeat(padding);
|
|
29
25
|
};
|
|
30
26
|
|
|
31
|
-
// Get user info
|
|
32
27
|
let userInfo = null;
|
|
33
28
|
let accountCount = 0;
|
|
34
29
|
|
|
35
|
-
if (service
|
|
30
|
+
if (service?.user) {
|
|
36
31
|
userInfo = service.user;
|
|
37
32
|
} else if (service) {
|
|
38
33
|
const result = await service.getUser();
|
|
39
|
-
if (result.success)
|
|
40
|
-
userInfo = result.user;
|
|
41
|
-
}
|
|
34
|
+
if (result.success) userInfo = result.user;
|
|
42
35
|
}
|
|
43
36
|
|
|
44
|
-
// Get account count
|
|
45
37
|
if (connections.count() > 0) {
|
|
46
38
|
const accounts = await connections.getAllAccounts();
|
|
47
39
|
accountCount = accounts.length;
|
|
48
40
|
} else if (service) {
|
|
49
41
|
const result = await service.getTradingAccounts();
|
|
50
|
-
if (result.success)
|
|
51
|
-
accountCount = result.accounts.length;
|
|
52
|
-
}
|
|
42
|
+
if (result.success) accountCount = result.accounts.length;
|
|
53
43
|
}
|
|
54
44
|
|
|
55
45
|
spinner.succeed('User info loaded');
|
|
@@ -62,21 +52,17 @@ const showUserInfo = async (service) => {
|
|
|
62
52
|
} else {
|
|
63
53
|
draw2ColHeader('PROFILE', 'CONNECTIONS', boxWidth);
|
|
64
54
|
|
|
65
|
-
// Username
|
|
66
55
|
const username = userInfo.userName || userInfo.username || 'Unknown';
|
|
67
56
|
const connCount = connections.count() || 1;
|
|
68
57
|
console.log(chalk.cyan('║') + fmtRow('Username:', chalk.cyan(username.toUpperCase()), col1) + chalk.cyan('│') + fmtRow('Connections:', chalk.cyan(connCount.toString()), col2) + chalk.cyan('║'));
|
|
69
58
|
|
|
70
|
-
// Email
|
|
71
59
|
const email = userInfo.email || 'N/A';
|
|
72
60
|
console.log(chalk.cyan('║') + fmtRow('Email:', chalk.white(email), col1) + chalk.cyan('│') + fmtRow('Accounts:', chalk.cyan(accountCount.toString()), col2) + chalk.cyan('║'));
|
|
73
61
|
|
|
74
|
-
// User ID
|
|
75
62
|
const userId = userInfo.userId || userInfo.id || 'N/A';
|
|
76
63
|
const platform = service.propfirm?.name || 'ProjectX';
|
|
77
64
|
console.log(chalk.cyan('║') + fmtRow('User ID:', chalk.gray(userId.toString()), col1) + chalk.cyan('│') + fmtRow('Platform:', chalk.magenta(platform), col2) + chalk.cyan('║'));
|
|
78
65
|
|
|
79
|
-
// First/Last Name
|
|
80
66
|
const firstName = userInfo.firstName || '';
|
|
81
67
|
const lastName = userInfo.lastName || '';
|
|
82
68
|
const fullName = (firstName + ' ' + lastName).trim() || 'N/A';
|
|
@@ -86,7 +72,7 @@ const showUserInfo = async (service) => {
|
|
|
86
72
|
drawBoxFooter(boxWidth);
|
|
87
73
|
console.log();
|
|
88
74
|
|
|
89
|
-
await
|
|
75
|
+
await prompts.waitForEnter();
|
|
90
76
|
};
|
|
91
77
|
|
|
92
78
|
module.exports = { showUserInfo };
|
package/src/utils/index.js
CHANGED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized prompts utility - lightweight, no external UI
|
|
3
|
+
* Uses readline for simple input, keeps our custom box design
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create readline interface
|
|
10
|
+
*/
|
|
11
|
+
const createRL = () => readline.createInterface({
|
|
12
|
+
input: process.stdin,
|
|
13
|
+
output: process.stdout
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Wait for Enter key
|
|
18
|
+
*/
|
|
19
|
+
const waitForEnter = (message = 'Press Enter to continue...') => new Promise(resolve => {
|
|
20
|
+
const rl = createRL();
|
|
21
|
+
rl.question(message, () => { rl.close(); resolve(); });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Simple text input
|
|
26
|
+
*/
|
|
27
|
+
const textInput = (message, defaultVal = '') => new Promise(resolve => {
|
|
28
|
+
const rl = createRL();
|
|
29
|
+
const prompt = defaultVal ? `${message} (${defaultVal}): ` : `${message}: `;
|
|
30
|
+
rl.question(prompt, (answer) => {
|
|
31
|
+
rl.close();
|
|
32
|
+
resolve(answer.trim() || defaultVal);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Password input (hidden)
|
|
38
|
+
*/
|
|
39
|
+
const passwordInput = (message) => new Promise(resolve => {
|
|
40
|
+
const rl = createRL();
|
|
41
|
+
process.stdout.write(`${message}: `);
|
|
42
|
+
|
|
43
|
+
if (process.stdin.isTTY) {
|
|
44
|
+
process.stdin.setRawMode(true);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let password = '';
|
|
48
|
+
const onData = (char) => {
|
|
49
|
+
char = char.toString();
|
|
50
|
+
|
|
51
|
+
if (char === '\n' || char === '\r') {
|
|
52
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
53
|
+
process.stdin.removeListener('data', onData);
|
|
54
|
+
console.log();
|
|
55
|
+
rl.close();
|
|
56
|
+
resolve(password);
|
|
57
|
+
} else if (char === '\u0003') { // Ctrl+C
|
|
58
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
59
|
+
process.stdin.removeListener('data', onData);
|
|
60
|
+
rl.close();
|
|
61
|
+
resolve(null);
|
|
62
|
+
} else if (char === '\u007F' || char === '\b') { // Backspace
|
|
63
|
+
if (password.length > 0) {
|
|
64
|
+
password = password.slice(0, -1);
|
|
65
|
+
process.stdout.write('\b \b');
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
password += char;
|
|
69
|
+
process.stdout.write('*');
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
process.stdin.on('data', onData);
|
|
74
|
+
process.stdin.resume();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Select from options using arrow keys
|
|
79
|
+
*/
|
|
80
|
+
const selectOption = (message, options) => new Promise(resolve => {
|
|
81
|
+
if (!process.stdin.isTTY) {
|
|
82
|
+
// Fallback for non-TTY
|
|
83
|
+
const rl = createRL();
|
|
84
|
+
console.log(message);
|
|
85
|
+
options.forEach((opt, i) => console.log(` ${i + 1}. ${opt.label}`));
|
|
86
|
+
rl.question('Enter number: ', (answer) => {
|
|
87
|
+
rl.close();
|
|
88
|
+
const idx = parseInt(answer) - 1;
|
|
89
|
+
resolve(options[idx]?.value || null);
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let selectedIndex = 0;
|
|
95
|
+
const maxIndex = options.length - 1;
|
|
96
|
+
|
|
97
|
+
const render = () => {
|
|
98
|
+
// Move cursor up and clear lines
|
|
99
|
+
if (selectedIndex > 0 || options.length > 1) {
|
|
100
|
+
process.stdout.write(`\x1B[${options.length}A`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
options.forEach((opt, i) => {
|
|
104
|
+
const prefix = i === selectedIndex ? '› ' : ' ';
|
|
105
|
+
const style = i === selectedIndex ? '\x1B[36m' : '\x1B[90m'; // cyan : gray
|
|
106
|
+
process.stdout.write(`\x1B[2K${style}${prefix}${opt.label}\x1B[0m\n`);
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Initial render
|
|
111
|
+
console.log(`\x1B[36m${message}\x1B[0m`);
|
|
112
|
+
options.forEach((opt, i) => {
|
|
113
|
+
const prefix = i === selectedIndex ? '› ' : ' ';
|
|
114
|
+
const style = i === selectedIndex ? '\x1B[36m' : '\x1B[90m';
|
|
115
|
+
console.log(`${style}${prefix}${opt.label}\x1B[0m`);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
readline.emitKeypressEvents(process.stdin);
|
|
119
|
+
process.stdin.setRawMode(true);
|
|
120
|
+
process.stdin.resume();
|
|
121
|
+
|
|
122
|
+
const onKeypress = (str, key) => {
|
|
123
|
+
if (key.name === 'up' || key.name === 'k') {
|
|
124
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : maxIndex;
|
|
125
|
+
render();
|
|
126
|
+
} else if (key.name === 'down' || key.name === 'j') {
|
|
127
|
+
selectedIndex = selectedIndex < maxIndex ? selectedIndex + 1 : 0;
|
|
128
|
+
render();
|
|
129
|
+
} else if (key.name === 'return') {
|
|
130
|
+
cleanup();
|
|
131
|
+
resolve(options[selectedIndex].value);
|
|
132
|
+
} else if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {
|
|
133
|
+
cleanup();
|
|
134
|
+
resolve(null);
|
|
135
|
+
} else if (str >= '1' && str <= '9') {
|
|
136
|
+
const idx = parseInt(str) - 1;
|
|
137
|
+
if (idx <= maxIndex) {
|
|
138
|
+
cleanup();
|
|
139
|
+
resolve(options[idx].value);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const cleanup = () => {
|
|
145
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
146
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
147
|
+
// Clear the menu display
|
|
148
|
+
process.stdout.write(`\x1B[${options.length + 1}A`);
|
|
149
|
+
for (let i = 0; i <= options.length; i++) {
|
|
150
|
+
process.stdout.write('\x1B[2K\n');
|
|
151
|
+
}
|
|
152
|
+
process.stdout.write(`\x1B[${options.length + 1}A`);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
process.stdin.on('keypress', onKeypress);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Confirm yes/no
|
|
160
|
+
*/
|
|
161
|
+
const confirmPrompt = (message, defaultVal = true) => new Promise(resolve => {
|
|
162
|
+
const rl = createRL();
|
|
163
|
+
const hint = defaultVal ? '(Y/n)' : '(y/N)';
|
|
164
|
+
rl.question(`${message} ${hint}: `, (answer) => {
|
|
165
|
+
rl.close();
|
|
166
|
+
const a = answer.toLowerCase().trim();
|
|
167
|
+
if (a === '') resolve(defaultVal);
|
|
168
|
+
else if (a === 'y' || a === 'yes') resolve(true);
|
|
169
|
+
else if (a === 'n' || a === 'no') resolve(false);
|
|
170
|
+
else resolve(defaultVal);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Number input
|
|
176
|
+
*/
|
|
177
|
+
const numberInput = (message, defaultVal = 1, min = 1, max = 1000) => new Promise(resolve => {
|
|
178
|
+
const rl = createRL();
|
|
179
|
+
rl.question(`${message} (${defaultVal}): `, (answer) => {
|
|
180
|
+
rl.close();
|
|
181
|
+
const n = parseInt(answer) || defaultVal;
|
|
182
|
+
if (n < min) resolve(min);
|
|
183
|
+
else if (n > max) resolve(max);
|
|
184
|
+
else resolve(n);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
waitForEnter,
|
|
190
|
+
selectOption,
|
|
191
|
+
textInput,
|
|
192
|
+
passwordInput,
|
|
193
|
+
confirmPrompt,
|
|
194
|
+
numberInput
|
|
195
|
+
};
|