hedgequantx 2.6.126 → 2.6.127

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.126",
3
+ "version": "2.6.127",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -78,32 +78,21 @@ const oneAccountMenu = async (service) => {
78
78
  // Use the service attached to the account (from getAllAccounts), fallback to getServiceForAccount
79
79
  const accountService = selectedAccount.service || connections.getServiceForAccount(selectedAccount.accountId) || service;
80
80
 
81
- // Select symbols (up to MAX_SYMBOLS)
82
- const contracts = await selectSymbols(accountService, selectedAccount);
83
- if (!contracts || contracts.length === 0) return;
81
+ // Select symbol
82
+ const contract = await selectSymbol(accountService, selectedAccount);
83
+ if (!contract) return;
84
84
 
85
- // Configure algo (shared config for all symbols)
86
- const config = await configureAlgo(selectedAccount, contracts);
85
+ // Configure algo
86
+ const config = await configureAlgo(selectedAccount, contract);
87
87
  if (!config) return;
88
88
 
89
- // Launch algo for all selected symbols
90
- if (contracts.length === 1) {
91
- // Single symbol - use original launchAlgo
92
- await launchAlgo(accountService, selectedAccount, contracts[0], config);
93
- } else {
94
- // Multiple symbols - launch parallel trading
95
- await launchMultiAlgo(accountService, selectedAccount, contracts, config);
96
- }
89
+ await launchAlgo(accountService, selectedAccount, contract, config);
97
90
  };
98
91
 
99
- // Maximum number of symbols to trade simultaneously
100
- const MAX_SYMBOLS = 5;
101
-
102
92
  /**
103
93
  * Symbol selection - sorted with popular indices first
104
- * Allows selecting multiple symbols (up to MAX_SYMBOLS)
105
94
  */
