hedgequantx 2.4.36 → 2.4.38
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 +5 -5
- package/src/menus/connect.js +16 -16
- package/src/menus/dashboard.js +27 -27
- package/src/pages/accounts.js +6 -6
- package/src/pages/algo/copy-trading.js +15 -15
- package/src/pages/algo/index.js +6 -6
- package/src/pages/algo/one-account.js +21 -21
- package/src/pages/algo/ui.js +12 -12
- package/src/pages/orders.js +3 -3
- package/src/pages/positions.js +3 -3
- package/src/pages/stats.js +21 -21
- package/src/pages/user.js +5 -5
- package/src/utils/prompts.js +2 -2
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -225,15 +225,15 @@ const run = async () => {
|
|
|
225
225
|
await bannerClosed();
|
|
226
226
|
|
|
227
227
|
// Restore session
|
|
228
|
-
const spinner = ora({ text: '
|
|
228
|
+
const spinner = ora({ text: 'RESTORING SESSION...', color: 'yellow' }).start();
|
|
229
229
|
const restored = await connections.restoreFromStorage();
|
|
230
230
|
|
|
231
231
|
if (restored) {
|
|
232
|
-
spinner.succeed('
|
|
232
|
+
spinner.succeed('SESSION RESTORED');
|
|
233
233
|
currentService = connections.getAll()[0].service;
|
|
234
234
|
await refreshStats();
|
|
235
235
|
} else {
|
|
236
|
-
spinner.info('
|
|
236
|
+
spinner.info('NO ACTIVE SESSION');
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
// Main loop
|
|
@@ -246,7 +246,7 @@ const run = async () => {
|
|
|
246
246
|
const choice = await mainMenu();
|
|
247
247
|
|
|
248
248
|
if (choice === 'exit') {
|
|
249
|
-
console.log(chalk.gray('
|
|
249
|
+
console.log(chalk.gray('GOODBYE!'));
|
|
250
250
|
process.exit(0);
|
|
251
251
|
}
|
|
252
252
|
|
|
@@ -304,7 +304,7 @@ const run = async () => {
|
|
|
304
304
|
connections.disconnectAll();
|
|
305
305
|
currentService = null;
|
|
306
306
|
clearCachedStats();
|
|
307
|
-
console.log(chalk.yellow('
|
|
307
|
+
console.log(chalk.yellow('DISCONNECTED'));
|
|
308
308
|
break;
|
|
309
309
|
}
|
|
310
310
|
}
|
package/src/menus/connect.js
CHANGED
|
@@ -22,12 +22,12 @@ const loginPrompt = async (propfirmName) => {
|
|
|
22
22
|
console.log(chalk.cyan(`Connecting to ${propfirmName}...`));
|
|
23
23
|
console.log();
|
|
24
24
|
|
|
25
|
-
const username = await prompts.textInput('
|
|
25
|
+
const username = await prompts.textInput('USERNAME:', '', (input) => {
|
|
26
26
|
try { validateUsername(input); return undefined; } catch (e) { return e.message; }
|
|
27
27
|
});
|
|
28
28
|
if (!username) return null;
|
|
29
29
|
|
|
30
|
-
const pwd = await prompts.passwordInput('
|
|
30
|
+
const pwd = await prompts.passwordInput('PASSWORD:', (input) => {
|
|
31
31
|
try { validatePassword(input); return undefined; } catch (e) { return e.message; }
|
|
32
32
|
});
|
|
33
33
|
if (!pwd) return null;
|
|
@@ -68,10 +68,10 @@ const projectXMenu = async () => {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
71
|
-
console.log(chalk.cyan('║') + ' ' + chalk.red('[X]
|
|
71
|
+
console.log(chalk.cyan('║') + ' ' + chalk.red('[X] BACK') + ' '.repeat(W - 10) + chalk.cyan('║'));
|
|
72
72
|
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
73
73
|
|
|
74
|
-
const input = await prompts.textInput(chalk.cyan('
|
|
74
|
+
const input = await prompts.textInput(chalk.cyan('SELECT NUMBER (OR X):'));
|
|
75
75
|
if (!input || input.toLowerCase() === 'x') return null;
|
|
76
76
|
|
|
77
77
|
const action = parseInt(input);
|
|
@@ -81,7 +81,7 @@ const projectXMenu = async () => {
|
|
|
81
81
|
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
82
82
|
if (!credentials) return null;
|
|
83
83
|
|
|
84
|
-
const spinner = ora({ text: '
|
|
84
|
+
const spinner = ora({ text: 'AUTHENTICATING...', color: 'yellow' }).start();
|
|
85
85
|
|
|
86
86
|
try {
|
|
87
87
|
const service = new ProjectXService(selectedPropfirm.key);
|
|
@@ -93,7 +93,7 @@ const projectXMenu = async () => {
|
|
|
93
93
|
spinner.succeed(`Connected to ${service.propfirm.name}`);
|
|
94
94
|
return service;
|
|
95
95
|
} else {
|
|
96
|
-
spinner.fail(result.error || '
|
|
96
|
+
spinner.fail(result.error || 'AUTHENTICATION FAILED');
|
|
97
97
|
return null;
|
|
98
98
|
}
|
|
99
99
|
} catch (error) {
|
|
@@ -139,10 +139,10 @@ const rithmicMenu = async () => {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
|
|
142
|
-
console.log(chalk.cyan('║') + ' ' + chalk.red('[X]
|
|
142
|
+
console.log(chalk.cyan('║') + ' ' + chalk.red('[X] BACK') + ' '.repeat(innerWidth - 10) + chalk.cyan('║'));
|
|
143
143
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
144
144
|
|
|
145
|
-
const input = await prompts.textInput(chalk.cyan('
|
|
145
|
+
const input = await prompts.textInput(chalk.cyan('SELECT NUMBER (OR X):'));
|
|
146
146
|
if (!input || input.toLowerCase() === 'x') return null;
|
|
147
147
|
|
|
148
148
|
const action = parseInt(input);
|
|
@@ -152,7 +152,7 @@ const rithmicMenu = async () => {
|
|
|
152
152
|
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
153
153
|
if (!credentials) return null;
|
|
154
154
|
|
|
155
|
-
const spinner = ora({ text: '
|
|
155
|
+
const spinner = ora({ text: 'CONNECTING TO RITHMIC...', color: 'yellow' }).start();
|
|
156
156
|
|
|
157
157
|
try {
|
|
158
158
|
const service = new RithmicService(selectedPropfirm.key);
|
|
@@ -166,7 +166,7 @@ const rithmicMenu = async () => {
|
|
|
166
166
|
await new Promise(r => setTimeout(r, 1500));
|
|
167
167
|
return service;
|
|
168
168
|
} else {
|
|
169
|
-
spinner.fail(result.error || '
|
|
169
|
+
spinner.fail(result.error || 'AUTHENTICATION FAILED');
|
|
170
170
|
await new Promise(r => setTimeout(r, 2000));
|
|
171
171
|
return null;
|
|
172
172
|
}
|
|
@@ -200,10 +200,10 @@ const tradovateMenu = async () => {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
|
|
203
|
-
console.log(chalk.cyan('║') + ' ' + chalk.red('[X]
|
|
203
|
+
console.log(chalk.cyan('║') + ' ' + chalk.red('[X] BACK') + ' '.repeat(innerWidth - 10) + chalk.cyan('║'));
|
|
204
204
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
205
205
|
|
|
206
|
-
const input = await prompts.textInput(chalk.cyan('
|
|
206
|
+
const input = await prompts.textInput(chalk.cyan('SELECT NUMBER (OR X):'));
|
|
207
207
|
if (!input || input.toLowerCase() === 'x') return null;
|
|
208
208
|
|
|
209
209
|
const action = parseInt(input);
|
|
@@ -213,7 +213,7 @@ const tradovateMenu = async () => {
|
|
|
213
213
|
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
214
214
|
if (!credentials) return null;
|
|
215
215
|
|
|
216
|
-
const spinner = ora({ text: '
|
|
216
|
+
const spinner = ora({ text: 'CONNECTING TO TRADOVATE...', color: 'yellow' }).start();
|
|
217
217
|
|
|
218
218
|
try {
|
|
219
219
|
const service = new TradovateService(selectedPropfirm.key);
|
|
@@ -226,7 +226,7 @@ const tradovateMenu = async () => {
|
|
|
226
226
|
spinner.succeed(`Connected to ${service.propfirm.name}`);
|
|
227
227
|
return service;
|
|
228
228
|
} else {
|
|
229
|
-
spinner.fail(result.error || '
|
|
229
|
+
spinner.fail(result.error || 'AUTHENTICATION FAILED');
|
|
230
230
|
return null;
|
|
231
231
|
}
|
|
232
232
|
} catch (error) {
|
|
@@ -257,11 +257,11 @@ const addPropAccountMenu = async () => {
|
|
|
257
257
|
};
|
|
258
258
|
|
|
259
259
|
menuRow(chalk.cyan('[1] ProjectX'), chalk.cyan('[2] Rithmic'));
|
|
260
|
-
menuRow(chalk.cyan('[3] Tradovate'), chalk.red('[X]
|
|
260
|
+
menuRow(chalk.cyan('[3] Tradovate'), chalk.red('[X] BACK'));
|
|
261
261
|
|
|
262
262
|
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
263
263
|
|
|
264
|
-
const input = await prompts.textInput(chalk.cyan('
|
|
264
|
+
const input = await prompts.textInput(chalk.cyan('SELECT (1/2/3/X):'));
|
|
265
265
|
if (!input || input.toLowerCase() === 'x') return null;
|
|
266
266
|
|
|
267
267
|
const num = parseInt(input);
|
package/src/menus/dashboard.js
CHANGED
|
@@ -32,7 +32,7 @@ const dashboardMenu = async (service) => {
|
|
|
32
32
|
|
|
33
33
|
// Continue from banner (use ╠ not ╔)
|
|
34
34
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
35
|
-
console.log(makeLine(chalk.yellow.bold('
|
|
35
|
+
console.log(makeLine(chalk.yellow.bold('WELCOME, HQX TRADER!'), 'center'));
|
|
36
36
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
37
37
|
|
|
38
38
|
// Show connected propfirms
|
|
@@ -62,14 +62,14 @@ const dashboardMenu = async (service) => {
|
|
|
62
62
|
|
|
63
63
|
// Yellow icons: ✔ for each stat
|
|
64
64
|
const icon = chalk.yellow('✔ ');
|
|
65
|
-
const statsPlain = `✔
|
|
65
|
+
const statsPlain = `✔ CONNECTIONS: ${statsInfo.connections} ✔ ACCOUNTS: ${statsInfo.accounts} ✔ BALANCE: ${balStr} ✔ P&L: ${pnlDisplay}`;
|
|
66
66
|
const statsLeftPad = Math.max(0, Math.floor((W - statsPlain.length) / 2));
|
|
67
67
|
const statsRightPad = Math.max(0, W - statsPlain.length - statsLeftPad);
|
|
68
68
|
|
|
69
69
|
console.log(chalk.cyan('║') + ' '.repeat(statsLeftPad) +
|
|
70
|
-
icon + chalk.white(`
|
|
71
|
-
icon + chalk.white(`
|
|
72
|
-
icon + chalk.white('
|
|
70
|
+
icon + chalk.white(`CONNECTIONS: ${statsInfo.connections}`) + ' ' +
|
|
71
|
+
icon + chalk.white(`ACCOUNTS: ${statsInfo.accounts}`) + ' ' +
|
|
72
|
+
icon + chalk.white('BALANCE: ') + balColor(balStr) + ' ' +
|
|
73
73
|
icon + chalk.white('P&L: ') + pnlColor(pnlDisplay) +
|
|
74
74
|
' '.repeat(Math.max(0, statsRightPad)) + chalk.cyan('║'));
|
|
75
75
|
}
|
|
@@ -86,14 +86,14 @@ const dashboardMenu = async (service) => {
|
|
|
86
86
|
console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
-
menuRow(chalk.cyan('[1]
|
|
90
|
-
menuRow(chalk.cyan('[+]
|
|
91
|
-
menuRow(chalk.yellow('[U]
|
|
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'));
|
|
92
92
|
|
|
93
93
|
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
94
94
|
|
|
95
95
|
// Simple input - no duplicate menu
|
|
96
|
-
const input = await prompts.textInput(chalk.cyan('
|
|
96
|
+
const input = await prompts.textInput(chalk.cyan('SELECT (1/2/+/A/U/X)'));
|
|
97
97
|
|
|
98
98
|
const actionMap = {
|
|
99
99
|
'1': 'accounts',
|
|
@@ -121,8 +121,8 @@ const handleUpdate = async () => {
|
|
|
121
121
|
currentVersion = require('../../package.json').version || 'unknown';
|
|
122
122
|
} catch (e) {}
|
|
123
123
|
|
|
124
|
-
console.log(chalk.cyan(`\n
|
|
125
|
-
spinner = ora({ text: '
|
|
124
|
+
console.log(chalk.cyan(`\n CURRENT VERSION: v${currentVersion}`));
|
|
125
|
+
spinner = ora({ text: 'CHECKING FOR UPDATES...', color: 'yellow' }).start();
|
|
126
126
|
|
|
127
127
|
let latestVersion;
|
|
128
128
|
try {
|
|
@@ -136,23 +136,23 @@ const handleUpdate = async () => {
|
|
|
136
136
|
throw new Error('Invalid version format');
|
|
137
137
|
}
|
|
138
138
|
} catch (e) {
|
|
139
|
-
spinner.fail('
|
|
140
|
-
console.log(chalk.gray(`
|
|
141
|
-
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'));
|
|
142
142
|
await prompts.waitForEnter();
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
spinner.succeed(`
|
|
146
|
+
spinner.succeed(`LATEST VERSION: v${latestVersion}`);
|
|
147
147
|
|
|
148
148
|
if (currentVersion === latestVersion) {
|
|
149
|
-
console.log(chalk.green('
|
|
149
|
+
console.log(chalk.green(' ALREADY UP TO DATE!'));
|
|
150
150
|
await prompts.waitForEnter();
|
|
151
151
|
return;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
console.log(chalk.yellow(`
|
|
155
|
-
spinner = ora({ text: '
|
|
154
|
+
console.log(chalk.yellow(` UPDATE AVAILABLE: v${currentVersion} → v${latestVersion}`));
|
|
155
|
+
spinner = ora({ text: 'INSTALLING UPDATE...', color: 'yellow' }).start();
|
|
156
156
|
|
|
157
157
|
try {
|
|
158
158
|
// Try with sudo first on Unix systems
|
|
@@ -167,16 +167,16 @@ const handleUpdate = async () => {
|
|
|
167
167
|
encoding: 'utf8'
|
|
168
168
|
});
|
|
169
169
|
} catch (e) {
|
|
170
|
-
spinner.fail('
|
|
171
|
-
console.log(chalk.gray(`
|
|
172
|
-
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:'));
|
|
173
173
|
console.log(chalk.white(' sudo npm install -g hedgequantx@latest'));
|
|
174
174
|
await prompts.waitForEnter();
|
|
175
175
|
return;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
spinner.succeed(`
|
|
179
|
-
console.log(chalk.cyan('
|
|
178
|
+
spinner.succeed(`UPDATED TO v${latestVersion}!`);
|
|
179
|
+
console.log(chalk.cyan(' RESTARTING HQX...'));
|
|
180
180
|
|
|
181
181
|
await new Promise(r => setTimeout(r, 1500));
|
|
182
182
|
|
|
@@ -189,15 +189,15 @@ const handleUpdate = async () => {
|
|
|
189
189
|
child.unref();
|
|
190
190
|
process.exit(0);
|
|
191
191
|
} catch (e) {
|
|
192
|
-
console.log(chalk.yellow('\n
|
|
192
|
+
console.log(chalk.yellow('\n PLEASE RESTART HQX MANUALLY:'));
|
|
193
193
|
console.log(chalk.white(' hqx'));
|
|
194
194
|
await prompts.waitForEnter();
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
} catch (error) {
|
|
198
|
-
if (spinner) spinner.fail('
|
|
199
|
-
console.log(chalk.gray(`
|
|
200
|
-
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'));
|
|
201
201
|
await prompts.waitForEnter();
|
|
202
202
|
}
|
|
203
203
|
};
|
package/src/pages/accounts.js
CHANGED
|
@@ -29,12 +29,12 @@ 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
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
|
}
|
|
@@ -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
|
|
@@ -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;
|
|
@@ -119,7 +119,7 @@ const showAccounts = async (service) => {
|
|
|
119
119
|
// Status
|
|
120
120
|
const status1 = ACCOUNT_STATUS[acc1.status] || { text: 'Unknown', color: 'gray' };
|
|
121
121
|
const status2 = acc2 ? (ACCOUNT_STATUS[acc2.status] || { text: 'Unknown', color: 'gray' }) : null;
|
|
122
|
-
console.log(chalk.cyan('║') + fmtRow('
|
|
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
125
|
const type1 = ACCOUNT_TYPE[acc1.type] || { text: 'Unknown', color: 'white' };
|
|
@@ -48,11 +48,11 @@ const copyTradingMenu = async () => {
|
|
|
48
48
|
console.log();
|
|
49
49
|
|
|
50
50
|
// Fetch all accounts
|
|
51
|
-
const spinner = ora({ text: '
|
|
51
|
+
const spinner = ora({ text: 'FETCHING ACCOUNTS...', color: 'yellow' }).start();
|
|
52
52
|
const allAccounts = await fetchAllAccounts(allConns);
|
|
53
53
|
|
|
54
54
|
if (allAccounts.length < 2) {
|
|
55
|
-
spinner.fail('
|
|
55
|
+
spinner.fail('NEED AT LEAST 2 ACTIVE ACCOUNTS');
|
|
56
56
|
await prompts.waitForEnter();
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
@@ -61,14 +61,14 @@ const copyTradingMenu = async () => {
|
|
|
61
61
|
|
|
62
62
|
// Step 1: Select Lead Account
|
|
63
63
|
console.log(chalk.cyan(' Step 1: Select LEAD Account'));
|
|
64
|
-
const leadIdx = await selectAccount('
|
|
64
|
+
const leadIdx = await selectAccount('LEAD ACCOUNT:', allAccounts, -1);
|
|
65
65
|
if (leadIdx === null || leadIdx === -1) return;
|
|
66
66
|
const lead = allAccounts[leadIdx];
|
|
67
67
|
|
|
68
68
|
// Step 2: Select Follower Account
|
|
69
69
|
console.log();
|
|
70
70
|
console.log(chalk.cyan(' Step 2: Select FOLLOWER Account'));
|
|
71
|
-
const followerIdx = await selectAccount('
|
|
71
|
+
const followerIdx = await selectAccount('FOLLOWER ACCOUNT:', allAccounts, leadIdx);
|
|
72
72
|
if (followerIdx === null || followerIdx === -1) return;
|
|
73
73
|
const follower = allAccounts[followerIdx];
|
|
74
74
|
|
|
@@ -82,10 +82,10 @@ const copyTradingMenu = async () => {
|
|
|
82
82
|
console.log();
|
|
83
83
|
console.log(chalk.cyan(' Step 4: Configure Parameters'));
|
|
84
84
|
|
|
85
|
-
const leadContracts = await prompts.numberInput('
|
|
85
|
+
const leadContracts = await prompts.numberInput('LEAD CONTRACTS:', 1, 1, 10);
|
|
86
86
|
if (leadContracts === null) return;
|
|
87
87
|
|
|
88
|
-
const followerContracts = await prompts.numberInput('
|
|
88
|
+
const followerContracts = await prompts.numberInput('FOLLOWER CONTRACTS:', leadContracts, 1, 10);
|
|
89
89
|
if (followerContracts === null) return;
|
|
90
90
|
|
|
91
91
|
const dailyTarget = await prompts.numberInput('Daily target ($):', 400, 1, 10000);
|
|
@@ -95,9 +95,9 @@ const copyTradingMenu = async () => {
|
|
|
95
95
|
if (maxRisk === null) return;
|
|
96
96
|
|
|
97
97
|
// Step 5: Privacy
|
|
98
|
-
const showNames = await prompts.selectOption('
|
|
99
|
-
{ label: '
|
|
100
|
-
{ label: '
|
|
98
|
+
const showNames = await prompts.selectOption('ACCOUNT NAMES:', [
|
|
99
|
+
{ label: 'HIDE ACCOUNT NAMES', value: false },
|
|
100
|
+
{ label: 'SHOW ACCOUNT NAMES', value: true },
|
|
101
101
|
]);
|
|
102
102
|
if (showNames === null) return;
|
|
103
103
|
|
|
@@ -110,7 +110,7 @@ const copyTradingMenu = async () => {
|
|
|
110
110
|
console.log(chalk.cyan(` Target: $${dailyTarget} | Risk: $${maxRisk}`));
|
|
111
111
|
console.log();
|
|
112
112
|
|
|
113
|
-
const confirm = await prompts.confirmPrompt('
|
|
113
|
+
const confirm = await prompts.confirmPrompt('START COPY TRADING?', true);
|
|
114
114
|
if (!confirm) return;
|
|
115
115
|
|
|
116
116
|
// Launch
|
|
@@ -173,7 +173,7 @@ const selectAccount = async (message, accounts, excludeIdx) => {
|
|
|
173
173
|
};
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
-
options.push({ label: '<
|
|
176
|
+
options.push({ label: '< CANCEL', value: -1 });
|
|
177
177
|
return prompts.selectOption(message, options);
|
|
178
178
|
};
|
|
179
179
|
|
|
@@ -183,7 +183,7 @@ const selectAccount = async (message, accounts, excludeIdx) => {
|
|
|
183
183
|
* @returns {Promise<Object|null>}
|
|
184
184
|
*/
|
|
185
185
|
const selectSymbol = async (service) => {
|
|
186
|
-
const spinner = ora({ text: '
|
|
186
|
+
const spinner = ora({ text: 'LOADING SYMBOLS...', color: 'yellow' }).start();
|
|
187
187
|
|
|
188
188
|
try {
|
|
189
189
|
// Try ProjectX API first for consistency
|
|
@@ -198,7 +198,7 @@ const selectSymbol = async (service) => {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
if (!contracts || !contracts.length) {
|
|
201
|
-
spinner.fail('
|
|
201
|
+
spinner.fail('NO CONTRACTS AVAILABLE');
|
|
202
202
|
await prompts.waitForEnter();
|
|
203
203
|
return null;
|
|
204
204
|
}
|
|
@@ -226,9 +226,9 @@ const selectSymbol = async (service) => {
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
options.push({ label: '', value: null, disabled: true });
|
|
229
|
-
options.push({ label: chalk.gray('<
|
|
229
|
+
options.push({ label: chalk.gray('< CANCEL'), value: null });
|
|
230
230
|
|
|
231
|
-
return prompts.selectOption('
|
|
231
|
+
return prompts.selectOption('TRADING SYMBOL:', options);
|
|
232
232
|
} catch (err) {
|
|
233
233
|
spinner.fail(`Error loading contracts: ${err.message}`);
|
|
234
234
|
await prompts.waitForEnter();
|
package/src/pages/algo/index.js
CHANGED
|
@@ -20,14 +20,14 @@ const algoTradingMenu = async (service) => {
|
|
|
20
20
|
try {
|
|
21
21
|
console.log();
|
|
22
22
|
console.log(chalk.gray(getSeparator()));
|
|
23
|
-
console.log(chalk.yellow.bold('
|
|
23
|
+
console.log(chalk.yellow.bold(' ALGO-TRADING'));
|
|
24
24
|
console.log(chalk.gray(getSeparator()));
|
|
25
25
|
console.log();
|
|
26
26
|
|
|
27
|
-
const action = await prompts.selectOption(chalk.yellow('
|
|
28
|
-
{ value: 'one_account', label: '
|
|
29
|
-
{ value: 'copy_trading', label: '
|
|
30
|
-
{ value: 'back', label: '<
|
|
27
|
+
const action = await prompts.selectOption(chalk.yellow('SELECT MODE:'), [
|
|
28
|
+
{ value: 'one_account', label: 'ONE ACCOUNT' },
|
|
29
|
+
{ value: 'copy_trading', label: 'COPY TRADING' },
|
|
30
|
+
{ value: 'back', label: '< BACK' }
|
|
31
31
|
]);
|
|
32
32
|
|
|
33
33
|
log.debug('Algo mode selected', { action });
|
|
@@ -50,7 +50,7 @@ const algoTradingMenu = async (service) => {
|
|
|
50
50
|
return action;
|
|
51
51
|
} catch (err) {
|
|
52
52
|
log.error('Algo menu error:', err.message);
|
|
53
|
-
console.log(chalk.red(`
|
|
53
|
+
console.log(chalk.red(` ERROR: ${err.message}`));
|
|
54
54
|
await prompts.waitForEnter();
|
|
55
55
|
return 'back';
|
|
56
56
|
}
|
|
@@ -27,18 +27,18 @@ const oneAccountMenu = async (service) => {
|
|
|
27
27
|
if (!market.isOpen && !market.message.includes('early')) {
|
|
28
28
|
console.log();
|
|
29
29
|
console.log(chalk.red(` ${market.message}`));
|
|
30
|
-
console.log(chalk.gray('
|
|
30
|
+
console.log(chalk.gray(' ALGO TRADING IS ONLY AVAILABLE WHEN MARKET IS OPEN'));
|
|
31
31
|
console.log();
|
|
32
32
|
await prompts.waitForEnter();
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const spinner = ora({ text: '
|
|
36
|
+
const spinner = ora({ text: 'FETCHING ACTIVE ACCOUNTS...', color: 'yellow' }).start();
|
|
37
37
|
|
|
38
38
|
const allAccounts = await connections.getAllAccounts();
|
|
39
39
|
|
|
40
40
|
if (!allAccounts?.length) {
|
|
41
|
-
spinner.fail('
|
|
41
|
+
spinner.fail('NO ACCOUNTS FOUND');
|
|
42
42
|
await prompts.waitForEnter();
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
@@ -46,12 +46,12 @@ const oneAccountMenu = async (service) => {
|
|
|
46
46
|
const activeAccounts = allAccounts.filter(acc => acc.status === 0);
|
|
47
47
|
|
|
48
48
|
if (!activeAccounts.length) {
|
|
49
|
-
spinner.fail('
|
|
49
|
+
spinner.fail('NO ACTIVE ACCOUNTS');
|
|
50
50
|
await prompts.waitForEnter();
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
spinner.succeed(`
|
|
54
|
+
spinner.succeed(`FOUND ${activeAccounts.length} ACTIVE ACCOUNT(S)`);
|
|
55
55
|
|
|
56
56
|
// Select account - display RAW API fields
|
|
57
57
|
const options = activeAccounts.map(acc => {
|
|
@@ -65,9 +65,9 @@ const oneAccountMenu = async (service) => {
|
|
|
65
65
|
value: acc
|
|
66
66
|
};
|
|
67
67
|
});
|
|
68
|
-
options.push({ label: '<
|
|
68
|
+
options.push({ label: '< BACK', value: 'back' });
|
|
69
69
|
|
|
70
|
-
const selectedAccount = await prompts.selectOption('
|
|
70
|
+
const selectedAccount = await prompts.selectOption('SELECT ACCOUNT:', options);
|
|
71
71
|
if (!selectedAccount || selectedAccount === 'back') return;
|
|
72
72
|
|
|
73
73
|
// Use the service attached to the account (from getAllAccounts), fallback to getServiceForAccount
|
|
@@ -88,11 +88,11 @@ const oneAccountMenu = async (service) => {
|
|
|
88
88
|
* Symbol selection - sorted with popular indices first
|
|
89
89
|
*/
|
|
90
90
|
const selectSymbol = async (service, account) => {
|
|
91
|
-
const spinner = ora({ text: '
|
|
91
|
+
const spinner = ora({ text: 'LOADING SYMBOLS...', color: 'yellow' }).start();
|
|
92
92
|
|
|
93
93
|
const contractsResult = await service.getContracts();
|
|
94
94
|
if (!contractsResult.success || !contractsResult.contracts?.length) {
|
|
95
|
-
spinner.fail('
|
|
95
|
+
spinner.fail('FAILED TO LOAD CONTRACTS');
|
|
96
96
|
return null;
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -119,7 +119,7 @@ const selectSymbol = async (service, account) => {
|
|
|
119
119
|
return nameA.localeCompare(nameB);
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
-
spinner.succeed(`
|
|
122
|
+
spinner.succeed(`FOUND ${contracts.length} CONTRACTS`);
|
|
123
123
|
|
|
124
124
|
// Display sorted contracts from API
|
|
125
125
|
const options = contracts.map(c => ({
|
|
@@ -127,9 +127,9 @@ const selectSymbol = async (service, account) => {
|
|
|
127
127
|
value: c
|
|
128
128
|
}));
|
|
129
129
|
|
|
130
|
-
options.push({ label: chalk.gray('<
|
|
130
|
+
options.push({ label: chalk.gray('< BACK'), value: 'back' });
|
|
131
131
|
|
|
132
|
-
const contract = await prompts.selectOption(chalk.yellow('
|
|
132
|
+
const contract = await prompts.selectOption(chalk.yellow('SELECT SYMBOL:'), options);
|
|
133
133
|
return contract === 'back' || contract === null ? null : contract;
|
|
134
134
|
};
|
|
135
135
|
|
|
@@ -138,28 +138,28 @@ const selectSymbol = async (service, account) => {
|
|
|
138
138
|
*/
|
|
139
139
|
const configureAlgo = async (account, contract) => {
|
|
140
140
|
console.log();
|
|
141
|
-
console.log(chalk.cyan('
|
|
141
|
+
console.log(chalk.cyan(' CONFIGURE ALGO PARAMETERS'));
|
|
142
142
|
console.log();
|
|
143
143
|
|
|
144
|
-
const contracts = await prompts.numberInput('
|
|
144
|
+
const contracts = await prompts.numberInput('NUMBER OF CONTRACTS:', 1, 1, 10);
|
|
145
145
|
if (contracts === null) return null;
|
|
146
146
|
|
|
147
|
-
const dailyTarget = await prompts.numberInput('
|
|
147
|
+
const dailyTarget = await prompts.numberInput('DAILY TARGET ($):', 1000, 1, 10000);
|
|
148
148
|
if (dailyTarget === null) return null;
|
|
149
149
|
|
|
150
|
-
const maxRisk = await prompts.numberInput('
|
|
150
|
+
const maxRisk = await prompts.numberInput('MAX RISK ($):', 500, 1, 5000);
|
|
151
151
|
if (maxRisk === null) return null;
|
|
152
152
|
|
|
153
|
-
const showName = await prompts.confirmPrompt('
|
|
153
|
+
const showName = await prompts.confirmPrompt('SHOW ACCOUNT NAME?', false);
|
|
154
154
|
if (showName === null) return null;
|
|
155
155
|
|
|
156
|
-
const confirm = await prompts.confirmPrompt('
|
|
156
|
+
const confirm = await prompts.confirmPrompt('START ALGO TRADING?', true);
|
|
157
157
|
if (!confirm) return null;
|
|
158
158
|
|
|
159
159
|
// Show spinner while initializing
|
|
160
|
-
const initSpinner = ora({ text: '
|
|
160
|
+
const initSpinner = ora({ text: 'INITIALIZING ALGO TRADING...', color: 'yellow' }).start();
|
|
161
161
|
await new Promise(r => setTimeout(r, 500));
|
|
162
|
-
initSpinner.succeed('
|
|
162
|
+
initSpinner.succeed('LAUNCHING ALGO...');
|
|
163
163
|
|
|
164
164
|
return { contracts, dailyTarget, maxRisk, showName };
|
|
165
165
|
};
|
|
@@ -546,7 +546,7 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
546
546
|
// Summary
|
|
547
547
|
renderSessionSummary(stats, stopReason);
|
|
548
548
|
|
|
549
|
-
console.log('\n
|
|
549
|
+
console.log('\n RETURNING TO MENU IN 3 SECONDS...');
|
|
550
550
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
551
551
|
};
|
|
552
552
|
|
package/src/pages/algo/ui.js
CHANGED
|
@@ -183,7 +183,7 @@ class AlgoUI {
|
|
|
183
183
|
this._line(chalk.cyan(GM));
|
|
184
184
|
|
|
185
185
|
// Row 2: Qty | P&L
|
|
186
|
-
const r2c1 = buildCell('
|
|
186
|
+
const r2c1 = buildCell('QTY', (stats.qty || '1').toString(), chalk.cyan, colL);
|
|
187
187
|
const r2c2 = buildCell('P&L', pnlStr, pnlColor, colR);
|
|
188
188
|
row(r2c1.padded, r2c2.padded);
|
|
189
189
|
|
|
@@ -192,8 +192,8 @@ class AlgoUI {
|
|
|
192
192
|
// Row 3: Target | Risk
|
|
193
193
|
const targetStr = stats.target !== null && stats.target !== undefined ? '$' + stats.target.toFixed(2) : '--';
|
|
194
194
|
const riskStr = stats.risk !== null && stats.risk !== undefined ? '$' + stats.risk.toFixed(2) : '--';
|
|
195
|
-
const r3c1 = buildCell('
|
|
196
|
-
const r3c2 = buildCell('
|
|
195
|
+
const r3c1 = buildCell('TARGET', targetStr, chalk.green, colL);
|
|
196
|
+
const r3c2 = buildCell('RISK', riskStr, chalk.red, colR);
|
|
197
197
|
row(r3c1.padded, r3c2.padded);
|
|
198
198
|
|
|
199
199
|
this._line(chalk.cyan(GM));
|
|
@@ -201,15 +201,15 @@ class AlgoUI {
|
|
|
201
201
|
// Row 4: Trades | Latency (API response time) - UPPERCASE BOLD
|
|
202
202
|
const r4c1t = ` ${chalk.bold('TRADES')}: ${chalk.cyan.bold(stats.trades || 0)} ${chalk.bold('W/L')}: ${chalk.green.bold(stats.wins || 0)}/${chalk.red.bold(stats.losses || 0)}`;
|
|
203
203
|
const r4c1p = ` TRADES: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
|
|
204
|
-
const r4c2 = buildCell('
|
|
204
|
+
const r4c2 = buildCell('LATENCY', `${stats.latency || 0}MS`, latencyColor, colR);
|
|
205
205
|
row(r4c1t + pad(colL - r4c1p.length), r4c2.padded);
|
|
206
206
|
|
|
207
207
|
this._line(chalk.cyan(GM));
|
|
208
208
|
|
|
209
209
|
// Row 5: Connection | Propfirm
|
|
210
210
|
const connection = stats.platform || 'ProjectX';
|
|
211
|
-
const r5c1 = buildCell('
|
|
212
|
-
const r5c2 = buildCell('
|
|
211
|
+
const r5c1 = buildCell('CONNECTION', connection, chalk.cyan, colL);
|
|
212
|
+
const r5c2 = buildCell('PROPFIRM', stats.propfirm || 'N/A', chalk.cyan, colR);
|
|
213
213
|
row(r5c1.padded, r5c2.padded);
|
|
214
214
|
|
|
215
215
|
this._line(chalk.cyan(GB));
|
|
@@ -244,22 +244,22 @@ class AlgoUI {
|
|
|
244
244
|
|
|
245
245
|
// Row 2: Symbol (centered, single row)
|
|
246
246
|
const symbol = (stats.symbol || stats.leadSymbol || 'N/A').substring(0, 60);
|
|
247
|
-
const symbolText = `
|
|
247
|
+
const symbolText = `SYMBOL: ${symbol}`;
|
|
248
248
|
const symbolPadded = center(symbolText, W);
|
|
249
249
|
this._line(chalk.cyan(BOX.V) + chalk.yellow(symbolPadded) + chalk.cyan(BOX.V));
|
|
250
250
|
|
|
251
251
|
this._line(chalk.cyan(GT));
|
|
252
252
|
|
|
253
253
|
// Row 3: Lead Qty | Follower Qty
|
|
254
|
-
const r3c1 = buildCell('
|
|
255
|
-
const r3c2 = buildCell('
|
|
254
|
+
const r3c1 = buildCell('QTY', (stats.leadQty || '1').toString(), chalk.cyan, colL);
|
|
255
|
+
const r3c2 = buildCell('QTY', (stats.followerQty || '1').toString(), chalk.cyan, colR);
|
|
256
256
|
row(r3c1.padded, r3c2.padded);
|
|
257
257
|
|
|
258
258
|
this._line(chalk.cyan(GM));
|
|
259
259
|
|
|
260
260
|
// Row 4: Target | Risk
|
|
261
|
-
const r4c1 = buildCell('
|
|
262
|
-
const r4c2 = buildCell('
|
|
261
|
+
const r4c1 = buildCell('TARGET', '$' + (stats.target || 0).toFixed(2), chalk.green, colL);
|
|
262
|
+
const r4c2 = buildCell('RISK', '$' + (stats.risk || 0).toFixed(2), chalk.red, colR);
|
|
263
263
|
row(r4c1.padded, r4c2.padded);
|
|
264
264
|
|
|
265
265
|
this._line(chalk.cyan(GM));
|
|
@@ -443,7 +443,7 @@ const renderSessionSummary = (stats, stopReason) => {
|
|
|
443
443
|
const pnlStr = `${pnl >= 0 ? '+' : ''}$${Math.abs(pnl).toFixed(2)}`;
|
|
444
444
|
const pnlColor = pnl >= 0 ? chalk.green : chalk.red;
|
|
445
445
|
const targetStr = `$${(stats.target || 0).toFixed(2)}`;
|
|
446
|
-
row('P&L', pnlStr, pnlColor, '
|
|
446
|
+
row('P&L', pnlStr, pnlColor, 'TARGET', targetStr, chalk.cyan);
|
|
447
447
|
|
|
448
448
|
// Bottom border
|
|
449
449
|
console.log(chalk.cyan(BOX.BOT + BOX.H.repeat(W) + BOX.BR));
|
package/src/pages/orders.js
CHANGED
|
@@ -19,14 +19,14 @@ const showOrders = async (service) => {
|
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
21
|
// Step 1: Get connections
|
|
22
|
-
spinner = ora({ text: '
|
|
22
|
+
spinner = ora({ text: 'LOADING CONNECTIONS...', color: 'yellow' }).start();
|
|
23
23
|
|
|
24
24
|
const allConns = connections.count() > 0
|
|
25
25
|
? connections.getAll()
|
|
26
26
|
: (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
|
|
27
27
|
|
|
28
28
|
if (allConns.length === 0) {
|
|
29
|
-
spinner.fail('
|
|
29
|
+
spinner.fail('NO CONNECTIONS FOUND');
|
|
30
30
|
await prompts.waitForEnter();
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
@@ -90,7 +90,7 @@ const showOrders = async (service) => {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
spinner = ora({ text: '
|
|
93
|
+
spinner = ora({ text: 'PREPARING DISPLAY...', color: 'yellow' }).start();
|
|
94
94
|
spinner.succeed(`Total: ${allOrders.length} order(s)`);
|
|
95
95
|
console.log();
|
|
96
96
|
|
package/src/pages/positions.js
CHANGED
|
@@ -19,14 +19,14 @@ const showPositions = async (service) => {
|
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
21
|
// Step 1: Get connections
|
|
22
|
-
spinner = ora({ text: '
|
|
22
|
+
spinner = ora({ text: 'LOADING CONNECTIONS...', color: 'yellow' }).start();
|
|
23
23
|
|
|
24
24
|
const allConns = connections.count() > 0
|
|
25
25
|
? connections.getAll()
|
|
26
26
|
: (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
|
|
27
27
|
|
|
28
28
|
if (allConns.length === 0) {
|
|
29
|
-
spinner.fail('
|
|
29
|
+
spinner.fail('NO CONNECTIONS FOUND');
|
|
30
30
|
await prompts.waitForEnter();
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
@@ -90,7 +90,7 @@ const showPositions = async (service) => {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
spinner = ora({ text: '
|
|
93
|
+
spinner = ora({ text: 'PREPARING DISPLAY...', color: 'yellow' }).start();
|
|
94
94
|
spinner.succeed(`Total: ${allPositions.length} position(s)`);
|
|
95
95
|
console.log();
|
|
96
96
|
|
package/src/pages/stats.js
CHANGED
|
@@ -23,7 +23,7 @@ const showStats = async (service) => {
|
|
|
23
23
|
let spinner;
|
|
24
24
|
|
|
25
25
|
try {
|
|
26
|
-
spinner = ora({ text: '
|
|
26
|
+
spinner = ora({ text: 'LOADING STATS...', color: 'yellow' }).start();
|
|
27
27
|
|
|
28
28
|
// Get all connections
|
|
29
29
|
const allConns = connections.count() > 0
|
|
@@ -31,7 +31,7 @@ const showStats = async (service) => {
|
|
|
31
31
|
: (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
|
|
32
32
|
|
|
33
33
|
if (allConns.length === 0) {
|
|
34
|
-
spinner.fail('
|
|
34
|
+
spinner.fail('NO CONNECTIONS FOUND');
|
|
35
35
|
await prompts.waitForEnter();
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
@@ -73,7 +73,7 @@ const showStats = async (service) => {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
if (allAccountsData.length === 0) {
|
|
76
|
-
spinner.fail('
|
|
76
|
+
spinner.fail('NO ACCOUNTS FOUND');
|
|
77
77
|
await prompts.waitForEnter();
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
@@ -91,7 +91,7 @@ const showStats = async (service) => {
|
|
|
91
91
|
const activeAccounts = allAccountsData.filter(acc => acc.status === 0);
|
|
92
92
|
|
|
93
93
|
if (activeAccounts.length === 0) {
|
|
94
|
-
spinner.fail('
|
|
94
|
+
spinner.fail('NO ACTIVE ACCOUNTS FOUND');
|
|
95
95
|
await prompts.waitForEnter();
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
@@ -255,7 +255,7 @@ const showStats = async (service) => {
|
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
spinner.succeed('
|
|
258
|
+
spinner.succeed('STATS LOADED');
|
|
259
259
|
console.log();
|
|
260
260
|
|
|
261
261
|
// ========== DISPLAY ==========
|
|
@@ -337,13 +337,13 @@ const showStats = async (service) => {
|
|
|
337
337
|
: 'N/A';
|
|
338
338
|
const startBalStr = totalStartingBalance > 0 ? '$' + totalStartingBalance.toLocaleString(undefined, {minimumFractionDigits: 2}) : 'N/A';
|
|
339
339
|
|
|
340
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
341
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
342
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
343
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
344
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
345
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
346
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
340
|
+
console.log(chalk.cyan('\u2551') + fmtRow('CONNECTIONS:', chalk.cyan(connTypeStr.join(', ') || String(connections.count() || 1)), col1) + chalk.cyan('\u2502') + fmtRow('TOTAL TRADES:', hasTradeData || stats.totalTrades > 0 ? chalk.white(String(stats.totalTrades)) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
341
|
+
console.log(chalk.cyan('\u2551') + fmtRow('TOTAL ACCOUNTS:', chalk.cyan(String(activeAccounts.length)), col1) + chalk.cyan('\u2502') + fmtRow('WINNING TRADES:', hasTradeData || stats.winningTrades > 0 ? chalk.green(String(stats.winningTrades)) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
342
|
+
console.log(chalk.cyan('\u2551') + fmtRow('TOTAL BALANCE:', totalBalanceColor(balanceStr), col1) + chalk.cyan('\u2502') + fmtRow('LOSING TRADES:', hasTradeData || stats.losingTrades > 0 ? chalk.red(String(stats.losingTrades)) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
343
|
+
console.log(chalk.cyan('\u2551') + fmtRow('STARTING BALANCE:', chalk.white(startBalStr), col1) + chalk.cyan('\u2502') + fmtRow('WIN RATE:', winRate !== 'N/A' ? (parseFloat(winRate) >= 50 ? chalk.green(winRate + '%') : chalk.yellow(winRate + '%')) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
344
|
+
console.log(chalk.cyan('\u2551') + fmtRow('TOTAL P&L:', pnlColor(pnlStr), col1) + chalk.cyan('\u2502') + fmtRow('LONG TRADES:', hasTradeData ? chalk.white(stats.longTrades + (longWinRate !== 'N/A' ? ' (' + longWinRate + '%)' : '')) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
345
|
+
console.log(chalk.cyan('\u2551') + fmtRow('OPEN POSITIONS:', chalk.white(String(totalOpenPositions)), col1) + chalk.cyan('\u2502') + fmtRow('SHORT TRADES:', hasTradeData ? chalk.white(stats.shortTrades + (shortWinRate !== 'N/A' ? ' (' + shortWinRate + '%)' : '')) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
346
|
+
console.log(chalk.cyan('\u2551') + fmtRow('OPEN ORDERS:', chalk.white(String(totalOpenOrders)), col1) + chalk.cyan('\u2502') + fmtRow('VOLUME:', hasTradeData ? chalk.white(stats.totalVolume + ' contracts') : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
347
347
|
|
|
348
348
|
// ========== P&L METRICS ==========
|
|
349
349
|
draw2ColSeparator(boxWidth);
|
|
@@ -361,11 +361,11 @@ const showStats = async (service) => {
|
|
|
361
361
|
|
|
362
362
|
const netPnLStr = hasTradeData ? (netPnL >= 0 ? chalk.green('$' + netPnL.toFixed(2)) : chalk.red('-$' + Math.abs(netPnL).toFixed(2))) : chalk.gray('N/A');
|
|
363
363
|
|
|
364
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
365
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
366
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
367
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
368
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
364
|
+
console.log(chalk.cyan('\u2551') + fmtRow('NET P&L:', netPnLStr, col1) + chalk.cyan('\u2502') + fmtRow('PROFIT FACTOR:', pfColor, col2) + chalk.cyan('\u2551'));
|
|
365
|
+
console.log(chalk.cyan('\u2551') + fmtRow('GROSS PROFIT:', hasTradeData ? chalk.green('$' + stats.totalWinAmount.toFixed(2)) : chalk.gray('N/A'), col1) + chalk.cyan('\u2502') + fmtRow('MAX CONSEC. WINS:', hasTradeData ? chalk.green(String(stats.maxConsecutiveWins)) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
366
|
+
console.log(chalk.cyan('\u2551') + fmtRow('GROSS LOSS:', hasTradeData ? chalk.red('-$' + stats.totalLossAmount.toFixed(2)) : chalk.gray('N/A'), col1) + chalk.cyan('\u2502') + fmtRow('MAX CONSEC. LOSS:', hasTradeData ? (stats.maxConsecutiveLosses > 0 ? chalk.red(String(stats.maxConsecutiveLosses)) : chalk.green('0')) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
367
|
+
console.log(chalk.cyan('\u2551') + fmtRow('AVG WIN:', hasTradeData ? chalk.green('$' + avgWin) : chalk.gray('N/A'), col1) + chalk.cyan('\u2502') + fmtRow('BEST TRADE:', hasTradeData ? chalk.green('$' + stats.bestTrade.toFixed(2)) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
368
|
+
console.log(chalk.cyan('\u2551') + fmtRow('AVG LOSS:', hasTradeData ? (stats.losingTrades > 0 ? chalk.red('-$' + avgLoss) : chalk.green('$0.00')) : chalk.gray('N/A'), col1) + chalk.cyan('\u2502') + fmtRow('WORST TRADE:', hasTradeData ? (stats.worstTrade < 0 ? chalk.red(worstTradeStr) : chalk.green(worstTradeStr)) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
369
369
|
|
|
370
370
|
// ========== QUANTITATIVE METRICS ==========
|
|
371
371
|
draw2ColSeparator(boxWidth);
|
|
@@ -376,10 +376,10 @@ const showStats = async (service) => {
|
|
|
376
376
|
const ddColor = maxDrawdown === 0 ? chalk.gray : maxDrawdown <= 5 ? chalk.green : maxDrawdown <= 15 ? chalk.yellow : chalk.red;
|
|
377
377
|
const rrColor = riskRewardRatio === 'N/A' ? chalk.gray : parseFloat(riskRewardRatio) >= 2 ? chalk.green : parseFloat(riskRewardRatio) >= 1 ? chalk.yellow : chalk.red;
|
|
378
378
|
|
|
379
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
380
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
381
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
382
|
-
console.log(chalk.cyan('\u2551') + fmtRow('
|
|
379
|
+
console.log(chalk.cyan('\u2551') + fmtRow('SHARPE RATIO:', sharpeColor(sharpeRatio), col1) + chalk.cyan('\u2502') + fmtRow('RISK/REWARD:', rrColor(riskRewardRatio), col2) + chalk.cyan('\u2551'));
|
|
380
|
+
console.log(chalk.cyan('\u2551') + fmtRow('SORTINO RATIO:', sortinoColor(sortinoRatio), col1) + chalk.cyan('\u2502') + fmtRow('CALMAR RATIO:', calmarRatio === 'N/A' ? chalk.gray(calmarRatio) : chalk.white(calmarRatio), col2) + chalk.cyan('\u2551'));
|
|
381
|
+
console.log(chalk.cyan('\u2551') + fmtRow('MAX DRAWDOWN:', hasTradeData && maxDrawdown > 0 ? ddColor(maxDrawdown.toFixed(2) + '%') : chalk.gray('N/A'), col1) + chalk.cyan('\u2502') + fmtRow('EXPECTANCY:', hasTradeData ? (expectancy >= 0 ? chalk.green('$' + expectancy.toFixed(2)) : chalk.red('$' + expectancy.toFixed(2))) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
382
|
+
console.log(chalk.cyan('\u2551') + fmtRow('STD DEVIATION:', hasTradeData ? chalk.white('$' + stdDev.toFixed(2)) : chalk.gray('N/A'), col1) + chalk.cyan('\u2502') + fmtRow('AVG TRADE:', hasTradeData ? (avgReturn >= 0 ? chalk.green('$' + avgReturn.toFixed(2)) : chalk.red('$' + avgReturn.toFixed(2))) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
383
383
|
|
|
384
384
|
drawBoxFooter(boxWidth);
|
|
385
385
|
|
package/src/pages/user.js
CHANGED
|
@@ -26,7 +26,7 @@ const showUserInfo = async (service) => {
|
|
|
26
26
|
|
|
27
27
|
try {
|
|
28
28
|
// Step 1: Get user info
|
|
29
|
-
spinner = ora({ text: '
|
|
29
|
+
spinner = ora({ text: 'LOADING USER INFO...', color: 'yellow' }).start();
|
|
30
30
|
|
|
31
31
|
let userInfo = null;
|
|
32
32
|
|
|
@@ -39,10 +39,10 @@ const showUserInfo = async (service) => {
|
|
|
39
39
|
} catch (e) {}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
spinner.succeed('
|
|
42
|
+
spinner.succeed('USER INFO LOADED');
|
|
43
43
|
|
|
44
44
|
// Step 2: Get account count
|
|
45
|
-
spinner = ora({ text: '
|
|
45
|
+
spinner = ora({ text: 'COUNTING ACCOUNTS...', color: 'yellow' }).start();
|
|
46
46
|
|
|
47
47
|
let accountCount = 0;
|
|
48
48
|
|
|
@@ -71,10 +71,10 @@ const showUserInfo = async (service) => {
|
|
|
71
71
|
|
|
72
72
|
const username = userInfo.userName || userInfo.username || 'Unknown';
|
|
73
73
|
const connCount = connections.count() || 1;
|
|
74
|
-
console.log(chalk.cyan('║') + fmtRow('
|
|
74
|
+
console.log(chalk.cyan('║') + fmtRow('USERNAME:', chalk.cyan(username.toUpperCase()), col1) + chalk.cyan('│') + fmtRow('CONNECTIONS:', chalk.cyan(String(connCount)), col2) + chalk.cyan('║'));
|
|
75
75
|
|
|
76
76
|
const email = userInfo.email || 'N/A';
|
|
77
|
-
console.log(chalk.cyan('║') + fmtRow('
|
|
77
|
+
console.log(chalk.cyan('║') + fmtRow('EMAIL:', chalk.white(email), col1) + chalk.cyan('│') + fmtRow('ACCOUNTS:', chalk.cyan(String(accountCount)), col2) + chalk.cyan('║'));
|
|
78
78
|
|
|
79
79
|
const userId = userInfo.userId || userInfo.id || 'N/A';
|
|
80
80
|
const platform = service?.propfirm?.name || 'ProjectX';
|
package/src/utils/prompts.js
CHANGED
|
@@ -80,10 +80,10 @@ const nativePrompt = (message) => {
|
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
82
|
* Wait for Enter key
|
|
83
|
-
* @param {string} [message='
|
|
83
|
+
* @param {string} [message='PRESS ENTER TO CONTINUE...'] - Message to display
|
|
84
84
|
* @returns {Promise<void>}
|
|
85
85
|
*/
|
|
86
|
-
const waitForEnter = async (message = '
|
|
86
|
+
const waitForEnter = async (message = 'PRESS ENTER TO CONTINUE...') => {
|
|
87
87
|
await nativePrompt(message);
|
|
88
88
|
};
|
|
89
89
|
|