hedgequantx 1.2.65 → 1.2.66

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.65",
3
+ "version": "1.2.66",
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/pages/algo.js CHANGED
@@ -306,8 +306,8 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
306
306
  let hqxConnected = false;
307
307
  let algoRunning = false;
308
308
  let stopReason = null;
309
+ let latency = 0;
309
310
 
310
- // Activity logs
311
311
  // Stats
312
312
  let stats = {
313
313
  trades: 0,
@@ -401,7 +401,9 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
401
401
  // Info Box
402
402
  console.log(chalk.cyan(' ╔════════════════════════════════════════════════════════════════════╗'));
403
403
  console.log(chalk.cyan(' ║') + chalk.white(` Account: ${chalk.cyan(accountName.padEnd(25))} Symbol: ${chalk.yellow(symbolName.padEnd(10))} Qty: ${chalk.cyan(numContracts.toString().padEnd(3))}`) + chalk.cyan('║'));
404
- console.log(chalk.cyan(' ║') + chalk.white(` Target: ${chalk.green(('$' + dailyTarget.toFixed(2)).padEnd(12))} Risk: ${chalk.red(('$' + maxRisk.toFixed(2)).padEnd(12))} Server: ${hqxConnected ? chalk.green('ON ') : chalk.red('OFF')} `) + chalk.cyan('║'));
404
+ const latencyStr = hqxConnected ? (latency > 0 ? `${latency}ms` : '---') : '---';
405
+ const latencyColor = latency < 100 ? chalk.green : (latency < 300 ? chalk.yellow : chalk.red);
406
+ console.log(chalk.cyan(' ║') + chalk.white(` Target: ${chalk.green(('$' + dailyTarget.toFixed(2)).padEnd(12))} Risk: ${chalk.red(('$' + maxRisk.toFixed(2)).padEnd(12))} Server: ${hqxConnected ? chalk.green('ON') : chalk.red('OFF')} ${latencyColor(latencyStr.padEnd(6))}`) + chalk.cyan('║'));
405
407
 
406
408
  // Stats line
407
409
  const pnlColor = stats.pnl >= 0 ? chalk.green : chalk.red;
@@ -554,32 +556,45 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
554
556
 
555
557
  // Wait for X key OR auto-stop (target/risk reached)
556
558
  await new Promise((resolve) => {
559
+ let resolved = false;
560
+
561
+ const cleanup = () => {
562
+ if (resolved) return;
563
+ resolved = true;
564
+ clearInterval(checkInterval);
565
+ if (process.stdin.isTTY) {
566
+ try {
567
+ process.stdin.setRawMode(false);
568
+ process.stdin.removeAllListeners('keypress');
569
+ } catch (e) {}
570
+ }
571
+ resolve();
572
+ };
573
+
557
574
  // Check for auto-stop every 500ms
558
575
  const checkInterval = setInterval(() => {
559
576
  if (!algoRunning || stopReason) {
560
- clearInterval(checkInterval);
561
- if (process.stdin.isTTY && process.stdin.isRaw) {
562
- process.stdin.setRawMode(false);
563
- }
564
- resolve();
577
+ cleanup();
565
578
  }
566
579
  }, 500);
567
580
 
568
- // Also listen for X key
581
+ // Listen for X key
569
582
  if (process.stdin.isTTY) {
570
- readline.emitKeypressEvents(process.stdin);
571
- process.stdin.setRawMode(true);
572
-
573
- const onKeypress = (str, key) => {
574
- if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
575
- clearInterval(checkInterval);
576
- process.stdin.setRawMode(false);
577
- process.stdin.removeListener('keypress', onKeypress);
578
- resolve();
579
- }
580
- };
581
-
582
- process.stdin.on('keypress', onKeypress);
583
+ try {
584
+ readline.emitKeypressEvents(process.stdin);
585
+ process.stdin.setRawMode(true);
586
+ process.stdin.resume();
587
+
588
+ process.stdin.on('keypress', (str, key) => {
589
+ if (!key) return;
590
+ const keyName = key.name?.toLowerCase();
591
+ if (keyName === 'x' || (key.ctrl && keyName === 'c')) {
592
+ cleanup();
593
+ }
594
+ });
595
+ } catch (e) {
596
+ // Fallback: just wait for auto-stop
597
+ }
583
598
  }
584
599
  });