106
- const selectSymbols = async (service, account) => {
95
+ const selectSymbol = async (service, account) => {
107
96
  const spinner = ora({ text: 'LOADING SYMBOLS...', color: 'yellow' }).start();
108
97
 
109
98
  const contractsResult = await service.getContracts();
@@ -137,102 +126,28 @@ const selectSymbols = async (service, account) => {
137
126
 
138
127
  spinner.succeed(`FOUND ${contracts.length} CONTRACTS`);
139
128
 
140
- // Multi-symbol selection loop
141
- const selectedContracts = [];
129
+ // Display sorted contracts from API (uniform format: NAME - DESCRIPTION)
130
+ const options = contracts.map(c => {
131
+ const name = c.name || c.symbol || c.baseSymbol;
132
+ const desc = c.description || '';
133
+ const label = desc ? `${name} - ${desc}` : name;
134
+ return { label, value: c };
135
+ });
142
136
 
143
- while (selectedContracts.length < MAX_SYMBOLS) {
144
- console.log();
145
- if (selectedContracts.length > 0) {
146
- console.log(chalk.cyan(` SELECTED (${selectedContracts.length}/${MAX_SYMBOLS}):`));
147
- selectedContracts.forEach((c, i) => {
148
- const name = c.name || c.symbol || c.baseSymbol;
149
- console.log(chalk.green(` ${i + 1}. ${name}`));
150
- });
151
- console.log();
152
- }
153
-
154
- // Filter out already selected contracts
155
- const availableContracts = contracts.filter(c => {
156
- const cId = c.id || c.symbol || c.name;
157
- return !selectedContracts.some(sc => (sc.id || sc.symbol || sc.name) === cId);
158
- });
159
-
160
- // Build options
161
- const options = availableContracts.map(c => {
162
- const name = c.name || c.symbol || c.baseSymbol;
163
- const desc = c.description || '';
164
- const label = desc ? `${name} - ${desc}` : name;
165
- return { label, value: c };
166
- });
167
-
168
- // Add done/back options
169
- if (selectedContracts.length > 0) {
170
- options.unshift({ label: chalk.green(`✓ DONE (trade ${selectedContracts.length} symbol${selectedContracts.length > 1 ? 's' : ''})`), value: 'done' });
171
- }
172
- options.push({ label: chalk.gray('< BACK'), value: 'back' });
173
-
174
- const promptText = selectedContracts.length === 0
175
- ? chalk.yellow(`SELECT SYMBOL (1-${MAX_SYMBOLS}):`)
176
- : chalk.yellow(`ADD SYMBOL (${selectedContracts.length + 1}/${MAX_SYMBOLS}) OR DONE:`);
177
-
178
- const choice = await prompts.selectOption(promptText, options);
179
-
180
- if (choice === 'back' || choice === null) {
181
- if (selectedContracts.length === 0) {
182
- return null; // Go back
183
- }
184
- // Remove last selected
185
- selectedContracts.pop();
186
- continue;
187
- }
188
-
189
- if (choice === 'done') {
190
- break; // Done selecting
191
- }
192
-
193
- // Add selected contract
194
- selectedContracts.push(choice);
195
-
196
- if (selectedContracts.length >= MAX_SYMBOLS) {
197
- console.log(chalk.yellow(` MAX ${MAX_SYMBOLS} SYMBOLS REACHED`));
198
- break;
199
- }
200
- }
137
+ options.push({ label: chalk.gray('< BACK'), value: 'back' });
201
138
 
202
- return selectedContracts.length > 0 ? selectedContracts : null;
203
- };
204
-
205
- /**
206
- * Legacy single symbol selection (kept for compatibility)
207
- */
208
- const selectSymbol = async (service, account) => {
209
- const contracts = await selectSymbols(service, account);
210
- return contracts && contracts.length > 0 ? contracts[0] : null;
139
+ const contract = await prompts.selectOption(chalk.yellow('SELECT SYMBOL:'), options);
140
+ return contract === 'back' || contract === null ? null : contract;
211
141
  };
212
142
 
213
143
  /**
214
144
  * Configure algo
215
- * @param {Object} account - Trading account
216
- * @param {Array|Object} contractsOrContract - Single contract or array of contracts
217
145
  */
218
- const configureAlgo = async (account, contractsOrContract) => {
219
- // Normalize to array
220
- const contractList = Array.isArray(contractsOrContract) ? contractsOrContract : [contractsOrContract];
221
-
146
+ const configureAlgo = async (account, contract) => {
222
147
  console.log();
223
148
  console.log(chalk.cyan(' CONFIGURE ALGO PARAMETERS'));
224
149
  console.log();
225
150
 
226
- // Show selected symbols
227
- if (contractList.length > 1) {
228
- console.log(chalk.white(` TRADING ${contractList.length} SYMBOLS:`));
229
- contractList.forEach((c, i) => {
230
- const name = c.name || c.symbol || c.baseSymbol;
231
- console.log(chalk.yellow(` ${i + 1}. ${name}`));
232
- });
233
- console.log();
234
- }
235
-
236
151
  const contracts = await prompts.numberInput('NUMBER OF CONTRACTS:', 1, 1, 10);
237
152
  if (contracts === null) return null;
238
153
 
@@ -1368,291 +1283,4 @@ const launchAlgo = async (service, account, contract, config) => {
1368
1283
  await prompts.waitForEnter();
1369
1284
  };
1370
1285
 
1371
- /**
1372
- * Launch multi-symbol algo trading
1373
- * Runs multiple symbols in parallel on the same account
1374
- *
1375
- * @param {Object} service - Trading service
1376
- * @param {Object} account - Trading account
1377
- * @param {Array} contracts - Array of contracts to trade
1378
- * @param {Object} config - Algo configuration
1379
- */
1380
- const launchMultiAlgo = async (service, account, contracts, config) => {
1381
- const { contracts: qty, dailyTarget, maxRisk, showName } = config;
1382
-
1383
- const accountName = showName
1384
- ? (account.accountName || account.rithmicAccountId || account.accountId)
1385
- : 'HQX *****';
1386
- const connectionType = account.platform || 'ProjectX';
1387
- const useFastPath = isRithmicFastPath(service);
1388
-
1389
- // Build symbol list string
1390
- const symbolNames = contracts.map(c => c.name || c.symbol || c.baseSymbol);
1391
- const symbolsStr = symbolNames.join(', ');
1392
-
1393
- const ui = new AlgoUI({
1394
- subtitle: `MULTI-SYMBOL (${contracts.length})`,
1395
- mode: 'one-account'
1396
- });
1397
-
1398
- // Shared stats across all symbols
1399
- const stats = {
1400
- accountName,
1401
- symbol: symbolsStr,
1402
- qty,
1403
- target: dailyTarget,
1404
- risk: maxRisk,
1405
- propfirm: account.propfirm || 'Unknown',
1406
- platform: connectionType,
1407
- pnl: null,
1408
- openPnl: null,
1409
- closedPnl: null,
1410
- balance: null,
1411
- buyingPower: null,
1412
- margin: null,
1413
- netLiquidation: null,
1414
- position: 0,
1415
- entryPrice: 0,
1416
- lastPrice: 0,
1417
- trades: 0,
1418
- wins: 0,
1419
- losses: 0,
1420
- sessionPnl: 0,
1421
- latency: 0,
1422
- connected: false,
1423
- startTime: Date.now(),
1424
- aiSupervision: config.enableAI || false,
1425
- aiMode: null,
1426
- agentCount: 0,
1427
- fastPath: useFastPath,
1428
- avgEntryLatency: 0,
1429
- avgFillLatency: 0,
1430
- entryLatencies: [],
1431
- // Multi-symbol specific
1432
- activeSymbols: contracts.length,
1433
- symbolStats: {}, // Per-symbol stats
1434
- };
1435
-
1436
- // Initialize per-symbol stats
1437
- contracts.forEach(c => {
1438
- const name = c.name || c.symbol;
1439
- stats.symbolStats[name] = {
1440
- position: 0,
1441
- trades: 0,
1442
- wins: 0,
1443
- losses: 0,
1444
- pnl: 0,
1445
- };
1446
- });
1447
-
1448
- let running = true;
1449
- let stopReason = null;
1450
-
1451
- // Track market feeds and position managers for cleanup
1452
- const marketFeeds = [];
1453
- const positionManagers = [];
1454
-
1455
- ui.render(stats);
1456
- ui.addLog('info', `MULTI-SYMBOL MODE: ${contracts.length} symbols`);
1457
- symbolNames.forEach(name => ui.addLog('info', ` → ${name}`));
1458
-
1459
- // Launch trading for each symbol
1460
- const symbolPromises = contracts.map(async (contract, index) => {
1461
- const symbolName = contract.name || contract.symbol;
1462
- const contractId = contract.id || contract.symbol || contract.name;
1463
- const tickSize = contract.tickSize ?? null;
1464
- const tickValue = contract.tickValue ?? null;
1465
-
1466
- // Initialize strategy for this symbol
1467
- const strategy = (USE_HFT_STRATEGY && useFastPath) ? hftStrategy : M1;
1468
- if (tickSize !== null && tickValue !== null) {
1469
- strategy.initialize(contractId, tickSize, tickValue);
1470
- }
1471
-
1472
- // Initialize Position Manager for fast path
1473
- let positionManager = null;
1474
- if (useFastPath) {
1475
- positionManager = new PositionManager(service, strategy);
1476
- if (tickSize !== null && tickValue !== null) {
1477
- positionManager.setContractInfo(symbolName, { tickSize, tickValue, contractId });
1478
- }
1479
- positionManager.start();
1480
- positionManagers.push(positionManager);
1481
-
1482
- // Position manager events
1483
- positionManager.on('entryFilled', ({ position, fillLatencyMs }) => {
1484
- stats.entryLatencies.push(fillLatencyMs);
1485
- stats.avgFillLatency = stats.entryLatencies.reduce((a, b) => a + b, 0) / stats.entryLatencies.length;
1486
- const side = position.side === 0 ? 'LONG' : 'SHORT';
1487
- ui.addLog('filled', `[${symbolName}] ${side} ${position.size}x @ ${position.entryPrice} | ${fillLatencyMs}ms`);
1488
- });
1489
-
1490
- positionManager.on('exitFilled', ({ exitPrice, pnlTicks, holdDurationMs }) => {
1491
- const holdSec = (holdDurationMs / 1000).toFixed(1);
1492
- if (pnlTicks !== null && tickValue !== null) {
1493
- const pnlDollars = pnlTicks * tickValue;
1494
- stats.sessionPnl += pnlDollars;
1495
- stats.symbolStats[symbolName].pnl += pnlDollars;
1496
-
1497
- if (pnlDollars >= 0) {
1498
- stats.wins++;
1499
- stats.symbolStats[symbolName].wins++;
1500
- ui.addLog('win', `[${symbolName}] +$${pnlDollars.toFixed(2)} @ ${exitPrice} | ${holdSec}s`);
1501
- } else {
1502
- stats.losses++;
1503
- stats.symbolStats[symbolName].losses++;
1504
- ui.addLog('loss', `[${symbolName}] -$${Math.abs(pnlDollars).toFixed(2)} @ ${exitPrice} | ${holdSec}s`);
1505
- }
1506
- stats.trades++;
1507
- stats.symbolStats[symbolName].trades++;
1508
- }
1509
- ui.render(stats);
1510
- });
1511
- }
1512
-
1513
- // Connect market feed for this symbol
1514
- const MarketFeedClass = useFastPath ? RithmicMarketDataFeed : MarketDataFeed;
1515
- const marketFeed = new MarketFeedClass(service);
1516
- marketFeeds.push(marketFeed);
1517
-
1518
- try {
1519
- await marketFeed.connect(contractId, account.accountId);
1520
- ui.addLog('success', `[${symbolName}] Market feed connected`);
1521
- stats.connected = true;
1522
-
1523
- // Handle market data ticks
1524
- marketFeed.on('tick', (tick) => {
1525
- if (!running) return;
1526
-
1527
- stats.latency = tick.latency || 0;
1528
- stats.lastPrice = tick.price || tick.last || 0;
1529
-
1530
- // Get strategy signal
1531
- const signal = strategy.onTick ? strategy.onTick(tick) : strategy.analyze?.(tick);
1532
-
1533
- if (signal && signal.action && signal.action !== 'hold') {
1534
- if (useFastPath && positionManager) {
1535
- // Fast path execution
1536
- if (signal.action === 'buy' || signal.action === 'long') {
1537
- positionManager.openPosition(symbolName, contract.exchange || 'CME', 0, qty, account.accountId);
1538
- } else if (signal.action === 'sell' || signal.action === 'short') {
1539
- positionManager.openPosition(symbolName, contract.exchange || 'CME', 1, qty, account.accountId);
1540
- } else if (signal.action === 'close' || signal.action === 'exit') {
1541
- positionManager.closePosition(symbolName);
1542
- }
1543
- }
1544
- }
1545
-
1546
- ui.render(stats);
1547
- });
1548
-
1549
- } catch (err) {
1550
- ui.addLog('error', `[${symbolName}] Failed to connect: ${err.message}`);
1551
- }
1552
- });
1553
-
1554
- // Wait for all symbols to initialize
1555
- await Promise.all(symbolPromises);
1556
-
1557
- // Keyboard handler for emergency stop
1558
- let emergencyStopInProgress = false;
1559
-
1560
- const setupKeyHandler = () => {
1561
- if (!process.stdin.isTTY) return;
1562
- readline.emitKeypressEvents(process.stdin);
1563
- process.stdin.setRawMode(true);
1564
- process.stdin.resume();
1565
-
1566
- const onKey = async (str, key) => {
1567
- if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
1568
- if (emergencyStopInProgress) return;
1569
- emergencyStopInProgress = true;
1570
- running = false;
1571
- stopReason = 'manual';
1572
-
1573
- ui.addLog('warning', '████ EMERGENCY STOP - CLOSING ALL POSITIONS ████');
1574
-
1575
- // Close all positions on all symbols
1576
- for (const pm of positionManagers) {
1577
- try {
1578
- pm.closeAllPositions();
1579
- } catch (e) {}
1580
- }
1581
-
1582
- ui.addLog('info', 'All positions closed');
1583
- ui.render(stats);
1584
- }
1585
- };
1586
- process.stdin.on('keypress', onKey);
1587
- return () => {
1588
- process.stdin.removeListener('keypress', onKey);
1589
- if (process.stdin.isTTY) process.stdin.setRawMode(false);
1590
- };
1591
- };
1592
-
1593
- const cleanupKeys = setupKeyHandler();
1594
-
1595
- // Wait for stop
1596
- await new Promise(resolve => {
1597
- const check = setInterval(() => {
1598
- if (!running) {
1599
- clearInterval(check);
1600
- resolve();
1601
- }
1602
- }, 100);
1603
- });
1604
-
1605
- // Cleanup
1606
- for (const pm of positionManagers) {
1607
- try { pm.stop(); } catch {}
1608
- }
1609
-
1610
- for (const feed of marketFeeds) {
1611
- try { await feed.disconnect(); } catch {}
1612
- }
1613
-
1614
- try { if (cleanupKeys) cleanupKeys(); } catch {}
1615
- try { ui.cleanup(); } catch {}
1616
-
1617
- try {
1618
- if (process.stdin.isTTY) {
1619
- process.stdin.setRawMode(false);
1620
- }
1621
- process.stdin.resume();
1622
- } catch {}
1623
-
1624
- // Duration
1625
- const durationMs = Date.now() - stats.startTime;
1626
- const hours = Math.floor(durationMs / 3600000);
1627
- const minutes = Math.floor((durationMs % 3600000) / 60000);
1628
- const seconds = Math.floor((durationMs % 60000) / 1000);
1629
- stats.duration = hours > 0
1630
- ? `${hours}h ${minutes}m ${seconds}s`
1631
- : minutes > 0
1632
- ? `${minutes}m ${seconds}s`
1633
- : `${seconds}s`;
1634
-
1635
- // Summary with per-symbol breakdown
1636
- console.log();
1637
- console.log(chalk.cyan('═══════════════════════════════════════════════════════════════'));
1638
- console.log(chalk.cyan(' MULTI-SYMBOL SESSION SUMMARY'));
1639
- console.log(chalk.cyan('═══════════════════════════════════════════════════════════════'));
1640
- console.log();
1641
-
1642
- for (const [symbol, symStats] of Object.entries(stats.symbolStats)) {
1643
- const winRate = symStats.trades > 0 ? ((symStats.wins / symStats.trades) * 100).toFixed(0) : 0;
1644
- const pnlColor = symStats.pnl >= 0 ? chalk.green : chalk.red;
1645
- console.log(chalk.white(` ${symbol}:`));
1646
- console.log(` Trades: ${symStats.trades} | WR: ${winRate}% | P&L: ${pnlColor('$' + symStats.pnl.toFixed(2))}`);
1647
- }
1648
-
1649
- console.log();
1650
- console.log(chalk.white(` TOTAL: ${stats.trades} trades | ${stats.wins}W/${stats.losses}L | Session P&L: ${stats.sessionPnl >= 0 ? chalk.green('$' + stats.sessionPnl.toFixed(2)) : chalk.red('$' + stats.sessionPnl.toFixed(2))}`));
1651
- console.log(chalk.white(` Duration: ${stats.duration}`));
1652
- console.log();
1653
- console.log(chalk.cyan('═══════════════════════════════════════════════════════════════'));
1654
-
1655
- await prompts.waitForEnter();
1656
- };
1657
-
1658
1286
  module.exports = { oneAccountMenu };