hedgequantx 1.5.5 → 1.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "1.5.5",
3
+ "version": "1.5.7",
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": {
package/src/app.js CHANGED
@@ -259,35 +259,26 @@ const mainMenu = async () => {
259
259
  console.log(chalk.cyan('║') + leftText + ' '.repeat(Math.max(0, leftPad)) + rightText + ' '.repeat(Math.max(0, rightPad)) + chalk.cyan('║'));
260
260
  };
261
261
 
262
- menuRow(chalk.cyan('[1] ProjectX'), chalk.cyan('[2] Rithmic'));
263
- menuRow(chalk.cyan('[3] Tradovate'), chalk.red('[X] Exit'));
264
-
265
262
  console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
266
263
  console.log();
267
264
 
265
+ // Use list type - more stable stdin handling
268
266
  const { action } = await inquirer.prompt([
269
267
  {
270
- type: 'input',
268
+ type: 'list',
271
269
  name: 'action',
272
- message: chalk.cyan('Enter choice (1/2/3/X):'),
273
- validate: (input) => {
274
- const valid = ['1', '2', '3', 'x', 'X'];
275
- if (valid.includes(input)) return true;
276
- return 'Please enter 1, 2, 3 or X';
277
- }
270
+ message: chalk.cyan('Select platform:'),
271
+ choices: [
272
+ { name: chalk.cyan('[1] ProjectX'), value: 'projectx' },
273
+ { name: chalk.cyan('[2] Rithmic'), value: 'rithmic' },
274
+ { name: chalk.cyan('[3] Tradovate'), value: 'tradovate' },
275
+ { name: chalk.red('[X] Exit'), value: 'exit' }
276
+ ],
277
+ loop: false
278
278
  }
279
279
  ]);
280
280
 
281
- // Map input to action
282
- const actionMap = {
283
- '1': 'projectx',
284
- '2': 'rithmic',
285
- '3': 'tradovate',
286
- 'x': 'exit',
287
- 'X': 'exit'
288
- };
289
-
290
- return actionMap[action] || 'exit';
281
+ return action;
291
282
  };
292
283
 