585
600
 
@@ -1014,6 +1029,26 @@ const launchCopyTrading = async (config) => {
1014
1029
  pnl: 0
1015
1030
  };
1016
1031
 
1032
+ // Log colors
1033
+ const typeColors = {
1034
+ info: chalk.cyan,
1035
+ success: chalk.green,
1036
+ trade: chalk.green.bold,
1037
+ copy: chalk.yellow.bold,
1038
+ error: chalk.red,
1039
+ warning: chalk.yellow
1040
+ };
1041
+
1042
+ const getIcon = (type) => {
1043
+ switch(type) {
1044
+ case 'trade': return '[>]';
1045
+ case 'copy': return '[+]';
1046
+ case 'error': return '[X]';
1047
+ case 'success': return '[OK]';
1048
+ default: return '[.]';
1049
+ }
1050
+ };
1051
+
1017
1052
  const addLog = (type, message) => {
1018
1053
  const timestamp = new Date().toLocaleTimeString();
1019
1054
  logs.push({ timestamp, type, message });
@@ -1022,74 +1057,52 @@ const launchCopyTrading = async (config) => {
1022
1057
 
1023
1058
  const displayUI = () => {
1024
1059
  console.clear();
1060
+
1061
+ // Logo
1062
+ const logo = [
1063
+ '██╗ ██╗ ██████╗ ██╗ ██╗',
1064
+ '██║ ██║██╔═══██╗╚██╗██╔╝',
1065
+ '███████║██║ ██║ ╚███╔╝ ',
1066
+ '██╔══██║██║▄▄ ██║ ██╔██╗ ',
1067
+ '██║ ██║╚██████╔╝██╔╝ ██╗',
1068
+ '╚═╝ ╚═╝ ╚══▀▀═╝ ╚═╝ ╚═╝'
1069
+ ];
1070
+
1025
1071
  console.log();
1026
- console.log(chalk.gray(getSeparator()));
1027
- console.log(chalk.green.bold(' HQX Copy Trading'));
1028
- console.log(chalk.gray(getSeparator()));
1029
- console.log(chalk.white(` Status: ${isRunning ? chalk.green('RUNNING') : chalk.red('STOPPED')}`));
1030
- console.log(chalk.gray(getSeparator()));
1031
- console.log();
1032
-
1033
- // Risk Management
1034
- console.log(chalk.white.bold(' RISK MANAGEMENT'));
1035
- const targetProgress = Math.min(100, (stats.pnl / dailyTarget) * 100);
1036
- const riskProgress = Math.min(100, (Math.abs(Math.min(0, stats.pnl)) / maxRisk) * 100);
1037
- console.log(chalk.white(` Target: ${chalk.green('$' + dailyTarget.toFixed(2))} | Progress: ${targetProgress >= 100 ? chalk.green.bold(targetProgress.toFixed(1) + '%') : chalk.yellow(targetProgress.toFixed(1) + '%')}`));
1038
- console.log(chalk.white(` Risk: ${chalk.red('$' + maxRisk.toFixed(2))} | Used: ${riskProgress >= 100 ? chalk.red.bold(riskProgress.toFixed(1) + '%') : chalk.cyan(riskProgress.toFixed(1) + '%')}`));
1039
- console.log(chalk.white(` P&L: ${stats.pnl >= 0 ? chalk.green('+$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2))}`));
1040
- console.log();
1041
-
1042
- // Lead info
1043
- console.log(chalk.white.bold(' LEAD'));
1044
- console.log(chalk.white(` ${chalk.cyan(lead.account.accountName)} @ ${chalk.magenta(lead.account.propfirm)}`));
1045
- console.log(chalk.white(` ${chalk.cyan(lead.symbol.value)} x ${lead.contracts}`));
1046
- console.log();
1047
-
1048
- // Follower info
1049
- console.log(chalk.white.bold(' FOLLOWER'));
1050
- console.log(chalk.white(` ${chalk.cyan(follower.account.accountName)} @ ${chalk.magenta(follower.account.propfirm)}`));
1051
- console.log(chalk.white(` ${chalk.cyan(follower.symbol.value)} x ${follower.contracts}`));
1072
+ logo.forEach(line => {
1073
+ console.log(chalk.cyan(' ' + line));
1074
+ });
1075
+ console.log(chalk.gray(' Copy Trading System'));
1052
1076
  console.log();
1053
1077
 
1054
- // Stats
1055
- console.log(chalk.gray(getSeparator()));
1056
- console.log(chalk.white(' Stats: ') +
1057
- chalk.gray('Lead Trades: ') + chalk.cyan(stats.leadTrades) +
1058
- chalk.gray(' | Copied: ') + chalk.green(stats.copiedTrades) +
1059
- chalk.gray(' | Errors: ') + chalk.red(stats.errors)
1060
- );
1061
- console.log(chalk.gray(getSeparator()));
1078
+ // Info Box
1079
+ const pnlColor = stats.pnl >= 0 ? chalk.green : chalk.red;
1080
+ const pnlStr = (stats.pnl >= 0 ? '+$' : '-$') + Math.abs(stats.pnl).toFixed(2);
1081
+
1082
+ console.log(chalk.cyan(' ╔════════════════════════════════════════════════════════════════════╗'));
1083
+ console.log(chalk.cyan(' ║') + chalk.white(` LEAD: ${chalk.cyan((lead.account.accountName || '').substring(0, 20).padEnd(20))} ${chalk.yellow((lead.symbol.value || '').padEnd(10))} x${lead.contracts}`) + chalk.cyan(' ║'));
1084
+ console.log(chalk.cyan(' ║') + chalk.white(` FOLLOWER: ${chalk.cyan((follower.account.accountName || '').substring(0, 20).padEnd(20))} ${chalk.yellow((follower.symbol.value || '').padEnd(10))} x${follower.contracts}`) + chalk.cyan(' ║'));
1085
+ console.log(chalk.cyan(' ╠════════════════════════════════════════════════════════════════════╣'));
1086
+ console.log(chalk.cyan(' ║') + chalk.white(` Target: ${chalk.green(('$' + dailyTarget.toFixed(2)).padEnd(10))} Risk: ${chalk.red(('$' + maxRisk.toFixed(2)).padEnd(10))} P&L: ${pnlColor(pnlStr.padEnd(12))}`) + chalk.cyan(' ║'));
1087
+ console.log(chalk.cyan(' ║') + chalk.white(` Lead Trades: ${chalk.cyan(stats.leadTrades.toString().padEnd(4))} Copied: ${chalk.green(stats.copiedTrades.toString().padEnd(4))} Errors: ${chalk.red(stats.errors.toString().padEnd(4))}`) + chalk.cyan(' ║'));
1088
+ console.log(chalk.cyan(' ╠════════════════════════════════════════════════════════════════════╣'));
1089
+ console.log(chalk.cyan(' ║') + chalk.white(' Activity Log ') + chalk.yellow('Press X to stop') + chalk.cyan(' ║'));
1090
+ console.log(chalk.cyan(' ╠════════════════════════════════════════════════════════════════════╣'));
1062
1091
 
1063
1092
  // Logs
1064
- console.log(chalk.white.bold(' Activity Log'));
1065
- console.log(chalk.gray(getSeparator()));
1066
-
1067
1093
  if (logs.length === 0) {
1068
- console.log(chalk.gray(' Monitoring lead account for trades...'));
1094
+ console.log(chalk.cyan(' ║') + chalk.gray(' Monitoring lead account for trades...'.padEnd(68)) + chalk.cyan('║'));
1069
1095
  } else {
1070
- const typeColors = {
1071
- info: chalk.cyan,
1072
- success: chalk.green,
1073
- trade: chalk.green.bold,
1074
- copy: chalk.yellow.bold,
1075
- error: chalk.red,
1076
- warning: chalk.yellow
1077
- };
1078
-
1079
1096
  logs.forEach(log => {
1080
1097
  const color = typeColors[log.type] || chalk.white;
1081
- const icon = log.type === 'trade' ? '[>]' :
1082
- log.type === 'copy' ? '[+]' :
1083
- log.type === 'error' ? '[X]' :
1084
- log.type === 'success' ? '[OK]' : '[.]';
1085
- console.log(chalk.gray(` [${log.timestamp}]`) + ' ' + color(`${icon} ${log.message}`));
1098
+ const icon = getIcon(log.type);
1099
+ const logLine = `[${log.timestamp}] ${icon} ${log.message}`;
1100
+ const truncated = logLine.length > 66 ? logLine.substring(0, 63) + '...' : logLine;
1101
+ console.log(chalk.cyan('') + ' ' + color(truncated.padEnd(67)) + chalk.cyan('║'));
1086
1102
  });
1087
1103
  }
1088
1104
 
1089
- console.log(chalk.gray(getSeparator()));
1090
- console.log();
1091
- console.log(chalk.yellow(' Press X to stop copy trading...'));
1092
- console.log();
1105
+ console.log(chalk.cyan(' ╚════════════════════════════════════════════════════════════════════╝'));
1093
1106
  };
1094
1107
 
1095
1108
  addLog('info', 'Copy trading initialized');
@@ -30,6 +30,8 @@ class HQXServerService {
30
30
  this.listeners = new Map();
31
31
  this.heartbeatInterval = null;
32
32
  this.messageQueue = [];
33
+ this.latency = 0;
34
+ this.lastPingTime = 0;
33
35
  }
34
36
 
35
37
  /**
@@ -200,6 +202,14 @@ class HQXServerService {
200
202
  * Handle incoming messages
201
203
  */
202
204
  _handleMessage(message) {
205
+ // Calculate latency from server timestamp
206
+ if (message.timestamp) {
207
+ this.latency = Date.now() - message.timestamp;
208
+ if (this.latency < 0) this.latency = 0; // Handle clock skew
209
+ if (this.latency > 5000) this.latency = 0; // Ignore unrealistic values
210
+ this._emit('latency', { latency: this.latency });
211
+ }
212
+
203
213
  switch (message.type) {
204
214
  case 'signal':
205
215
  this._emit('signal', message.data);
@@ -217,12 +227,23 @@ class HQXServerService {
217
227
  this._emit('error', message.data);
218
228
  break;
219
229
  case 'pong':
220
- // Heartbeat response
230
+ // Calculate ping latency
231
+ if (this.lastPingTime > 0) {
232
+ this.latency = Date.now() - this.lastPingTime;
233
+ this._emit('latency', { latency: this.latency });
234
+ }
221
235
  break;
222
236
  default:
223
237
  this._emit('message', message);
224
238
  }
225
239
  }
240
+
241
+ /**
242
+ * Get current latency
243
+ */
244
+ getLatency() {
245
+ return this.latency;
246
+ }
226
247
 
227
248
  /**
228
249
  * Send message to server
@@ -303,9 +324,10 @@ class HQXServerService {
303
324
  _startHeartbeat() {
304
325
  this.heartbeatInterval = setInterval(() => {
305
326
  if (this.connected) {
306
- this.send('ping', { timestamp: Date.now() });
327
+ this.lastPingTime = Date.now();
328
+ this.send('ping', { timestamp: this.lastPingTime });
307
329
  }
308
- }, 30000);
330
+ }, 5000); // Ping every 5 seconds for more accurate latency
309
331
  }
310
332
 
311
333
  _stopHeartbeat() {