hedgequantx 1.7.6 → 1.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,46 +1,41 @@
1
1
  /**
2
2
  * Dashboard Menu - Main menu after login
3
- * Shows connected PropFirms and navigation options
4
3
  */
5
4
 
6
5
  const chalk = require('chalk');
7
- const inquirer = require('inquirer');
8
6
  const ora = require('ora');
9
7
  const { execSync, spawn } = require('child_process');
10
8
 
11
9
  const { connections } = require('../services');
12
10
  const { getLogoWidth, centerText, prepareStdin } = require('../ui');
13
11
  const { getCachedStats } = require('../services/stats-cache');
12
+ const { prompts } = require('../utils');
14
13
 
15
14
  /**
16
15
  * Dashboard menu after login
17
- * @param {Object} service - Connected service
18
16
  */
19
17
  const dashboardMenu = async (service) => {
20
- // Ensure stdin is ready for prompts
21
18
  prepareStdin();
22
19
 
23
20
  const boxWidth = getLogoWidth();
24
- const W = boxWidth - 2; // Inner width (without borders)
21
+ const W = boxWidth - 2;
25
22
 
26
- // Helper to create a line that fits exactly in the box
27
23
  const makeLine = (content, align = 'left') => {
28
24
  const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
29
25
  const padding = W - plainLen;
30
26
  if (align === 'center') {
31
27
  const leftPad = Math.floor(padding / 2);
32
- const rightPad = padding - leftPad;
33
- return chalk.cyan('║') + ' '.repeat(leftPad) + content + ' '.repeat(rightPad) + chalk.cyan('║');
28
+ return chalk.cyan('║') + ' '.repeat(leftPad) + content + ' '.repeat(padding - leftPad) + chalk.cyan('║');
34
29
  }
35
30
  return chalk.cyan('║') + content + ' '.repeat(Math.max(0, padding)) + chalk.cyan('║');
36
31
  };
37
32
 
38
- // Dashboard box header
39
- console.log(chalk.cyan('' + '═'.repeat(W) + ''));
33
+ // Continue from banner (no top border)
34
+ console.log(chalk.cyan('' + '═'.repeat(W) + ''));
40
35
  console.log(makeLine(chalk.yellow.bold('Welcome, HQX Trader!'), 'center'));
41
36
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
42
37
 
43
- // Show connected propfirms centered on one line (max 3)
38
+ // Show connected propfirms
44
39
  const allConns = connections.getAll();
45
40
  if (allConns.length > 0) {
46
41
  const propfirms = allConns.slice(0, 3).map(c => c.propfirm || c.type || 'Connected');
@@ -48,38 +43,30 @@ const dashboardMenu = async (service) => {
48
43
  console.log(makeLine(propfirmText, 'center'));
49
44
  }
50
45
 
51
- // Show stats bar (Connections, Accounts, Balance, P&L)
46
+ // Stats bar
52
47
  const statsInfo = getCachedStats();
53
-
54
48
  if (statsInfo) {
55
49
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
56
50
 
57
- const connStr = `Connections: ${statsInfo.connections}`;
58
- const accStr = `Accounts: ${statsInfo.accounts}`;
59
-
60
- const balStr = statsInfo.balance !== null
61
- ? `$${statsInfo.balance.toLocaleString()}`
62
- : '--';
51
+ const balStr = statsInfo.balance !== null ? `$${statsInfo.balance.toLocaleString()}` : '--';
63
52
  const balColor = statsInfo.balance !== null ? chalk.green : chalk.gray;
64
53
 
65
54
  let pnlDisplay, pnlColor;
66
55
  if (statsInfo.pnl !== null) {
67
- const pnlSign = statsInfo.pnl >= 0 ? '+' : '';
68
56
  pnlColor = statsInfo.pnl >= 0 ? chalk.green : chalk.red;
69
- pnlDisplay = `${pnlSign}$${Math.abs(statsInfo.pnl).toLocaleString()}`;
57
+ pnlDisplay = `${statsInfo.pnl >= 0 ? '+' : ''}$${Math.abs(statsInfo.pnl).toLocaleString()}`;
70
58
  } else {
71
59
  pnlColor = chalk.gray;
72
60
  pnlDisplay = '--';
73
61
  }
74
62
 
75
- const statsText = connStr + ' ' + accStr + ' Balance: ' + balStr + ' P&L: ' + pnlDisplay;
76
- const statsPlain = `${connStr} ${accStr} Balance: ${balStr} P&L: ${pnlDisplay}`;
63
+ const statsPlain = `Connections: ${statsInfo.connections} Accounts: ${statsInfo.accounts} Balance: ${balStr} P&L: ${pnlDisplay}`;
77
64
  const statsLeftPad = Math.floor((W - statsPlain.length) / 2);
78
65
  const statsRightPad = W - statsPlain.length - statsLeftPad;
79
66
 
80
67
  console.log(chalk.cyan('║') + ' '.repeat(statsLeftPad) +
81
- chalk.white(connStr) + ' ' +
82
- chalk.white(accStr) + ' ' +
68
+ chalk.white(`Connections: ${statsInfo.connections}`) + ' ' +
69
+ chalk.white(`Accounts: ${statsInfo.accounts}`) + ' ' +
83
70
  chalk.white('Balance: ') + balColor(balStr) + ' ' +
84
71
  chalk.white('P&L: ') + pnlColor(pnlDisplay) +
85
72
  ' '.repeat(Math.max(0, statsRightPad)) + chalk.cyan('║'));
@@ -87,9 +74,8 @@ const dashboardMenu = async (service) => {
87
74
 
88
75
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
89
76
 
90
- // Menu options in 2 columns
77
+ // Menu in 2 columns
91
78
  const col1Width = Math.floor(W / 2);
92
-
93
79
  const menuRow = (left, right) => {
94
80
  const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '');
95
81
  const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '');
@@ -98,153 +84,96 @@ const dashboardMenu = async (service) => {
98
84
  console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
99
85
  };
100
86
 
87
+ menuRow(chalk.cyan('[1] View Accounts'), chalk.cyan('[2] View Stats'));
88
+ menuRow(chalk.cyan('[+] Add Prop-Account'), chalk.magenta('[A] Algo-Trading'));
89
+ menuRow(chalk.yellow('[U] Update HQX'), chalk.red('[X] Disconnect'));
90
+
101
91
  console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
102
92
 
103
- // Use list type for stable input handling
104
- const { action } = await inquirer.prompt([
105
- {
106
- type: 'list',
107
- name: 'action',
108
- message: chalk.cyan('Select:'),
109
- choices: [
110
- { name: chalk.cyan('[1] View Accounts'), value: 'accounts' },
111
- { name: chalk.cyan('[2] View Stats'), value: 'stats' },
112
- { name: chalk.cyan('[+] Add Prop-Account'), value: 'add_prop_account' },
113
- { name: chalk.magenta('[A] Algo-Trading'), value: 'algotrading' },
114
- { name: chalk.yellow('[U] Update HQX'), value: 'update' },
115
- { name: chalk.red('[X] Disconnect'), value: 'disconnect' }
116
- ],
117
- loop: false
118
- }
119
- ]);
93
+ // Simple input - no duplicate menu
94
+ const input = await prompts.textInput('Select (1/2/+/A/U/X)');
120
95
 
121
- return action;
122
- };
123
-
124
- /**
125
- * Wait for user to press Enter
126
- */
127
- const waitForEnter = async () => {
128
- prepareStdin();
129
- try {
130
- await inquirer.prompt([{ type: 'input', name: 'c', message: 'Press Enter to continue...' }]);
131
- } catch (e) {
132
- // Ignore prompt errors
133
- }
96
+ const actionMap = {
97
+ '1': 'accounts',
98
+ '2': 'stats',
99
+ '+': 'add_prop_account',
100
+ 'a': 'algotrading',
101
+ 'u': 'update',
102
+ 'x': 'disconnect'
103
+ };
104
+
105
+ return actionMap[(input || '').toLowerCase()] || null;
134
106
  };
135
107
 
136
108
  /**
137
- * Handles the update process with auto-restart
138
- * Robust version that handles all edge cases
109
+ * Handle update process
139
110
  */
140
111
  const handleUpdate = async () => {
141
112
  prepareStdin();
142
113
 
143
114
  let spinner = null;
144
115
  let currentVersion = 'unknown';
145
- let latestVersion = null;
146
116
 
147
117
  try {
148
- // Get current version safely
149
118
  try {
150
- const pkg = require('../../package.json');
151
- currentVersion = pkg.version || 'unknown';
152
- } catch (e) {
153
- currentVersion = 'unknown';
154
- }
119
+ currentVersion = require('../../package.json').version || 'unknown';
120
+ } catch (e) {}
155
121
 
156
122
  spinner = ora({ text: 'Checking for updates...', color: 'yellow' }).start();
157
123
 
158
- // Check latest version on npm with timeout
159
- spinner.text = 'Checking npm registry...';
124
+ let latestVersion;
160
125
  try {
161
- const result = execSync('npm view hedgequantx version 2>/dev/null', {
162
- stdio: 'pipe',
163
- timeout: 15000, // 15 second timeout
164
- encoding: 'utf8'
165
- });
166
- latestVersion = (result || '').toString().trim();
126
+ latestVersion = execSync('npm view hedgequantx version 2>/dev/null', {
127
+ stdio: 'pipe', timeout: 15000, encoding: 'utf8'
128
+ }).trim();
167
129
 
168
- // Validate version format (x.y.z)
169
130
  if (!latestVersion || !/^\d+\.\d+\.\d+/.test(latestVersion)) {
170
- throw new Error('Invalid version format received');
131
+ throw new Error('Invalid version');
171
132
  }
172
133
  } catch (e) {
173
134
  spinner.fail('Cannot reach npm registry');
174
- console.log(chalk.gray(' Check your internet connection'));
175
- console.log();
176
- await waitForEnter();
135
+ await prompts.waitForEnter();
177
136
  return;
178
137
  }
179
138
 
180
- // Compare versions
181
139
  if (currentVersion === latestVersion) {
182
140
  spinner.succeed(`Already up to date! (v${currentVersion})`);
183
- console.log();
184
141
  await new Promise(r => setTimeout(r, 2000));
185
142
  return;
186
143
  }
187
144
 
188
- // Show version info and update automatically
189
145
  spinner.text = `Updating v${currentVersion} → v${latestVersion}...`;
190
146
 
191
147
  try {
192
148
  execSync('npm install -g hedgequantx@latest 2>/dev/null', {
193
- stdio: 'pipe',
194
- timeout: 120000,
195
- encoding: 'utf8'
149
+ stdio: 'pipe', timeout: 120000, encoding: 'utf8'
196
150
  });
197
151
  } catch (e) {
198
152
  spinner.fail('Update failed');
199
- console.log();
200
- console.log(chalk.yellow(' Try manually:'));
201
- console.log(chalk.white(' npm install -g hedgequantx@latest'));
202
- console.log();
203
- await waitForEnter();
153
+ console.log(chalk.yellow(' Try: npm install -g hedgequantx@latest'));
154
+ await prompts.waitForEnter();
204
155
  return;
205
156
  }
206
157
 
207
158
  spinner.succeed(`Updated: v${currentVersion} → v${latestVersion}`);
208
- console.log();
209
- console.log(chalk.cyan(' Restarting HedgeQuantX CLI...'));
210
- console.log();
159
+ console.log(chalk.cyan(' Restarting...'));
211
160
 
212
- // Auto restart after 2 seconds
213
161
  await new Promise(r => setTimeout(r, 2000));
214
162
 
215
- // Restart the CLI
216
163
  try {
217
- const child = spawn('hedgequantx', [], {
218
- stdio: 'inherit',
219
- detached: true,
220
- shell: true
221
- });
164
+ const child = spawn('hedgequantx', [], { stdio: 'inherit', detached: true, shell: true });
222
165
  child.unref();
223
166
  process.exit(0);
224
167
  } catch (e) {
225
- console.log(chalk.yellow(' Could not auto-restart. Please run: hedgequantx'));
226
- console.log();
227
- await waitForEnter();
168
+ console.log(chalk.yellow(' Please run: hedgequantx'));
169
+ await prompts.waitForEnter();
228
170
  }
229
171
 
230
172
  } catch (error) {
231
- // Catch-all for any unexpected errors
232
- if (spinner) {
233
- try { spinner.fail('Update error'); } catch (e) {}
234
- }
235
- console.log();
236
- console.log(chalk.red(' An error occurred during update'));
237
- if (error && error.message) {
238
- console.log(chalk.gray(` ${error.message.substring(0, 100)}`));
239
- }
240
- console.log();
241
- console.log(chalk.yellow(' Try manually: npm install -g hedgequantx@latest'));
242
- console.log();
243
- await waitForEnter();
173
+ if (spinner) spinner.fail('Update error');
174
+ console.log(chalk.yellow(' Try: npm install -g hedgequantx@latest'));
175
+ await prompts.waitForEnter();
244
176
  }
245
177
  };
246
178
 
247
- module.exports = {
248
- dashboardMenu,
249
- handleUpdate
250
- };
179
+ module.exports = { dashboardMenu, handleUpdate };
@@ -1,35 +1,30 @@
1
1
  /**
2
- * @fileoverview Accounts page
3
- * @module pages/accounts
2
+ * Accounts 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 { ACCOUNT_STATUS, ACCOUNT_TYPE } = require('../config');
12
- const { getLogoWidth, getColWidths, drawBoxHeader, drawBoxFooter, draw2ColHeader, visibleLength, padText } = require('../ui');
10
+ const { getLogoWidth, getColWidths, drawBoxHeader, drawBoxFooter, draw2ColHeader, visibleLength } = require('../ui');
11
+ const { prompts } = require('../utils');
13
12
 
14
13
  /**
15
- * Shows all accounts from all connections
16
- * @param {Object} service - Current service
14
+ * Show all accounts
17
15
  */
18
16
  const showAccounts = async (service) => {
19
17
  const spinner = ora({ text: 'Fetching accounts...', color: 'yellow' }).start();
20
18
  const boxWidth = getLogoWidth();
21
19
  const { col1, col2 } = getColWidths(boxWidth);
22
20
 
23
- // Helper for row formatting
24
21
  const fmtRow = (label, value, colW) => {
25
22
  const labelStr = ' ' + label.padEnd(12);
26
23
  const valueVisible = visibleLength(value || '');
27
- const totalVisible = labelStr.length + valueVisible;
28
- const padding = Math.max(0, colW - totalVisible);
24
+ const padding = Math.max(0, colW - labelStr.length - valueVisible);
29
25
  return chalk.white(labelStr) + value + ' '.repeat(padding);
30
26
  };
31
27
 
32
- // Get accounts from all connections
33
28
  let allAccounts = [];
34
29
 
35
30
  if (connections.count() > 0) {
@@ -38,14 +33,10 @@ const showAccounts = async (service) => {
38
33
  const result = await conn.service.getTradingAccounts();
39
34
  if (result.success && result.accounts) {
40
35
  result.accounts.forEach(account => {
41
- allAccounts.push({
42
- ...account,
43
- propfirm: conn.propfirm || conn.type,
44
- service: conn.service
45
- });
36
+ allAccounts.push({ ...account, propfirm: conn.propfirm || conn.type, service: conn.service });
46
37
  });
47
38
  }
48
- } catch (e) { /* ignore */ }
39
+ } catch (e) {}
49
40
  }
50
41
  } else if (service) {
51
42
  const result = await service.getTradingAccounts();
@@ -56,7 +47,7 @@ const showAccounts = async (service) => {
56
47
 
57
48
  if (allAccounts.length === 0) {
58
49
  spinner.fail('No accounts found');
59
- await inquirer.prompt([{ type: 'input', name: 'c', message: 'Press Enter to continue...' }]);
50
+ await prompts.waitForEnter();
60
51
  return;
61
52
  }
62
53
 
@@ -65,7 +56,6 @@ const showAccounts = async (service) => {
65
56
 
66
57
  drawBoxHeader('TRADING ACCOUNTS', boxWidth);
67
58
 
68
- // Display accounts 2 per row
69
59
  for (let i = 0; i < allAccounts.length; i += 2) {
70
60
  const acc1 = allAccounts[i];
71
61
  const acc2 = allAccounts[i + 1];
@@ -80,7 +70,7 @@ const showAccounts = async (service) => {
80
70
  const pf2 = acc2 ? chalk.magenta(acc2.propfirm || 'Unknown') : '';
81
71
  console.log(chalk.cyan('║') + fmtRow('PropFirm:', pf1, col1) + chalk.cyan('│') + (acc2 ? fmtRow('PropFirm:', pf2, col2) : ' '.repeat(col2)) + chalk.cyan('║'));
82
72
 
83
- // Balance - show '--' if null (not available from API)
73
+ // Balance
84
74
  const bal1 = acc1.balance;
85
75
  const bal2 = acc2 ? acc2.balance : null;
86
76
  const balStr1 = bal1 !== null && bal1 !== undefined ? '$' + bal1.toLocaleString() : '--';
@@ -99,10 +89,9 @@ const showAccounts = async (service) => {
99
89
  const type2 = acc2 ? (ACCOUNT_TYPE[acc2.type] || { text: 'Unknown', color: 'white' }) : null;
100
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('║'));
101
91
 
102
- // Account ID
92
+ // ID
103
93
  console.log(chalk.cyan('║') + fmtRow('ID:', chalk.gray(acc1.accountId), col1) + chalk.cyan('│') + (acc2 ? fmtRow('ID:', chalk.gray(acc2.accountId), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
104
94
 
105
- // Separator between pairs
106
95
  if (i + 2 < allAccounts.length) {
107
96
  console.log(chalk.cyan('╠') + chalk.cyan('═'.repeat(col1)) + chalk.cyan('╪') + chalk.cyan('═'.repeat(col2)) + chalk.cyan('╣'));
108
97
  }
@@ -111,7 +100,7 @@ const showAccounts = async (service) => {
111
100
  drawBoxFooter(boxWidth);
112
101
  console.log();
113
102
 
114
- await inquirer.prompt([{ type: 'input', name: 'c', message: 'Press Enter to continue...' }]);
103
+ await prompts.waitForEnter();
115
104
  };
116
105
 
117
106
  module.exports = { showAccounts };