hedgequantx 2.6.130 → 2.6.132

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.130",
3
+ "version": "2.6.132",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -10,7 +10,7 @@ const ora = require('ora');
10
10
  const readline = require('readline');
11
11
 
12
12
  const { connections } = require('../../services');
13
- const { AlgoUI, renderSessionSummary } = require('./ui');
13
+ const { AlgoUI, renderSessionSummary, renderMultiSymbolSummary } = require('./ui');
14
14
  const { prompts } = require('../../utils');
15
15
  const { checkMarketHours } = require('../../services/projectx/market');
16
16
  const { FAST_SCALPING } = require('../../config/settings');
@@ -1477,6 +1477,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1477
1477
  wins: 0,
1478
1478
  losses: 0,
1479
1479
  pnl: 0,
1480
+ openPnl: 0, // Per-symbol unrealized P&L
1480
1481
  tickCount: 0,
1481
1482
  };
1482
1483
  });
@@ -1504,6 +1505,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1504
1505
  const positionManagers = {};
1505
1506
  const strategies = {};
1506
1507
  const pendingOrders = {}; // Track pending orders per symbol
1508
+ const disabledSymbols = new Set(); // Symbols disabled after SL hit
1507
1509
 
1508
1510
  contracts.forEach(contract => {
1509
1511
  const symbolName = contract.name || contract.symbol;
@@ -1577,6 +1579,11 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1577
1579
  stats.losses++;
1578
1580
  stats.symbolStats[symbolName].losses++;
1579
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;
1580
1587
  }
1581
1588
  } else if (pnlTicks !== null) {
1582
1589
  // Log with ticks only
@@ -1590,9 +1597,17 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1590
1597
  stats.losses++;
1591
1598
  stats.symbolStats[symbolName].losses++;
1592
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;
1593
1605
  }
1594
1606
  }
1595
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);
1596
1611
  pendingOrders[symbolName] = false;
1597
1612
  ui.render(stats);
1598
1613
  });
@@ -1610,6 +1625,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1610
1625
  // ═══════════════════════════════════════════════════════════════════════
1611
1626
  strategy.on('signal', async (signal) => {
1612
1627
  if (!running) return;
1628
+ if (disabledSymbols.has(symbolName)) return; // Skip disabled symbols (SL hit)
1613
1629
  if (pendingOrders[symbolName]) return;
1614
1630
  if (!pm.canEnter(symbolName)) return;
1615
1631
 
@@ -1773,6 +1789,32 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1773
1789
  algoLogger.error(ui, 'CONNECTION ERROR', e.message);
1774
1790
  }
1775
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
+
1776
1818
  // ═══════════════════════════════════════════════════════════════════════════
1777
1819
  // REAL-TIME P&L VIA WEBSOCKET (same as launchAlgo)
1778
1820
  // ═══════════════════════════════════════════════════════════════════════════
@@ -1800,10 +1842,14 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1800
1842
  }
1801
1843
 
1802
1844
  stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
1845
+
1846
+ // Check target/risk on every P&L update
1847
+ checkTargetRisk();
1848
+
1803
1849
  ui.render(stats);
1804
1850
  });
1805
1851
 
