hedgequantx 2.6.128 → 2.6.129

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.128",
3
+ "version": "2.6.129",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -1385,6 +1385,8 @@ const launchAlgo = async (service, account, contract, config) => {
1385
1385
  * Uses single market feed connection with multiple subscriptions
1386
1386
  * Each symbol has its own PositionManager and strategy instance
1387
1387
  *
1388
+ * Same logic as launchAlgo but for multiple symbols
1389
+ *
1388
1390
  * @param {Object} service - Rithmic trading service
1389
1391
  * @param {Object} account - Trading account
1390
1392
  * @param {Array} contracts - Array of contracts to trade
@@ -1407,7 +1409,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1407
1409
  mode: 'one-account'
1408
1410
  });
1409
1411
 
1410
- // Shared stats
1412
+ // Shared stats (same structure as launchAlgo)
1411
1413
  const stats = {
1412
1414
  accountName,
1413
1415
  symbol: symbolsDisplay,
@@ -1420,7 +1422,12 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1420
1422
  openPnl: null,
1421
1423
  closedPnl: null,
1422
1424
  balance: null,
1425
+ buyingPower: null,
1426
+ margin: null,
1427
+ netLiquidation: null,
1423
1428
  position: 0,
1429
+ entryPrice: 0,
1430
+ lastPrice: 0,
1424
1431
  trades: 0,
1425
1432
  wins: 0,
1426
1433
  losses: 0,
@@ -1429,7 +1436,12 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1429
1436
  connected: false,
1430
1437
  startTime: Date.now(),
1431
1438
  aiSupervision: enableAI || false,
1439
+ aiMode: null,
1440
+ agentCount: 0,
1432
1441
  fastPath: true,
1442
+ avgEntryLatency: 0,
1443
+ avgFillLatency: 0,
1444
+ entryLatencies: [],
1433
1445
  // Per-symbol tracking
1434
1446
  symbolStats: {},
1435
1447
  };
@@ -1437,23 +1449,42 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1437
1449
  // Initialize per-symbol stats
1438
1450
  contracts.forEach(c => {
1439
1451
  const name = c.name || c.symbol;
1440
- stats.symbolStats[name] = { position: 0, trades: 0, wins: 0, losses: 0, pnl: 0 };
1452
+ stats.symbolStats[name] = {
1453
+ position: 0,
1454
+ trades: 0,
1455
+ wins: 0,
1456
+ losses: 0,
1457
+ pnl: 0,
1458
+ tickCount: 0,
1459
+ };
1441
1460
  });
1442
1461
 
1443
1462
  let running = true;
1444
1463
  let stopReason = null;
1464
+ let tickCount = 0;
1465
+
1466
+ // Store contract info for later use
1467
+ const contractInfoMap = {};
1468
+ contracts.forEach(c => {
1469
+ const name = c.name || c.symbol;
1470
+ contractInfoMap[name] = {
1471
+ tickSize: c.tickSize ?? null,
1472
+ tickValue: c.tickValue ?? null,
1473
+ contractId: c.id || c.symbol || c.name,
1474
+ exchange: c.exchange || 'CME',
1475
+ };
1476
+ });
1445
1477
 
1446
1478
  // ═══════════════════════════════════════════════════════════════════════════
1447
- // POSITION MANAGERS - One per symbol
1479
+ // POSITION MANAGERS & STRATEGIES - One per symbol
1448
1480
  // ═══════════════════════════════════════════════════════════════════════════
1449
1481
  const positionManagers = {};
1450
1482
  const strategies = {};
1483
+ const pendingOrders = {}; // Track pending orders per symbol
1451
1484
 
