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,87 +1,56 @@
1
1
  /**
2
2
  * Connection Menus - PropFirm platform selection and login
3
- * Handles ProjectX, Rithmic, and Tradovate connections
4
3
  */
5
4
 
6
5
  const chalk = require('chalk');
7
- const inquirer = require('inquirer');
8
6
  const ora = require('ora');
9
7
 
10
8
  const { ProjectXService, connections } = require('../services');
11
9
  const { RithmicService } = require('../services/rithmic');
12
10
  const { TradovateService } = require('../services/tradovate');
13
11
  const { getPropFirmsByPlatform } = require('../config');
14
- const { getDevice, getLogoWidth, centerText, prepareStdin } = require('../ui');
12
+ const { getLogoWidth, centerText, prepareStdin } = require('../ui');
15
13
  const { validateUsername, validatePassword } = require('../security');
14
+ const { prompts } = require('../utils');
16
15
 
17
16
  /**
18
- * Login prompt with validation
19
- * @param {string} propfirmName - PropFirm display name
20
- * @returns {Promise<{username: string, password: string}>}
17
+ * Login prompt
21
18
  */
22
19
  const loginPrompt = async (propfirmName) => {
23
20
  prepareStdin();
24
- const device = getDevice();
25
21
  console.log();
26
22
  console.log(chalk.cyan(`Connecting to ${propfirmName}...`));
27
23
  console.log();
28
24
 
29
- const credentials = await inquirer.prompt([
30
- {
31
- type: 'input',
32
- name: 'username',
33
- message: chalk.white.bold('Username:'),
34
- validate: (input) => {
35
- try {
36
- validateUsername(input);
37
- return true;
38
- } catch (e) {
39
- return e.message;
40
- }
41
- }
42
- },
43
- {
44
- type: 'password',
45
- name: 'password',
46
- message: chalk.white.bold('Password:'),
47
- mask: '*',
48
- validate: (input) => {
49
- try {
50
- validatePassword(input);
51
- return true;
52
- } catch (e) {
53
- return e.message;
54
- }
55
- }
56
- }
57
- ]);
25
+ const username = await prompts.textInput('Username:', '', (input) => {
26
+ try { validateUsername(input); return undefined; } catch (e) { return e.message; }
27
+ });
28
+ if (!username) return null;
29
+
30
+ const pwd = await prompts.passwordInput('Password:', (input) => {
31
+ try { validatePassword(input); return undefined; } catch (e) { return e.message; }
32
+ });
33
+ if (!pwd) return null;
58
34
 
59
- return credentials;
35
+ return { username, password: pwd };
60
36
  };
61
37
 
62
38
  /**
63
- * ProjectX platform connection menu
39
+ * ProjectX menu
64
40
  */
