hedgequantx 1.2.134 → 1.2.136

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": "1.2.134",
3
+ "version": "1.2.136",
4
4
  "description": "Prop Futures Algo Trading CLI - Connect to Topstep, Alpha Futures, and other prop firms",
5
5
  "main": "src/app.js",
6
6
  "bin": {
package/src/app.js CHANGED
@@ -26,6 +26,41 @@ const { algoTradingMenu } = require('./pages/algo');
26
26
  let currentService = null;
27
27
  let currentPlatform = null; // 'projectx' or 'rithmic'
28
28
 
29
+ /**
30
+ * Global terminal restoration - ensures terminal is always restored on exit
31
+ */
32
+ const restoreTerminal = () => {
33
+ try {
34
+ // Exit alternate screen buffer
35
+ process.stdout.write('\x1B[?1049l');
36
+ // Show cursor
37
+ process.stdout.write('\x1B[?25h');
38
+ // Disable raw mode
39
+ if (process.stdin.isTTY && process.stdin.isRaw) {
40
+ process.stdin.setRawMode(false);
41
+ }
42
+ // Remove all keypress listeners
43
+ process.stdin.removeAllListeners('keypress');
44
+ } catch (e) {
45
+ // Ignore errors during cleanup
46
+ }
47
+ };
48
+
49
+ // Register global handlers to restore terminal on exit/crash
50
+ process.on('exit', restoreTerminal);
51
+ process.on('SIGINT', () => { restoreTerminal(); process.exit(0); });
52
+ process.on('SIGTERM', () => { restoreTerminal(); process.exit(0); });
53
+ process.on('uncaughtException', (err) => {
54
+ restoreTerminal();
55
+ console.error(chalk.red('Uncaught Exception:'), err.message);
56
+ process.exit(1);
57
+ });
58
+ process.on('unhandledRejection', (reason) => {
59
+ restoreTerminal();
60
+ console.error(chalk.red('Unhandled Rejection:'), reason);
61
+ process.exit(1);
62
+ });
63
+
29
64
  /**
30
65
  * Displays the application banner with stats if connected
31
66
  */
package/src/pages/algo.js CHANGED
@@ -1467,7 +1467,10 @@ const launchCopyTrading = async (config) => {
1467
1467
  followerTrades: 0,
1468
1468
  signals: 0,
1469
1469
  errors: 0,
1470
- pnl: 0
1470
+ pnl: 0,
1471
+ trades: 0,
1472
+ wins: 0,
1473
+ losses: 0
1471
1474
  };
1472
1475
 
1473
1476
  // Log colors
@@ -1538,6 +1541,11 @@ const launchCopyTrading = async (config) => {
1538
1541
  const pnlColor = stats.pnl >= 0 ? chalk.green : chalk.red;
1539
1542
  const pnlStr = (stats.pnl >= 0 ? '+$' : '-$') + Math.abs(stats.pnl).toFixed(2);
1540
1543
 
1544
+ // Latency formatting
1545
+ const latencyMs = latency > 0 ? latency : 0;
1546
+ const latencyStr = `${latencyMs}ms`;
1547
+ const latencyColor = latencyMs < 100 ? chalk.green : (latencyMs < 300 ? chalk.yellow : chalk.red);
1548
+
1541
1549
  // Current date
1542
1550
  const now = new Date();
1543
1551
  const dateStr = now.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' });
@@ -1626,6 +1634,12 @@ const launchCopyTrading = async (config) => {
1626
1634
  const r5c2plain = ` Copied: ${stats.copiedTrades} Errors: ${stats.errors}`;
1627
1635
  const r5c2 = r5c2text + safePad(colR - r5c2plain.length);
1628
1636
 
1637
+ // Row 6: Trades + W/L | Latency
1638
+ const r6c1text = ` Trades: ${chalk.cyan(stats.trades || 0)} W/L: ${chalk.green(stats.wins || 0)}/${chalk.red(stats.losses || 0)}`;
1639
+ const r6c1plain = ` Trades: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
1640
+ const r6c1 = r6c1text + safePad(colL - r6c1plain.length);
1641
+ const r6c2 = buildCell('Latency', latencyStr, latencyColor, colR);
1642
+
1629
1643
  // Grid separators
1630
1644
  const GRID_TOP = '\u2560' + '\u2550'.repeat(colL) + '\u2564' + '\u2550'.repeat(colR) + '\u2563';
1631
1645
  const GRID_MID = '\u2560' + '\u2550'.repeat(colL) + '\u256A' + '\u2550'.repeat(colR) + '\u2563';
@@ -1642,6 +1656,8 @@ const launchCopyTrading = async (config) => {
1642
1656
  bufferLine(chalk.cyan(V) + r4c1.padded + chalk.cyan(VS) + r4c2.padded + chalk.cyan(V));
1643
1657
  bufferLine(chalk.cyan(GRID_MID));
1644
1658
  bufferLine(chalk.cyan(V) + r5c1 + chalk.cyan(VS) + r5c2 + chalk.cyan(V));
1659
+ bufferLine(chalk.cyan(GRID_MID));
1660
+ bufferLine(chalk.cyan(V) + r6c1 + chalk.cyan(VS) + r6c2.padded + chalk.cyan(V));
1645
1661
  bufferLine(chalk.cyan(GRID_BOT));
1646
1662
 
1647
1663
  // Activity log header with spinner and centered date
@@ -1824,81 +1840,95 @@ const launchCopyTrading = async (config) => {
1824
1840
  }
1825
1841
  };
1826
1842
 
1827
- // Setup HQX Server event handlers
1828
- if (hqxConnected) {
1829
- hqxServer.on('latency', (data) => {
1830
- latency = data.latency || 0;
1831
- });
1843
+ // Setup HQX Server event handlers (attach before connection, check hqxConnected inside)
1844
+ hqxServer.on('latency', (data) => {
1845
+ latency = data.latency || 0;
1846
+ });
1832
1847
 
1833
- hqxServer.on('log', (data) => {
1834
- addLog(data.type || 'info', data.message);
1835
- });
1848
+ hqxServer.on('log', (data) => {
1849
+ addLog(data.type || 'info', data.message);
1850
+ });
1836
1851
 
1837
- hqxServer.on('signal', async (data) => {
1838
- stats.signals = (stats.signals || 0) + 1;
1839
- const side = data.side === 'long' ? 'BUY' : 'SELL';
1840
- addLog('signal', `${side} Signal @ ${data.entry?.toFixed(2) || 'N/A'} | SL: ${data.stop?.toFixed(2) || 'N/A'} | TP: ${data.target?.toFixed(2) || 'N/A'}`);
1841
-
1842
- // Execute on both accounts
1852
+ hqxServer.on('signal', async (data) => {
1853
+ stats.signals = (stats.signals || 0) + 1;
1854
+ const side = data.side === 'long' ? 'BUY' : 'SELL';
1855
+ addLog('signal', `${side} Signal @ ${data.entry?.toFixed(2) || 'N/A'} | SL: ${data.stop?.toFixed(2) || 'N/A'} | TP: ${data.target?.toFixed(2) || 'N/A'}`);
1856
+
1857
+ // Execute on both accounts
1858
+ if (hqxConnected) {
1843
1859
  await executeSignalOnBothAccounts(data);
1844
- displayUI();
1845
- });
1860
+ }
1861
+ displayUI();
1862
+ });
1846
1863
 
1847
- hqxServer.on('trade', async (data) => {
1848
- stats.pnl += data.pnl || 0;
1849
- if (data.pnl > 0) {
1850
- addLog('trade', `Closed +$${data.pnl.toFixed(2)} (${data.reason || 'take_profit'})`);
1851
- } else {
1852
- addLog('loss', `Closed -$${Math.abs(data.pnl).toFixed(2)} (${data.reason || 'stop_loss'})`);
1853
- }
1864
+ hqxServer.on('trade', async (data) => {
1865
+ stats.pnl += data.pnl || 0;
1866
+ if (data.pnl > 0) {
1867
+ stats.wins = (stats.wins || 0) + 1;
1868
+ addLog('trade', `Closed +$${data.pnl.toFixed(2)} (${data.reason || 'take_profit'})`);
1869
+ } else {
1870
+ stats.losses = (stats.losses || 0) + 1;
1871
+ addLog('loss', `Closed -$${Math.abs(data.pnl).toFixed(2)} (${data.reason || 'stop_loss'})`);
1872
+ }
1873
+ stats.trades = (stats.trades || 0) + 1;
1874
+
1875
+ // Print updated stats like One Account
1876
+ const statsType = stats.pnl >= 0 ? 'info' : 'loss';
1877
+ addLog(statsType, `Stats: Trades: ${stats.trades} | Wins: ${stats.wins || 0} | P&L: $${stats.pnl.toFixed(2)}`);
1854
1878
 
1855
- // Check daily target
1856
- if (stats.pnl >= dailyTarget) {
1857
- stopReason = 'target';
1858
- addLog('success', `Daily target reached! +$${stats.pnl.toFixed(2)}`);
1859
- isRunning = false;
1860
- hqxServer.stopAlgo();
1861
- await closePositionsOnBothAccounts('target');
1862
- }
1879
+ // Check daily target
1880
+ if (stats.pnl >= dailyTarget) {
1881
+ stopReason = 'target';
1882
+ addLog('success', `Daily target reached! +$${stats.pnl.toFixed(2)}`);
1883
+ isRunning = false;
1884
+ if (hqxConnected) hqxServer.stopAlgo();
1885
+ await closePositionsOnBothAccounts('target');
1886
+ }
1863
1887
 
1864
- // Check max risk
1865
- if (stats.pnl <= -maxRisk) {
1866
- stopReason = 'risk';
1867
- addLog('error', `Max risk reached! -$${Math.abs(stats.pnl).toFixed(2)}`);
1868
- isRunning = false;
1869
- hqxServer.stopAlgo();
1870
- await closePositionsOnBothAccounts('risk');
1871
- }
1888
+ // Check max risk
1889
+ if (stats.pnl <= -maxRisk) {
1890
+ stopReason = 'risk';
1891
+ addLog('error', `Max risk reached! -$${Math.abs(stats.pnl).toFixed(2)}`);
1892
+ isRunning = false;
1893
+ if (hqxConnected) hqxServer.stopAlgo();
1894
+ await closePositionsOnBothAccounts('risk');
1895
+ }
1872
1896
 
1873
- displayUI();
1874
- });
1897
+ displayUI();
1898
+ });
1875
1899
 
1876
- hqxServer.on('stats', (data) => {
1877
- const realizedPnl = data.pnl || 0;
1878
- const unrealizedPnl = data.position?.pnl || 0;
1879
- stats.pnl = realizedPnl + unrealizedPnl;
1880
- });
1900
+ hqxServer.on('stats', (data) => {
1901
+ const realizedPnl = data.pnl || 0;
1902
+ const unrealizedPnl = data.position?.pnl || 0;
1903
+ stats.pnl = realizedPnl + unrealizedPnl;
1904
+ stats.trades = data.trades || stats.trades;
1905
+ stats.wins = data.wins || stats.wins;
1906
+ stats.losses = data.losses || stats.losses;
1907
+ });
1881
1908
 
1882
- hqxServer.on('error', (data) => {
1883
- const errorMsg = data.message || 'Unknown error';
1884
- addLog('error', errorMsg);
1885
-
1886
- // If algo failed to start, switch to monitor mode
1887
- if (errorMsg.includes('Failed to start') || errorMsg.includes('WebSocket failed') || errorMsg.includes('Échec')) {
1888
- if (hqxConnected) {
1889
- hqxConnected = false;
1890
- addLog('warning', 'Switching to Monitor Mode (watching Lead positions)');
1891
- displayUI();
1892
- }
1909
+ hqxServer.on('error', (data) => {
1910
+ const errorMsg = data.message || 'Unknown error';
1911
+ addLog('error', errorMsg);
1912
+
1913
+ // If algo failed to start, switch to monitor mode
1914
+ if (errorMsg.includes('Failed to start') || errorMsg.includes('WebSocket failed') || errorMsg.includes('Échec')) {
1915
+ if (hqxConnected) {
1916
+ hqxConnected = false;
1917
+ addLog('warning', 'Switching to Monitor Mode (watching Lead positions)');
1918
+ displayUI();
1893
1919
  }
1894
- });
1920
+ }
1921
+ });
1895
1922
 
1896
- hqxServer.on('disconnected', () => {
1897
- hqxConnected = false;
1898
- if (!stopReason) {
1899
- addLog('warning', 'HQX Server disconnected - Switching to Monitor Mode');
1900
- }
1901
- });
1923
+ hqxServer.on('disconnected', () => {
1924
+ hqxConnected = false;
1925
+ if (!stopReason) {
1926
+ addLog('warning', 'HQX Server disconnected - Switching to Monitor Mode');
1927
+ }
1928
+ });
1929
+
1930
+ // Start algo if connected
1931
+ if (hqxConnected) {
1902
1932
 
1903
1933
  // Start the Ultra-Scalping algo
1904
1934
  addLog('info', 'Starting HQX Ultra-Scalping...');
@@ -2058,20 +2088,27 @@ const launchCopyTrading = async (config) => {
2058
2088
 
2059
2089
  // Also listen for X key
2060
2090
  if (process.stdin.isTTY) {
2061
- readline.emitKeypressEvents(process.stdin);
2062
- process.stdin.setRawMode(true);
2063
-
2064
- const onKeypress = (str, key) => {
2065
- if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
2066
- clearInterval(checkInterval);
2067
- clearInterval(monitorInterval);
2068
- process.stdin.setRawMode(false);
2069
- process.stdin.removeListener('keypress', onKeypress);
2070
- resolve();
2071
- }
2072
- };
2073
-
2074
- process.stdin.on('keypress', onKeypress);
2091
+ try {
2092
+ readline.emitKeypressEvents(process.stdin);
2093
+ process.stdin.setRawMode(true);
2094
+ process.stdin.resume();
2095
+
2096
+ const onKeypress = (str, key) => {
2097
+ if (!key) return;
2098
+ const keyName = key.name?.toLowerCase();
2099
+ if (keyName === 'x' || (key.ctrl && keyName === 'c')) {
2100
+ clearInterval(checkInterval);
2101
+ clearInterval(monitorInterval);
2102
+ process.stdin.setRawMode(false);
2103
+ process.stdin.removeListener('keypress', onKeypress);
2104
+ resolve();
2105
+ }
2106
+ };
2107
+
2108
+ process.stdin.on('keypress', onKeypress);
2109
+ } catch (e) {
2110
+ // Fallback: just wait for auto-stop
2111
+ }
2075
2112
  }
2076
2113
  });
2077
2114
 
@@ -2100,6 +2137,16 @@ const launchCopyTrading = async (config) => {
2100
2137
  await closePositionsOnBothAccounts('user_stop');
2101
2138
  }
2102
2139
 
2140
+ // Restore stdin to normal mode
2141
+ try {
2142
+ if (process.stdin.isTTY) {
2143
+ process.stdin.setRawMode(false);
2144
+ }
2145
+ process.stdin.removeAllListeners('keypress');
2146
+ } catch (e) {
2147
+ // Ignore stdin restoration errors
2148
+ }
2149
+
2103
2150
  // Exit alternate screen buffer and show cursor
2104
2151
  process.stdout.write('\x1B[?1049l');
2105
2152
  process.stdout.write('\x1B[?25h');