hedgequantx 1.7.5 → 1.7.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.
@@ -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 };
@@ -1,18 +1,15 @@
1
1
  /**
2
- * Copy Trading Mode - Mirror trades from Lead to Follower
3
- * Lightweight - UI + HQX Server handles all execution
2
+ * Copy Trading Mode
4
3
  */
5
4
 
6
5
  const chalk = require('chalk');
7
6
  const ora = require('ora');
8
- const inquirer = require('inquirer');
9
7
  const readline = require('readline');
10
8
 
11
9
  const { connections } = require('../../services');
12
10
  const { HQXServerService } = require('../../services/hqx-server');
13
- const { FUTURES_SYMBOLS } = require('../../config');
14
11
  const { AlgoUI } = require('./ui');
15
- const { logger } = require('../../utils');
12
+ const { logger, prompts } = require('../../utils');
16
13
 
17
14
  const log = logger.scope('CopyTrading');
18
15
 
@@ -22,14 +19,13 @@ const log = logger.scope('CopyTrading');
22
19
  const copyTradingMenu = async () => {
23
20
  log.info('Copy Trading menu opened');
24
21
  const allConns = connections.getAll();
25
- log.debug('Connections found', { count: allConns.length });
26
22
 
27
23
  if (allConns.length < 2) {
28
24
  console.log();
29
25
  console.log(chalk.yellow(' Copy Trading requires 2 connected accounts'));
30
26
  console.log(chalk.gray(' Connect to another PropFirm first'));
31
27
  console.log();
32
- await inquirer.prompt([{ type: 'input', name: 'c', message: 'Press Enter...' }]);
28
+ await prompts.waitForEnter();
33
29
  return;
34
30
  }
35
31
 
@@ -37,7 +33,6 @@ const copyTradingMenu = async () => {
37
33
  console.log(chalk.magenta.bold(' Copy Trading Setup'));
38
34
  console.log();
39
35
 
40
- // Get all active accounts from all connections
41
36
  const spinner = ora({ text: 'Fetching accounts...', color: 'yellow' }).start();
42
37
 
43
38
  const allAccounts = [];
@@ -47,86 +42,54 @@ const copyTradingMenu = async () => {
47
42
  if (result.success && result.accounts) {
48
43
  const active = result.accounts.filter(a => a.status === 0);
49
44
  for (const acc of active) {
50
- allAccounts.push({
51
- account: acc,
52
- service: conn.service,
53
- propfirm: conn.propfirm,
54
- type: conn.type
55
- });
45
+ allAccounts.push({ account: acc, service: conn.service, propfirm: conn.propfirm, type: conn.type });
56
46
  }
57
47
  }
58
- } catch (e) { /* ignore */ }
48
+ } catch (e) {}
59
49
  }
60
50
 
61
51
  if (allAccounts.length < 2) {
62
52
  spinner.fail('Need at least 2 active accounts');
63
- await inquirer.prompt([{ type: 'input', name: 'c', message: 'Press Enter...' }]);
53
+ await prompts.waitForEnter();
64
54
  return;
65
55
  }
66
56
 
67
57
  spinner.succeed(`Found ${allAccounts.length} active accounts`);
68
- log.debug('Active accounts loaded', { count: allAccounts.length, accounts: allAccounts.map(a => ({ propfirm: a.propfirm, name: a.account.accountName })) });
69
58
 
70
59
  // Step 1: Select Lead Account
