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 +1 -1
- package/src/app.js +35 -0
- package/src/pages/algo.js +126 -79
package/package.json
CHANGED
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
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
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
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1848
|
+
hqxServer.on('log', (data) => {
|
|
1849
|
+
addLog(data.type || 'info', data.message);
|
|
1850
|
+
});
|
|
1836
1851
|
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
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
|
-
|
|
1845
|
-
|
|
1860
|
+
}
|
|
1861
|
+
displayUI();
|
|
1862
|
+
});
|
|
1846
1863
|
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
}
|
|
1852
|
-
|
|
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
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
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
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
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
|
-
|
|
1874
|
-
|
|
1897
|
+
displayUI();
|
|
1898
|
+
});
|
|
1875
1899
|
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
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
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
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
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
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
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
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');
|