293
284
  /**
@@ -106,44 +106,28 @@ const dashboardMenu = async (service) => {
106
106
  console.log(chalk.cyan('║') + ' ' + left + leftPad + ' ' + (right || '') + rightPad + chalk.cyan('║'));
107
107
  };
108
108
 
109
- menuRow(chalk.cyan('[1] View Accounts'), chalk.cyan('[2] View Stats'));
110
- menuRow(chalk.cyan('[+] Add Prop-Account'), chalk.cyan('[A] Algo-Trading'));
111
- menuRow(chalk.yellow('[U] Update HQX'), chalk.red('[X] Disconnect'));
112
-
113
109
  console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
114
110
  console.log();
115
111
 
116
- // Ensure stdin is ready before prompt
117
- prepareStdin();
118
- await new Promise(resolve => setTimeout(resolve, 100));
119
-
112
+ // Use list type instead of input - more stable stdin handling
120
113
  const { action } = await inquirer.prompt([
121
114
  {
122
- type: 'input',
115
+ type: 'list',
123
116
  name: 'action',
124
- message: chalk.cyan('Enter choice (1/2/+/A/U/X):'),
125
- validate: (input) => {
126
- const valid = ['1', '2', '+', 'a', 'A', 'u', 'U', 'x', 'X'];
127
- if (valid.includes(input)) return true;
128
- return 'Please enter a valid option';
129
- }
117
+ message: chalk.cyan('Select action:'),
118
+ choices: [
119
+ { name: chalk.cyan('[1] View Accounts'), value: 'accounts' },
120
+ { name: chalk.cyan('[2] View Stats'), value: 'stats' },
121
+ { name: chalk.cyan('[+] Add Prop-Account'), value: 'add_prop_account' },
122
+ { name: chalk.magenta('[A] Algo-Trading'), value: 'algotrading' },
123
+ { name: chalk.yellow('[U] Update HQX'), value: 'update' },
124
+ { name: chalk.red('[X] Disconnect'), value: 'disconnect' }
125
+ ],
126
+ loop: false
130
127
  }
131
128
  ]);
132
129
 
133
- // Map input to action
134
- const actionMap = {
135
- '1': 'accounts',
136
- '2': 'stats',
137
- '+': 'add_prop_account',
138
- 'a': 'algotrading',
139
- 'A': 'algotrading',
140
- 'u': 'update',
141
- 'U': 'update',
142
- 'x': 'disconnect',
143
- 'X': 'disconnect'
144
- };
145
-
146
- return actionMap[action] || 'accounts';
130
+ return action;
147
131
  };
148
132
 
149
133
  /**
@@ -136,33 +136,41 @@ const copyTradingMenu = async () => {
136
136
  console.log();
137
137
  console.log(chalk.cyan(' Step 5: Configure Parameters'));
138
138
 
139
- const { leadContracts } = await inquirer.prompt([{
140
- type: 'number',
141
- name: 'leadContracts',
139
+ const { leadContractsInput } = await inquirer.prompt([{
140
+ type: 'input',
141
+ name: 'leadContractsInput',
142
142
  message: 'Lead contracts:',
143
- default: 1
143
+ default: '1',
144
+ validate: v => !isNaN(parseInt(v)) && parseInt(v) > 0 ? true : 'Enter a positive number'
144
145
  }]);
146
+ const leadContracts = parseInt(leadContractsInput) || 1;
145
147
 
146
- const { followerContracts } = await inquirer.prompt([{
147
- type: 'number',
148
- name: 'followerContracts',
148
+ const { followerContractsInput } = await inquirer.prompt([{
149
+ type: 'input',
150
+ name: 'followerContractsInput',
149
151
  message: 'Follower contracts:',
150
- default: leadContracts
152
+ default: String(leadContracts),
153
+ validate: v => !isNaN(parseInt(v)) && parseInt(v) > 0 ? true : 'Enter a positive number'
151
154
  }]);
155
+ const followerContracts = parseInt(followerContractsInput) || leadContracts;
152
156
 
153
- const { dailyTarget } = await inquirer.prompt([{
154
- type: 'number',
155
- name: 'dailyTarget',
157
+ const { dailyTargetInput } = await inquirer.prompt([{
158
+ type: 'input',
159
+ name: 'dailyTargetInput',
156
160
  message: 'Daily target ($):',
157
- default: 400
161
+ default: '400',
162
+ validate: v => !isNaN(parseInt(v)) && parseInt(v) > 0 ? true : 'Enter a positive number'
158
163
  }]);
164
+ const dailyTarget = parseInt(dailyTargetInput) || 400;
159
165
 
160
- const { maxRisk } = await inquirer.prompt([{
161
- type: 'number',
162
- name: 'maxRisk',
166
+ const { maxRiskInput } = await inquirer.prompt([{
167
+ type: 'input',
168
+ name: 'maxRiskInput',
163
169
  message: 'Max risk ($):',
164
- default: 200
170
+ default: '200',
171
+ validate: v => !isNaN(parseInt(v)) && parseInt(v) > 0 ? true : 'Enter a positive number'
165
172
  }]);
173
+ const maxRisk = parseInt(maxRiskInput) || 200;
166
174
 
167
175
  // Step 6: Privacy
168
176
  const { showNames } = await inquirer.prompt([{
@@ -146,11 +146,15 @@ class AlgoUI {
146
146
 
147
147
  this._line(chalk.cyan(GT));
148
148
 
149
- // Row 1: Account | Symbol
150
- const r1c1 = buildCell('Account', stats.accountName || 'N/A', chalk.cyan, colL);
151
- const r1c2t = ` Symbol: ${chalk.yellow(stats.symbol || 'N/A')} Qty: ${chalk.cyan(stats.contracts || 1)}`;
152
- const r1c2p = ` Symbol: ${stats.symbol || 'N/A'} Qty: ${stats.contracts || 1}`;
153
- row(r1c1.padded, r1c2t + pad(colR - r1c2p.length));
149
+ // Row 1: Account | Symbol (truncate long values)
150
+ const accName = (stats.accountName || 'N/A').substring(0, 35);
151
+ const symName = (stats.symbol || 'N/A').substring(0, 25);
152
+ const qtyStr = stats.contracts || '1/1';
153
+
154
+ const r1c1 = buildCell('Account', accName, chalk.cyan, colL);
155
+ const r1c2t = ` Symbol: ${chalk.yellow(symName)} Qty: ${chalk.cyan(qtyStr)}`;
156
+ const r1c2p = ` Symbol: ${symName} Qty: ${qtyStr}`;
157
+ row(r1c1.padded, r1c2t + pad(Math.max(0, colR - r1c2p.length)));
154
158
 
155
159
  this._line(chalk.cyan(GM));
156
160
 
@@ -193,9 +197,9 @@ class AlgoUI {
193
197
  this._line(chalk.cyan(BOX.V) + chalk.white(left) + ' '.repeat(midPad) + chalk.cyan(mid) + ' '.repeat(space - midPad - mid.length) + chalk.yellow(right) + chalk.cyan(BOX.V));
194
198
  this._line(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
195
199
 
196
- // Logs (oldest at top, newest at bottom - like a terminal)
197
- // Take the last maxLogs entries (most recent), keep chronological order
198
- const visible = logs.slice(-maxLogs);
200
+ // Logs: newest at top, oldest at bottom
201
+ // Take the last maxLogs entries and reverse for display
202
+ const visible = logs.slice(-maxLogs).reverse();
199
203
 
200
204
  if (visible.length === 0) {
201
205
  this._line(chalk.cyan(BOX.V) + chalk.gray(fitToWidth(' Waiting for activity...', W)) + chalk.cyan(BOX.V));
@@ -203,17 +207,17 @@ class AlgoUI {
203
207
  this._line(chalk.cyan(BOX.V) + ' '.repeat(W) + chalk.cyan(BOX.V));
204
208
  }
205
209
  } else {
206
- // First draw empty lines for padding (so logs stick to bottom)
207
- for (let i = visible.length; i < maxLogs; i++) {
208
- this._line(chalk.cyan(BOX.V) + ' '.repeat(W) + chalk.cyan(BOX.V));
209
- }
210
- // Then draw logs (oldest first, newest last/at bottom)
210
+ // Draw logs (newest first at top)
211
211
  visible.forEach(log => {
212
212
  const color = LOG_COLORS[log.type] || chalk.white;
213
213
  const icon = LOG_ICONS[log.type] || LOG_ICONS.info;
214
214
  const line = ` [${log.timestamp}] ${icon} ${log.message}`;
215
215
  this._line(chalk.cyan(BOX.V) + color(fitToWidth(line, W)) + chalk.cyan(BOX.V));
216
216
  });
217
+ // Pad remaining lines at bottom
218
+ for (let i = visible.length; i < maxLogs; i++) {
219
+ this._line(chalk.cyan(BOX.V) + ' '.repeat(W) + chalk.cyan(BOX.V));
220
+ }
217
221
  }
218
222
 
219
223
  // Bottom border