71
- console.log(chalk.cyan(' Step 1: Select LEAD Account (source of trades)'));
72
- const leadChoices = allAccounts.map((a, i) => ({
73
- name: `${a.propfirm} - ${a.account.accountName || a.account.accountId} ($${a.account.balance.toLocaleString()})`,
60
+ console.log(chalk.cyan(' Step 1: Select LEAD Account'));
61
+ const leadOptions = allAccounts.map((a, i) => ({
62
+ label: `${a.propfirm} - ${a.account.accountName || a.account.accountId} ($${a.account.balance.toLocaleString()})`,
74
63
  value: i
75
64
  }));
76
- leadChoices.push({ name: chalk.yellow('< Cancel'), value: -1 });
65
+ leadOptions.push({ label: '< Cancel', value: -1 });
77
66
 
78
- const { leadIdx } = await inquirer.prompt([{
79
- type: 'list',
80
- name: 'leadIdx',
81
- message: 'Lead Account:',
82
- choices: leadChoices
83
- }]);
84
-
85
- if (leadIdx === -1) {
86
- log.debug('User cancelled at lead selection');
87
- return;
88
- }
67
+ const leadIdx = await prompts.selectOption('Lead Account:', leadOptions);
68
+ if (leadIdx === null || leadIdx === -1) return;
89
69
  const lead = allAccounts[leadIdx];
90
- log.debug('Lead account selected', { propfirm: lead.propfirm, account: lead.account.accountName });
91
70
 
92
71
  // Step 2: Select Follower Account
93
72
  console.log();
94
- console.log(chalk.cyan(' Step 2: Select FOLLOWER Account (copies trades)'));
95
- const followerChoices = allAccounts
73
+ console.log(chalk.cyan(' Step 2: Select FOLLOWER Account'));
74
+ const followerOptions = allAccounts
96
75
  .map((a, i) => ({ a, i }))
97
76
  .filter(x => x.i !== leadIdx)
98
77
  .map(x => ({
99
- name: `${x.a.propfirm} - ${x.a.account.accountName || x.a.account.accountId} ($${x.a.account.balance.toLocaleString()})`,
78
+ label: `${x.a.propfirm} - ${x.a.account.accountName || x.a.account.accountId} ($${x.a.account.balance.toLocaleString()})`,
100
79
  value: x.i
101
80
  }));
102
- followerChoices.push({ name: chalk.yellow('< Cancel'), value: -1 });
103
-
104
- const { followerIdx } = await inquirer.prompt([{
105
- type: 'list',
106
- name: 'followerIdx',
107
- message: 'Follower Account:',
108
- choices: followerChoices
109
- }]);
81
+ followerOptions.push({ label: '< Cancel', value: -1 });
110
82
 
111
- if (followerIdx === -1) {
112
- log.debug('User cancelled at follower selection');
113
- return;
114
- }
83
+ const followerIdx = await prompts.selectOption('Follower Account:', followerOptions);
84
+ if (followerIdx === null || followerIdx === -1) return;
115
85
  const follower = allAccounts[followerIdx];
116
- log.debug('Follower account selected', { propfirm: follower.propfirm, account: follower.account.accountName });
117
86
 
118
- // Step 3: Select Symbol for Lead
87
+ // Step 3-4: Select Symbols
119
88
  console.log();
120
89
  console.log(chalk.cyan(' Step 3: Select Symbol for LEAD'));
121
- log.debug('Selecting symbol for lead', { serviceType: lead.type });
122
90
  const leadSymbol = await selectSymbol(lead.service, 'Lead');
123
- if (!leadSymbol) {
124
- log.debug('Lead symbol selection failed or cancelled');
125
- return;
126
- }
127
- log.debug('Lead symbol selected', { symbol: leadSymbol.name || leadSymbol.symbol });
91
+ if (!leadSymbol) return;
128
92
 
129
- // Step 4: Select Symbol for Follower
130
93
  console.log();
131
94
  console.log(chalk.cyan(' Step 4: Select Symbol for FOLLOWER'));
132
95
  const followerSymbol = await selectSymbol(follower.service, 'Follower');
@@ -136,53 +99,24 @@ const copyTradingMenu = async () => {
136
99
  console.log();
137
100
  console.log(chalk.cyan(' Step 5: Configure Parameters'));
138
101
 
139
- const { leadContractsInput } = await inquirer.prompt([{
140
- type: 'input',
141
- name: 'leadContractsInput',
142
- message: 'Lead contracts:',
143
- default: '1',
144
- validate: v => !isNaN(parseInt(v)) && parseInt(v) > 0 ? true : 'Enter a positive number'
145
- }]);
146
- const leadContracts = parseInt(leadContractsInput) || 1;
147
-
148
- const { followerContractsInput } = await inquirer.prompt([{
149
- type: 'input',
150
- name: 'followerContractsInput',
151
- message: 'Follower contracts:',
152
- default: String(leadContracts),
153
- validate: v => !isNaN(parseInt(v)) && parseInt(v) > 0 ? true : 'Enter a positive number'
154
- }]);
155
- const followerContracts = parseInt(followerContractsInput) || leadContracts;
156
-
157
- const { dailyTargetInput } = await inquirer.prompt([{
158
- type: 'input',
159
- name: 'dailyTargetInput',
160
- message: 'Daily target ($):',
161
- default: '400',
162
- validate: v => !isNaN(parseInt(v)) && parseInt(v) > 0 ? true : 'Enter a positive number'
163
- }]);
164
- const dailyTarget = parseInt(dailyTargetInput) || 400;
165
-
166
- const { maxRiskInput } = await inquirer.prompt([{
167
- type: 'input',
168
- name: 'maxRiskInput',
169
- message: 'Max risk ($):',
170
- default: '200',
171
- validate: v => !isNaN(parseInt(v)) && parseInt(v) > 0 ? true : 'Enter a positive number'
172
- }]);
173
- const maxRisk = parseInt(maxRiskInput) || 200;
102
+ const leadContracts = await prompts.numberInput('Lead contracts:', 1, 1, 10);
103
+ if (leadContracts === null) return;
104
+
105
+ const followerContracts = await prompts.numberInput('Follower contracts:', leadContracts, 1, 10);
106
+ if (followerContracts === null) return;
107
+
108
+ const dailyTarget = await prompts.numberInput('Daily target ($):', 400, 1, 10000);
109
+ if (dailyTarget === null) return;
110
+
111
+ const maxRisk = await prompts.numberInput('Max risk ($):', 200, 1, 5000);
112
+ if (maxRisk === null) return;
174
113
 
175
114
  // Step 6: Privacy
176
- const { privacyChoice } = await inquirer.prompt([{
177
- type: 'list',
178
- name: 'privacyChoice',
179
- message: 'Account names:',
180
- choices: [
181
- { name: 'Hide account names', value: false },
182
- { name: 'Show account names', value: true }
183
- ]
184
- }]);
185
- const showNames = privacyChoice;
115
+ const showNames = await prompts.selectOption('Account names:', [
116
+ { label: 'Hide account names', value: false },
117
+ { label: 'Show account names', value: true }
118
+ ]);
119
+ if (showNames === null) return;
186
120
 
187
121
  // Confirm
188
122
  console.log();
@@ -192,22 +126,14 @@ const copyTradingMenu = async () => {
192
126
  console.log(chalk.gray(` Target: $${dailyTarget} | Risk: $${maxRisk}`));
193
127
  console.log();
194
128
 
195
- const { confirm } = await inquirer.prompt([{
196
- type: 'confirm',
197
- name: 'confirm',
198
- message: chalk.yellow('Start Copy Trading?'),
199
- default: true
200
- }]);
201
-
129
+ const confirm = await prompts.confirmPrompt('Start Copy Trading?', true);
202
130
  if (!confirm) return;
203
131
 
204
132
  // Launch
205
133
  await launchCopyTrading({
206
134
  lead: { ...lead, symbol: leadSymbol, contracts: leadContracts },
207
135
  follower: { ...follower, symbol: followerSymbol, contracts: followerContracts },
208
- dailyTarget,
209
- maxRisk,
210
- showNames
136
+ dailyTarget, maxRisk, showNames
211
137
  });
212
138
  };
213
139
 
@@ -215,59 +141,31 @@ const copyTradingMenu = async () => {
215
141
  * Symbol selection helper
216
142
  */
217
143
  const selectSymbol = async (service, label) => {
218
- log.debug('selectSymbol called', { label, hasGetContracts: typeof service.getContracts === 'function' });
219
144
  try {
220
145
  let contracts = [];
221
146
 
222
- // Try getContracts first
223
147
  if (typeof service.getContracts === 'function') {
224
148
  const result = await service.getContracts();
225
- log.debug('getContracts result', { success: result?.success, count: result?.contracts?.length });
226
149
  if (result.success && result.contracts?.length > 0) {
227
150
  contracts = result.contracts;
228
151
  }
229
152
  }
230
153
 
231
- // Fallback to searchContracts if no contracts yet
232
154
  if (contracts.length === 0 && typeof service.searchContracts === 'function') {
233
- log.debug('Trying searchContracts fallback');
234
- // For Rithmic, searchContracts returns array directly
235
155
  const searchResult = await service.searchContracts('ES');
236
- log.debug('searchContracts result', { result: searchResult });
237
-
238
- if (Array.isArray(searchResult)) {
239
- contracts = searchResult;
240
- } else if (searchResult?.contracts) {
241
- contracts = searchResult.contracts;
242
- }
156
+ if (Array.isArray(searchResult)) contracts = searchResult;
157
+ else if (searchResult?.contracts) contracts = searchResult.contracts;
243
158
  }
244
159
 
245
- // If still no contracts, show error
246
160
  if (!contracts || contracts.length === 0) {
247
- log.error('No contracts available');
248
- console.log(chalk.red(' No contracts available for this service'));
161
+ console.log(chalk.red(' No contracts available'));
249
162
  return null;
250
163
  }
251
164
 
252
- log.debug('Contracts loaded', { count: contracts.length });
253
-
254
- // Build choices - simple list without categories
255
- const choices = contracts.map(c => ({
256
- name: c.name || c.symbol,
257
- value: c
258
- }));
259
- choices.push(new inquirer.Separator());
260
- choices.push({ name: chalk.yellow('< Cancel'), value: null });
261
-
262
- const { symbol } = await inquirer.prompt([{
263
- type: 'list',
264
- name: 'symbol',
265
- message: `${label} Symbol:`,
266
- choices,
267
- pageSize: 15
268
- }]);
165
+ const options = contracts.map(c => ({ label: c.name || c.symbol, value: c }));
166
+ options.push({ label: '< Cancel', value: null });
269
167
 
270
- return symbol;
168
+ return await prompts.selectOption(`${label} Symbol:`, options);
271
169
  } catch (e) {
272
170
  return null;
273
171
  }
@@ -282,33 +180,23 @@ const launchCopyTrading = async (config) => {
282
180
  const leadName = showNames ? (lead.account.accountName || lead.account.accountId) : 'HQX Lead *****';
283
181
  const followerName = showNames ? (follower.account.accountName || follower.account.accountId) : 'HQX Follower *****';
284
182
 
285
- // UI with copy trading subtitle
286
183
  const ui = new AlgoUI({ subtitle: 'HQX Copy Trading' });
287
184
 
288
- // Combined stats
289
185
  const stats = {
290
- leadName,
291
- followerName,
186
+ leadName, followerName,
292
187
  leadSymbol: lead.symbol.name,
293
188
  followerSymbol: follower.symbol.name,
294
189
  leadQty: lead.contracts,
295
190
  followerQty: follower.contracts,
296
- target: dailyTarget,
297
- risk: maxRisk,
298
- pnl: 0,
299
- trades: 0,
300
- wins: 0,
301
- losses: 0,
302
- latency: 0,
303
- connected: false
191
+ target: dailyTarget, risk: maxRisk,
192
+ pnl: 0, trades: 0, wins: 0, losses: 0,
193
+ latency: 0, connected: false
304
194
  };
305
195
 
306
196
  let running = true;
307
197
  let stopReason = null;
308
198
 
309
- // Connect to HQX Server
310
199
  const hqx = new HQXServerService();
311
-
312
200
  const spinner = ora({ text: 'Connecting to HQX Server...', color: 'yellow' }).start();
313
201
 
314
202
  try {
@@ -326,7 +214,6 @@ const launchCopyTrading = async (config) => {
326
214
 
327
215
  // Event handlers
328
216
  hqx.on('latency', (d) => { stats.latency = d.latency || 0; });
329
-
330
217
  hqx.on('log', (d) => {
331
218
  let msg = d.message;
332
219
  if (!showNames) {
@@ -335,7 +222,6 @@ const launchCopyTrading = async (config) => {
335
222
  }
336
223
  ui.addLog(d.type || 'info', msg);
337
224
  });
338
-
339
225
  hqx.on('trade', (d) => {
340
226
  stats.trades++;
341
227
  stats.pnl += d.pnl || 0;
@@ -343,68 +229,47 @@ const launchCopyTrading = async (config) => {
343
229
  ui.addLog(d.pnl >= 0 ? 'trade' : 'loss', `${d.pnl >= 0 ? '+' : ''}$${d.pnl.toFixed(2)}`);
344
230
 
345
231
  if (stats.pnl >= dailyTarget) {
346
- stopReason = 'target';
347
- running = false;
232
+ stopReason = 'target'; running = false;
348
233
  ui.addLog('success', `TARGET! +$${stats.pnl.toFixed(2)}`);
349
234
  hqx.stopAlgo();
350
235
  } else if (stats.pnl <= -maxRisk) {
351
- stopReason = 'risk';
352
- running = false;
236
+ stopReason = 'risk'; running = false;
353
237
  ui.addLog('error', `MAX RISK! -$${Math.abs(stats.pnl).toFixed(2)}`);
354
238
  hqx.stopAlgo();
355
239
  }
356
240
  });
357
-
358
- hqx.on('copy', (d) => {
359
- ui.addLog('trade', `COPIED: ${d.side} ${d.quantity}x to Follower`);
360
- });
361
-
241
+ hqx.on('copy', (d) => { ui.addLog('trade', `COPIED: ${d.side} ${d.quantity}x`); });
362
242
  hqx.on('error', (d) => { ui.addLog('error', d.message); });
363
243
  hqx.on('disconnected', () => { stats.connected = false; });
364
244
 
365
- // Start copy trading on server
245
+ // Start on server
366
246
  if (stats.connected) {
367
247
  ui.addLog('info', 'Starting Copy Trading...');
368
248
 
369
- // Get credentials
370
249
  let leadCreds = null, followerCreds = null;
371
-
372
- if (lead.service.getRithmicCredentials) {
373
- leadCreds = lead.service.getRithmicCredentials();
374
- }
375
- if (follower.service.getRithmicCredentials) {
376
- followerCreds = follower.service.getRithmicCredentials();
377
- }
250
+ if (lead.service.getRithmicCredentials) leadCreds = lead.service.getRithmicCredentials();
251
+ if (follower.service.getRithmicCredentials) followerCreds = follower.service.getRithmicCredentials();
378
252
 
379
253
  hqx.startCopyTrading({
380
- // Lead config
381
254
  leadAccountId: lead.account.accountId,
382
255
  leadContractId: lead.symbol.id || lead.symbol.contractId,
383
256
  leadSymbol: lead.symbol.symbol || lead.symbol.name,
384
257
  leadContracts: lead.contracts,
385
258
  leadPropfirm: lead.propfirm,
386
- leadToken: lead.service.getToken ? lead.service.getToken() : null,
259
+ leadToken: lead.service.getToken?.() || null,
387
260
  leadRithmicCredentials: leadCreds,
388
-
389
- // Follower config
390
261
  followerAccountId: follower.account.accountId,
391
262
  followerContractId: follower.symbol.id || follower.symbol.contractId,
392
263
  followerSymbol: follower.symbol.symbol || follower.symbol.name,
393
264
  followerContracts: follower.contracts,
394
265
  followerPropfirm: follower.propfirm,
395
- followerToken: follower.service.getToken ? follower.service.getToken() : null,
266
+ followerToken: follower.service.getToken?.() || null,
396
267
  followerRithmicCredentials: followerCreds,
397
-
398
- // Targets
399
- dailyTarget,
400
- maxRisk
268
+ dailyTarget, maxRisk
401
269
  });
402
270
  }
403
271
 
404
- // UI refresh
405
- const refreshInterval = setInterval(() => {
406
- if (running) ui.render(stats);
407
- }, 250);
272
+ const refreshInterval = setInterval(() => { if (running) ui.render(stats); }, 250);
408
273
 
409
274
  // Keyboard
410
275
  const setupKeys = () => {
@@ -415,12 +280,10 @@ const launchCopyTrading = async (config) => {
415
280
 
416
281
  const handler = (str, key) => {
417
282
  if (key && (key.name === 'x' || (key.ctrl && key.name === 'c'))) {
418
- running = false;
419
- stopReason = 'manual';
283
+ running = false; stopReason = 'manual';
420
284
  }
421
285
  };
422
286
  process.stdin.on('keypress', handler);
423
-
424
287
  return () => {
425
288
  process.stdin.removeListener('keypress', handler);
426
289
  if (process.stdin.isTTY) process.stdin.setRawMode(false);
@@ -429,22 +292,13 @@ const launchCopyTrading = async (config) => {
429
292
 
430
293
  const cleanupKeys = setupKeys();
431
294
 
432
- // Wait
433
295
  await new Promise(resolve => {
434
- const check = setInterval(() => {
435
- if (!running) { clearInterval(check); resolve(); }
436
- }, 100);
296
+ const check = setInterval(() => { if (!running) { clearInterval(check); resolve(); } }, 100);
437
297
  });
438
298
 
439
- // Cleanup
440
299
  clearInterval(refreshInterval);
441
300
  if (cleanupKeys) cleanupKeys();
442
-
443
- if (stats.connected) {
444
- hqx.stopAlgo();
445
- hqx.disconnect();
446
- }
447
-
301
+ if (stats.connected) { hqx.stopAlgo(); hqx.disconnect(); }
448
302
  ui.cleanup();
449
303
 
450
304
  // Summary
@@ -454,11 +308,10 @@ const launchCopyTrading = async (config) => {
454
308
  console.log();
455
309
  console.log(chalk.white(` Stop: ${stopReason || 'unknown'}`));
456
310
  console.log(chalk.white(` Trades: ${stats.trades} (W: ${stats.wins} / L: ${stats.losses})`));
457
- const c = stats.pnl >= 0 ? chalk.green : chalk.red;
458
- console.log(c(` P&L: ${stats.pnl >= 0 ? '+' : ''}$${stats.pnl.toFixed(2)}`));
311
+ console.log((stats.pnl >= 0 ? chalk.green : chalk.red)(` P&L: ${stats.pnl >= 0 ? '+' : ''}$${stats.pnl.toFixed(2)}`));
459
312
  console.log();
460
313
 
461
- await inquirer.prompt([{ type: 'input', name: 'c', message: 'Press Enter...' }]);
314
+ await prompts.waitForEnter();
462
315
  };
463
316
 
464
317
  module.exports = { copyTradingMenu };
@@ -1,12 +1,10 @@
1
1
  /**
2
2
  * Algo Trading - Main Menu
3
- * Lightweight entry point
4
3
  */
5
4
 
6
5
  const chalk = require('chalk');
7
- const inquirer = require('inquirer');
8
6
  const { getSeparator } = require('../../ui');
9
- const { logger } = require('../../utils');
7
+ const { logger, prompts } = require('../../utils');
10
8
 
11
9
  const log = logger.scope('AlgoMenu');
12
10
 
@@ -25,22 +23,17 @@ const algoTradingMenu = async (service) => {
25
23
  console.log(chalk.gray(getSeparator()));
26
24
  console.log();
27
25
 
28
- const { action } = await inquirer.prompt([
29
- {
30
- type: 'list',
31
- name: 'action',
32
- message: chalk.white.bold('Select Mode:'),
33
- choices: [
34
- { name: chalk.cyan('One Account'), value: 'one_account' },
35
- { name: chalk.green('Copy Trading'), value: 'copy_trading' },
36
- new inquirer.Separator(),
37
- { name: chalk.yellow('< Back'), value: 'back' }
38
- ],
39
- pageSize: 10,
40
- loop: false
41
- }
26
+ const action = await prompts.selectOption('Select Mode:', [
27
+ { value: 'one_account', label: 'One Account' },
28
+ { value: 'copy_trading', label: 'Copy Trading' },
29
+ { value: 'back', label: '< Back' }
42
30
  ]);
43
31
 
32
+ if (!action || action === 'back') {
33
+ log.debug('User went back');
34
+ return 'back';
35
+ }
36
+
44
37
  log.debug('Algo mode selected', { action });
45
38
 
46
39
  switch (action) {
@@ -52,9 +45,6 @@ const algoTradingMenu = async (service) => {
52
45
  log.info('Starting Copy Trading mode');
53
46
  await copyTradingMenu();
54
47
  break;
55
- case 'back':
56
- log.debug('User went back');
57
- break;
58
48
  }
59
49
 
60
50
  return action;