hedgequantx 1.8.47 → 1.8.49

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.47",
3
+ "version": "1.8.49",
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": {
@@ -28,8 +28,8 @@ const showAccounts = async (service) => {
28
28
  let spinner;
29
29
 
30
30
  try {
31
- // Step 1: Get connections
32
- spinner = ora({ text: 'Loading connections...', color: 'yellow' }).start();
31
+ // Single spinner for loading (appears below the dashboard header)
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
 
@@ -38,16 +38,11 @@ const showAccounts = async (service) => {
38
38
  await prompts.waitForEnter();
39
39
  return;
40
40
  }
41
-
42
- spinner.succeed(`Found ${allConns.length} connection(s)`);
43
41
 
44
- // Step 2: Fetch accounts from each connection
45
- for (let i = 0; i < allConns.length; i++) {
46
- const conn = allConns[i];
42
+ // Fetch accounts from each connection
43
+ for (const conn of allConns) {
47
44
  const propfirmName = conn.propfirm || conn.type || 'Unknown';
48
45
 
49
- spinner = ora({ text: `Fetching accounts from ${propfirmName}...`, color: 'yellow' }).start();
50
-
51
46
  try {
52
47
  const result = await conn.service.getTradingAccounts();
53
48
  if (result.success && result.accounts && result.accounts.length > 0) {
@@ -58,29 +53,21 @@ const showAccounts = async (service) => {
58
53
  service: conn.service
59
54
  });
60
55
  });
61
- spinner.succeed(`${propfirmName}: ${result.accounts.length} account(s)`);
62
- } else {
63
- spinner.warn(`${propfirmName}: No accounts found`);
64
56
  }
65
57
  } catch (e) {
66
- spinner.fail(`${propfirmName}: Failed to fetch accounts`);
58
+ // Silent fail
67
59
  }
68
60
  }
69
61
 
70
- // Step 3: Check if we have accounts
71
62
  if (allAccounts.length === 0) {
72
- console.log(chalk.yellow('\n No accounts found across all connections.'));
63
+ spinner.fail('No accounts found');
73
64
  await prompts.waitForEnter();
74
65
  return;
75
66
  }
76
67
 
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;
68
+ // Fetch additional data for each account
81
69
  for (const account of allAccounts) {
82
70
  try {
83
- // Try to get fresh balance/P&L if available
84
71
  if (account.service && typeof account.service.getAccountBalance === 'function') {
85
72
  const balanceResult = await account.service.getAccountBalance(account.accountId);
86
73
  if (balanceResult.success) {
@@ -88,14 +75,10 @@ const showAccounts = async (service) => {
88
75
  account.profitAndLoss = balanceResult.profitAndLoss;
89
76
  }
90
77
  }
91
- detailsLoaded++;
92
- spinner.text = `Loading account details... (${detailsLoaded}/${allAccounts.length})`;
93
- } catch (e) {
94
- // Continue with existing data
95
- }
78
+ } catch (e) {}
96
79
  }
97
80
 
98
- spinner.succeed(`Loaded ${allAccounts.length} account(s)`);
81
+ spinner.succeed('Accounts loaded');
99
82
  console.log();
100
83
 
101
84
  // Display accounts
@@ -17,8 +17,8 @@ const showStats = async (service) => {
17
17
  let spinner;
18
18
 
19
19
  try {
20
- // Step 1: Get connections
21
- spinner = ora({ text: 'Loading connections...', color: 'yellow' }).start();
20
+ // Single spinner for loading
21
+ spinner = ora({ text: 'Loading stats...', color: 'yellow' }).start();
22
22
 
23
23
  const allConns = connections.count() > 0
24
24
  ? connections.getAll()
@@ -29,14 +29,12 @@ const showStats = async (service) => {
29
29
  await prompts.waitForEnter();
30
30
  return;
31
31
  }
32
- spinner.succeed(`Found ${allConns.length} connection(s)`);
33
32
 
34
- // Step 2: Fetch accounts from each connection
33
+ // Fetch accounts from each connection
35
34
  let allAccountsData = [];
36
35
 
37
36
  for (const conn of allConns) {
38
37
  const propfirmName = conn.propfirm || conn.type || 'Unknown';
39
- spinner = ora({ text: `Fetching accounts from ${propfirmName}...`, color: 'yellow' }).start();
40
38
 
41
39
  try {
42
40
  const result = await conn.service.getTradingAccounts();
@@ -48,17 +46,12 @@ const showStats = async (service) => {
48
46
  service: conn.service
49
47
  });
50
48
  });
51
- spinner.succeed(`${propfirmName}: ${result.accounts.length} account(s)`);
52
- } else {
53
- spinner.warn(`${propfirmName}: No accounts`);
54
49
  }
