hedgequantx 1.2.63 → 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.63",
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,
@@ -342,11 +342,11 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
342
342
  }
343
343
  };
344
344
 
345
- // Add log (newest first)
345
+ // Add log (oldest first, newest at bottom)
346
346
  const addLog = (type, message) => {
347
347
  const timestamp = new Date().toLocaleTimeString();
348
- logs.unshift({ timestamp, type, message }); // Add at beginning
349
- if (logs.length > MAX_LOGS) logs.pop(); // Remove oldest
348
+ logs.push({ timestamp, type, message }); // Add at end
349
+ if (logs.length > MAX_LOGS) logs.shift(); // Remove oldest from top
350
350
  };
351
351
 
352
352
  // Print log and refresh display
@@ -381,33 +381,52 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
381
381
  console.clear();
382
382
  const marketStatus = checkMarketStatus();
383
383
 
384
- // Header
384
+ // Logo
385
+ const logo = [
386
+ '██╗ ██╗ ██████╗ ██╗ ██╗',
387
+ '██║ ██║██╔═══██╗╚██╗██╔╝',
388
+ '███████║██║ ██║ ╚███╔╝ ',
389
+ '██╔══██║██║▄▄ ██║ ██╔██╗ ',
390
+ '██║ ██║╚██████╔╝██╔╝ ██╗',
391
+ '╚═╝ ╚═╝ ╚══▀▀═╝ ╚═╝ ╚═╝'
392
+ ];
393
+
394
+ console.log();
395
+ logo.forEach(line => {
396
+ console.log(chalk.cyan(' ' + line));
397
+ });
398
+ console.log(chalk.gray(' Ultra-Scalping Algorithm'));
385
399
  console.log();
386
- console.log(chalk.gray(getSeparator()));
387
- console.log(chalk.cyan.bold(' HQX Ultra-Scalping Algo') + chalk.gray(' | ') + chalk.yellow('Press X to stop'));
388
- console.log(chalk.gray(getSeparator()));
389
- console.log(chalk.white(` Account: ${chalk.cyan(accountName)} | Symbol: ${chalk.cyan(symbolName)} | Qty: ${chalk.cyan(numContracts)}`));
390
- console.log(chalk.white(` Target: ${chalk.green('$' + dailyTarget.toFixed(2))} | Risk: ${chalk.red('$' + maxRisk.toFixed(2))} | Server: ${hqxConnected ? chalk.green('ON') : chalk.red('OFF')}`));
400
+
401
+ // Info Box
402
+ console.log(chalk.cyan(' ╔════════════════════════════════════════════════════════════════════╗'));
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
+ 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('║'));
391
407
 
392
408
  // Stats line
393
409
  const pnlColor = stats.pnl >= 0 ? chalk.green : chalk.red;
394
- const pnlStr = stats.pnl >= 0 ? '+$' + stats.pnl.toFixed(2) : '-$' + Math.abs(stats.pnl).toFixed(2);
395
- console.log(chalk.white(` P&L: ${pnlColor(pnlStr)} | Trades: ${chalk.cyan(stats.trades)} | Wins: ${chalk.green(stats.wins)} | Losses: ${chalk.red(stats.losses)}`));
396
-
397
- console.log(chalk.gray(getSeparator()));
410
+ const pnlStr = (stats.pnl >= 0 ? '+$' : '-$') + Math.abs(stats.pnl).toFixed(2);
411
+ console.log(chalk.cyan(' ║') + chalk.white(` P&L: ${pnlColor(pnlStr.padEnd(12))} Trades: ${chalk.cyan(stats.trades.toString().padEnd(4))} W: ${chalk.green(stats.wins.toString().padEnd(3))} L: ${chalk.red(stats.losses.toString().padEnd(3))} `) + chalk.cyan('║'));
412
+ console.log(chalk.cyan(' ╠════════════════════════════════════════════════════════════════════╣'));
413
+ console.log(chalk.cyan(' ║') + chalk.white(' Activity Log ') + chalk.yellow('Press X to stop') + chalk.cyan(' ║'));
414
+ console.log(chalk.cyan(' ╠════════════════════════════════════════════════════════════════════╣'));
398
415
 