65
41
  const projectXMenu = async () => {
66
42
  const propfirms = getPropFirmsByPlatform('ProjectX');
67
43
  const boxWidth = getLogoWidth();
68
- const W = boxWidth - 2; // Inner width
44
+ const W = boxWidth - 2;
69
45
  const col1Width = Math.floor(W / 2);
70
46
 
71
- // Build numbered list
72
- const numbered = propfirms.map((pf, i) => ({
73
- num: i + 1,
74
- key: pf.key,
75
- name: pf.displayName
76
- }));
47
+ const numbered = propfirms.map((pf, i) => ({ num: i + 1, key: pf.key, name: pf.displayName }));
77
48
 
78
- // PropFirm selection box
79
49
  console.log();
80
50
  console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
81
51
  console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM (ProjectX)', W)) + chalk.cyan('║'));
82
52
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
83
53
 
84
- // Display in 2 columns
85
54
  const menuRow = (left, right) => {
86
55
  const leftPlain = left ? left.replace(/\x1b\[[0-9;]*m/g, '') : '';
87
56
  const rightPlain = right ? right.replace(/\x1b\[[0-9;]*m/g, '') : '';
@@ -90,7 +59,6 @@ const projectXMenu = async () => {
90
59
  console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
91
60
  };
92
61
 
93
- // Display propfirms in 2 columns
94
62
  for (let i = 0; i < numbered.length; i += 2) {
95
63
  const left = numbered[i];
96
64
  const right = numbered[i + 1];
@@ -99,31 +67,20 @@ const projectXMenu = async () => {
99
67
  menuRow(leftText, rightText);
100
68
  }
101
69
 
102
- // Back option
103
70
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
104
- const backLine = ' ' + chalk.red('[X] Back') + ' '.repeat(W - 10);
105
- console.log(chalk.cyan('║') + backLine + chalk.cyan('║'));
71
+ console.log(chalk.cyan('║') + ' ' + chalk.red('[X] Back') + ' '.repeat(W - 10) + chalk.cyan('║'));
106
72
  console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
107
- console.log();
108
-
109
- const { action } = await inquirer.prompt([
110
- {
111
- type: 'input',
112
- name: 'action',
113
- message: chalk.cyan('Select:'),
114
- prefix: ''
115
- }
116
- ]);
117
73
 
118
- const input = (action || '').toLowerCase().trim();
119
- if (input === 'x') return null;
74
+ const options = numbered.map(pf => ({ value: pf.num, label: `[${pf.num}] ${pf.name}` }));
75
+ options.push({ value: -1, label: '[X] Back' });
120
76
 
121
- const selectedIdx = parseInt(input) - 1;
122
- if (isNaN(selectedIdx) || selectedIdx < 0 || selectedIdx >= numbered.length) return null;
77
+ const action = await prompts.selectOption('Select:', options);
78
+ if (!action || action === -1) return null;
123
79
 
124
- const selectedPropfirm = numbered[selectedIdx];
125
-
80
+ const selectedPropfirm = numbered[action - 1];
126
81
  const credentials = await loginPrompt(selectedPropfirm.name);
82
+ if (!credentials) return null;
83
+
127
84
  const spinner = ora({ text: 'Authenticating...', color: 'yellow' }).start();
128
85
 
129
86
  try {
@@ -146,7 +103,7 @@ const projectXMenu = async () => {
146
103
  };
147
104
 
148
105
  /**
149
- * Rithmic platform connection menu
106
+ * Rithmic menu
150
107
  */
151
108
  const rithmicMenu = async () => {
152
109
  const propfirms = getPropFirmsByPlatform('Rithmic');
@@ -155,23 +112,14 @@ const rithmicMenu = async () => {
155
112
  const numCols = 3;
156
113
  const colWidth = Math.floor(innerWidth / numCols);
157
114
 
158
- // Build numbered list
159
- const numbered = propfirms.map((pf, i) => ({
160
- num: i + 1,
161
- key: pf.key,
162
- name: pf.displayName,
163
- systemName: pf.rithmicSystem
164
- }));
115
+ const numbered = propfirms.map((pf, i) => ({ num: i + 1, key: pf.key, name: pf.displayName, systemName: pf.rithmicSystem }));
165
116
 
166
- // PropFirm selection box
167
117
  console.log();
168
118
  console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
169
119
  console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM (RITHMIC)', innerWidth)) + chalk.cyan('║'));
170
120
  console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
171
121
 
172
- // Display in 3 columns with fixed width alignment
173
122
  const rows = Math.ceil(numbered.length / numCols);
174
-
175
123
  for (let row = 0; row < rows; row++) {
176
124
  let line = '';
177
125
  for (let col = 0; col < numCols; col++) {
@@ -181,45 +129,29 @@ const rithmicMenu = async () => {
181
129
  const numStr = item.num.toString().padStart(2, ' ');
182
130
  const coloredText = chalk.cyan(`[${numStr}]`) + ' ' + chalk.white(item.name);
183
131
  const textLen = 4 + 1 + item.name.length;
184
- const padding = colWidth - textLen - 2;
185
- line += ' ' + coloredText + ' '.repeat(Math.max(0, padding));
132
+ line += ' ' + coloredText + ' '.repeat(Math.max(0, colWidth - textLen - 2));
186
133
  } else {
187
134
  line += ' '.repeat(colWidth);
188
135
  }
189
136
  }
190
137
  const lineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
191
- const adjust = innerWidth - lineLen;
192
- console.log(chalk.cyan('║') + line + ' '.repeat(Math.max(0, adjust)) + chalk.cyan('║'));
138
+ console.log(chalk.cyan('║') + line + ' '.repeat(Math.max(0, innerWidth - lineLen)) + chalk.cyan('║'));
193
139
  }
194
140
 
195
141
  console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
196
- const backText = ' ' + chalk.red('[X] Back');
197
- const backLen = '[X] Back'.length + 2;
198
- console.log(chalk.cyan('║') + backText + ' '.repeat(innerWidth - backLen) + chalk.cyan('║'));
142
+ console.log(chalk.cyan('║') + ' ' + chalk.red('[X] Back') + ' '.repeat(innerWidth - 10) + chalk.cyan('║'));
199
143
  console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
200
- console.log();
201
144
 
202
- const validInputs = numbered.map(n => n.num.toString());
203
- validInputs.push('x', 'X');
145
+ const options = numbered.map(pf => ({ value: pf.num, label: `[${pf.num}] ${pf.name}` }));
146
+ options.push({ value: -1, label: '[X] Back' });
204
147
 
205
- const { action } = await inquirer.prompt([
206
- {
207
- type: 'input',
208
- name: 'action',
209
- message: chalk.cyan(`Enter choice (1-${numbered.length}/X):`),
210
- validate: (input) => {
211
- if (validInputs.includes(input)) return true;
212
- return `Please enter 1-${numbered.length} or X`;
213
- }
214
- }
215
- ]);
216
-
217
- if (action.toLowerCase() === 'x') return null;
148
+ const action = await prompts.selectOption('Select:', options);
149
+ if (!action || action === -1) return null;
218
150
 
219
- const selectedIdx = parseInt(action) - 1;
220
- const selectedPropfirm = numbered[selectedIdx];
221
-
151
+ const selectedPropfirm = numbered[action - 1];
222
152
  const credentials = await loginPrompt(selectedPropfirm.name);
153
+ if (!credentials) return null;
154
+
223
155
  const spinner = ora({ text: 'Connecting to Rithmic...', color: 'yellow' }).start();
224
156
 
225
157
  try {
@@ -229,11 +161,8 @@ const rithmicMenu = async () => {
229
161
  if (result.success) {
230
162
  spinner.text = 'Fetching accounts...';
231
163
  const accResult = await service.getTradingAccounts();
232
-
233
164
  connections.add('rithmic', service, service.propfirm.name);
234
165
  spinner.succeed(`Connected to ${service.propfirm.name} (${accResult.accounts?.length || 0} accounts)`);
235
-
236
- // Small pause to see the success message
237
166
  await new Promise(r => setTimeout(r, 1500));
238
167
  return service;
239
168
  } else {
@@ -249,27 +178,20 @@ const rithmicMenu = async () => {
249
178
  };
250
179
 
251
180
  /**
252
- * Tradovate platform connection menu
181
+ * Tradovate menu
253
182
  */
254
183
  const tradovateMenu = async () => {
255
184
  const propfirms = getPropFirmsByPlatform('Tradovate');
256
185
  const boxWidth = getLogoWidth();
257
186
  const innerWidth = boxWidth - 2;
258
187
 
259
- // Build numbered list
260
- const numbered = propfirms.map((pf, i) => ({
261
- num: i + 1,
262
- key: pf.key,
263
- name: pf.displayName
264
- }));
188
+ const numbered = propfirms.map((pf, i) => ({ num: i + 1, key: pf.key, name: pf.displayName }));
265
189
 
266
- // PropFirm selection box
267
190
  console.log();
268
191
  console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
269
192
  console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM (TRADOVATE)', innerWidth)) + chalk.cyan('║'));
270
193
  console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
271
194
 
272
- // Display propfirms
273
195
  for (const item of numbered) {
274
196
  const numStr = item.num.toString().padStart(2, ' ');
275
197
  const text = ' ' + chalk.cyan(`[${numStr}]`) + ' ' + chalk.white(item.name);
@@ -278,33 +200,19 @@ const tradovateMenu = async () => {
278
200
  }
279
201
 
280
202
  console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
281
- const backText = ' ' + chalk.red('[X] Back');
282
- const backLen = '[X] Back'.length + 2;
283
- console.log(chalk.cyan('║') + backText + ' '.repeat(innerWidth - backLen) + chalk.cyan('║'));
203
+ console.log(chalk.cyan('║') + ' ' + chalk.red('[X] Back') + ' '.repeat(innerWidth - 10) + chalk.cyan('║'));
284
204
  console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
285
- console.log();
286
205
 
287
- const validInputs = numbered.map(n => n.num.toString());
288
- validInputs.push('x', 'X');
206
+ const options = numbered.map(pf => ({ value: pf.num, label: `[${pf.num}] ${pf.name}` }));
207
+ options.push({ value: -1, label: '[X] Back' });
289
208
 
290
- const { action } = await inquirer.prompt([
291
- {
292
- type: 'input',
293
- name: 'action',
294
- message: chalk.cyan(`Enter choice (1-${numbered.length}/X):`),
295
- validate: (input) => {
296
- if (validInputs.includes(input)) return true;
297
- return `Please enter 1-${numbered.length} or X`;
298
- }
299
- }
300
- ]);
301
-
302
- if (action.toLowerCase() === 'x') return null;
209
+ const action = await prompts.selectOption('Select:', options);
210
+ if (!action || action === -1) return null;
303
211
 
304
- const selectedIdx = parseInt(action) - 1;
305
- const selectedPropfirm = numbered[selectedIdx];
306
-
212
+ const selectedPropfirm = numbered[action - 1];
307
213
  const credentials = await loginPrompt(selectedPropfirm.name);
214
+ if (!credentials) return null;
215
+
308
216
  const spinner = ora({ text: 'Connecting to Tradovate...', color: 'yellow' }).start();
309
217
 
310
218
  try {
@@ -314,7 +222,6 @@ const tradovateMenu = async () => {
314
222
  if (result.success) {
315
223
  spinner.text = 'Fetching accounts...';
316
224
  await service.getTradingAccounts();
317
-
318
225
  connections.add('tradovate', service, service.propfirm.name);
319
226
  spinner.succeed(`Connected to ${service.propfirm.name}`);
320
227
  return service;
@@ -329,11 +236,11 @@ const tradovateMenu = async () => {
329
236
  };
330
237
 
331
238
  /**
332
- * Add Prop Account menu (select platform)
239
+ * Add Prop Account menu
333
240
  */
334
241
  const addPropAccountMenu = async () => {
335
242
  const boxWidth = getLogoWidth();
336
- const W = boxWidth - 2; // Inner width
243
+ const W = boxWidth - 2;
337
244
  const col1Width = Math.floor(W / 2);
338
245
 
339
246
  console.log();
@@ -353,32 +260,13 @@ const addPropAccountMenu = async () => {
353
260
  menuRow(chalk.cyan('[3] Tradovate'), chalk.red('[X] Back'));
354
261
 
355
262
  console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
356
- console.log();
357
263
 
358
- const { action } = await inquirer.prompt([
359
- {
360
- type: 'input',
361
- name: 'action',
362
- message: chalk.cyan('Select:'),
363
- prefix: ''
364
- }
264
+ return await prompts.selectOption('Select:', [
265
+ { value: 'projectx', label: '[1] ProjectX' },
266
+ { value: 'rithmic', label: '[2] Rithmic' },
267
+ { value: 'tradovate', label: '[3] Tradovate' },
268
+ { value: null, label: '[X] Back' }
365
269
  ]);
366
-
367
- const input = (action || '').toLowerCase().trim();
368
- const actionMap = {
369
- '1': 'projectx',
370
- '2': 'rithmic',
371
- '3': 'tradovate',
372
- 'x': null
373
- };
374
-
375
- return actionMap[input] || null;
376
270
  };
377
271
 
378
- module.exports = {
379
- loginPrompt,
380
- projectXMenu,
381
- rithmicMenu,
382
- tradovateMenu,
383
- addPropAccountMenu
384
- };
272
+ module.exports = { loginPrompt, projectXMenu, rithmicMenu, tradovateMenu, addPropAccountMenu };