hedgequantx 1.2.65 → 1.2.67

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.67",
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;
@@ -463,6 +465,11 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
463
465
  }
464
466
 
465
467
  // Setup event handlers - logs scroll down naturally
468
+ hqxServer.on('latency', (data) => {
469
+ latency = data.latency || 0;
470
+ displayUI(); // Refresh UI with new latency
471
+ });
472
+
466
473
  hqxServer.on('log', (data) => {
467
474
  printLog(data.type || 'info', data.message);
468
475
  });
@@ -554,32 +561,45 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
554
561
 
555
562
  // Wait for X key OR auto-stop (target/risk reached)
556
563
  await new Promise((resolve) => {
564
+ let resolved = false;
565
+
566
+ const cleanup = () => {
567
+ if (resolved) return;
568
+ resolved = true;
569
+ clearInterval(checkInterval);
570
+ if (process.stdin.isTTY) {
571
+ try {
572
+ process.stdin.setRawMode(false);
573
+ process.stdin.removeAllListeners('keypress');
574
+ } catch (e) {}
575
+ }
576
+ resolve();
577
+ };
578
+
557
579
  // Check for auto-stop every 500ms
558
580
  const checkInterval = setInterval(() => {
559
581
  if (!algoRunning || stopReason) {
560
- clearInterval(checkInterval);
561
- if (process.stdin.isTTY && process.stdin.isRaw) {
562
- process.stdin.setRawMode(false);
563
- }
564
- resolve();
582
+ cleanup();
565
583
  }
566
584
  }, 500);
567
585
 
568
- // Also listen for X key
586
+ // Listen for X key
569
587
  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);
588
+ try {
589
+ readline.emitKeypressEvents(process.stdin);
590
+ process.stdin.setRawMode(true);
591
+ process.stdin.resume();
592
+
593
+ process.stdin.on('keypress', (str, key) => {
594
+ if (!key) return;
595
+ const keyName = key.name?.toLowerCase();
596
+ if (keyName === 'x' || (key.ctrl && keyName === 'c')) {
597
+ cleanup();
598
+ }
599
+ });
600
+ } catch (e) {
601
+ // Fallback: just wait for auto-stop
602
+ }
583
603
  }
584
604
  });
585
605
 
@@ -1014,6 +1034,26 @@ const launchCopyTrading = async (config) => {
1014
1034
  pnl: 0
1015
1035
  };
1016
1036
 
1037
+ // Log colors
1038
+ const typeColors = {
1039
+ info: chalk.cyan,
1040
+ success: chalk.green,
1041
+ trade: chalk.green.bold,
1042
+ copy: chalk.yellow.bold,
1043
+ error: chalk.red,
1044
+ warning: chalk.yellow
1045
+ };
1046
+
1047
+ const getIcon = (type) => {
1048
+ switch(type) {
1049
+ case 'trade': return '[>]';
1050
+ case 'copy': return '[+]';
1051
+ case 'error': return '[X]';
1052
+ case 'success': return '[OK]';
1053
+ default: return '[.]';
1054
+ }
1055
+ };
1056
+
1017
1057
  const addLog = (type, message) => {
1018
1058
  const timestamp = new Date().toLocaleTimeString();
1019
1059
  logs.push({ timestamp, type, message });
@@ -1022,74 +1062,52 @@ const launchCopyTrading = async (config) => {
1022
1062
 
1023
1063
  const displayUI = () => {
1024
1064
  console.clear();
1065
+
1066
+ // Logo
1067
+ const logo = [
1068
+ '██╗ ██╗ ██████╗ ██╗ ██╗',
1069
+ '██║ ██║██╔═══██╗╚██╗██╔╝',
1070
+ '███████║██║ ██║ ╚███╔╝ ',
1071
+ '██╔══██║██║▄▄ ██║ ██╔██╗ ',
1072
+ '██║ ██║╚██████╔╝██╔╝ ██╗',
1073
+ '╚═╝ ╚═╝ ╚══▀▀═╝ ╚═╝ ╚═╝'
1074
+ ];
1075
+
1025
1076
  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}`));
1077
+ logo.forEach(line => {
1078
+ console.log(chalk.cyan(' ' + line));
1079
+ });
1080
+ console.log(chalk.gray(' Copy Trading System'));
1052
1081
  console.log();
1053
1082
 
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()));
1083
+ // Info Box
1084
+ const pnlColor = stats.pnl >= 0 ? chalk.green : chalk.red;
1085
+ const pnlStr = (stats.pnl >= 0 ? '+$' : '-$') + Math.abs(stats.pnl).toFixed(2);
1086
+
1087
+ console.log(chalk.cyan(' ╔════════════════════════════════════════════════════════════════════╗'));
1088
+ 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(' ║'));
1089
+ 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(' ║'));
1090
+ console.log(chalk.cyan(' ╠════════════════════════════════════════════════════════════════════╣'));
1091
+ 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(' ║'));
1092
+ 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(' ║'));
1093
+ console.log(chalk.cyan(' ╠════════════════════════════════════════════════════════════════════╣'));
1094
+ console.log(chalk.cyan(' ║') + chalk.white(' Activity Log ') + chalk.yellow('Press X to stop') + chalk.cyan(' ║'));
1095
+ console.log(chalk.cyan(' ╠════════════════════════════════════════════════════════════════════╣'));
1062
1096
 
1063
1097
  // Logs
1064
- console.log(chalk.white.bold(' Activity Log'));
1065
- console.log(chalk.gray(getSeparator()));
1066
-
1067
1098
  if (logs.length === 0) {
1068
- console.log(chalk.gray(' Monitoring lead account for trades...'));
1099
+ console.log(chalk.cyan(' ║') + chalk.gray(' Monitoring lead account for trades...'.padEnd(68)) + chalk.cyan('║'));
1069
1100
  } 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
1101
  logs.forEach(log => {
1080
1102
  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}`));
1103
+ const icon = getIcon(log.type);
1104
+ const logLine = `[${log.timestamp}] ${icon} ${log.message}`;
1105
+ const truncated = logLine.length > 66 ? logLine.substring(0, 63) + '...' : logLine;
1106
+ console.log(chalk.cyan('') + ' ' + color(truncated.padEnd(67)) + chalk.cyan('║'));
1086
1107
  });
1087
1108
  }
1088
1109
 
1089
- console.log(chalk.gray(getSeparator()));
1090
- console.log();
1091
- console.log(chalk.yellow(' Press X to stop copy trading...'));
1092
- console.log();
1110
+ console.log(chalk.cyan(' ╚════════════════════════════════════════════════════════════════════╝'));
1093
1111
  };
1094
1112
 
1095
1113
  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() {