1452
1485
  contracts.forEach(contract => {
1453
1486
  const symbolName = contract.name || contract.symbol;
1454
- const contractId = contract.id || contract.symbol || contract.name;
1455
- const tickSize = contract.tickSize ?? null;
1456
- const tickValue = contract.tickValue ?? null;
1487
+ const { tickSize, tickValue, contractId } = contractInfoMap[symbolName];
1457
1488
 
1458
1489
  // Create strategy instance for this symbol
1459
1490
  const strategy = Object.create(hftStrategy);
@@ -1461,6 +1492,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1461
1492
  strategy.initialize(contractId, tickSize, tickValue);
1462
1493
  }
1463
1494
  strategies[symbolName] = strategy;
1495
+ pendingOrders[symbolName] = false;
1464
1496
 
1465
1497
  // Create position manager for this symbol
1466
1498
  const pm = new PositionManager(service, strategy);
@@ -1470,11 +1502,16 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1470
1502
  pm.start();
1471
1503
  positionManagers[symbolName] = pm;
1472
1504
 
1473
- // Position manager events
1505
+ // ═══════════════════════════════════════════════════════════════════════
1506
+ // POSITION MANAGER EVENTS (same as launchAlgo)
1507
+ // ═══════════════════════════════════════════════════════════════════════
1474
1508
  pm.on('entryFilled', ({ position, fillLatencyMs }) => {
1509
+ stats.entryLatencies.push(fillLatencyMs);
1510
+ stats.avgFillLatency = stats.entryLatencies.reduce((a, b) => a + b, 0) / stats.entryLatencies.length;
1475
1511
  const side = position.side === 0 ? 'LONG' : 'SHORT';
1476
1512
  ui.addLog('filled', `[${symbolName}] ${side} ${position.size}x @ ${position.entryPrice} | ${fillLatencyMs}ms`);
1477
1513
  stats.symbolStats[symbolName].position = position.side === 0 ? position.size : -position.size;
1514
+ ui.render(stats);
1478
1515
  });
1479
1516
 
1480
1517
  pm.on('exitFilled', ({ exitPrice, pnlTicks, holdDurationMs }) => {
@@ -1486,44 +1523,109 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1486
1523
  stats.symbolStats[symbolName].trades++;
1487
1524
  stats.trades++;
1488
1525
 
1526
+ // Record trade for Recovery Math
1527
+ recoveryMath.recordTrade({
1528
+ pnl: pnlDollars,
1529
+ ticks: pnlTicks,
1530
+ side: pnlTicks >= 0 ? 'win' : 'loss',
1531
+ duration: holdDurationMs,
1532
+ });
1533
+
1534
+ // Update Recovery Mode state
1535
+ const recovery = recoveryMath.updateSessionPnL(
1536
+ stats.sessionPnl,
1537
+ FAST_SCALPING.RECOVERY?.ACTIVATION_PNL || -300,
1538
+ FAST_SCALPING.RECOVERY?.DEACTIVATION_PNL || -100
1539
+ );
1540
+
1541
+ if (recovery.justActivated) {
1542
+ stats.recoveryMode = true;
1543
+ ui.addLog('warning', `RECOVERY MODE ON - Kelly: ${(recoveryMath.calcKelly() * 100).toFixed(0)}%`);
1544
+ } else if (recovery.justDeactivated) {
1545
+ stats.recoveryMode = false;
1546
+ ui.addLog('success', `RECOVERY MODE OFF - Session P&L: $${stats.sessionPnl.toFixed(2)}`);
1547
+ }
1548
+
1489
1549
  if (pnlDollars >= 0) {
1490
1550
  stats.wins++;
1491
1551
  stats.symbolStats[symbolName].wins++;
1492
- ui.addLog('win', `[${symbolName}] +$${pnlDollars.toFixed(2)} | ${holdSec}s`);
1552
+ ui.addLog('win', `[${symbolName}] +$${pnlDollars.toFixed(2)} @ ${exitPrice} | ${holdSec}s`);
1553
+ } else {
1554
+ stats.losses++;
1555
+ stats.symbolStats[symbolName].losses++;
1556
+ ui.addLog('loss', `[${symbolName}] -$${Math.abs(pnlDollars).toFixed(2)} @ ${exitPrice} | ${holdSec}s`);
1557
+ }
1558
+ } else if (pnlTicks !== null) {
1559
+ // Log with ticks only
1560
+ stats.trades++;
1561
+ stats.symbolStats[symbolName].trades++;
1562
+ if (pnlTicks >= 0) {
1563
+ stats.wins++;
1564
+ stats.symbolStats[symbolName].wins++;
1565
+ ui.addLog('win', `[${symbolName}] +${pnlTicks} ticks | ${holdSec}s`);
1493
1566
  } else {
1494
1567
  stats.losses++;
1495
1568
  stats.symbolStats[symbolName].losses++;
1496
- ui.addLog('loss', `[${symbolName}] -$${Math.abs(pnlDollars).toFixed(2)} | ${holdSec}s`);
1569
+ ui.addLog('loss', `[${symbolName}] ${pnlTicks} ticks | ${holdSec}s`);
1497
1570
  }
1498
1571
  }
1499
1572
  stats.symbolStats[symbolName].position = 0;
1573
+ pendingOrders[symbolName] = false;
1500
1574
  ui.render(stats);
1501
1575
  });
