hedgequantx 2.6.129 → 2.6.131

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.129",
3
+ "version": "2.6.131",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -181,17 +181,18 @@ const selectSymbol = async (service, account, allowMultiple = false) => {
181
181
  }
182
182
 
183
183
  // Multi-symbol selection mode
184
+ // Each contract will have a 'qty' property for number of contracts
184
185
  const selectedContracts = [];
185
186
 
186
187
  while (selectedContracts.length < MAX_MULTI_SYMBOLS) {
187
188
  console.log();
188
189
 
189
- // Show already selected symbols
190
+ // Show already selected symbols with quantities
190
191
  if (selectedContracts.length > 0) {
191
192
  console.log(chalk.cyan(` SELECTED (${selectedContracts.length}/${MAX_MULTI_SYMBOLS}):`));
192
193
  selectedContracts.forEach((c, i) => {
193
194
  const name = c.name || c.symbol;
194
- console.log(chalk.green(` ${i + 1}. ${name}`));
195
+ console.log(chalk.green(` ${i + 1}. ${name} x${c.qty}`));
195
196
  });
196
197
  console.log();
197
198
  }
@@ -225,6 +226,13 @@ const selectSymbol = async (service, account, allowMultiple = false) => {
225
226
 
226
227
  if (choice === 'done') break;
227
228
 
229
+ // Ask for number of contracts for this symbol
230
+ const symbolName = choice.name || choice.symbol;
231
+ const qty = await prompts.numberInput(`CONTRACTS FOR ${symbolName}:`, 1, 1, 10);
232
+ if (qty === null) continue; // User cancelled, don't add symbol
233
+
234
+ // Add qty to contract object
235
+ choice.qty = qty;
228
236
  selectedContracts.push(choice);
229
237
 
230
238
  if (selectedContracts.length >= MAX_MULTI_SYMBOLS) {
@@ -243,21 +251,29 @@ const selectSymbol = async (service, account, allowMultiple = false) => {
243
251
  */
244
252
  const configureAlgo = async (account, contractOrContracts) => {
245
253
  const contractList = Array.isArray(contractOrContracts) ? contractOrContracts : [contractOrContracts];
254
+ const isMultiSymbol = contractList.length > 1;
246
255
 
247
256
  console.log();
248
257
  console.log(chalk.cyan(' CONFIGURE ALGO PARAMETERS'));
249
258
 
250
- // Show selected symbols
251
- if (contractList.length > 1) {
259
+ // Show selected symbols with quantities (multi-symbol mode)
260
+ if (isMultiSymbol) {
252
261
  console.log(chalk.white(` Trading ${contractList.length} symbols:`));
253
262
  contractList.forEach((c, i) => {
254
- console.log(chalk.yellow(` ${i + 1}. ${c.name || c.symbol}`));
263
+ const name = c.name || c.symbol;
264
+ const qty = c.qty || 1;
265
+ console.log(chalk.yellow(` ${i + 1}. ${name} x${qty}`));
255
266
  });
256
267
  }
257
268
  console.log();
258
269
 
259
- const contracts = await prompts.numberInput('NUMBER OF CONTRACTS:', 1, 1, 10);
260
- if (contracts === null) return null;
270
+ // Only ask for contracts in single-symbol mode
271
+ // In multi-symbol mode, qty is already set per symbol
272
+ let contracts = 1;
273
+ if (!isMultiSymbol) {
274
+ contracts = await prompts.numberInput('NUMBER OF CONTRACTS:', 1, 1, 10);
275
+ if (contracts === null) return null;
276
+ }
261
277
 
262
278
  const dailyTarget = await prompts.numberInput('DAILY TARGET ($):', 1000, 1, 10000);
263
279
  if (dailyTarget === null) return null;
@@ -1393,27 +1409,33 @@ const launchAlgo = async (service, account, contract, config) => {
1393
1409
  * @param {Object} config - Algo configuration
1394
1410
  */
1395
1411
  const launchMultiSymbolRithmic = async (service, account, contracts, config) => {
1396
- const { contracts: qty, dailyTarget, maxRisk, showName, enableAI } = config;
1412
+ const { dailyTarget, maxRisk, showName, enableAI } = config;
1397
1413
 
1398
1414
  const accountName = showName
1399
1415
  ? (account.accountName || account.rithmicAccountId || account.accountId)
1400
1416
  : 'HQX *****';
1401
1417
  const rithmicAccountId = account.rithmicAccountId || account.accountId;
1402
1418
 
1403
- // Build symbols string for UI
1404
- const symbolNames = contracts.map(c => c.name || c.symbol);
1405
- const symbolsDisplay = symbolNames.join(', ');
1419
+ // Build symbols string for UI (with quantities)
1420
+ const symbolsDisplay = contracts.map(c => {
1421
+ const name = c.name || c.symbol;
1422
+ const qty = c.qty || 1;
1423
+ return `${name}x${qty}`;
1424
+ }).join(', ');
1406
1425
 
1407
1426
  const ui = new AlgoUI({
1408
1427
  subtitle: `MULTI-SYMBOL (${contracts.length})`,
1409
1428
  mode: 'one-account'
1410
1429
  });
1411
1430
 
1431
+ // Calculate total qty across all symbols
1432
+ const totalQty = contracts.reduce((sum, c) => sum + (c.qty || 1), 0);
1433
+
1412
1434
  // Shared stats (same structure as launchAlgo)
1413
1435
  const stats = {
1414
1436
  accountName,
1415
1437
  symbol: symbolsDisplay,
1416
- qty,
1438
+ qty: totalQty,
1417
1439
  target: dailyTarget,
1418
1440
  risk: maxRisk,
1419
1441
  propfirm: account.propfirm || 'Unknown',
@@ -1455,6 +1477,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1455
1477
  wins: 0,
1456
1478
  losses: 0,
1457
1479
  pnl: 0,
1480
+ openPnl: 0, // Per-symbol unrealized P&L
1458
1481
  tickCount: 0,
1459
1482
  };
1460
1483
  });
@@ -1463,7 +1486,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1463
1486
  let stopReason = null;
1464
1487
  let tickCount = 0;
1465
1488
 
1466
- // Store contract info for later use
1489
+ // Store contract info for later use (including qty per symbol)
1467
1490
  const contractInfoMap = {};
1468
1491
  contracts.forEach(c => {
1469
1492
  const name = c.name || c.symbol;
@@ -1472,6 +1495,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1472
1495
  tickValue: c.tickValue ?? null,
1473
1496
  contractId: c.id || c.symbol || c.name,
1474
1497
  exchange: c.exchange || 'CME',
1498
+ qty: c.qty || 1, // Per-symbol quantity
1475
1499
  };
1476
1500
  });
1477
1501
 
@@ -1481,6 +1505,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1481
1505
  const positionManagers = {};
1482
1506
  const strategies = {};
1483
1507
  const pendingOrders = {}; // Track pending orders per symbol
1508
+ const disabledSymbols = new Set(); // Symbols disabled after SL hit
1484
1509
 
1485
1510
  contracts.forEach(contract => {
1486
1511
  const symbolName = contract.name || contract.symbol;
@@ -1554,6 +1579,11 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1554
1579
  stats.losses++;
1555
1580
  stats.symbolStats[symbolName].losses++;
1556
1581
  ui.addLog('loss', `[${symbolName}] -$${Math.abs(pnlDollars).toFixed(2)} @ ${exitPrice} | ${holdSec}s`);
1582
+
1583
+ // Disable symbol after loss (SL hit) - other symbols continue
1584
+ disabledSymbols.add(symbolName);
1585
+ ui.addLog('warning', `[${symbolName}] DISABLED - SL hit, other symbols continue`);
1586
+ stats.activeSymbols = contracts.length - disabledSymbols.size;
1557
1587
  }
1558
1588
  } else if (pnlTicks !== null) {
1559
1589
  // Log with ticks only
@@ -1567,9 +1597,17 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1567
1597
  stats.losses++;
1568
1598
  stats.symbolStats[symbolName].losses++;
1569
1599
  ui.addLog('loss', `[${symbolName}] ${pnlTicks} ticks | ${holdSec}s`);
1600
+
1601
+ // Disable symbol after loss (SL hit) - other symbols continue
1602
+ disabledSymbols.add(symbolName);
1603
+ ui.addLog('warning', `[${symbolName}] DISABLED - SL hit, other symbols continue`);
1604
+ stats.activeSymbols = contracts.length - disabledSymbols.size;
1570
1605
  }
1571
1606
  }
1572
1607
  stats.symbolStats[symbolName].position = 0;
1608
+ stats.symbolStats[symbolName].openPnl = 0; // Reset open P&L when position closed
1609
+ // Recalculate total Open P&L
1610
+ stats.openPnl = Object.values(stats.symbolStats).reduce((sum, s) => sum + (s.openPnl || 0), 0);
1573
1611
  pendingOrders[symbolName] = false;
1574
1612
  ui.render(stats);
1575
1613
  });
@@ -1587,6 +1625,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1587
1625
  // ═══════════════════════════════════════════════════════════════════════
1588
1626
  strategy.on('signal', async (signal) => {
1589
1627
  if (!running) return;
1628
+ if (disabledSymbols.has(symbolName)) return; // Skip disabled symbols (SL hit)
1590
1629
  if (pendingOrders[symbolName]) return;
1591
1630
  if (!pm.canEnter(symbolName)) return;
1592
1631
 
@@ -1594,19 +1633,22 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1594
1633
  const orderSide = direction === 'long' ? 0 : 1;
1595
1634
  const sideStr = direction === 'long' ? 'LONG' : 'SHORT';
1596
1635
 
1636
+ // Use per-symbol quantity
1637
+ const symbolQty = contractInfoMap[symbolName].qty;
1638
+
1597
1639
  // Calculate risk amount
1598
1640
  const kelly = Math.min(0.25, confidence || 0.15);
1599
1641
  const riskAmount = Math.round(maxRisk * kelly);
1600
1642
  const riskPct = Math.round((riskAmount / maxRisk) * 100);
1601
1643
 
1602
1644
  pendingOrders[symbolName] = true;
1603
- ui.addLog('entry', `[${symbolName}] ${sideStr} ${qty}x | risk: $${riskAmount} (${riskPct}%)`);
1645
+ ui.addLog('entry', `[${symbolName}] ${sideStr} ${symbolQty}x | risk: $${riskAmount} (${riskPct}%)`);
1604
1646
 
1605
1647
  const orderData = {
1606
1648
  accountId: rithmicAccountId,
1607
1649
  symbol: symbolName,
1608
1650
  exchange: contractInfoMap[symbolName].exchange,
1609
- size: qty,
1651
+ size: symbolQty,
1610
1652
  side: orderSide,
1611
1653
  };
1612
1654
 
@@ -1747,6 +1789,32 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1747
1789
  algoLogger.error(ui, 'CONNECTION ERROR', e.message);
1748
1790
  }
1749
1791
 
1792
+ // ═══════════════════════════════════════════════════════════════════════════
1793
+ // TARGET/RISK CHECK - Stop algo when limits reached
1794
+ // ═══════════════════════════════════════════════════════════════════════════
1795
+ const checkTargetRisk = () => {
1796
+ if (!running) return;
1797
+
1798
+ const totalPnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
1799
+
1800
+ // Daily target reached - STOP with profit
1801
+ if (totalPnl >= dailyTarget) {
1802
+ stopReason = 'target';
1803
+ running = false;
1804
+ algoLogger.info(ui, 'TARGET REACHED', `+$${totalPnl.toFixed(2)} >= $${dailyTarget}`);
1805
+ ui.addLog('success', `████ DAILY TARGET REACHED: +$${totalPnl.toFixed(2)} ████`);
1806
+ emergencyStopAll(); // Close all positions
1807
+ }
1808
+ // Max risk reached - STOP to protect capital
1809
+ else if (totalPnl <= -maxRisk) {
1810
+ stopReason = 'risk';
1811
+ running = false;
1812
+ algoLogger.info(ui, 'MAX RISK HIT', `-$${Math.abs(totalPnl).toFixed(2)} <= -$${maxRisk}`);
1813
+ ui.addLog('error', `████ MAX RISK REACHED: -$${Math.abs(totalPnl).toFixed(2)} ████`);
1814
+ emergencyStopAll(); // Close all positions
1815
+ }
1816
+ };
1817
+
1750
1818
  // ═══════════════════════════════════════════════════════════════════════════
1751
1819
  // REAL-TIME P&L VIA WEBSOCKET (same as launchAlgo)
1752
1820
  // ═══════════════════════════════════════════════════════════════════════════
@@ -1774,10 +1842,14 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1774
1842
  }
1775
1843
 
1776
1844
  stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
1845
+
1846
+ // Check target/risk on every P&L update
1847
+ checkTargetRisk();
1848
+
1777
1849
  ui.render(stats);
1778
1850
  });