399
416
  // Logs (newest first - already in correct order)
400
417
  if (logs.length === 0) {
401
- console.log(chalk.gray(' Waiting for activity...'));
418
+ console.log(chalk.cyan(' ║') + chalk.gray(' Waiting for activity...'.padEnd(68)) + chalk.cyan('║'));
402
419
  } else {
403
420
  logs.forEach(log => {
404
421
  const color = typeColors[log.type] || chalk.white;
405
422
  const icon = getIcon(log.type);
406
- console.log(chalk.gray(` [${log.timestamp}]`) + ' ' + color(`${icon} ${log.message}`));
423
+ const logLine = `[${log.timestamp}] ${icon} ${log.message}`;
424
+ const truncated = logLine.length > 66 ? logLine.substring(0, 63) + '...' : logLine;
425
+ console.log(chalk.cyan(' ║') + ' ' + color(truncated.padEnd(67)) + chalk.cyan('║'));
407
426
  });
408
427
  }
409
428
 
410
- console.log(chalk.gray(getSeparator()));
429
+ console.log(chalk.cyan(' ╚════════════════════════════════════════════════════════════════════╝'));
411
430
  };
412
431
 
413
432
  // Connect to HQX Server
@@ -537,32 +556,45 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
537
556
 
538
557
  // Wait for X key OR auto-stop (target/risk reached)
539
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
+
540
574
  // Check for auto-stop every 500ms
541
575
  const checkInterval = setInterval(() => {
542
576
  if (!algoRunning || stopReason) {
543
- clearInterval(checkInterval);
544
- if (process.stdin.isTTY && process.stdin.isRaw) {
545
- process.stdin.setRawMode(false);
546
- }
547
- resolve();
577
+ cleanup();
548
578
  }
549
579
  }, 500);
550
580
 
551
- // Also listen for X key
581
+ // Listen for X key
552
582
  if (process.stdin.isTTY) {
553
- readline.emitKeypressEvents(process.stdin);
554
- process.stdin.setRawMode(true);
555
-
556
- const onKeypress = (str, key) => {
557
- if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
558
- clearInterval(checkInterval);
559
- process.stdin.setRawMode(false);
560
- process.stdin.removeListener('keypress', onKeypress);
561
- resolve();
562
- }
563
- };
564
-
565
- 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
+ }
566
598
  }
567
599
  });
568
600
 
@@ -997,6 +1029,26 @@ const launchCopyTrading = async (config) => {
997
1029
  pnl: 0
998
1030
  };
