hedgequantx 1.8.44 → 1.8.46

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "1.8.44",
3
+ "version": "1.8.46",
4
4
  "description": "Prop Futures Algo Trading CLI - Connect to Topstep, Alpha Futures, and other prop firms",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -43,7 +43,7 @@ const dashboardMenu = async (service) => {
43
43
  console.log(makeLine(propfirmText, 'center'));
44
44
  }
45
45
 
46
- // Stats bar
46
+ // Stats bar with yellow icons
47
47
  const statsInfo = getCachedStats();
48
48
  if (statsInfo) {
49
49
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
@@ -60,15 +60,17 @@ const dashboardMenu = async (service) => {
60
60
  pnlDisplay = '--';
61
61
  }
62
62
 
63
- const statsPlain = `Connections: ${statsInfo.connections} Accounts: ${statsInfo.accounts} Balance: ${balStr} P&L: ${pnlDisplay}`;
63
+ // Yellow icons: for each stat
64
+ const icon = chalk.yellow('✔ ');
65
+ const statsPlain = `✔ Connections: ${statsInfo.connections} ✔ Accounts: ${statsInfo.accounts} ✔ Balance: ${balStr} ✔ P&L: ${pnlDisplay}`;
64
66
  const statsLeftPad = Math.floor((W - statsPlain.length) / 2);
65
67
  const statsRightPad = W - statsPlain.length - statsLeftPad;
66
68
 
67
69
  console.log(chalk.cyan('║') + ' '.repeat(statsLeftPad) +
68
- chalk.white(`Connections: ${statsInfo.connections}`) + ' ' +
69
- chalk.white(`Accounts: ${statsInfo.accounts}`) + ' ' +
70
- chalk.white('Balance: ') + balColor(balStr) + ' ' +
71
- chalk.white('P&L: ') + pnlColor(pnlDisplay) +
70
+ icon + chalk.white(`Connections: ${statsInfo.connections}`) + ' ' +
71
+ icon + chalk.white(`Accounts: ${statsInfo.accounts}`) + ' ' +
72
+ icon + chalk.white('Balance: ') + balColor(balStr) + ' ' +
73
+ icon + chalk.white('P&L: ') + pnlColor(pnlDisplay) +
72
74
  ' '.repeat(Math.max(0, statsRightPad)) + chalk.cyan('║'));
73
75
  }
74
76
 
@@ -14,7 +14,6 @@ const { prompts } = require('../utils');
14
14
  * Show all accounts
15
15
  */
