hedgequantx 1.7.6 → 1.7.7
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 +2 -2
- package/src/app.js +38 -159
- package/src/menus/connect.js +54 -166
- package/src/menus/dashboard.js +43 -118
- 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 +87 -0
|
@@ -1,30 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* One Account Mode
|
|
3
|
-
* Lightweight - UI + HQX Server connection only
|
|
2
|
+
* One Account Mode
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
const chalk = require('chalk');
|
|
7
6
|
const ora = require('ora');
|
|
8
|
-
const inquirer = require('inquirer');
|
|
9
7
|
const readline = require('readline');
|
|
10
8
|
|
|
11
9
|
const { connections } = require('../../services');
|
|
12
10
|
const { HQXServerService } = require('../../services/hqx-server');
|
|
13
|
-
const {
|
|
14
|
-
const {
|
|
11
|
+
const { AlgoUI } = require('./ui');
|
|
12
|
+
const { prompts } = require('../../utils');
|
|
15
13
|
|
|
16
14
|
/**
|
|
17
|
-
* One Account Menu
|
|
15
|
+
* One Account Menu
|
|
18
16
|
*/
|
|
19
17
|
const oneAccountMenu = async (service) => {
|
|
20
18
|
const spinner = ora({ text: 'Fetching active accounts...', color: 'yellow' }).start();
|
|
21
19
|
|
|
22
|
-
// Get ALL accounts from ALL connections
|
|
23
20
|
const allAccounts = await connections.getAllAccounts();
|
|
24
21
|
|
|
25
22
|
if (!allAccounts?.length) {
|
|
26
23
|
spinner.fail('No accounts found');
|
|
27
|
-
await
|
|
24
|
+
await prompts.waitForEnter();
|
|
28
25
|
return;
|
|
29
26
|
}
|
|
30
27
|
|
|
@@ -32,32 +29,22 @@ const oneAccountMenu = async (service) => {
|
|
|
32
29
|
|
|
33
30
|
if (!activeAccounts.length) {
|
|
34
31
|
spinner.fail('No active accounts');
|
|
35
|
-
await
|
|
32
|
+
await prompts.waitForEnter();
|
|
36
33
|
return;
|
|
37
34
|
}
|
|
38
35
|
|
|
39
36
|
spinner.succeed(`Found ${activeAccounts.length} active account(s)`);
|
|
40
37
|
|
|
41
|
-
// Select account
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
value: acc
|
|
52
|
-
})),
|
|
53
|
-
new inquirer.Separator(),
|
|
54
|
-
{ name: chalk.yellow('< Back'), value: 'back' }
|
|
55
|
-
]
|
|
56
|
-
}]);
|
|
57
|
-
|
|
58
|
-
if (selectedAccount === 'back') return;
|
|
59
|
-
|
|
60
|
-
// Find the service for this account
|
|
38
|
+
// Select account
|
|
39
|
+
const options = activeAccounts.map(acc => ({
|
|
40
|
+
label: `${acc.accountName || acc.accountId} (${acc.propfirm || 'Unknown'}) - $${(acc.balance || 0).toLocaleString()}`,
|
|
41
|
+
value: acc
|
|
42
|
+
}));
|
|
43
|
+
options.push({ label: '< Back', value: 'back' });
|
|
44
|
+
|
|
45
|
+
const selectedAccount = await prompts.selectOption('Select Account:', options);
|
|
46
|
+
if (!selectedAccount || selectedAccount === 'back') return;
|
|
47
|
+
|
|
61
48
|
const accountService = connections.getServiceForAccount(selectedAccount.accountId) || service;
|
|
62
49
|
|
|
63
50
|
// Select symbol
|
|
@@ -68,7 +55,6 @@ const oneAccountMenu = async (service) => {
|
|
|
68
55
|
const config = await configureAlgo(selectedAccount, contract);
|
|
69
56
|
if (!config) return;
|
|
70
57
|
|
|
71
|
-
// Launch with the correct service for this account
|
|
72
58
|
await launchAlgo(accountService, selectedAccount, contract, config);
|
|
73
59
|
};
|
|
74
60
|
|
|
@@ -94,74 +80,40 @@ const selectSymbol = async (service, account) => {
|
|
|
94
80
|
categories[cat].push(c);
|
|
95
81
|
}
|
|
96
82
|
|
|
97
|
-
//
|
|
98
|
-
const
|
|
83
|
+
// Flatten with categories as hints
|
|
84
|
+
const options = [];
|
|
99
85
|
for (const [cat, contracts] of Object.entries(categories)) {
|
|
100
|
-
choices.push(new inquirer.Separator(chalk.gray(`--- ${cat} ---`)));
|
|
101
86
|
for (const c of contracts.slice(0, 10)) {
|
|
102
|
-
|
|
87
|
+
options.push({ label: `[${cat}] ${c.name || c.symbol}`, value: c });
|
|
103
88
|
}
|
|
104
89
|
}
|
|
105
|
-
|
|
106
|
-
choices.push({ name: chalk.yellow('< Back'), value: 'back' });
|
|
107
|
-
|
|
108
|
-
const { contract } = await inquirer.prompt([{
|
|
109
|
-
type: 'list',
|
|
110
|
-
name: 'contract',
|
|
111
|
-
message: 'Select Symbol:',
|
|
112
|
-
choices,
|
|
113
|
-
pageSize: 20
|
|
114
|
-
}]);
|
|
90
|
+
options.push({ label: '< Back', value: 'back' });
|
|
115
91
|
|
|
92
|
+
const contract = await prompts.selectOption('Select Symbol:', options);
|
|
116
93
|
return contract === 'back' ? null : contract;
|
|
117
94
|
};
|
|
118
95
|
|
|
119
96
|
/**
|
|
120
|
-
* Configure algo
|
|
97
|
+
* Configure algo
|
|
121
98
|
*/
|
|
122
99
|
const configureAlgo = async (account, contract) => {
|
|
123
100
|
console.log();
|
|
124
101
|
console.log(chalk.cyan(' Configure Algo Parameters'));
|
|
125
102
|
console.log();
|
|
126
103
|
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
validate: v => v > 0 && v <= 10 ? true : 'Enter 1-10'
|
|
133
|
-
}]);
|
|
134
|
-
|
|
135
|
-
const { dailyTarget } = await inquirer.prompt([{
|
|
136
|
-
type: 'number',
|
|
137
|
-
name: 'dailyTarget',
|
|
138
|
-
message: 'Daily target ($):',
|
|
139
|
-
default: 200,
|
|
140
|
-
validate: v => v > 0 ? true : 'Must be positive'
|
|
141
|
-
}]);
|
|
142
|
-
|
|
143
|
-
const { maxRisk } = await inquirer.prompt([{
|
|
144
|
-
type: 'number',
|
|
145
|
-
name: 'maxRisk',
|
|
146
|
-
message: 'Max risk ($):',
|
|
147
|
-
default: 100,
|
|
148
|
-
validate: v => v > 0 ? true : 'Must be positive'
|
|
149
|
-
}]);
|
|
150
|
-
|
|
151
|
-
const { showName } = await inquirer.prompt([{
|
|
152
|
-
type: 'confirm',
|
|
153
|
-
name: 'showName',
|
|
154
|
-
message: 'Show account name?',
|
|
155
|
-
default: true
|
|
156
|
-
}]);
|
|
157
|
-
|
|
158
|
-
const { confirm } = await inquirer.prompt([{
|
|
159
|
-
type: 'confirm',
|
|
160
|
-
name: 'confirm',
|
|
161
|
-
message: chalk.yellow('Start algo trading?'),
|
|
162
|
-
default: true
|
|
163
|
-
}]);
|
|
104
|
+
const contracts = await prompts.numberInput('Number of contracts:', 1, 1, 10);
|
|
105
|
+
if (contracts === null) return null;
|
|
106
|
+
|
|
107
|
+
const dailyTarget = await prompts.numberInput('Daily target ($):', 200, 1, 10000);
|
|
108
|
+
if (dailyTarget === null) return null;
|
|
164
109
|
|
|
110
|
+
const maxRisk = await prompts.numberInput('Max risk ($):', 100, 1, 5000);
|
|
111
|
+
if (maxRisk === null) return null;
|
|
112
|
+
|
|
113
|
+
const showName = await prompts.confirmPrompt('Show account name?', true);
|
|
114
|
+
if (showName === null) return null;
|
|
115
|
+
|
|
116
|
+
const confirm = await prompts.confirmPrompt('Start algo trading?', true);
|
|
165
117
|
if (!confirm) return null;
|
|
166
118
|
|
|
167
119
|
return { contracts, dailyTarget, maxRisk, showName };
|
|
@@ -175,30 +127,19 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
175
127
|
const accountName = showName ? (account.accountName || account.accountId) : 'HQX *****';
|
|
176
128
|
const symbolName = contract.name || contract.symbol;
|
|
177
129
|
|
|
178
|
-
// Initialize UI
|
|
179
130
|
const ui = new AlgoUI({ subtitle: 'HQX Ultra-Scalping' });
|
|
180
131
|
|
|
181
|
-
// Stats state
|
|
182
132
|
const stats = {
|
|
183
|
-
accountName,
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
risk: maxRisk,
|
|
188
|
-
pnl: 0,
|
|
189
|
-
trades: 0,
|
|
190
|
-
wins: 0,
|
|
191
|
-
losses: 0,
|
|
192
|
-
latency: 0,
|
|
193
|
-
connected: false
|
|
133
|
+
accountName, symbol: symbolName, contracts,
|
|
134
|
+
target: dailyTarget, risk: maxRisk,
|
|
135
|
+
pnl: 0, trades: 0, wins: 0, losses: 0,
|
|
136
|
+
latency: 0, connected: false
|
|
194
137
|
};
|
|
195
138
|
|
|
196
139
|
let running = true;
|
|
197
140
|
let stopReason = null;
|
|
198
141
|
|
|
199
|
-
// Connect to HQX Server
|
|
200
142
|
const hqx = new HQXServerService();
|
|
201
|
-
|
|
202
143
|
const spinner = ora({ text: 'Connecting to HQX Server...', color: 'yellow' }).start();
|
|
203
144
|
|
|
204
145
|
try {
|
|
@@ -211,106 +152,72 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
211
152
|
|
|
212
153
|
spinner.succeed('Connected to HQX Server');
|
|
213
154
|
stats.connected = true;
|
|
214
|
-
|
|
215
155
|
} catch (err) {
|
|
216
156
|
spinner.warn('HQX Server unavailable - offline mode');
|
|
217
|
-
stats.connected = false;
|
|
218
157
|
}
|
|
219
158
|
|
|
220
159
|
// Event handlers
|
|
221
|
-
hqx.on('latency', (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
msg = msg.replace(new RegExp(account.accountName, 'gi'), 'HQX *****');
|
|
227
|
-
}
|
|
228
|
-
ui.addLog(data.type || 'info', msg);
|
|
160
|
+
hqx.on('latency', (d) => { stats.latency = d.latency || 0; });
|
|
161
|
+
hqx.on('log', (d) => {
|
|
162
|
+
let msg = d.message;
|
|
163
|
+
if (!showName && account.accountName) msg = msg.replace(new RegExp(account.accountName, 'gi'), 'HQX *****');
|
|
164
|
+
ui.addLog(d.type || 'info', msg);
|
|
229
165
|
});
|
|
230
|
-
|
|
231
|
-
hqx.on('signal', (data) => {
|
|
166
|
+
hqx.on('signal', (d) => {
|
|
232
167
|
stats.signals = (stats.signals || 0) + 1;
|
|
233
|
-
|
|
234
|
-
ui.addLog('signal', `${side} @ ${data.entry?.toFixed(2) || 'MKT'}`);
|
|
168
|
+
ui.addLog('signal', `${d.side === 'long' ? 'BUY' : 'SELL'} @ ${d.entry?.toFixed(2) || 'MKT'}`);
|
|
235
169
|
});
|
|
236
|
-
|
|
237
|
-
hqx.on('trade', (data) => {
|
|
170
|
+
hqx.on('trade', (d) => {
|
|
238
171
|
stats.trades++;
|
|
239
|
-
stats.pnl +=
|
|
240
|
-
if (
|
|
241
|
-
|
|
242
|
-
ui.addLog('trade', `+$${data.pnl.toFixed(2)}`);
|
|
243
|
-
} else {
|
|
244
|
-
stats.losses++;
|
|
245
|
-
ui.addLog('loss', `-$${Math.abs(data.pnl).toFixed(2)}`);
|
|
246
|
-
}
|
|
172
|
+
stats.pnl += d.pnl || 0;
|
|
173
|
+
if (d.pnl >= 0) { stats.wins++; ui.addLog('trade', `+$${d.pnl.toFixed(2)}`); }
|
|
174
|
+
else { stats.losses++; ui.addLog('loss', `-$${Math.abs(d.pnl).toFixed(2)}`); }
|
|
247
175
|
|
|
248
|
-
// Check targets
|
|
249
176
|
if (stats.pnl >= dailyTarget) {
|
|
250
|
-
stopReason = 'target';
|
|
251
|
-
|
|
252
|
-
ui.addLog('success', `TARGET REACHED! +$${stats.pnl.toFixed(2)}`);
|
|
177
|
+
stopReason = 'target'; running = false;
|
|
178
|
+
ui.addLog('success', `TARGET! +$${stats.pnl.toFixed(2)}`);
|
|
253
179
|
hqx.stopAlgo();
|
|
254
180
|
} else if (stats.pnl <= -maxRisk) {
|
|
255
|
-
stopReason = 'risk';
|
|
256
|
-
running = false;
|
|
181
|
+
stopReason = 'risk'; running = false;
|
|
257
182
|
ui.addLog('error', `MAX RISK! -$${Math.abs(stats.pnl).toFixed(2)}`);
|
|
258
183
|
hqx.stopAlgo();
|
|
259
184
|
}
|
|
260
185
|
});
|
|
186
|
+
hqx.on('error', (d) => { ui.addLog('error', d.message || 'Error'); });
|
|
187
|
+
hqx.on('disconnected', () => { stats.connected = false; ui.addLog('warning', 'Disconnected'); });
|
|
261
188
|
|
|
262
|
-
|
|
263
|
-
ui.addLog('error', data.message || 'Error');
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
hqx.on('disconnected', () => {
|
|
267
|
-
stats.connected = false;
|
|
268
|
-
ui.addLog('warning', 'Server disconnected');
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// Start algo on server
|
|
189
|
+
// Start on server
|
|
272
190
|
if (stats.connected) {
|
|
273
191
|
ui.addLog('info', 'Starting algo...');
|
|
274
192
|
|
|
275
|
-
// Get credentials if Rithmic
|
|
276
193
|
let rithmicCreds = null;
|
|
277
|
-
if (service.getRithmicCredentials)
|
|
278
|
-
rithmicCreds = service.getRithmicCredentials();
|
|
279
|
-
}
|
|
194
|
+
if (service.getRithmicCredentials) rithmicCreds = service.getRithmicCredentials();
|
|
280
195
|
|
|
281
196
|
hqx.startAlgo({
|
|
282
197
|
accountId: account.accountId,
|
|
283
198
|
contractId: contract.id || contract.contractId,
|
|
284
199
|
symbol: contract.symbol || contract.name,
|
|
285
|
-
contracts,
|
|
286
|
-
dailyTarget,
|
|
287
|
-
maxRisk,
|
|
200
|
+
contracts, dailyTarget, maxRisk,
|
|
288
201
|
propfirm: account.propfirm || 'topstep',
|
|
289
|
-
propfirmToken: service.getToken
|
|
202
|
+
propfirmToken: service.getToken?.() || null,
|
|
290
203
|
rithmicCredentials: rithmicCreds
|
|
291
204
|
});
|
|
292
205
|
}
|
|
293
206
|
|
|
294
|
-
|
|
295
|
-
const refreshInterval = setInterval(() => {
|
|
296
|
-
if (running) ui.render(stats);
|
|
297
|
-
}, 250);
|
|
207
|
+
const refreshInterval = setInterval(() => { if (running) ui.render(stats); }, 250);
|
|
298
208
|
|
|
299
|
-
// Keyboard
|
|
209
|
+
// Keyboard
|
|
300
210
|
const setupKeyHandler = () => {
|
|
301
211
|
if (!process.stdin.isTTY) return;
|
|
302
|
-
|
|
303
212
|
readline.emitKeypressEvents(process.stdin);
|
|
304
213
|
process.stdin.setRawMode(true);
|
|
305
214
|
process.stdin.resume();
|
|
306
215
|
|
|
307
216
|
const onKey = (str, key) => {
|
|
308
217
|
if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
|
|
309
|
-
running = false;
|
|
310
|
-
stopReason = 'manual';
|
|
218
|
+
running = false; stopReason = 'manual';
|
|
311
219
|
}
|
|
312
220
|
};
|
|
313
|
-
|
|
314
221
|
process.stdin.on('keypress', onKey);
|
|
315
222
|
return () => {
|
|
316
223
|
process.stdin.removeListener('keypress', onKey);
|
|
@@ -320,39 +227,26 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
320
227
|
|
|
321
228
|
const cleanupKeys = setupKeyHandler();
|
|
322
229
|
|
|
323
|
-
// Wait for stop
|
|
324
230
|
await new Promise(resolve => {
|
|
325
|
-
const check = setInterval(() => {
|
|
326
|
-
if (!running) {
|
|
327
|
-
clearInterval(check);
|
|
328
|
-
resolve();
|
|
329
|
-
}
|
|
330
|
-
}, 100);
|
|
231
|
+
const check = setInterval(() => { if (!running) { clearInterval(check); resolve(); } }, 100);
|
|
331
232
|
});
|
|
332
233
|
|
|
333
|
-
// Cleanup
|
|
334
234
|
clearInterval(refreshInterval);
|
|
335
235
|
if (cleanupKeys) cleanupKeys();
|
|
336
|
-
|
|
337
|
-
if (stats.connected) {
|
|
338
|
-
hqx.stopAlgo();
|
|
339
|
-
hqx.disconnect();
|
|
340
|
-
}
|
|
341
|
-
|
|
236
|
+
if (stats.connected) { hqx.stopAlgo(); hqx.disconnect(); }
|
|
342
237
|
ui.cleanup();
|
|
343
238
|
|
|
344
|
-
//
|
|
239
|
+
// Summary
|
|
345
240
|
console.clear();
|
|
346
241
|
console.log();
|
|
347
242
|
console.log(chalk.cyan(' === Session Summary ==='));
|
|
348
243
|
console.log();
|
|
349
244
|
console.log(chalk.white(` Stop Reason: ${stopReason || 'unknown'}`));
|
|
350
245
|
console.log(chalk.white(` Trades: ${stats.trades} (W: ${stats.wins} / L: ${stats.losses})`));
|
|
351
|
-
|
|
352
|
-
console.log(pnlColor(` P&L: ${stats.pnl >= 0 ? '+' : ''}$${stats.pnl.toFixed(2)}`));
|
|
246
|
+
console.log((stats.pnl >= 0 ? chalk.green : chalk.red)(` P&L: ${stats.pnl >= 0 ? '+' : ''}$${stats.pnl.toFixed(2)}`));
|
|
353
247
|
console.log();
|
|
354
248
|
|
|
355
|
-
await
|
|
249
|
+
await prompts.waitForEnter();
|
|
356
250
|
};
|
|
357
251
|
|
|
358
252
|
module.exports = { oneAccountMenu };
|
package/src/pages/orders.js
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @module pages/orders
|
|
2
|
+
* Orders 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 { ORDER_STATUS, ORDER_TYPE, ORDER_SIDE } = require('../config');
|
|
12
|
-
const { getLogoWidth, drawBoxHeader, drawBoxFooter, drawBoxRow, drawBoxSeparator
|
|
10
|
+
const { getLogoWidth, drawBoxHeader, drawBoxFooter, drawBoxRow, drawBoxSeparator } = require('../ui');
|
|
11
|
+
const { prompts } = require('../utils');
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
|
-
*
|
|
16
|
-
* @param {Object} service - Current service
|
|
14
|
+
* Show all orders
|
|
17
15
|
*/
|
|
18
16
|
const showOrders = async (service) => {
|
|
19
17
|
const spinner = ora({ text: 'Fetching orders...', color: 'yellow' }).start();
|
|
20
18
|
const boxWidth = getLogoWidth();
|
|
21
19
|
|
|
22
|
-
// Get all accounts first
|
|
23
20
|
let allAccounts = [];
|
|
24
21
|
|
|
25
22
|
if (connections.count() > 0) {
|
|
@@ -28,14 +25,10 @@ const showOrders = async (service) => {
|
|
|
28
25
|
const result = await conn.service.getTradingAccounts();
|
|
29
26
|
if (result.success && result.accounts) {
|
|
30
27
|
result.accounts.forEach(account => {
|
|
31
|
-
allAccounts.push({
|
|
32
|
-
...account,
|
|
33
|
-
propfirm: conn.propfirm || conn.type,
|
|
34
|
-
service: conn.service
|
|
35
|
-
});
|
|
28
|
+
allAccounts.push({ ...account, propfirm: conn.propfirm || conn.type, service: conn.service });
|
|
36
29
|
});
|
|
37
30
|
}
|
|
38
|
-
} catch (e) {
|
|
31
|
+
} catch (e) {}
|
|
39
32
|
}
|
|
40
33
|
} else if (service) {
|
|
41
34
|
const result = await service.getTradingAccounts();
|
|
@@ -44,22 +37,17 @@ const showOrders = async (service) => {
|
|
|
44
37
|
}
|
|
45
38
|
}
|
|
46
39
|
|
|
47
|
-
// Get orders for each account
|
|
48
40
|
let allOrders = [];
|
|
49
41
|
|
|
50
42
|
for (const account of allAccounts) {
|
|
51
43
|
try {
|
|
52
44
|
const result = await account.service.getOrders(account.accountId);
|
|
53
|
-
if (result.success && result.orders
|
|
45
|
+
if (result.success && result.orders?.length > 0) {
|
|
54
46
|
result.orders.forEach(order => {
|
|
55
|
-
allOrders.push({
|
|
56
|
-
...order,
|
|
57
|
-
accountName: account.accountName || account.name,
|
|
58
|
-
propfirm: account.propfirm
|
|
59
|
-
});
|
|
47
|
+
allOrders.push({ ...order, accountName: account.accountName || account.name, propfirm: account.propfirm });
|
|
60
48
|
});
|
|
61
49
|
}
|
|
62
|
-
} catch (e) {
|
|
50
|
+
} catch (e) {}
|
|
63
51
|
}
|
|
64
52
|
|
|
65
53
|
spinner.succeed(`Found ${allOrders.length} order(s)`);
|
|
@@ -70,19 +58,10 @@ const showOrders = async (service) => {
|
|
|
70
58
|
if (allOrders.length === 0) {
|
|
71
59
|
drawBoxRow(chalk.gray(' No orders found'), boxWidth);
|
|
72
60
|
} else {
|
|
73
|
-
|
|
74
|
-
const header = ' ' +
|
|
75
|
-
'Symbol'.padEnd(12) +
|
|
76
|
-
'Side'.padEnd(6) +
|
|
77
|
-
'Type'.padEnd(8) +
|
|
78
|
-
'Qty'.padEnd(6) +
|
|
79
|
-
'Price'.padEnd(10) +
|
|
80
|
-
'Status'.padEnd(12) +
|
|
81
|
-
'Account';
|
|
61
|
+
const header = ' ' + 'Symbol'.padEnd(12) + 'Side'.padEnd(6) + 'Type'.padEnd(8) + 'Qty'.padEnd(6) + 'Price'.padEnd(10) + 'Status'.padEnd(12) + 'Account';
|
|
82
62
|
drawBoxRow(chalk.white.bold(header), boxWidth);
|
|
83
63
|
drawBoxSeparator(boxWidth);
|
|
84
64
|
|
|
85
|
-
// Order rows
|
|
86
65
|
for (const order of allOrders) {
|
|
87
66
|
const symbol = (order.contractId || order.symbol || 'Unknown').substring(0, 11);
|
|
88
67
|
const sideInfo = ORDER_SIDE[order.side] || { text: '?', color: 'white' };
|
|
@@ -108,7 +87,7 @@ const showOrders = async (service) => {
|
|
|
108
87
|
drawBoxFooter(boxWidth);
|
|
109
88
|
console.log();
|
|
110
89
|
|
|
111
|
-
await
|
|
90
|
+
await prompts.waitForEnter();
|
|
112
91
|
};
|
|
113
92
|
|
|
114
93
|
module.exports = { showOrders };
|
package/src/pages/positions.js
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @module pages/positions
|
|
2
|
+
* Positions 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 { ORDER_SIDE } = require('../config');
|
|
12
|
-
const { getLogoWidth, drawBoxHeader, drawBoxFooter, drawBoxRow, drawBoxSeparator
|
|
10
|
+
const { getLogoWidth, drawBoxHeader, drawBoxFooter, drawBoxRow, drawBoxSeparator } = require('../ui');
|
|
11
|
+
const { prompts } = require('../utils');
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
|
-
*
|
|
16
|
-
* @param {Object} service - Current service
|
|
14
|
+
* Show all open positions
|
|
17
15
|
*/
|
|
18
16
|
const showPositions = async (service) => {
|
|
19
17
|
const spinner = ora({ text: 'Fetching positions...', color: 'yellow' }).start();
|
|
20
18
|
const boxWidth = getLogoWidth();
|
|
21
|
-
const innerWidth = boxWidth - 2;
|
|
22
19
|
|
|
23
|
-
// Get all accounts first
|
|
24
20
|
let allAccounts = [];
|
|
25
21
|
|
|
26
22
|
if (connections.count() > 0) {
|
|
@@ -29,14 +25,10 @@ const showPositions = async (service) => {
|
|
|
29
25
|
const result = await conn.service.getTradingAccounts();
|
|
30
26
|
if (result.success && result.accounts) {
|
|
31
27
|
result.accounts.forEach(account => {
|
|
32
|
-
allAccounts.push({
|
|
33
|
-
...account,
|
|
34
|
-
propfirm: conn.propfirm || conn.type,
|
|
35
|
-
service: conn.service
|
|
36
|
-
});
|
|
28
|
+
allAccounts.push({ ...account, propfirm: conn.propfirm || conn.type, service: conn.service });
|
|
37
29
|
});
|
|
38
30
|
}
|
|
39
|
-
} catch (e) {
|
|
31
|
+
} catch (e) {}
|
|
40
32
|
}
|
|
41
33
|
} else if (service) {
|
|
42
34
|
const result = await service.getTradingAccounts();
|
|
@@ -45,22 +37,17 @@ const showPositions = async (service) => {
|
|
|
45
37
|
}
|
|
46
38
|
}
|
|
47
39
|
|
|
48
|
-
// Get positions for each account
|
|
49
40
|
let allPositions = [];
|
|
50
41
|
|
|
51
42
|
for (const account of allAccounts) {
|
|
52
43
|
try {
|
|
53
44
|
const result = await account.service.getPositions(account.accountId);
|
|
54
|
-
if (result.success && result.positions
|
|
45
|
+
if (result.success && result.positions?.length > 0) {
|
|
55
46
|
result.positions.forEach(pos => {
|
|
56
|
-
allPositions.push({
|
|
57
|
-
...pos,
|
|
58
|
-
accountName: account.accountName || account.name,
|
|
59
|
-
propfirm: account.propfirm
|
|
60
|
-
});
|
|
47
|
+
allPositions.push({ ...pos, accountName: account.accountName || account.name, propfirm: account.propfirm });
|
|
61
48
|
});
|
|
62
49
|
}
|
|
63
|
-
} catch (e) {
|
|
50
|
+
} catch (e) {}
|
|
64
51
|
}
|
|
65
52
|
|
|
66
53
|
spinner.succeed(`Found ${allPositions.length} position(s)`);
|
|
@@ -71,18 +58,10 @@ const showPositions = async (service) => {
|
|
|
71
58
|
if (allPositions.length === 0) {
|
|
72
59
|
drawBoxRow(chalk.gray(' No open positions'), boxWidth);
|
|
73
60
|
} else {
|
|
74
|
-
|
|
75
|
-
const header = ' ' +
|
|
76
|
-
'Symbol'.padEnd(15) +
|
|
77
|
-
'Side'.padEnd(8) +
|
|
78
|
-
'Size'.padEnd(8) +
|
|
79
|
-
'Entry'.padEnd(12) +
|
|
80
|
-
'P&L'.padEnd(12) +
|
|
81
|
-
'Account';
|
|
61
|
+
const header = ' ' + 'Symbol'.padEnd(15) + 'Side'.padEnd(8) + 'Size'.padEnd(8) + 'Entry'.padEnd(12) + 'P&L'.padEnd(12) + 'Account';
|
|
82
62
|
drawBoxRow(chalk.white.bold(header), boxWidth);
|
|
83
63
|
drawBoxSeparator(boxWidth);
|
|
84
64
|
|
|
85
|
-
// Position rows
|
|
86
65
|
for (const pos of allPositions) {
|
|
87
66
|
const symbol = (pos.contractId || pos.symbol || 'Unknown').substring(0, 14);
|
|
88
67
|
const sideInfo = ORDER_SIDE[pos.side] || { text: 'Unknown', color: 'white' };
|
|
@@ -109,7 +88,7 @@ const showPositions = async (service) => {
|
|
|
109
88
|
drawBoxFooter(boxWidth);
|
|
110
89
|
console.log();
|
|
111
90
|
|
|
112
|
-
await
|
|
91
|
+
await prompts.waitForEnter();
|
|
113
92
|
};
|
|
114
93
|
|
|
115
94
|
module.exports = { showPositions };
|
package/src/pages/stats.js
CHANGED
|
@@ -4,21 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
const chalk = require('chalk');
|
|
6
6
|
const ora = require('ora');
|
|
7
|
-
const inquirer = require('inquirer');
|
|
8
7
|
const asciichart = require('asciichart');
|
|
9
8
|
|
|
10
9
|
const { connections } = require('../services');
|
|
11
10
|
const { ACCOUNT_STATUS, ACCOUNT_TYPE } = require('../config');
|
|
12
|
-
const {
|
|
13
|
-
|
|
14
|
-
visibleLength,
|
|
15
|
-
drawBoxHeader,
|
|
16
|
-
drawBoxFooter,
|
|
17
|
-
getColWidths,
|
|
18
|
-
draw2ColHeader,
|
|
19
|
-
draw2ColSeparator,
|
|
20
|
-
fmtRow
|
|
21
|
-
} = require('../ui');
|
|
11
|
+
const { getLogoWidth, visibleLength, drawBoxHeader, drawBoxFooter, getColWidths, draw2ColHeader, draw2ColSeparator, fmtRow } = require('../ui');
|
|
12
|
+
const { prompts } = require('../utils');
|
|
22
13
|
|
|
23
14
|
/**
|
|
24
15
|
* Show Stats Page
|
|
@@ -53,7 +44,7 @@ const showStats = async (service) => {
|
|
|
53
44
|
|
|
54
45
|
if (allAccountsData.length === 0) {
|
|
55
46
|
spinner.fail('No accounts found');
|
|
56
|
-
await
|
|
47
|
+
await prompts.waitForEnter();
|
|
57
48
|
return;
|
|
58
49
|
}
|
|
59
50
|
|
|
@@ -70,7 +61,7 @@ const showStats = async (service) => {
|
|
|
70
61
|
|
|
71
62
|
if (activeAccounts.length === 0) {
|
|
72
63
|
spinner.fail('No active accounts found');
|
|
73
|
-
await
|
|
64
|
+
await prompts.waitForEnter();
|
|
74
65
|
return;
|
|
75
66
|
}
|
|
76
67
|
|
|
@@ -498,7 +489,7 @@ const showStats = async (service) => {
|
|
|
498
489
|
drawBoxFooter(boxWidth);
|
|
499
490
|
console.log();
|
|
500
491
|
|
|
501
|
-
await
|
|
492
|
+
await prompts.waitForEnter();
|
|
502
493
|
};
|
|
503
494
|
|
|
504
495
|
module.exports = { showStats };
|