1502
1576
 
1503
- // Strategy signals
1577
+ pm.on('holdComplete', () => {
1578
+ ui.addLog('ready', `[${symbolName}] Hold complete - monitoring exit`);
1579
+ });
1580
+
1581
+ pm.on('breakevenActivated', ({ breakevenPrice, pnlTicks }) => {
1582
+ ui.addLog('be', `[${symbolName}] BE @ ${breakevenPrice} | +${pnlTicks} ticks`);
1583
+ });
1584
+
1585
+ // ═══════════════════════════════════════════════════════════════════════
1586
+ // STRATEGY SIGNALS
1587
+ // ═══════════════════════════════════════════════════════════════════════
1504
1588
  strategy.on('signal', async (signal) => {
1505
1589
  if (!running) return;
1590
+ if (pendingOrders[symbolName]) return;
1506
1591
  if (!pm.canEnter(symbolName)) return;
1507
1592
 
1508
- const { direction } = signal;
1593
+ const { direction, confidence } = signal;
1509
1594
  const orderSide = direction === 'long' ? 0 : 1;
1510
1595
  const sideStr = direction === 'long' ? 'LONG' : 'SHORT';
1511
1596
 
1512
- ui.addLog('entry', `[${symbolName}] ${sideStr} signal`);
1597
+ // Calculate risk amount
1598
+ const kelly = Math.min(0.25, confidence || 0.15);
1599
+ const riskAmount = Math.round(maxRisk * kelly);
1600
+ const riskPct = Math.round((riskAmount / maxRisk) * 100);
1601
+
1602
+ pendingOrders[symbolName] = true;
1603
+ ui.addLog('entry', `[${symbolName}] ${sideStr} ${qty}x | risk: $${riskAmount} (${riskPct}%)`);
1513
1604
 
1514
1605
  const orderData = {
1515
1606
  accountId: rithmicAccountId,
1516
1607
  symbol: symbolName,
1517
- exchange: contract.exchange || 'CME',
1608
+ exchange: contractInfoMap[symbolName].exchange,
1518
1609
  size: qty,
1519
1610
  side: orderSide,
1520
1611
  };
1521
1612
 
1522
- const entryResult = service.fastEntry(orderData);
1523
- if (entryResult.success) {
1524
- pm.registerEntry(entryResult, orderData, { tickSize, tickValue, contractId });
1525
- } else {
1526
- ui.addLog('error', `[${symbolName}] Entry failed: ${entryResult.error}`);
1613
+ try {
1614
+ const entryResult = service.fastEntry(orderData);
1615
+ if (entryResult.success) {
1616
+ pm.registerEntry(entryResult, orderData, contractInfoMap[symbolName]);
1617
+
1618
+ // Update avg entry latency
1619
+ stats.avgEntryLatency = stats.entryLatencies.length > 0
1620
+ ? (stats.avgEntryLatency * stats.entryLatencies.length + entryResult.latencyMs) / (stats.entryLatencies.length + 1)
1621
+ : entryResult.latencyMs;
1622
+ } else {
1623
+ ui.addLog('error', `[${symbolName}] Entry failed: ${entryResult.error}`);
1624
+ pendingOrders[symbolName] = false;
1625
+ }
1626
+ } catch (e) {
1627
+ ui.addLog('error', `[${symbolName}] Order error: ${e.message}`);
1628
+ pendingOrders[symbolName] = false;
1527
1629
  }
1528
1630
  });
1529
1631
  });
@@ -1535,13 +1637,14 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1535
1637
 
1536
1638
  marketFeed.on('connected', () => {
1537
1639
  stats.connected = true;
1538
- ui.addLog('success', 'Market feed connected');
1539
- ui.render(stats);
1640
+ algoLogger.dataConnected(ui, 'RTC');
1641
+ algoLogger.algoOperational(ui, 'RITHMIC');
1540
1642
  });