1806
- // Position-level updates (for Open P&L)
1852
+ // Position-level updates (for Open P&L per symbol)
1807
1853
  service.on('positionUpdate', (pos) => {
1808
1854
  if (!pos || pos.accountId !== rithmicAccountId) return;
1809
1855
 
@@ -1813,14 +1859,20 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1813
1859
  if (posSymbol === baseSymbol || posSymbol === symbolName || symbolName.startsWith(posSymbol)) {
1814
1860
  stats.symbolStats[symbolName].position = pos.quantity || 0;
1815
1861
 
1816
- // Update Open P&L from position
1862
+ // Update Open P&L for this symbol
1817
1863
  if (pos.openPnl !== undefined && pos.openPnl !== null) {
1818
- 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);
1819
1868
  stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
1820
1869
 
1821
1870
  if (stats.balance !== null) {
1822
1871
  stats.netLiquidation = stats.balance + stats.openPnl;
1823
1872
  }
1873
+
1874
+ // Check target/risk on Open P&L changes too
1875
+ checkTargetRisk();
1824
1876
  }
1825
1877
  break;
1826
1878
  }
@@ -1922,32 +1974,14 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
1922
1974
  const hours = Math.floor(durationMs / 3600000);
1923
1975
  const minutes = Math.floor((durationMs % 3600000) / 60000);
1924
1976
  const seconds = Math.floor((durationMs % 60000) / 1000);
1925
- const duration = hours > 0
1977
+ stats.duration = hours > 0
1926
1978
  ? `${hours}h ${minutes}m ${seconds}s`
1927
1979
  : minutes > 0
1928
1980
  ? `${minutes}m ${seconds}s`
1929
1981
  : `${seconds}s`;
1930
1982
 
1931
- console.log();
1932
- console.log(chalk.cyan('═══════════════════════════════════════════════════════════════'));
1933
- console.log(chalk.cyan(' MULTI-SYMBOL SESSION SUMMARY'));
1934
- console.log(chalk.cyan('═══════════════════════════════════════════════════════════════'));
1935
- console.log();
1936
-
1937
- for (const [symbol, symStats] of Object.entries(stats.symbolStats)) {
1938
- const winRate = symStats.trades > 0 ? ((symStats.wins / symStats.trades) * 100).toFixed(0) : 0;
1939
- const pnlColor = symStats.pnl >= 0 ? chalk.green : chalk.red;
1940
- console.log(chalk.white(` ${symbol}:`));
1941
- console.log(` Trades: ${symStats.trades} | WR: ${winRate}% | P&L: ${pnlColor('$' + symStats.pnl.toFixed(2))}`);
1942
- }
1943
-
1944
- console.log();
1945
- const totalPnlColor = stats.sessionPnl >= 0 ? chalk.green : chalk.red;
1946
- console.log(chalk.white(` TOTAL: ${stats.trades} trades | ${stats.wins}W/${stats.losses}L`));
1947
- console.log(chalk.white(` Session P&L: ${totalPnlColor('$' + stats.sessionPnl.toFixed(2))}`));
1948
- console.log(chalk.white(` Duration: ${duration}`));
1949
- console.log();
1950
- console.log(chalk.cyan('═══════════════════════════════════════════════════════════════'));
1983
+ // Render multi-symbol summary with same style as single-symbol
1984
+ renderMultiSymbolSummary(stats, stopReason, stats.symbolStats);
1951
1985
 
1952
1986
  await prompts.waitForEnter();
1953
1987
  };
@@ -547,4 +547,123 @@ const renderSessionSummary = (stats, stopReason) => {
547
547
  console.log();
548
548
  };
549
549
 
