hedgequantx 2.6.157 → 2.6.159

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": "2.6.157",
3
+ "version": "2.6.159",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Algo Trading Configuration
3
+ * Symbol selection and algo parameters configuration
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const chalk = require('chalk');
9
+ const ora = require('ora');
10
+ const { prompts } = require('../../utils');
11
+ const aiService = require('../../services/ai');
12
+ const { MAX_MULTI_SYMBOLS } = require('./algo-utils');
13
+
14
+ /**
15
+ * Symbol selection - sorted with popular indices first
16
+ * Supports selecting multiple symbols (up to MAX_MULTI_SYMBOLS)
17
+ * @param {Object} service - Trading service
18
+ * @param {Object} account - Trading account
19
+ * @param {boolean} allowMultiple - Allow selecting multiple symbols
20
+ * @returns {Object|Array|null} Single contract or array of contracts
21
+ */
22
+ const selectSymbol = async (service, account, allowMultiple = false) => {
23
+ const spinner = ora({ text: 'LOADING SYMBOLS...', color: 'yellow' }).start();
24
+
25
+ const contractsResult = await service.getContracts();
26
+ if (!contractsResult.success || !contractsResult.contracts?.length) {
27
+ spinner.fail('FAILED TO LOAD CONTRACTS');
28
+ return null;
29
+ }
30
+
31
+ let contracts = contractsResult.contracts;
32
+
33
+ // Sort: Popular indices first (ES, NQ, MES, MNQ, RTY, YM, etc.)
34
+ const popularPrefixes = ['ES', 'NQ', 'MES', 'MNQ', 'M2K', 'RTY', 'YM', 'MYM', 'NKD', 'GC', 'SI', 'CL'];
35
+
36
+ contracts.sort((a, b) => {
37
+ const nameA = a.name || '';
38
+ const nameB = b.name || '';
39
+
40
+ const idxA = popularPrefixes.findIndex(p => nameA.startsWith(p));
41
+ const idxB = popularPrefixes.findIndex(p => nameB.startsWith(p));
42
+
43
+ if (idxA !== -1 && idxB !== -1) return idxA - idxB;
44
+ if (idxA !== -1) return -1;
45
+ if (idxB !== -1) return 1;
46
+ return nameA.localeCompare(nameB);
47
+ });
48
+
49
+ spinner.succeed(`FOUND ${contracts.length} CONTRACTS`);
50
+
51
+ const options = contracts.map(c => {
52
+ const name = c.name || c.symbol || c.baseSymbol;
53
+ const desc = c.description || '';
54
+ const label = desc ? `${name} - ${desc}` : name;
55
+ return { label, value: c };
56
+ });
57
+
58
+ options.push({ label: chalk.gray('< BACK'), value: 'back' });
59
+
60
+ // Single symbol mode
61
+ if (!allowMultiple) {
62
+ const contract = await prompts.selectOption(chalk.yellow('SELECT SYMBOL:'), options);
63
+ return contract === 'back' || contract === null ? null : contract;
64
+ }
65
+
66
+ // Multi-symbol selection mode
67
+ const selectedContracts = [];
68
+
69
+ while (selectedContracts.length < MAX_MULTI_SYMBOLS) {
70
+ console.log();
71
+
72
+ if (selectedContracts.length > 0) {
73
+ console.log(chalk.cyan(` SELECTED (${selectedContracts.length}/${MAX_MULTI_SYMBOLS}):`));
74
+ selectedContracts.forEach((c, i) => {
75
+ const name = c.name || c.symbol;
76
+ console.log(chalk.green(` ${i + 1}. ${name} x${c.qty}`));
77
+ });
78
+ console.log();
79
+ }
80
+
81
+ const availableOptions = options.filter(opt => {
82
+ if (opt.value === 'back') return true;
83
+ const optId = opt.value.id || opt.value.symbol || opt.value.name;
84
+ return !selectedContracts.some(sc => (sc.id || sc.symbol || sc.name) === optId);
85
+ });
86
+
87
+ if (selectedContracts.length > 0) {
88
+ availableOptions.unshift({
89
+ label: chalk.green(`✓ DONE - Start trading ${selectedContracts.length} symbol${selectedContracts.length > 1 ? 's' : ''}`),
90
+ value: 'done'
91
+ });
92
+ }
93
+
94
+ const promptText = selectedContracts.length === 0
95
+ ? chalk.yellow(`SELECT SYMBOL (1/${MAX_MULTI_SYMBOLS}):`)
96
+ : chalk.yellow(`ADD SYMBOL (${selectedContracts.length + 1}/${MAX_MULTI_SYMBOLS}) OR DONE:`);
97
+
98
+ const choice = await prompts.selectOption(promptText, availableOptions);
99
+
100
+ if (choice === null || choice === 'back') {
101
+ if (selectedContracts.length === 0) return null;
102
+ selectedContracts.pop();
103
+ continue;
104
+ }
105
+
106
+ if (choice === 'done') break;
107
+
108
+ const symbolName = choice.name || choice.symbol;
109
+ const qty = await prompts.numberInput(`CONTRACTS FOR ${symbolName}:`, 1, 1, 10);
110
+ if (qty === null) continue;
111
+
112
+ choice.qty = qty;
113
+ selectedContracts.push(choice);
114
+
115
+ if (selectedContracts.length >= MAX_MULTI_SYMBOLS) {
116
+ console.log(chalk.yellow(` Maximum ${MAX_MULTI_SYMBOLS} symbols reached`));
117
+ break;
118
+ }
119
+ }
120
+
121
+ return selectedContracts.length > 0 ? selectedContracts : null;
122
+ };
123
+
124
+ /**
125
+ * Configure algo parameters
126
+ * @param {Object} account - Trading account
127
+ * @param {Object|Array} contractOrContracts - Single contract or array of contracts
128
+ */
129
+ const configureAlgo = async (account, contractOrContracts) => {
130
+ const contractList = Array.isArray(contractOrContracts) ? contractOrContracts : [contractOrContracts];
131
+ const isMultiSymbol = contractList.length > 1;
132
+
133
+ console.log();
134
+ console.log(chalk.cyan(' CONFIGURE ALGO PARAMETERS'));
135
+
136
+ if (isMultiSymbol) {
137
+ console.log(chalk.white(` Trading ${contractList.length} symbols:`));
138
+ contractList.forEach((c, i) => {
139
+ const name = c.name || c.symbol;
140
+ const qty = c.qty || 1;
141
+ console.log(chalk.yellow(` ${i + 1}. ${name} x${qty}`));
142
+ });
143
+ }
144
+ console.log();
145
+
146
+ let contracts = 1;
147
+ if (!isMultiSymbol) {
148
+ contracts = await prompts.numberInput('NUMBER OF CONTRACTS:', 1, 1, 10);
149
+ if (contracts === null) return null;
150
+ }
151
+
152
+ const dailyTarget = await prompts.numberInput('DAILY TARGET ($):', 1000, 1, 10000);
153
+ if (dailyTarget === null) return null;
154
+
155
+ const maxRisk = await prompts.numberInput('MAX RISK ($):', 500, 1, 5000);
156
+ if (maxRisk === null) return null;
157
+
158
+ const showName = await prompts.confirmPrompt('SHOW ACCOUNT NAME?', false);
159
+ if (showName === null) return null;
160
+
161
+ const aiAgents = aiService.getAgents();
162
+ let enableAI = false;
163
+
164
+ if (aiAgents.length > 0) {
165
+ console.log();
166
+ console.log(chalk.magenta(` ${aiAgents.length} AI AGENT(S) AVAILABLE:`));
167
+ aiAgents.forEach((agent, i) => {
168
+ const modelInfo = agent.model ? chalk.gray(` (${agent.model})`) : '';
169
+ console.log(chalk.white(` ${i + 1}. ${agent.name}${modelInfo}`));
170
+ });
171
+ console.log();
172
+
173
+ enableAI = await prompts.confirmPrompt('ACTIVATE AI MODELS?', true);
174
+ if (enableAI === null) return null;
175
+
176
+ if (enableAI) {
177
+ const mode = aiAgents.length >= 2 ? 'CONSENSUS' : 'INDIVIDUAL';
178
+ console.log(chalk.green(` AI MODE: ${mode} (${aiAgents.length} agent${aiAgents.length > 1 ? 's' : ''})`));
179
+ } else {
180
+ console.log(chalk.gray(' AI AGENTS DISABLED FOR THIS SESSION'));
181
+ }
182
+ }
183
+
184
+ console.log();
185
+ const confirm = await prompts.confirmPrompt('START ALGO TRADING?', true);
186
+ if (!confirm) return null;
187
+
188
+ const initSpinner = ora({ text: 'INITIALIZING ALGO TRADING...', color: 'yellow' }).start();
189
+ await new Promise(r => setTimeout(r, 500));
190
+ initSpinner.succeed('LAUNCHING ALGO...');
191
+
192
+ return { contracts, dailyTarget, maxRisk, showName, enableAI };
193
+ };
194
+
195
+ module.exports = { selectSymbol, configureAlgo };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Algo Trading Utilities
3
+ * Shared functions and constants for algo trading
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ /**
9
+ * Format price to avoid floating point errors
10
+ * @param {number} price - Raw price
11
+ * @param {number} tickSize - Tick size (default 0.25)
12
+ * @returns {string} - Formatted price string
13
+ */
14
+ const formatPrice = (price, tickSize = 0.25) => {
15
+ if (price === null || price === undefined || isNaN(price)) return '--';
16
+ const rounded = Math.round(price / tickSize) * tickSize;
17
+ const decimals = tickSize < 1 ? Math.max(0, -Math.floor(Math.log10(tickSize))) : 0;
18
+ return rounded.toFixed(decimals);
19
+ };
20
+
21
+ /**
22
+ * Check if service supports fast path (Rithmic direct)
23
+ * @param {Object} service - Trading service
24
+ * @returns {boolean}
25
+ */
26
+ const isRithmicFastPath = (service) => {
27
+ return typeof service.fastEntry === 'function' &&
28
+ typeof service.fastExit === 'function' &&
29
+ service.orderConn?.isConnected;
30
+ };
31
+
32
+ // Maximum symbols for multi-symbol trading
33
+ const MAX_MULTI_SYMBOLS = 5;
34
+
35
+ // Use HFT tick-based strategy for Rithmic
36
+ const USE_HFT_STRATEGY = true;
37
+
38
+ // Timeout for async operations
39
+ const TIMEOUT_MS = 5000;
40
+
41
+ /**
42
+ * Wrap promise with timeout
43
+ */
44
+ const withTimeout = (promise, ms) => {
45
+ return Promise.race([
46
+ promise,
47
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), ms))
48
+ ]);
49
+ };
50
+
51
+ module.exports = {
52
+ formatPrice,
53
+ isRithmicFastPath,
54
+ MAX_MULTI_SYMBOLS,
55
+ USE_HFT_STRATEGY,
56
+ TIMEOUT_MS,
57
+ withTimeout
58
+ };
@@ -28,7 +28,7 @@ const { RithmicMarketDataFeed } = require('../../services/rithmic/market-data');
28
28
  const { algoLogger } = require('./logger');