1541
1643
 
1542
1644
  marketFeed.on('tick', (tick) => {
1543
1645
  if (!running) return;
1544
1646
 
1647
+ tickCount++;
1545
1648
  stats.latency = tick.latency || 0;
1546
1649
 
1547
1650
  // Route tick to correct strategy based on symbol
@@ -1551,14 +1654,29 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1551
1654
  for (const [symbolName, strategy] of Object.entries(strategies)) {
1552
1655
  const baseSymbol = symbolName.replace(/[A-Z]\d+$/, '');
1553
1656
  if (tickSymbol === baseSymbol || tickSymbol === symbolName || symbolName.startsWith(tickSymbol)) {
1657
+ stats.symbolStats[symbolName].tickCount++;
1658
+
1659
+ // Log first tick per symbol
1660
+ if (stats.symbolStats[symbolName].tickCount === 1) {
1661
+ algoLogger.info(ui, 'FIRST TICK', `[${symbolName}] price=${tick.price} bid=${tick.bid} ask=${tick.ask}`);
1662
+ } else if (stats.symbolStats[symbolName].tickCount === 100) {
1663
+ algoLogger.info(ui, 'DATA FLOWING', `[${symbolName}] 100 ticks received`);
1664
+ }
1665
+
1554
1666
  const tickData = {
1555
1667
  contractId: tick.contractId || symbolName,
1556
1668
  price: tick.price || tick.lastPrice || tick.bid,
1557
1669
  bid: tick.bid,
1558
1670
  ask: tick.ask,
1559
1671
  volume: tick.volume || tick.size || 1,
1672
+ side: tick.lastTradeSide || tick.side || 'unknown',
1560
1673
  timestamp: tick.timestamp || Date.now()
1561
1674
  };
1675
+
1676
+ // Update last price
1677
+ stats.lastPrice = tickData.price;
1678
+
1679
+ // Feed tick to strategy
1562
1680
  strategy.processTick(tickData);
1563
1681
 
1564
1682
  // Update price for position manager
@@ -1567,6 +1685,18 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1567
1685
  price: tickData.price,
1568
1686
  timestamp: tickData.timestamp,
1569
1687
  });
1688
+
1689
+ // Get momentum data from strategy for position manager
1690
+ const modelValues = strategy.getModelValues?.() || strategy.getModelValues?.(symbolName);
1691
+ if (modelValues && positionManagers[symbolName] && typeof positionManagers[symbolName].updateMomentum === 'function') {
1692
+ positionManagers[symbolName].updateMomentum(symbolName, {
1693
+ ofi: modelValues.ofi || 0,
1694
+ zscore: modelValues.zscore || 0,
1695
+ delta: modelValues.delta || 0,
1696
+ timestamp: tickData.timestamp,
1697
+ });
1698
+ }
1699
+
1570
1700
  break;
1571
1701
  }
1572
1702
  }
@@ -1575,14 +1705,31 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1575
1705
  });
1576
1706
 
1577
1707
  marketFeed.on('error', (err) => {
1578
- ui.addLog('error', `Feed error: ${err.message}`);
1708
+ algoLogger.error(ui, 'MARKET ERROR', err.message);
1579
1709
  });
1580
1710
 
1581
- // Connect and subscribe
1711
+ marketFeed.on('disconnected', (err) => {
1712
+ stats.connected = false;
1713
+ algoLogger.dataDisconnected(ui, 'WEBSOCKET', err?.message);
1714
+ });
1715
+
1716
+ // ═══════════════════════════════════════════════════════════════════════════
1717
+ // STARTUP LOGS (same as launchAlgo)
1718
+ // ═══════════════════════════════════════════════════════════════════════════
1719
+ const market = checkMarketHours();
1720
+ const sessionName = market.session || 'AMERICAN';
1721
+ const etTime = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', timeZone: 'America/New_York' });
1722
+
1723
+ algoLogger.connectingToEngine(ui, account.accountId);
1724
+ algoLogger.engineStarting(ui, 'RITHMIC', dailyTarget, maxRisk);
1725
+ algoLogger.marketOpen(ui, sessionName.toUpperCase(), etTime);
1726
+ algoLogger.info(ui, 'FAST PATH', `Rithmic direct | ${contracts.length} symbols | Target <${FAST_SCALPING.LATENCY_TARGET_MS}ms`);
1727
+
1582
1728
  ui.render(stats);