1779
1851
 
1780
- // Position-level updates (for Open P&L)
1852
+ // Position-level updates (for Open P&L per symbol)
1781
1853
  service.on('positionUpdate', (pos) => {
1782
1854
  if (!pos || pos.accountId !== rithmicAccountId) return;
1783
1855
 
@@ -1787,14 +1859,20 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1787
1859
  if (posSymbol === baseSymbol || posSymbol === symbolName || symbolName.startsWith(posSymbol)) {
1788
1860
  stats.symbolStats[symbolName].position = pos.quantity || 0;
1789
1861
 
1790
- // Update Open P&L from position
1862
+ // Update Open P&L for this symbol
1791
1863
  if (pos.openPnl !== undefined && pos.openPnl !== null) {
1792
- stats.openPnl = pos.openPnl;
1864
+ stats.symbolStats[symbolName].openPnl = pos.openPnl;
1865
+
1866
+ // Calculate total Open P&L from all symbols
1867
+ stats.openPnl = Object.values(stats.symbolStats).reduce((sum, s) => sum + (s.openPnl || 0), 0);
1793
1868
  stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
1794
1869
 
1795
1870
  if (stats.balance !== null) {
1796
1871
  stats.netLiquidation = stats.balance + stats.openPnl;
1797
1872
  }
1873
+
1874
+ // Check target/risk on Open P&L changes too
1875
+ checkTargetRisk();
1798
1876
  }
1799
1877
  break;
1800
1878
  }