550
- module.exports = { AlgoUI, checkMarketStatus, renderSessionSummary, LOG_COLORS, LOG_ICONS, stripAnsi, center, fitToWidth };
550
+ /**
551
+ * Render Multi-Symbol Session Summary - Same style as single-symbol
552
+ */
553
+ const renderMultiSymbolSummary = (stats, stopReason, symbolStats) => {
554
+ const W = 96;
555
+ const version = require('../../../package.json').version;
556
+
557
+ console.clear();
558
+ console.log();
559
+
560
+ // Top border
561
+ console.log(chalk.cyan(BOX.TOP + BOX.H.repeat(W) + BOX.TR));
562
+
563
+ // Logo
564
+ console.log(chalk.cyan(BOX.V) + chalk.cyan(' ██╗ ██╗███████╗██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗████████╗') + chalk.yellow('██╗ ██╗') + ' ' + chalk.cyan(BOX.V));
565
+ console.log(chalk.cyan(BOX.V) + chalk.cyan(' ██║ ██║██╔════╝██╔══██╗██╔════╝ ██╔════╝██╔═══██╗██║ ██║██╔══██╗████╗ ██║╚══██╔══╝') + chalk.yellow('╚██╗██╔╝') + ' ' + chalk.cyan(BOX.V));
566
+ console.log(chalk.cyan(BOX.V) + chalk.cyan(' ███████║█████╗ ██║ ██║██║ ███╗█████╗ ██║ ██║██║ ██║███████║██╔██╗ ██║ ██║ ') + chalk.yellow(' ╚███╔╝ ') + ' ' + chalk.cyan(BOX.V));
567
+ console.log(chalk.cyan(BOX.V) + chalk.cyan(' ██╔══██║██╔══╝ ██║ ██║██║ ██║██╔══╝ ██║▄▄ ██║██║ ██║██╔══██║██║╚██╗██║ ██║ ') + chalk.yellow(' ██╔██╗ ') + ' ' + chalk.cyan(BOX.V));
568
+ console.log(chalk.cyan(BOX.V) + chalk.cyan(' ██║ ██║███████╗██████╔╝╚██████╔╝███████╗╚██████╔╝╚██████╔╝██║ ██║██║ ╚████║ ██║ ') + chalk.yellow('██╔╝ ██╗') + ' ' + chalk.cyan(BOX.V));
569
+ console.log(chalk.cyan(BOX.V) + chalk.cyan(' ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ') + chalk.yellow('╚═╝ ╚═╝') + ' ' + chalk.cyan(BOX.V));
570
+
571
+ // Separator + title
572
+ console.log(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
573
+ console.log(chalk.cyan(BOX.V) + chalk.white(center(`PROP FUTURES ALGO TRADING v${version}`, W)) + chalk.cyan(BOX.V));
574
+ console.log(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
575
+ console.log(chalk.cyan(BOX.V) + chalk.yellow.bold(center('MULTI-SYMBOL SESSION SUMMARY', W)) + chalk.cyan(BOX.V));
576
+ console.log(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
577
+
578
+ // Per-symbol stats header
579
+ const colSymbol = 12;
580
+ const colTrades = 10;
581
+ const colWR = 10;
582
+ const colWins = 8;
583
+ const colLosses = 8;
584
+ const colPnL = 14;
585
+ const remaining = W - colSymbol - colTrades - colWR - colWins - colLosses - colPnL - 5; // 5 separators
586
+
587
+ // Header row
588
+ const headerSymbol = ' SYMBOL'.padEnd(colSymbol);
589
+ const headerTrades = 'TRADES'.padEnd(colTrades);
590
+ const headerWR = 'WIN RATE'.padEnd(colWR);
591
+ const headerWins = 'WINS'.padEnd(colWins);
592
+ const headerLosses = 'LOSSES'.padEnd(colLosses);
593
+ const headerPnL = 'P&L'.padEnd(colPnL + remaining);
594
+
595
+ console.log(chalk.cyan(BOX.V) + chalk.bold.white(headerSymbol) + chalk.cyan(BOX.VS) +
596
+ chalk.bold.white(headerTrades) + chalk.cyan(BOX.VS) +
597
+ chalk.bold.white(headerWR) + chalk.cyan(BOX.VS) +
598
+ chalk.bold.white(headerWins) + chalk.cyan(BOX.VS) +
599
+ chalk.bold.white(headerLosses) + chalk.cyan(BOX.VS) +
600
+ chalk.bold.white(headerPnL) + chalk.cyan(BOX.V));
601
+
602
+ console.log(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
603
+
604
+ // Per-symbol rows
605
+ for (const [symbol, symStats] of Object.entries(symbolStats)) {
606
+ const winRate = symStats.trades > 0 ? ((symStats.wins / symStats.trades) * 100).toFixed(0) + '%' : '0%';
607
+ const pnl = symStats.pnl || 0;
608
+ const pnlStr = (pnl >= 0 ? '+$' : '-$') + Math.abs(pnl).toFixed(2);
609
+ const pnlColor = pnl >= 0 ? chalk.green : chalk.red;
610
+ const wrColor = symStats.wins >= symStats.losses ? chalk.green : chalk.red;
611
+
612
+ const cellSymbol = (' ' + symbol).padEnd(colSymbol);
613
+ const cellTrades = String(symStats.trades || 0).padEnd(colTrades);
614
+ const cellWR = winRate.padEnd(colWR);
615
+ const cellWins = String(symStats.wins || 0).padEnd(colWins);
616
+ const cellLosses = String(symStats.losses || 0).padEnd(colLosses);
617
+ const cellPnL = pnlStr.padEnd(colPnL + remaining);
618
+
619
+ console.log(chalk.cyan(BOX.V) + chalk.yellow(cellSymbol) + chalk.cyan(BOX.VS) +
620
+ chalk.white(cellTrades) + chalk.cyan(BOX.VS) +
621
+ wrColor(cellWR) + chalk.cyan(BOX.VS) +
622
+ chalk.green(cellWins) + chalk.cyan(BOX.VS) +
623
+ chalk.red(cellLosses) + chalk.cyan(BOX.VS) +
624
+ pnlColor.bold(cellPnL) + chalk.cyan(BOX.V));
625
+ }
626
+
627
+ // Separator before totals
628
+ console.log(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
629
+
630
+ // Total row
631
+ const totalWinRate = stats.trades > 0 ? ((stats.wins / stats.trades) * 100).toFixed(0) + '%' : '0%';
632
+ const totalPnl = stats.sessionPnl || 0;
633
+ const totalPnlStr = (totalPnl >= 0 ? '+$' : '-$') + Math.abs(totalPnl).toFixed(2);
634
+ const totalPnlColor = totalPnl >= 0 ? chalk.green : chalk.red;
635
+ const totalWrColor = stats.wins >= stats.losses ? chalk.green : chalk.red;
636
+
637
+ const totalCellSymbol = ' TOTAL'.padEnd(colSymbol);
638
+ const totalCellTrades = String(stats.trades || 0).padEnd(colTrades);
639
+ const totalCellWR = totalWinRate.padEnd(colWR);
640
+ const totalCellWins = String(stats.wins || 0).padEnd(colWins);
641
+ const totalCellLosses = String(stats.losses || 0).padEnd(colLosses);
642
+ const totalCellPnL = totalPnlStr.padEnd(colPnL + remaining);
643
+
644
+ console.log(chalk.cyan(BOX.V) + chalk.bold.white(totalCellSymbol) + chalk.cyan(BOX.VS) +
645
+ chalk.bold.white(totalCellTrades) + chalk.cyan(BOX.VS) +
646
+ totalWrColor.bold(totalCellWR) + chalk.cyan(BOX.VS) +
647
+ chalk.bold.green(totalCellWins) + chalk.cyan(BOX.VS) +
648
+ chalk.bold.red(totalCellLosses) + chalk.cyan(BOX.VS) +
649
+ totalPnlColor.bold(totalCellPnL) + chalk.cyan(BOX.V));
650
+
651
+ // Separator
652
+ console.log(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
653
+
654
+ // Stop Reason & Duration row
655
+ const duration = stats.duration || '--';
656
+ const reasonColor = stopReason === 'target' ? chalk.green : stopReason === 'risk' ? chalk.red : chalk.yellow;
657
+ const reasonStr = (stopReason || 'manual').toUpperCase();
658
+ const infoText = ` STOP: ${reasonStr} | DURATION: ${duration} | TARGET: $${(stats.target || 0).toFixed(2)} | RISK: $${(stats.risk || 0).toFixed(2)}`;
659
+ const infoPlain = ` STOP: ${reasonStr} | DURATION: ${duration} | TARGET: $${(stats.target || 0).toFixed(2)} | RISK: $${(stats.risk || 0).toFixed(2)}`;
660
+ const infoColored = ` ${chalk.bold('STOP')}: ${reasonColor.bold(reasonStr)} | ${chalk.bold('DURATION')}: ${chalk.white(duration)} | ${chalk.bold('TARGET')}: ${chalk.cyan('$' + (stats.target || 0).toFixed(2))} | ${chalk.bold('RISK')}: ${chalk.red('$' + (stats.risk || 0).toFixed(2))}`;
661
+
662
+ console.log(chalk.cyan(BOX.V) + infoColored + ' '.repeat(Math.max(0, W - infoPlain.length)) + chalk.cyan(BOX.V));
663
+
664
+ // Bottom border
665
+ console.log(chalk.cyan(BOX.BOT + BOX.H.repeat(W) + BOX.BR));
666
+ console.log();
667
+ };
668
+
669
+ module.exports = { AlgoUI, checkMarketStatus, renderSessionSummary, renderMultiSymbolSummary, LOG_COLORS, LOG_ICONS, stripAnsi, center, fitToWidth };