1583
- ui.addLog('info', `MULTI-SYMBOL: ${contracts.length} symbols`);
1584
1729
 
1730
+ // Connect and subscribe
1585
1731
  try {
1732
+ algoLogger.info(ui, 'CONNECTING', `RITHMIC TICKER | ${contracts.length} symbols`);
1586
1733
  await marketFeed.connect();
1587
1734
  await new Promise(r => setTimeout(r, 1000));
1588
1735
 
@@ -1591,28 +1738,68 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1591
1738
  const symbolName = contract.name || contract.symbol;
1592
1739
  const exchange = contract.exchange || 'CME';
1593
1740
  marketFeed.subscribe(symbolName, exchange);
1594
- ui.addLog('success', `[${symbolName}] Subscribed`);
1741
+ algoLogger.info(ui, 'SUBSCRIBED', `${symbolName} (${exchange})`);
1595
1742
  }
1596
1743
  } else {
1597
- ui.addLog('error', 'Failed to connect market feed');
1744
+ algoLogger.error(ui, 'CONNECTION', 'Failed to connect market feed');
1598
1745
  }
1599
1746
  } catch (e) {
1600
- ui.addLog('error', `Connection error: ${e.message}`);
1747
+ algoLogger.error(ui, 'CONNECTION ERROR', e.message);
1601
1748
  }
1602
1749
 
1603
1750
  // ═══════════════════════════════════════════════════════════════════════════
1604
- // P&L UPDATES VIA WEBSOCKET
1751
+ // REAL-TIME P&L VIA WEBSOCKET (same as launchAlgo)
1605
1752
  // ═══════════════════════════════════════════════════════════════════════════
1606
1753
  if (typeof service.on === 'function') {
1754
+ // Account-level P&L updates
1607
1755
  service.on('pnlUpdate', (pnlData) => {
1608
1756
  if (pnlData.accountId !== rithmicAccountId) return;
1757
+
1609
1758
  if (pnlData.closedPositionPnl !== undefined) {
1610
1759
  stats.closedPnl = parseFloat(pnlData.closedPositionPnl);
1611
1760
  }
1612
1761
  if (pnlData.accountBalance !== undefined) {
1613
1762
  stats.balance = parseFloat(pnlData.accountBalance);
1614
1763
  }
1764
+ if (pnlData.availableBuyingPower !== undefined) {
1765
+ stats.buyingPower = parseFloat(pnlData.availableBuyingPower);
1766
+ }
1767
+ if (pnlData.marginBalance !== undefined) {
1768
+ stats.margin = parseFloat(pnlData.marginBalance);
1769
+ }
1770
+ if (pnlData.netLiquidation !== undefined) {
1771
+ stats.netLiquidation = parseFloat(pnlData.netLiquidation);
1772
+ } else if (stats.balance !== null) {
1773
+ stats.netLiquidation = stats.balance + (stats.openPnl || 0);
1774
+ }
1775
+
1615
1776
  stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
1777
+ ui.render(stats);
1778
+ });
1779
+
1780
+ // Position-level updates (for Open P&L)
1781
+ service.on('positionUpdate', (pos) => {
1782
+ if (!pos || pos.accountId !== rithmicAccountId) return;
1783
+
1784
+ const posSymbol = pos.symbol;
1785
+ for (const symbolName of Object.keys(stats.symbolStats)) {
1786
+ const baseSymbol = symbolName.replace(/[A-Z]\d+$/, '');
1787
+ if (posSymbol === baseSymbol || posSymbol === symbolName || symbolName.startsWith(posSymbol)) {
1788
+ stats.symbolStats[symbolName].position = pos.quantity || 0;
1789
+
1790
+ // Update Open P&L from position
1791
+ if (pos.openPnl !== undefined && pos.openPnl !== null) {
1792
+ stats.openPnl = pos.openPnl;
1793
+ stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
1794
+
1795
+ if (stats.balance !== null) {
1796
+ stats.netLiquidation = stats.balance + stats.openPnl;
1797
+ }
1798
+ }
1799
+ break;
1800
+ }
1801
+ }
1802
+ ui.render(stats);
1616
1803
  });
1617
1804
  }
1618
1805