999
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
+
1000
1052
  const addLog = (type, message) => {
1001
1053
  const timestamp = new Date().toLocaleTimeString();
1002
1054
  logs.push({ timestamp, type, message });
@@ -1005,74 +1057,52 @@ const launchCopyTrading = async (config) => {
1005
1057
 
1006
1058
  const displayUI = () => {
1007
1059
  console.clear();
1060
+
1061
+ // Logo
1062
+ const logo = [
1063
+ '██╗ ██╗ ██████╗ ██╗ ██╗',
1064
+ '██║ ██║██╔═══██╗╚██╗██╔╝',
1065
+ '███████║██║ ██║ ╚███╔╝ ',
1066
+ '██╔══██║██║▄▄ ██║ ██╔██╗ ',
1067
+ '██║ ██║╚██████╔╝██╔╝ ██╗',
1068
+ '╚═╝ ╚═╝ ╚══▀▀═╝ ╚═╝ ╚═╝'
1069
+ ];
1070
+
1008
1071
  console.log();
1009
- console.log(chalk.gray(getSeparator()));
1010
- console.log(chalk.green.bold(' HQX Copy Trading'));
1011
- console.log(chalk.gray(getSeparator()));
1012
- console.log(chalk.white(` Status: ${isRunning ? chalk.green('RUNNING') : chalk.red('STOPPED')}`));
1013
- console.log(chalk.gray(getSeparator()));
1014
- console.log();
1015
-
1016
- // Risk Management
1017
- console.log(chalk.white.bold(' RISK MANAGEMENT'));
1018
- const targetProgress = Math.min(100, (stats.pnl / dailyTarget) * 100);
1019
- const riskProgress = Math.min(100, (Math.abs(Math.min(0, stats.pnl)) / maxRisk) * 100);
1020
- console.log(chalk.white(` Target: ${chalk.green('$' + dailyTarget.toFixed(2))} | Progress: ${targetProgress >= 100 ? chalk.green.bold(targetProgress.toFixed(1) + '%') : chalk.yellow(targetProgress.toFixed(1) + '%')}`));
1021
- console.log(chalk.white(` Risk: ${chalk.red('$' + maxRisk.toFixed(2))} | Used: ${riskProgress >= 100 ? chalk.red.bold(riskProgress.toFixed(1) + '%') : chalk.cyan(riskProgress.toFixed(1) + '%')}`));
1022
- console.log(chalk.white(` P&L: ${stats.pnl >= 0 ? chalk.green('+$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2))}`));
1023
- console.log();
1024
-
1025
- // Lead info
1026
- console.log(chalk.white.bold(' LEAD'));
1027
- console.log(chalk.white(` ${chalk.cyan(lead.account.accountName)} @ ${chalk.magenta(lead.account.propfirm)}`));
1028
- console.log(chalk.white(` ${chalk.cyan(lead.symbol.value)} x ${lead.contracts}`));
1029
- console.log();
1030
-
1031
- // Follower info
1032
- console.log(chalk.white.bold(' FOLLOWER'));
1033
- console.log(chalk.white(` ${chalk.cyan(follower.account.accountName)} @ ${chalk.magenta(follower.account.propfirm)}`));
1034
- 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'));
1035
1076
  console.log();
1036
1077
 
1037
- // Stats
1038
- console.log(chalk.gray(getSeparator()));
1039
- console.log(chalk.white(' Stats: ') +
1040
- chalk.gray('Lead Trades: ') + chalk.cyan(stats.leadTrades) +
1041
- chalk.gray(' | Copied: ') + chalk.green(stats.copiedTrades) +
1042
- chalk.gray(' | Errors: ') + chalk.red(stats.errors)
1043
- );
1044
- 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(' ╠════════════════════════════════════════════════════════════════════╣'));
1045
1091
 
1046
1092
  // Logs
1047
- console.log(chalk.white.bold(' Activity Log'));
1048
- console.log(chalk.gray(getSeparator()));
1049
-
1050
1093
  if (logs.length === 0) {
1051
- 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('║'));
1052
1095
  } else {
1053
- const typeColors = {
1054
- info: chalk.cyan,
1055
- success: chalk.green,
1056
- trade: chalk.green.bold,
1057
- copy: chalk.yellow.bold,
1058
- error: chalk.red,
1059
- warning: chalk.yellow
1060
- };
1061
-
1062
1096
  logs.forEach(log => {
1063
1097
  const color = typeColors[log.type] || chalk.white;
1064
- const icon = log.type === 'trade' ? '[>]' :
1065
- log.type === 'copy' ? '[+]' :
1066
- log.type === 'error' ? '[X]' :
1067
- log.type === 'success' ? '[OK]' : '[.]';
1068
- 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('║'));
1069
1102
  });
1070
1103
  }
1071
1104
 
1072
- console.log(chalk.gray(getSeparator()));
1073
- console.log();
1074
- console.log(chalk.yellow(' Press X to stop copy trading...'));
1075
- console.log();
1105
+ console.log(chalk.cyan(' ╚════════════════════════════════════════════════════════════════════╝'));
1076
1106
  };
1077
1107
 
1078
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() {