29
29
  const { recoveryMath } = require('../../services/strategy/recovery-math');
30
30
  const aiService = require('../../services/ai');
31
- const { launchMultiSymbolRithmic } = require('./one-account');
31
+ const { launchMultiSymbolRithmic, selectSymbol, configureAlgo } = require('./one-account');
32
32
  const aiClient = require('../../services/ai/client');
33
33
 
34
34
  // Strategy template that the AI will fill
@@ -391,111 +391,69 @@ const customStrategyMenu = async (service) => {
391
391
 
392
392
  /**
393
393
  * Execute trading with custom strategy
394
- * Same as one-account but uses the custom strategy class
394
+ * Uses same flow as one-account.js
395
395
  */
396
396
  async function executeWithCustomStrategy(service, StrategyClass, strategyName) {
397
- const boxWidth = Math.max(getLogoWidth(), 98);
398
- const innerWidth = boxWidth - 2;
397
+ const { connections } = require('../../services');
399
398
 
400
- // Get accounts
401
- const accountsResult = await service.getTradingAccounts();
402
- if (!accountsResult.success || !accountsResult.accounts?.length) {
399
+ // Get all accounts (same as one-account.js)
400
+ const allAccounts = await connections.getAllAccounts();
401
+ if (!allAccounts || allAccounts.length === 0) {
403
402
  console.log(chalk.red('\n No trading accounts available.\n'));
404
403
  await prompts.waitForEnter();
405
404
  return;
406
405
  }
407
-
408
- // Account selection
409
- console.log(chalk.cyan('\n SELECT ACCOUNT:\n'));
410
- accountsResult.accounts.forEach((acc, i) => {
411
- const balance = acc.balance !== null ? `$${acc.balance.toLocaleString()}` : 'N/A';
412
- const propfirm = acc.propfirm || 'Unknown';
413
- const accId = acc.rithmicAccountId || acc.accountId;
414
- console.log(chalk.cyan(` [${i + 1}]`) + chalk.yellow(` ${propfirm}`) + chalk.gray(` (${accId})`) + chalk.green(` ${balance}`));
415
- });
416
-
417
- const accChoice = await prompts.numberInput('\n ACCOUNT #:', 1, 1, accountsResult.accounts.length);
418
- if (accChoice === null) return;
419
- const account = accountsResult.accounts[accChoice - 1];
420
-
421
- // Symbol selection
422
- const symbolInput = await prompts.textInput(chalk.cyan(' SYMBOL (e.g., MES, MNQ): '));
423
- if (!symbolInput) return;
424
- const symbols = symbolInput.toUpperCase().split(',').map(s => s.trim()).filter(Boolean);
425
-
426
- // Target and Risk
427
- const dailyTarget = await prompts.numberInput(' TARGET ($):', 1000, 100, 50000);
428
- if (dailyTarget === null) return;
429
-
430
- const maxRisk = await prompts.numberInput(' MAX RISK ($):', 500, 50, 10000);
431
- if (maxRisk === null) return;
432
-
433
- // Confirm
434
- console.log(chalk.cyan('\n ╔════════════════════════════════════════════════════════════╗'));
435
- console.log(chalk.cyan(' ║') + chalk.yellow.bold(' CONFIRM SETTINGS ') + chalk.cyan('║'));
436
- console.log(chalk.cyan(' ╠════════════════════════════════════════════════════════════╣'));
437
- console.log(chalk.cyan(' ║') + chalk.white(` Strategy: ${strategyName.substring(0, 47).padEnd(47)}`) + chalk.cyan('║'));
438
- console.log(chalk.cyan(' ║') + chalk.white(` Account: ${(account.name || account.accountId).substring(0, 47).padEnd(47)}`) + chalk.cyan('║'));
439
- console.log(chalk.cyan(' ║') + chalk.white(` Symbols: ${symbols.join(', ').substring(0, 47).padEnd(47)}`) + chalk.cyan('║'));
440
- console.log(chalk.cyan(' ║') + chalk.green(` Target: $${dailyTarget.toLocaleString().padEnd(46)}`) + chalk.cyan('║'));
441
- console.log(chalk.cyan(' ║') + chalk.red(` Risk: $${maxRisk.toLocaleString().padEnd(46)}`) + chalk.cyan('║'));
442
- console.log(chalk.cyan(' ╚════════════════════════════════════════════════════════════╝\n'));
443
-
444
- const startConfirm = await prompts.textInput(chalk.cyan(' START TRADING? (Y/n): '));
445
- if (startConfirm.toLowerCase() === 'n') {
446
- console.log(chalk.yellow('\n Cancelled.\n'));
447
- await prompts.waitForEnter();
448
- return;
449
- }
450
-
451
- // Launch with custom strategy
452
- console.log(chalk.green('\n Starting custom strategy trading...\n'));
453
406
 
454
- // Get front month contracts for each symbol
455
- const contracts = [];
456
- for (const symbol of symbols) {
457
- // Get front month contract from Rithmic API
458
- const frontMonth = await service.getFrontMonthContract(symbol);
459
- if (frontMonth && frontMonth.success) {
460
- contracts.push({
461
- name: frontMonth.symbol || symbol,
462
- symbol: frontMonth.symbol || symbol,
463
- exchange: frontMonth.exchange || 'CME',
464
- id: frontMonth.contractId || symbol,
465
- tickSize: frontMonth.tickSize,
466
- tickValue: frontMonth.tickValue,
467
- qty: 1, // Default 1 contract per symbol
468
- });
469
- } else {
470
- // Fallback - use symbol directly
471
- contracts.push({
472
- name: symbol,
473
- symbol: symbol,
474
- exchange: 'CME',
475
- id: symbol,
476
- tickSize: null,
477
- tickValue: null,
478
- qty: 1,
479
- });
480
- }
481
- }
482
-
483
- if (contracts.length === 0) {
484
- console.log(chalk.red('\n No valid contracts found.\n'));
407
+ // Filter active accounts
408
+ const activeAccounts = allAccounts.filter(acc => acc.status === 'active' || !acc.status);
409
+ if (activeAccounts.length === 0) {
410
+ console.log(chalk.red('\n No active trading accounts.\n'));
485
411
  await prompts.waitForEnter();
486
412
  return;
487
413
  }
488
414
 
489
- // Config for launchMultiSymbolRithmic
490
- const config = {
491
- dailyTarget,
492
- maxRisk,
493
- showName: true,
494
- enableAI: false, // Custom strategy doesn't need AI supervisor (it IS the AI strategy)
495
- };
496
-
415
+ // Account selection - same format as one-account.js
416
+ const options = activeAccounts.map(acc => {
417
+ const name = acc.accountName || acc.rithmicAccountId || acc.accountId;
418
+ const balance = acc.balance !== null && acc.balance !== undefined
419
+ ? ` - $${acc.balance.toLocaleString()}`
420
+ : '';
421
+ return {
422
+ label: `${name} (${acc.propfirm || acc.platform || 'Unknown'})${balance}`,
423
+ value: acc
424
+ };
425
+ });
426
+ options.push({ label: '< BACK', value: 'back' });
427
+
428
+ const selectedAccount = await prompts.selectOption('SELECT ACCOUNT:', options);
429
+ if (!selectedAccount || selectedAccount === 'back') return;
430
+
431
+ // Get the service for this account
432
+ const accountService = selectedAccount.service || connections.getServiceForAccount(selectedAccount.accountId) || service;
433
+
434
+ // Select symbol(s) - same as one-account.js
435
+ console.log();
436
+ const useMultiSymbol = await prompts.confirmPrompt(chalk.cyan('MULTI-SYMBOL MODE? (up to 5 symbols)'), false);
437
+ if (useMultiSymbol === null) return;
438
+
439
+ const contracts = await selectSymbol(accountService, selectedAccount, useMultiSymbol);
440
+ if (!contracts) return;
441
+
442
+ // Normalize to array
443
+ const contractList = Array.isArray(contracts) ? contracts : [contracts];
444
+
445
+ // Configure algo - same as one-account.js
446
+ const config = await configureAlgo(selectedAccount, contractList);
447
+ if (!config) return;
448
+
449
+ // Override: Custom strategy doesn't need AI supervisor (it IS the AI strategy)
450
+ config.enableAI = false;
451
+
452
+ // Show custom strategy info
453
+ console.log(chalk.magenta(`\n CUSTOM STRATEGY: ${strategyName}\n`));
454
+
497
455
  // Launch with custom strategy class
498
- await launchMultiSymbolRithmic(service, account, contracts, config, StrategyClass);
456
+ await launchMultiSymbolRithmic(accountService, selectedAccount, contractList, config, StrategyClass);
499
457
  }
500
458
 
501
459
  module.exports = { customStrategyMenu, generateStrategyCode, validateStrategyCode };
@@ -25,39 +25,13 @@ const { algoLogger } = require('./logger');
25
25
  const { recoveryMath } = require('../../services/strategy/recovery-math');
26
26
  const { sessionHistory, SessionHistory } = require('../../services/session-history');
27
27
 
28
- // Use HFT tick-based strategy for Rithmic (fast path), M1 for ProjectX
29
- const USE_HFT_STRATEGY = true;
30
-
31
28
  // AI Strategy Supervisor - observes, learns, and optimizes the strategy
32
29
  const aiService = require('../../services/ai');
33
30
  const StrategySupervisor = require('../../services/ai/strategy-supervisor');
34
31
 
35
- /**
36
- * Format price to avoid floating point errors
37
- * Uses the tick size to round properly
38
- * @param {number} price - Raw price
39
- * @param {number} tickSize - Tick size (default 0.25)
40
- * @returns {string} - Formatted price string
41
- */
42
- const formatPrice = (price, tickSize = 0.25) => {
43
- if (price === null || price === undefined || isNaN(price)) return '--';
44
- // Round to nearest tick, then format
45
- const rounded = Math.round(price / tickSize) * tickSize;
46
- // Determine decimal places from tick size
47
- const decimals = tickSize < 1 ? Math.max(0, -Math.floor(Math.log10(tickSize))) : 0;
48
- return rounded.toFixed(decimals);
49
- };
50
-
51
- /**
52
- * Check if service supports fast path (Rithmic direct)
53
- * @param {Object} service - Trading service
54
- * @returns {boolean}
55
- */
56
- const isRithmicFastPath = (service) => {
57
- return typeof service.fastEntry === 'function' &&
58
- typeof service.fastExit === 'function' &&
59
- service.orderConn?.isConnected;
60
- };
32
+ // Shared utilities
33
+ const { formatPrice, isRithmicFastPath, MAX_MULTI_SYMBOLS, USE_HFT_STRATEGY } = require('./algo-utils');
34
+ const { selectSymbol, configureAlgo } = require('./algo-config');
61
35
 
62
36
  /**
63
37
  * One Account Menu
@@ -133,208 +107,6 @@ const oneAccountMenu = async (service) => {
133
107
  }
134
108
  };
135
109
 
136
- // Maximum symbols for multi-symbol trading
137
- const MAX_MULTI_SYMBOLS = 5;
138
-
139
- /**
140
- * Symbol selection - sorted with popular indices first
141
- * Supports selecting multiple symbols (up to MAX_MULTI_SYMBOLS)
142
- * @param {Object} service - Trading service
143
- * @param {Object} account - Trading account
144
- * @param {boolean} allowMultiple - Allow selecting multiple symbols
145
- * @returns {Object|Array|null} Single contract or array of contracts
146
- */
147
- const selectSymbol = async (service, account, allowMultiple = false) => {
148
- const spinner = ora({ text: 'LOADING SYMBOLS...', color: 'yellow' }).start();
149
-
150
- const contractsResult = await service.getContracts();
151
- if (!contractsResult.success || !contractsResult.contracts?.length) {
152
- spinner.fail('FAILED TO LOAD CONTRACTS');
153
- return null;
154
- }
155
-
156
- let contracts = contractsResult.contracts;
157
-
158
- // Sort: Popular indices first (ES, NQ, MES, MNQ, RTY, YM, etc.)
159
- const popularPrefixes = ['ES', 'NQ', 'MES', 'MNQ', 'M2K', 'RTY', 'YM', 'MYM', 'NKD', 'GC', 'SI', 'CL'];
160
-
161
- contracts.sort((a, b) => {
162
- const nameA = a.name || '';
163
- const nameB = b.name || '';
164
-
165
- // Check if names start with popular prefixes
166
- const idxA = popularPrefixes.findIndex(p => nameA.startsWith(p));
167
- const idxB = popularPrefixes.findIndex(p => nameB.startsWith(p));
168
-
169
- // Both are popular - sort by popularity order
170
- if (idxA !== -1 && idxB !== -1) return idxA - idxB;
171
- // Only A is popular - A first
172
- if (idxA !== -1) return -1;
173
- // Only B is popular - B first
174
- if (idxB !== -1) return 1;
175
- // Neither - alphabetical
176
- return nameA.localeCompare(nameB);
177
- });
178
-
179
- spinner.succeed(`FOUND ${contracts.length} CONTRACTS`);
180
-
181
- // Display sorted contracts from API (uniform format: NAME - DESCRIPTION)
182
- const options = contracts.map(c => {
183
- const name = c.name || c.symbol || c.baseSymbol;
184
- const desc = c.description || '';
185
- const label = desc ? `${name} - ${desc}` : name;
186
- return { label, value: c };
187
- });
188
-
189
- options.push({ label: chalk.gray('< BACK'), value: 'back' });
190
-
191
- // Single symbol mode
192
- if (!allowMultiple) {
193
- const contract = await prompts.selectOption(chalk.yellow('SELECT SYMBOL:'), options);
194
- return contract === 'back' || contract === null ? null : contract;
195
- }
196
-
197
- // Multi-symbol selection mode
198
- // Each contract will have a 'qty' property for number of contracts
199
- const selectedContracts = [];
200
-
201
- while (selectedContracts.length < MAX_MULTI_SYMBOLS) {
202
- console.log();
203
-
204
- // Show already selected symbols with quantities
205
- if (selectedContracts.length > 0) {
206
- console.log(chalk.cyan(` SELECTED (${selectedContracts.length}/${MAX_MULTI_SYMBOLS}):`));
207
- selectedContracts.forEach((c, i) => {
208
- const name = c.name || c.symbol;
209
- console.log(chalk.green(` ${i + 1}. ${name} x${c.qty}`));
210
- });
211
- console.log();
212
- }
213
-
214
- // Filter out already selected
215
- const availableOptions = options.filter(opt => {
216
- if (opt.value === 'back') return true;
217
- const optId = opt.value.id || opt.value.symbol || opt.value.name;
218
- return !selectedContracts.some(sc => (sc.id || sc.symbol || sc.name) === optId);
219
- });
220
-
221
- // Add DONE option if at least 1 selected
222
- if (selectedContracts.length > 0) {
223
- availableOptions.unshift({
224
- label: chalk.green(`✓ DONE - Start trading ${selectedContracts.length} symbol${selectedContracts.length > 1 ? 's' : ''}`),
225
- value: 'done'
226
- });
227
- }
228
-
229
- const promptText = selectedContracts.length === 0
230
- ? chalk.yellow(`SELECT SYMBOL (1/${MAX_MULTI_SYMBOLS}):`)
231
- : chalk.yellow(`ADD SYMBOL (${selectedContracts.length + 1}/${MAX_MULTI_SYMBOLS}) OR DONE:`);
232
-
233
- const choice = await prompts.selectOption(promptText, availableOptions);
234
-
235
- if (choice === null || choice === 'back') {
236
- if (selectedContracts.length === 0) return null;
237
- selectedContracts.pop(); // Remove last
238
- continue;
239
- }
240
-
241
- if (choice === 'done') break;
242
-
243
- // Ask for number of contracts for this symbol
244
- const symbolName = choice.name || choice.symbol;
245
- const qty = await prompts.numberInput(`CONTRACTS FOR ${symbolName}:`, 1, 1, 10);
246
- if (qty === null) continue; // User cancelled, don't add symbol
247
-
248
- // Add qty to contract object
249
- choice.qty = qty;
250
- selectedContracts.push(choice);
251
-
252
- if (selectedContracts.length >= MAX_MULTI_SYMBOLS) {
253
- console.log(chalk.yellow(` Maximum ${MAX_MULTI_SYMBOLS} symbols reached`));
254
- break;
255
- }
256
- }
257
-
258
- return selectedContracts.length > 0 ? selectedContracts : null;
259
- };
260
-
261
- /**
262
- * Configure algo
263
- * @param {Object} account - Trading account
264
- * @param {Object|Array} contractOrContracts - Single contract or array of contracts
265
- */
266
- const configureAlgo = async (account, contractOrContracts) => {
267
- const contractList = Array.isArray(contractOrContracts) ? contractOrContracts : [contractOrContracts];
268
- const isMultiSymbol = contractList.length > 1;
269
-
270
- console.log();
271
- console.log(chalk.cyan(' CONFIGURE ALGO PARAMETERS'));
272
-
273
- // Show selected symbols with quantities (multi-symbol mode)
274
- if (isMultiSymbol) {
275
- console.log(chalk.white(` Trading ${contractList.length} symbols:`));
276
- contractList.forEach((c, i) => {
277
- const name = c.name || c.symbol;
278
- const qty = c.qty || 1;
279
- console.log(chalk.yellow(` ${i + 1}. ${name} x${qty}`));
280
- });
281
- }
282
- console.log();
283
-
284
- // Only ask for contracts in single-symbol mode
285
- // In multi-symbol mode, qty is already set per symbol
286
- let contracts = 1;
287
- if (!isMultiSymbol) {
288
- contracts = await prompts.numberInput('NUMBER OF CONTRACTS:', 1, 1, 10);
289
- if (contracts === null) return null;
290
- }
291
-
292
- const dailyTarget = await prompts.numberInput('DAILY TARGET ($):', 1000, 1, 10000);
293
- if (dailyTarget === null) return null;
294
-
295
- const maxRisk = await prompts.numberInput('MAX RISK ($):', 500, 1, 5000);
296
- if (maxRisk === null) return null;
297
-
298
- const showName = await prompts.confirmPrompt('SHOW ACCOUNT NAME?', false);
299
- if (showName === null) return null;
300
-
301
- // Check if AI agents are available
302
- const aiAgents = aiService.getAgents();
303
- let enableAI = false;
304
-
305
- if (aiAgents.length > 0) {
306
- // Show available agents
307
- console.log();
308
- console.log(chalk.magenta(` ${aiAgents.length} AI AGENT(S) AVAILABLE:`));
309
- aiAgents.forEach((agent, i) => {
310
- const modelInfo = agent.model ? chalk.gray(` (${agent.model})`) : '';
311
- console.log(chalk.white(` ${i + 1}. ${agent.name}${modelInfo}`));
312
- });
313
- console.log();
314
-
315
- enableAI = await prompts.confirmPrompt('ACTIVATE AI MODELS?', true);
316
- if (enableAI === null) return null;
317
-
318
- if (enableAI) {
319
- const mode = aiAgents.length >= 2 ? 'CONSENSUS' : 'INDIVIDUAL';
320
- console.log(chalk.green(` AI MODE: ${mode} (${aiAgents.length} agent${aiAgents.length > 1 ? 's' : ''})`));
321
- } else {
322
- console.log(chalk.gray(' AI AGENTS DISABLED FOR THIS SESSION'));
323
- }
324
- }
325
-
326
- console.log();
327
- const confirm = await prompts.confirmPrompt('START ALGO TRADING?', true);
328
- if (!confirm) return null;
329
-
330
- // Show spinner while initializing
331
- const initSpinner = ora({ text: 'INITIALIZING ALGO TRADING...', color: 'yellow' }).start();
332
- await new Promise(r => setTimeout(r, 500));
333
- initSpinner.succeed('LAUNCHING ALGO...');
334
-
335
- return { contracts, dailyTarget, maxRisk, showName, enableAI };
336
- };
337
-
338
110
  /**
339
111
  * Launch algo trading - HQX Ultra Scalping Strategy
340
112
  * Real-time market data + Strategy signals + Auto order execution
@@ -2194,4 +1966,4 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config, Cus
2194
1966
  await prompts.waitForEnter();
2195
1967
  };
2196
1968
 
2197
- module.exports = { oneAccountMenu, launchMultiSymbolRithmic };
1969
+ module.exports = { oneAccountMenu, launchMultiSymbolRithmic, selectSymbol, configureAlgo };