55
- } catch (e) {
56
- spinner.fail(`${propfirmName}: Failed`);
57
- }
50
+ } catch (e) {}
58
51
  }
59
52
 
60
53
  if (allAccountsData.length === 0) {
61
- console.log(chalk.yellow('\n No accounts found.'));
54
+ spinner.fail('No accounts found');
62
55
  await prompts.waitForEnter();
63
56
  return;
64
57
  }
@@ -76,12 +69,12 @@ const showStats = async (service) => {
76
69
  const activeAccounts = allAccountsData.filter(acc => acc.status === 0);
77
70
 
78
71
  if (activeAccounts.length === 0) {
79
- console.log(chalk.yellow('\n No active accounts found.'));
72
+ spinner.fail('No active accounts found');
80
73
  await prompts.waitForEnter();
81
74
  return;
82
75
  }
83
76
 
84
- // Step 3: Collect stats for each account
77
+ // Collect stats for each account
85
78
  let totalBalance = 0;
86
79
  let totalPnL = 0;
87
80
  let totalStartingBalance = 0;
@@ -94,9 +87,6 @@ const showStats = async (service) => {
94
87
  for (let i = 0; i < activeAccounts.length; i++) {
95
88
  const account = activeAccounts[i];
96
89
  const svc = account.service;
97
- const accName = String(account.accountId || account.accountName || `Account ${i+1}`).substring(0, 20);
98
-
99
- spinner = ora({ text: `Loading stats for ${accName}...`, color: 'yellow' }).start();
100
90
 
101
91
  try {
102
92
  // Balance
@@ -118,7 +108,6 @@ const showStats = async (service) => {
118
108
  }
119
109
 
120
110
  // Positions
121
- spinner.text = `Loading positions for ${accName}...`;
122
111
  try {
123
112
  const posResult = await svc.getPositions(account.accountId);
124
113
  if (posResult.success && posResult.positions) {
@@ -127,7 +116,6 @@ const showStats = async (service) => {
127
116
  } catch (e) {}
128
117
 
129
118
  // Orders
130
- spinner.text = `Loading orders for ${accName}...`;
131
119
  try {
132
120
  const ordResult = await svc.getOrders(account.accountId);
133
121
  if (ordResult.success && ordResult.orders) {
@@ -136,7 +124,6 @@ const showStats = async (service) => {
136
124
  } catch (e) {}
137
125
 
138
126
  // Lifetime stats
139
- spinner.text = `Loading lifetime stats for ${accName}...`;
140
127
  if (typeof svc.getLifetimeStats === 'function') {
141
128
  try {
142
129
  const lifetimeResult = await svc.getLifetimeStats(account.accountId);
@@ -147,7 +134,6 @@ const showStats = async (service) => {
147
134
  }
148
135
 
149
136
  // Trade history
150
- spinner.text = `Loading trade history for ${accName}...`;
151
137
  if (typeof svc.getTradeHistory === 'function') {
152
138
  try {
153
139
  const tradesResult = await svc.getTradeHistory(account.accountId, 30);
@@ -160,15 +146,10 @@ const showStats = async (service) => {
160
146
  }
161
147
  } catch (e) {}
162
148
  }
163
-
164
- spinner.succeed(`${accName}: Stats loaded`);
165
- } catch (e) {
166
- spinner.fail(`${accName}: Failed to load stats`);
167
- }
149
+ } catch (e) {}
168
150
  }
169
151
 
170
152
  // Aggregate stats
171
- spinner = ora({ text: 'Calculating metrics...', color: 'yellow' }).start();
172
153
 
173
154
  let stats = {
174
155
  totalTrades: 0, winningTrades: 0, losingTrades: 0,
package/src/ui/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  * UI Module Exports
3
3
  */
4
4
 
5
+ const chalk = require('chalk');
5
6
  const { detectDevice, getDevice, getSeparator } = require('./device');
6
7
  const {
7
8
  getLogoWidth,
@@ -24,6 +25,55 @@ const {
24
25
  } = require('./table');
25
26
  const { createBoxMenu } = require('./menu');
26
27
 
28
+ /**
29
+ * Display HQX Banner (without closing border)
30
+ */
31
+ const displayBanner = () => {
32
+ console.clear();
33
+ const termWidth = process.stdout.columns || 100;
34
+ const isMobile = termWidth < 60;
35
+ const boxWidth = isMobile ? Math.max(termWidth - 2, 40) : Math.max(getLogoWidth(), 98);
36
+ const innerWidth = boxWidth - 2;
37
+
38
+ let version = '1.0.0';
39
+ try { version = require('../../package.json').version; } catch (e) {}
40
+
41
+ console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
42
+
43
+ if (isMobile) {
44
+ const logoHQ = ['██╗ ██╗ ██████╗ ','██║ ██║██╔═══██╗','███████║██║ ██║','██╔══██║██║▄▄ ██║','██║ ██║╚██████╔╝','╚═╝ ╚═╝ ╚══▀▀═╝ '];
45
+ const logoX = ['██╗ ██╗','╚██╗██╔╝',' ╚███╔╝ ',' ██╔██╗ ','██╔╝ ██╗','╚═╝ ╚═╝'];
46
+ logoHQ.forEach((line, i) => {
47
+ const fullLine = chalk.cyan(line) + chalk.yellow(logoX[i]);
48
+ const totalLen = line.length + logoX[i].length;
49
+ const padding = innerWidth - totalLen;
50
+ const leftPad = Math.floor(padding / 2);
51
+ console.log(chalk.cyan('║') + ' '.repeat(leftPad) + fullLine + ' '.repeat(padding - leftPad) + chalk.cyan('║'));
52
+ });
53
+ } else {
54
+ const logo = [
55
+ '██╗ ██╗███████╗██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗████████╗',
56
+ '██║ ██║██╔════╝██╔══██╗██╔════╝ ██╔════╝██╔═══██╗██║ ██║██╔══██╗████╗ ██║╚══██╔══╝',
57
+ '███████║█████╗ ██║ ██║██║ ███╗█████╗ ██║ ██║██║ ██║███████║██╔██╗ ██║ ██║ ',
58
+ '██╔══██║██╔══╝ ██║ ██║██║ ██║██╔══╝ ██║▄▄ ██║██║ ██║██╔══██║██║╚██╗██║ ██║ ',
59
+ '██║ ██║███████╗██████╔╝╚██████╔╝███████╗╚██████╔╝╚██████╔╝██║ ██║██║ ╚████║ ██║ ',
60
+ '╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ '
61
+ ];
62
+ const logoX = ['██╗ ██╗','╚██╗██╔╝',' ╚███╔╝ ',' ██╔██╗ ','██╔╝ ██╗','╚═╝ ╚═╝'];
63
+ logo.forEach((line, i) => {
64
+ const fullLine = chalk.cyan(line) + chalk.yellow(logoX[i]);
65
+ const totalLen = line.length + logoX[i].length;
66
+ const padding = innerWidth - totalLen;
67
+ const leftPad = Math.floor(padding / 2);
68
+ console.log(chalk.cyan('║') + ' '.repeat(leftPad) + fullLine + ' '.repeat(padding - leftPad) + chalk.cyan('║'));
69
+ });
70
+ }
71
+
72
+ console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
73
+ const tagline = isMobile ? `HQX v${version}` : `Prop Futures Algo Trading v${version}`;
74
+ console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
75
+ };
76
+
27
77
  /**
28
78
  * Ensure stdin is ready for inquirer prompts
29
79
  * This fixes input leaking to bash after session restore or algo trading
@@ -69,5 +119,7 @@ module.exports = {
69
119
  // Menu
70
120
  createBoxMenu,
71
121
  // Stdin
72
- prepareStdin
122
+ prepareStdin,
123
+ // Banner
124
+ displayBanner
73
125
  };
@@ -46,13 +46,13 @@ const prepareStdin = () => {
46
46
  * Native readline prompt
47
47
  */
48
48
  const nativePrompt = (message) => {
49
- return new Promise((resolve, reject) => {
49
+ return new Promise((resolve) => {
50
50
  try {
51
51
  prepareStdin();
52
52
 
53
53
  // Always create a fresh readline for each prompt to avoid state issues
54
54
  if (rl && !rl.closed) {
55
- rl.close();
55
+ try { rl.close(); } catch (e) {}
56
56
  rl = null;
57
57
  }
58
58
 
@@ -62,18 +62,23 @@ const nativePrompt = (message) => {
62
62
  terminal: true
63
63
  });
64
64
 
65
- // Handle readline close/error
66
- rl.on('close', () => {
67
- resolve('');
68
- });
69
-
70
- rl.on('error', (err) => {
71
- resolve('');
72
- });
65
+ let answered = false;
73
66
 
74
67
  rl.question(message + ' ', (answer) => {
68
+ answered = true;
69
+ try { rl.close(); } catch (e) {}
70
+ rl = null;
75
71
  resolve(answer || '');
76
72
  });
73
+
74
+ // Handle readline close (e.g., Ctrl+C)
75
+ rl.on('close', () => {
76
+ if (!answered) {
77
+ rl = null;
78
+ resolve('');
79
+ }
80
+ });
81
+
77
82
  } catch (e) {
78
83
  resolve('');
79
84
  }