16
16
  const showAccounts = async (service) => {
17
- const spinner = ora({ text: 'Fetching accounts...', color: 'yellow' }).start();
18
17
  const boxWidth = getLogoWidth();
19
18
  const { col1, col2 } = getColWidths(boxWidth);
20
19
 
@@ -26,80 +25,136 @@ const showAccounts = async (service) => {
26
25
  };
27
26
 
28
27
  let allAccounts = [];
29
-
30
- if (connections.count() > 0) {
31
- for (const conn of connections.getAll()) {
28
+ let spinner;
29
+
30
+ try {
31
+ // Step 1: Get connections
32
+ spinner = ora({ text: 'Loading connections...', color: 'yellow' }).start();
33
+
34
+ const allConns = connections.count() > 0 ? connections.getAll() : (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
35
+
36
+ if (allConns.length === 0) {
37
+ spinner.fail('No connections found');
38
+ await prompts.waitForEnter();
39
+ return;
40
+ }
41
+
42
+ spinner.succeed(`Found ${allConns.length} connection(s)`);
43
+
44
+ // Step 2: Fetch accounts from each connection
45
+ for (let i = 0; i < allConns.length; i++) {
46
+ const conn = allConns[i];
47
+ const propfirmName = conn.propfirm || conn.type || 'Unknown';
48
+
49
+ spinner = ora({ text: `Fetching accounts from ${propfirmName}...`, color: 'yellow' }).start();
50
+
32
51
  try {
33
52
  const result = await conn.service.getTradingAccounts();
34
- if (result.success && result.accounts) {
53
+ if (result.success && result.accounts && result.accounts.length > 0) {
35
54
  result.accounts.forEach(account => {
36
- allAccounts.push({ ...account, propfirm: conn.propfirm || conn.type, service: conn.service });
55
+ allAccounts.push({
56
+ ...account,
57
+ propfirm: propfirmName,
58
+ service: conn.service
59
+ });
37
60
  });
61
+ spinner.succeed(`${propfirmName}: ${result.accounts.length} account(s)`);
62
+ } else {
63
+ spinner.warn(`${propfirmName}: No accounts found`);
38
64
  }
39
- } catch (e) {}
40
- }
41
- } else if (service) {
42
- const result = await service.getTradingAccounts();
43
- if (result.success && result.accounts) {
44
- allAccounts = result.accounts.map(a => ({ ...a, service, propfirm: service.propfirm.name }));
65
+ } catch (e) {
66
+ spinner.fail(`${propfirmName}: Failed to fetch accounts`);
67
+ }
45
68
  }
46
- }
47
-
48
- if (allAccounts.length === 0) {
49
- spinner.fail('No accounts found');
50
- await prompts.waitForEnter();
51
- return;
52
- }
53
-
54
- spinner.succeed(`Found ${allAccounts.length} account(s)`);
55
- console.log();
56
-
57
- drawBoxHeader('TRADING ACCOUNTS', boxWidth);
58
-
59
- for (let i = 0; i < allAccounts.length; i += 2) {
60
- const acc1 = allAccounts[i];
61
- const acc2 = allAccounts[i + 1];
62
-
63
- const name1 = String(acc1.accountId || `Account #${acc1.accountId}`);
64
- const name2 = acc2 ? String(acc2.accountId || `Account #${acc2.accountId}`) : '';
65
69
 
66
- draw2ColHeader(name1.substring(0, col1 - 4), name2 ? name2.substring(0, col2 - 4) : '', boxWidth);
67
-
68
- // PropFirm
69
- const pf1 = chalk.magenta(acc1.propfirm || 'Unknown');
70
- const pf2 = acc2 ? chalk.magenta(acc2.propfirm || 'Unknown') : '';
71
- console.log(chalk.cyan('║') + fmtRow('PropFirm:', pf1, col1) + chalk.cyan('│') + (acc2 ? fmtRow('PropFirm:', pf2, col2) : ' '.repeat(col2)) + chalk.cyan('║'));
72
-
73
- // Balance
74
- const bal1 = acc1.balance;
75
- const bal2 = acc2 ? acc2.balance : null;
76
- const balStr1 = bal1 !== null && bal1 !== undefined ? '$' + bal1.toLocaleString() : '--';
77
- const balStr2 = bal2 !== null && bal2 !== undefined ? '$' + bal2.toLocaleString() : '--';
78
- const balColor1 = bal1 === null ? chalk.gray : (bal1 >= 0 ? chalk.green : chalk.red);
79
- const balColor2 = bal2 === null ? chalk.gray : (bal2 >= 0 ? chalk.green : chalk.red);
80
- console.log(chalk.cyan('║') + fmtRow('Balance:', balColor1(balStr1), col1) + chalk.cyan('│') + (acc2 ? fmtRow('Balance:', balColor2(balStr2), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
81
-
82
- // Status
83
- const status1 = ACCOUNT_STATUS[acc1.status] || { text: 'Unknown', color: 'gray' };
84
- const status2 = acc2 ? (ACCOUNT_STATUS[acc2.status] || { text: 'Unknown', color: 'gray' }) : null;
85
- 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('║'));
70
+ // Step 3: Check if we have accounts
71
+ if (allAccounts.length === 0) {
72
+ console.log(chalk.yellow('\n No accounts found across all connections.'));
73
+ await prompts.waitForEnter();
74
+ return;
75
+ }
86
76
 
87
- // Type
88
- const type1 = ACCOUNT_TYPE[acc1.type] || { text: 'Unknown', color: 'white' };
89
- const type2 = acc2 ? (ACCOUNT_TYPE[acc2.type] || { text: 'Unknown', color: 'white' }) : null;
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('║'));
77
+ // Step 4: Fetch additional data for each account (balance, P&L)
78
+ spinner = ora({ text: 'Loading account details...', color: 'yellow' }).start();
79
+
80
+ let detailsLoaded = 0;
81
+ for (const account of allAccounts) {
82
+ try {
83
+ // Try to get fresh balance/P&L if available
84
+ if (account.service && typeof account.service.getAccountBalance === 'function') {
85
+ const balanceResult = await account.service.getAccountBalance(account.accountId);
86
+ if (balanceResult.success) {
87
+ account.balance = balanceResult.balance;
88
+ account.profitAndLoss = balanceResult.profitAndLoss;
89
+ }
90
+ }
91
+ detailsLoaded++;
92
+ spinner.text = `Loading account details... (${detailsLoaded}/${allAccounts.length})`;
93
+ } catch (e) {
94
+ // Continue with existing data
95
+ }
96
+ }
97
+
98
+ spinner.succeed(`Loaded ${allAccounts.length} account(s)`);
99
+ console.log();
100
+
101
+ // Display accounts
102
+ drawBoxHeader('TRADING ACCOUNTS', boxWidth);
103
+
104
+ for (let i = 0; i < allAccounts.length; i += 2) {
105
+ const acc1 = allAccounts[i];
106
+ const acc2 = allAccounts[i + 1];
107
+
108
+ const name1 = String(acc1.accountId || acc1.accountName || `Account #${i + 1}`);
109
+ const name2 = acc2 ? String(acc2.accountId || acc2.accountName || `Account #${i + 2}`) : '';
110
+
111
+ draw2ColHeader(name1.substring(0, col1 - 4), name2 ? name2.substring(0, col2 - 4) : '', boxWidth);
112
+
113
+ // PropFirm
114
+ const pf1 = chalk.magenta(acc1.propfirm || 'Unknown');
115
+ const pf2 = acc2 ? chalk.magenta(acc2.propfirm || 'Unknown') : '';
116
+ console.log(chalk.cyan('║') + fmtRow('PropFirm:', pf1, col1) + chalk.cyan('│') + (acc2 ? fmtRow('PropFirm:', pf2, col2) : ' '.repeat(col2)) + chalk.cyan('║'));
117
+
118
+ // Balance
119
+ const bal1 = acc1.balance;
120
+ const bal2 = acc2 ? acc2.balance : null;
121
+ const balStr1 = bal1 !== null && bal1 !== undefined ? '$' + Number(bal1).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '--';
122
+ const balStr2 = bal2 !== null && bal2 !== undefined ? '$' + Number(bal2).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '--';
123
+ const balColor1 = bal1 === null || bal1 === undefined ? chalk.gray : (bal1 >= 0 ? chalk.green : chalk.red);
124
+ const balColor2 = bal2 === null || bal2 === undefined ? chalk.gray : (bal2 >= 0 ? chalk.green : chalk.red);
125
+ console.log(chalk.cyan('║') + fmtRow('Balance:', balColor1(balStr1), col1) + chalk.cyan('│') + (acc2 ? fmtRow('Balance:', balColor2(balStr2), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
126
+
127
+ // P&L
128
+ const pnl1 = acc1.profitAndLoss;
129
+ const pnl2 = acc2 ? acc2.profitAndLoss : null;
130
+ const pnlStr1 = pnl1 !== null && pnl1 !== undefined ? (pnl1 >= 0 ? '+' : '') + '$' + Number(pnl1).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '--';
131
+ const pnlStr2 = pnl2 !== null && pnl2 !== undefined ? (pnl2 >= 0 ? '+' : '') + '$' + Number(pnl2).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '--';
132
+ const pnlColor1 = pnl1 === null || pnl1 === undefined ? chalk.gray : (pnl1 >= 0 ? chalk.green : chalk.red);
133
+ const pnlColor2 = pnl2 === null || pnl2 === undefined ? chalk.gray : (pnl2 >= 0 ? chalk.green : chalk.red);
134
+ console.log(chalk.cyan('║') + fmtRow('P&L:', pnlColor1(pnlStr1), col1) + chalk.cyan('│') + (acc2 ? fmtRow('P&L:', pnlColor2(pnlStr2), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
135
+
136
+ // Status
137
+ const status1 = ACCOUNT_STATUS[acc1.status] || { text: 'Unknown', color: 'gray' };
138
+ const status2 = acc2 ? (ACCOUNT_STATUS[acc2.status] || { text: 'Unknown', color: 'gray' }) : null;
139
+ 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('║'));
140
+
141
+ // Type
142
+ const type1 = ACCOUNT_TYPE[acc1.type] || { text: 'Unknown', color: 'white' };
143
+ const type2 = acc2 ? (ACCOUNT_TYPE[acc2.type] || { text: 'Unknown', color: 'white' }) : null;
144
+ 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('║'));
145
+
146
+ if (i + 2 < allAccounts.length) {
147
+ console.log(chalk.cyan('╠') + chalk.cyan('═'.repeat(col1)) + chalk.cyan('╪') + chalk.cyan('═'.repeat(col2)) + chalk.cyan('╣'));
148
+ }
149
+ }
91
150
 
92
- // ID
93
- console.log(chalk.cyan('║') + fmtRow('ID:', chalk.gray(String(acc1.accountId)), col1) + chalk.cyan('│') + (acc2 ? fmtRow('ID:', chalk.gray(String(acc2.accountId)), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
151
+ drawBoxFooter(boxWidth);
152
+ console.log();
94
153
 
95
- if (i + 2 < allAccounts.length) {
96
- console.log(chalk.cyan('╠') + chalk.cyan('═'.repeat(col1)) + chalk.cyan('╪') + chalk.cyan('═'.repeat(col2)) + chalk.cyan('╣'));
97
- }
154
+ } catch (error) {
155
+ if (spinner) spinner.fail('Error loading accounts: ' + error.message);
98
156
  }
99
157
 
100
- drawBoxFooter(boxWidth);
101
- console.log();
102
-
103
158
  await prompts.waitForEnter();
104
159
  };
105
160
 
@@ -14,78 +14,124 @@ const { prompts } = require('../utils');
14
14
  * Show all orders
15
15
  */
16
16
  const showOrders = async (service) => {
17
- const spinner = ora({ text: 'Fetching orders...', color: 'yellow' }).start();
18
17
  const boxWidth = getLogoWidth();
18
+ let spinner;
19
19
 
20
- let allAccounts = [];
21
-
22
- if (connections.count() > 0) {
23
- for (const conn of connections.getAll()) {
20
+ try {
21
+ // Step 1: Get connections
22
+ spinner = ora({ text: 'Loading connections...', color: 'yellow' }).start();
23
+
24
+ const allConns = connections.count() > 0
25
+ ? connections.getAll()
26
+ : (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
27
+
28
+ if (allConns.length === 0) {
29
+ spinner.fail('No connections found');
30
+ await prompts.waitForEnter();
31
+ return;
32
+ }
33
+ spinner.succeed(`Found ${allConns.length} connection(s)`);
34
+
35
+ // Step 2: Fetch accounts
36
+ let allAccounts = [];
37
+
38
+ for (const conn of allConns) {
39
+ const propfirmName = conn.propfirm || conn.type || 'Unknown';
40
+ spinner = ora({ text: `Fetching accounts from ${propfirmName}...`, color: 'yellow' }).start();
41
+
24
42
  try {
25
43
  const result = await conn.service.getTradingAccounts();
26
- if (result.success && result.accounts) {
44
+ if (result.success && result.accounts && result.accounts.length > 0) {
27
45
  result.accounts.forEach(account => {
28
- allAccounts.push({ ...account, propfirm: conn.propfirm || conn.type, service: conn.service });
46
+ allAccounts.push({
47
+ ...account,
48
+ propfirm: propfirmName,
49
+ service: conn.service
50
+ });
29
51
  });
52
+ spinner.succeed(`${propfirmName}: ${result.accounts.length} account(s)`);
53
+ } else {
54
+ spinner.warn(`${propfirmName}: No accounts`);
30
55
  }
31
- } catch (e) {}
56
+ } catch (e) {
57
+ spinner.fail(`${propfirmName}: Failed`);
58
+ }
32
59
  }
33
- } else if (service) {
34
- const result = await service.getTradingAccounts();
35
- if (result.success && result.accounts) {
36
- allAccounts = result.accounts.map(a => ({ ...a, service, propfirm: service.propfirm.name }));
60
+
61
+ if (allAccounts.length === 0) {
62
+ console.log(chalk.yellow('\n No accounts found.'));
63
+ await prompts.waitForEnter();
64
+ return;
37
65
  }
38
- }
39
66
 
40
- let allOrders = [];
41
-
42
- for (const account of allAccounts) {
43
- try {
44
- const result = await account.service.getOrders(account.accountId);
45
- if (result.success && result.orders?.length > 0) {
46
- result.orders.forEach(order => {
47
- allOrders.push({ ...order, accountName: account.accountId, propfirm: account.propfirm });
48
- });
67
+ // Step 3: Fetch orders for each account
68
+ let allOrders = [];
69
+
70
+ for (const account of allAccounts) {
71
+ const accName = String(account.accountId || account.accountName || 'Unknown').substring(0, 20);
72
+ spinner = ora({ text: `Fetching orders for ${accName}...`, color: 'yellow' }).start();
73
+
74
+ try {
75
+ const result = await account.service.getOrders(account.accountId);
76
+ if (result.success && result.orders && result.orders.length > 0) {
77
+ result.orders.forEach(order => {
78
+ allOrders.push({
79
+ ...order,
80
+ accountName: account.accountId,
81
+ propfirm: account.propfirm
82
+ });
83
+ });
84
+ spinner.succeed(`${accName}: ${result.orders.length} order(s)`);
85
+ } else {
86
+ spinner.succeed(`${accName}: No orders`);
87
+ }
88
+ } catch (e) {
89
+ spinner.fail(`${accName}: Failed to fetch orders`);
49
90
  }
50
- } catch (e) {}
51
- }
91
+ }
52
92
 
53
- spinner.succeed(`Found ${allOrders.length} order(s)`);
54
- console.log();
55
-
56
- drawBoxHeader('ORDERS', boxWidth);
57
-
58
- if (allOrders.length === 0) {
59
- drawBoxRow(chalk.gray(' No orders found'), boxWidth);
60
- } else {
61
- const header = ' ' + 'Symbol'.padEnd(12) + 'Side'.padEnd(6) + 'Type'.padEnd(8) + 'Qty'.padEnd(6) + 'Price'.padEnd(10) + 'Status'.padEnd(12) + 'Account';
62
- drawBoxRow(chalk.white.bold(header), boxWidth);
63
- drawBoxSeparator(boxWidth);
64
-
65
- for (const order of allOrders) {
66
- const symbol = (order.contractId || order.symbol || 'Unknown').substring(0, 11);
67
- const sideInfo = ORDER_SIDE[order.side] || { text: '?', color: 'white' };
68
- const type = ORDER_TYPE[order.type] || 'Unknown';
69
- const qty = order.size || order.quantity || 0;
70
- const price = order.limitPrice || order.price || 0;
71
- const statusInfo = ORDER_STATUS[order.status] || { text: 'Unknown', color: 'gray', icon: '[?]' };
72
- const account = String(order.accountName || 'Unknown').substring(0, 12);
73
-
74
- const row = ' ' +
75
- chalk.white(symbol.padEnd(12)) +
76
- chalk[sideInfo.color](sideInfo.text.substring(0, 4).padEnd(6)) +
77
- chalk.white(type.substring(0, 7).padEnd(8)) +
78
- chalk.white(qty.toString().padEnd(6)) +
79
- chalk.white((price > 0 ? price.toFixed(2) : 'MKT').padEnd(10)) +
80
- chalk[statusInfo.color]((statusInfo.icon + ' ' + statusInfo.text).substring(0, 11).padEnd(12)) +
81
- chalk.gray(account);
82
-
83
- drawBoxRow(row, boxWidth);
93
+ spinner = ora({ text: 'Preparing display...', color: 'yellow' }).start();
94
+ spinner.succeed(`Total: ${allOrders.length} order(s)`);
95
+ console.log();
96
+
97
+ // Display
98
+ drawBoxHeader('ORDERS', boxWidth);
99
+
100
+ if (allOrders.length === 0) {
101
+ drawBoxRow(chalk.gray(' No orders found'), boxWidth);
102
+ } else {
103
+ const header = ' ' + 'Symbol'.padEnd(12) + 'Side'.padEnd(6) + 'Type'.padEnd(8) + 'Qty'.padEnd(6) + 'Price'.padEnd(10) + 'Status'.padEnd(12) + 'Account';
104
+ drawBoxRow(chalk.white.bold(header), boxWidth);
105
+ drawBoxSeparator(boxWidth);
106
+
107
+ for (const order of allOrders) {
108
+ const symbol = String(order.contractId || order.symbol || 'Unknown').substring(0, 11);
109
+ const sideInfo = ORDER_SIDE[order.side] || { text: '?', color: 'white' };
110
+ const type = ORDER_TYPE[order.type] || 'Unknown';
111
+ const qty = order.size || order.quantity || 0;
112
+ const price = order.limitPrice || order.price || 0;
113
+ const statusInfo = ORDER_STATUS[order.status] || { text: 'Unknown', color: 'gray', icon: '[?]' };
114
+ const account = String(order.accountName || 'Unknown').substring(0, 12);
115
+
116
+ const row = ' ' +
117
+ chalk.white(symbol.padEnd(12)) +
118
+ chalk[sideInfo.color](sideInfo.text.substring(0, 4).padEnd(6)) +
119
+ chalk.white(String(type).substring(0, 7).padEnd(8)) +
120
+ chalk.white(String(qty).padEnd(6)) +
121
+ chalk.white((price > 0 ? price.toFixed(2) : 'MKT').padEnd(10)) +
122
+ chalk[statusInfo.color]((statusInfo.icon + ' ' + statusInfo.text).substring(0, 11).padEnd(12)) +
123
+ chalk.gray(account);
124
+
125
+ drawBoxRow(row, boxWidth);
126
+ }
84
127
  }
85
- }
86
128
 
87
- drawBoxFooter(boxWidth);
88
- console.log();
129
+ drawBoxFooter(boxWidth);
130
+ console.log();
131
+
132
+ } catch (error) {
133
+ if (spinner) spinner.fail('Error: ' + error.message);
134
+ }
89
135
 
90
136
  await prompts.waitForEnter();
91
137
  };
@@ -14,79 +14,125 @@ const { prompts } = require('../utils');
14
14
  * Show all open positions
15
15
  */
16
16
  const showPositions = async (service) => {
17
- const spinner = ora({ text: 'Fetching positions...', color: 'yellow' }).start();
18
17
  const boxWidth = getLogoWidth();
18
+ let spinner;
19
19
 
20
- let allAccounts = [];
21
-
22
- if (connections.count() > 0) {
23
- for (const conn of connections.getAll()) {
20
+ try {
21
+ // Step 1: Get connections
22
+ spinner = ora({ text: 'Loading connections...', color: 'yellow' }).start();
23
+
24
+ const allConns = connections.count() > 0
25
+ ? connections.getAll()
26
+ : (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
27
+
28
+ if (allConns.length === 0) {
29
+ spinner.fail('No connections found');
30
+ await prompts.waitForEnter();
31
+ return;
32
+ }
33
+ spinner.succeed(`Found ${allConns.length} connection(s)`);
34
+
35
+ // Step 2: Fetch accounts
36
+ let allAccounts = [];
37
+
38
+ for (const conn of allConns) {
39
+ const propfirmName = conn.propfirm || conn.type || 'Unknown';
40
+ spinner = ora({ text: `Fetching accounts from ${propfirmName}...`, color: 'yellow' }).start();
41
+
24
42
  try {
25
43
  const result = await conn.service.getTradingAccounts();
26
- if (result.success && result.accounts) {
44
+ if (result.success && result.accounts && result.accounts.length > 0) {
27
45
  result.accounts.forEach(account => {
28
- allAccounts.push({ ...account, propfirm: conn.propfirm || conn.type, service: conn.service });
46
+ allAccounts.push({
47
+ ...account,
48
+ propfirm: propfirmName,
49
+ service: conn.service
50
+ });
29
51
  });
52
+ spinner.succeed(`${propfirmName}: ${result.accounts.length} account(s)`);
53
+ } else {
54
+ spinner.warn(`${propfirmName}: No accounts`);
30
55
  }
31
- } catch (e) {}
56
+ } catch (e) {
57
+ spinner.fail(`${propfirmName}: Failed`);
58
+ }
32
59
  }
33
- } else if (service) {
34
- const result = await service.getTradingAccounts();
35
- if (result.success && result.accounts) {
36
- allAccounts = result.accounts.map(a => ({ ...a, service, propfirm: service.propfirm.name }));
60
+
61
+ if (allAccounts.length === 0) {
62
+ console.log(chalk.yellow('\n No accounts found.'));
63
+ await prompts.waitForEnter();
64
+ return;
37
65
  }
38
- }
39
66
 
40
- let allPositions = [];
41
-
42
- for (const account of allAccounts) {
43
- try {
44
- const result = await account.service.getPositions(account.accountId);
45
- if (result.success && result.positions?.length > 0) {
46
- result.positions.forEach(pos => {
47
- allPositions.push({ ...pos, accountName: account.accountId, propfirm: account.propfirm });
48
- });
67
+ // Step 3: Fetch positions for each account
68
+ let allPositions = [];
69
+
70
+ for (const account of allAccounts) {
71
+ const accName = String(account.accountId || account.accountName || 'Unknown').substring(0, 20);
72
+ spinner = ora({ text: `Fetching positions for ${accName}...`, color: 'yellow' }).start();
73
+
74
+ try {
75
+ const result = await account.service.getPositions(account.accountId);
76
+ if (result.success && result.positions && result.positions.length > 0) {
77
+ result.positions.forEach(pos => {
78
+ allPositions.push({
79
+ ...pos,
80
+ accountName: account.accountId,
81
+ propfirm: account.propfirm
82
+ });
83
+ });
84
+ spinner.succeed(`${accName}: ${result.positions.length} position(s)`);
85
+ } else {
86
+ spinner.succeed(`${accName}: No positions`);
87
+ }
88
+ } catch (e) {
89
+ spinner.fail(`${accName}: Failed to fetch positions`);
49
90
  }
50
- } catch (e) {}
51
- }
91
+ }
52
92
 
53
- spinner.succeed(`Found ${allPositions.length} position(s)`);
54
- console.log();
55
-
56
- drawBoxHeader('OPEN POSITIONS', boxWidth);
57
-
58
- if (allPositions.length === 0) {
59
- drawBoxRow(chalk.gray(' No open positions'), boxWidth);
60
- } else {
61
- const header = ' ' + 'Symbol'.padEnd(15) + 'Side'.padEnd(8) + 'Size'.padEnd(8) + 'Entry'.padEnd(12) + 'P&L'.padEnd(12) + 'Account';
62
- drawBoxRow(chalk.white.bold(header), boxWidth);
63
- drawBoxSeparator(boxWidth);
64
-
65
- for (const pos of allPositions) {
66
- const symbol = (pos.contractId || pos.symbol || 'Unknown').substring(0, 14);
67
- const sideInfo = ORDER_SIDE[pos.side] || { text: 'Unknown', color: 'white' };
68
- const size = Math.abs(pos.size || pos.quantity || 0);
69
- const entry = pos.averagePrice || pos.entryPrice || 0;
70
- const pnl = pos.profitAndLoss || pos.unrealizedPnl || 0;
71
- const account = String(pos.accountName || 'Unknown').substring(0, 15);
72
-
73
- const pnlColor = pnl >= 0 ? chalk.green : chalk.red;
74
- const pnlStr = (pnl >= 0 ? '+' : '') + '$' + pnl.toFixed(2);
75
-
76
- const row = ' ' +
77
- chalk.white(symbol.padEnd(15)) +
78
- chalk[sideInfo.color](sideInfo.text.padEnd(8)) +
79
- chalk.white(size.toString().padEnd(8)) +
80
- chalk.white(entry.toFixed(2).padEnd(12)) +
81
- pnlColor(pnlStr.padEnd(12)) +
82
- chalk.gray(account);
83
-
84
- drawBoxRow(row, boxWidth);
93
+ spinner = ora({ text: 'Preparing display...', color: 'yellow' }).start();
94
+ spinner.succeed(`Total: ${allPositions.length} position(s)`);
95
+ console.log();
96
+
97
+ // Display
98
+ drawBoxHeader('OPEN POSITIONS', boxWidth);
99
+
100
+ if (allPositions.length === 0) {
101
+ drawBoxRow(chalk.gray(' No open positions'), boxWidth);
102
+ } else {
103
+ const header = ' ' + 'Symbol'.padEnd(15) + 'Side'.padEnd(8) + 'Size'.padEnd(8) + 'Entry'.padEnd(12) + 'P&L'.padEnd(12) + 'Account';
104
+ drawBoxRow(chalk.white.bold(header), boxWidth);
105
+ drawBoxSeparator(boxWidth);
106
+
107
+ for (const pos of allPositions) {
108
+ const symbol = String(pos.contractId || pos.symbol || 'Unknown').substring(0, 14);
109
+ const sideInfo = ORDER_SIDE[pos.side] || { text: 'Unknown', color: 'white' };
110
+ const size = Math.abs(pos.size || pos.quantity || 0);
111
+ const entry = pos.averagePrice || pos.entryPrice || 0;
112
+ const pnl = pos.profitAndLoss || pos.unrealizedPnl || 0;
113
+ const account = String(pos.accountName || 'Unknown').substring(0, 15);
114
+
115
+ const pnlColor = pnl >= 0 ? chalk.green : chalk.red;
116
+ const pnlStr = (pnl >= 0 ? '+' : '') + '$' + pnl.toFixed(2);
117
+
118
+ const row = ' ' +
119
+ chalk.white(symbol.padEnd(15)) +
120
+ chalk[sideInfo.color](sideInfo.text.padEnd(8)) +
121
+ chalk.white(String(size).padEnd(8)) +
122
+ chalk.white(entry.toFixed(2).padEnd(12)) +
123
+ pnlColor(pnlStr.padEnd(12)) +
124
+ chalk.gray(account);
125
+
126
+ drawBoxRow(row, boxWidth);
127
+ }
85
128
  }
86
- }
87
129
 
88
- drawBoxFooter(boxWidth);
89
- console.log();
130
+ drawBoxFooter(boxWidth);
131
+ console.log();
132
+
133
+ } catch (error) {
134
+ if (spinner) spinner.fail('Error: ' + error.message);
135
+ }
90
136
 
91
137
  await prompts.waitForEnter();
92
138
  };