hedgequantx 1.2.57 → 1.2.59

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/pages/algo.js +185 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "1.2.57",
3
+ "version": "1.2.59",
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
@@ -215,15 +215,57 @@ const selectSymbolMenu = async (service, account) => {
215
215
  filter: (input) => parseInt(input)
216
216
  }
217
217
  ]);
218
+
219
+ // Risk Management
220
+ console.log();
221
+ console.log(chalk.cyan.bold(' Risk Management'));
222
+ console.log(chalk.gray(' Set your daily target and maximum risk to auto-stop the algo.'));
223
+ console.log();
224
+
225
+ const { dailyTarget } = await inquirer.prompt([
226
+ {
227
+ type: 'input',
228
+ name: 'dailyTarget',
229
+ message: chalk.white.bold('Daily Target ($):'),
230
+ default: '500',
231
+ validate: (input) => {
232
+ const num = parseFloat(input);
233
+ if (isNaN(num) || num <= 0) {
234
+ return 'Please enter a valid amount greater than 0';
235
+ }
236
+ return true;
237
+ },
238
+ filter: (input) => parseFloat(input)
239
+ }
240
+ ]);
241
+
242
+ const { maxRisk } = await inquirer.prompt([
243
+ {
244
+ type: 'input',
245
+ name: 'maxRisk',
246
+ message: chalk.white.bold('Max Risk ($):'),
247
+ default: '200',
248
+ validate: (input) => {
249
+ const num = parseFloat(input);
250
+ if (isNaN(num) || num <= 0) {
251
+ return 'Please enter a valid amount greater than 0';
252
+ }
253
+ return true;
254
+ },
255
+ filter: (input) => parseFloat(input)
256
+ }
257
+ ]);
218
258
 
219
259
  // Confirmation
220
260
  console.log();
221
261
  console.log(chalk.gray(getSeparator()));
222
262
  console.log(chalk.white.bold(' Algo Configuration:'));
223
263
  console.log(chalk.gray(getSeparator()));
224
- console.log(chalk.white(` Account: ${chalk.cyan(accountName)}`));
225
- console.log(chalk.white(` Symbol: ${chalk.cyan(contract.name || selectedSymbol.value)}`));
226
- console.log(chalk.white(` Contracts: ${chalk.cyan(contracts)}`));
264
+ console.log(chalk.white(` Account: ${chalk.cyan(accountName)}`));
265
+ console.log(chalk.white(` Symbol: ${chalk.cyan(contract.name || selectedSymbol.value)}`));
266
+ console.log(chalk.white(` Contracts: ${chalk.cyan(contracts)}`));
267
+ console.log(chalk.white(` Daily Target: ${chalk.green('$' + dailyTarget.toFixed(2))}`));
268
+ console.log(chalk.white(` Max Risk: ${chalk.red('$' + maxRisk.toFixed(2))}`));
227
269
  console.log(chalk.gray(getSeparator()));
228
270
  console.log();
229
271
 
@@ -244,13 +286,13 @@ const selectSymbolMenu = async (service, account) => {
244
286
  return;
245
287
  }
246
288
 
247
- await launchAlgo(service, account, contract, contracts);
289
+ await launchAlgo(service, account, contract, contracts, dailyTarget, maxRisk);
248
290
  };
249
291
 
250
292
  /**
251
293
  * Launch Algo with HQX Server Connection
252
294
  */
253
- const launchAlgo = async (service, account, contract, numContracts) => {
295
+ const launchAlgo = async (service, account, contract, numContracts, dailyTarget, maxRisk) => {
254
296
  const accountName = account.accountName || account.name || 'Account #' + account.accountId;
255
297
  const symbolName = contract.name || contract.symbol || contract.id;
256
298
  const symbol = contract.symbol || contract.id;
@@ -263,10 +305,11 @@ const launchAlgo = async (service, account, contract, numContracts) => {
263
305
  const hqxServer = new HQXServerService();
264
306
  let hqxConnected = false;
265
307
  let algoRunning = false;
308
+ let stopReason = null;
266
309
 
267
310
  // Activity logs
268
311
  const logs = [];
269
- const MAX_LOGS = 12;
312
+ const MAX_LOGS = 10;
270
313
 
271
314
  // Stats
272
315
  let stats = {
@@ -288,8 +331,30 @@ const launchAlgo = async (service, account, contract, numContracts) => {
288
331
  console.clear();
289
332
  };
290
333
 
334
+ // Check market hours
335
+ const checkMarketStatus = () => {
336
+ const now = new Date();
337
+ const utcDay = now.getUTCDay();
338
+ const utcHour = now.getUTCHours();
339
+ const isDST = (() => {
340
+ const jan = new Date(now.getFullYear(), 0, 1);
341
+ const jul = new Date(now.getFullYear(), 6, 1);
342
+ return now.getTimezoneOffset() < Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
343
+ })();
344
+ const ctOffset = isDST ? 5 : 6;
345
+ const ctHour = (utcHour - ctOffset + 24) % 24;
346
+ const ctDay = utcHour < ctOffset ? (utcDay + 6) % 7 : utcDay;
347
+
348
+ if (ctDay === 6) return { isOpen: false, message: 'Market closed (Saturday)' };
349
+ if (ctDay === 0 && ctHour < 17) return { isOpen: false, message: 'Market opens Sunday 5:00 PM CT' };
350
+ if (ctDay === 5 && ctHour >= 16) return { isOpen: false, message: 'Market closed (Friday after 4PM CT)' };
351
+ if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) return { isOpen: false, message: 'Daily maintenance (4:00-5:00 PM CT)' };
352
+ return { isOpen: true, message: 'Market OPEN' };
353
+ };
354
+
291
355
  const displayUI = () => {
292
356
  clearScreen();
357
+ const marketStatus = checkMarketStatus();
293
358
  console.log();
294
359
  console.log(chalk.gray(getSeparator()));
295
360
  console.log(chalk.cyan.bold(' HQX Ultra-Scalping Algo'));
@@ -298,8 +363,18 @@ const launchAlgo = async (service, account, contract, numContracts) => {
298
363
  console.log(chalk.white(` Account: ${chalk.cyan(accountName)}`));
299
364
  console.log(chalk.white(` Symbol: ${chalk.cyan(symbolName)}`));
300
365
  console.log(chalk.white(` Contracts: ${chalk.cyan(numContracts)}`));
301
- console.log(chalk.white(` Mode: ${hqxConnected ? chalk.green('LIVE') : chalk.yellow('OFFLINE')}`));
366
+ console.log(chalk.white(` Server: ${hqxConnected ? chalk.green('CONNECTED') : chalk.red('DISCONNECTED')}`));
367
+ console.log(chalk.white(` Market: ${marketStatus.isOpen ? chalk.green(marketStatus.message) : chalk.red(marketStatus.message)}`));
302
368
  console.log(chalk.gray(getSeparator()));
369
+
370
+ // Risk Management
371
+ console.log();
372
+ const targetProgress = Math.min(100, Math.max(0, (stats.pnl / dailyTarget) * 100));
373
+ const riskProgress = Math.min(100, (Math.abs(Math.min(0, stats.pnl)) / maxRisk) * 100);
374
+ console.log(chalk.white(' Target: ') + chalk.green('$' + dailyTarget.toFixed(2)) +
375
+ chalk.gray(' | Progress: ') + (targetProgress >= 100 ? chalk.green.bold(targetProgress.toFixed(1) + '%') : chalk.yellow(targetProgress.toFixed(1) + '%')));
376
+ console.log(chalk.white(' Risk: ') + chalk.red('$' + maxRisk.toFixed(2)) +
377
+ chalk.gray(' | Used: ') + (riskProgress >= 100 ? chalk.red.bold(riskProgress.toFixed(1) + '%') : chalk.cyan(riskProgress.toFixed(1) + '%')));
303
378
 
304
379
  // Stats bar
305
380
  console.log();
@@ -308,7 +383,7 @@ const launchAlgo = async (service, account, contract, numContracts) => {
308
383
  chalk.gray(' | Wins: ') + chalk.green(stats.wins) +
309
384
  chalk.gray(' | Losses: ') + chalk.red(stats.losses) +
310
385
  chalk.gray(' | Win Rate: ') + chalk.yellow(stats.winRate + '%') +
311
- chalk.gray(' | P&L: ') + (stats.pnl >= 0 ? chalk.green('$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2)))
386
+ chalk.gray(' | P&L: ') + (stats.pnl >= 0 ? chalk.green('+$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2)))
312
387
  );
313
388
  console.log();
314
389
 
@@ -409,6 +484,27 @@ const launchAlgo = async (service, account, contract, numContracts) => {
409
484
  addLog('trade', `Closed -$${Math.abs(data.pnl).toFixed(2)} (${data.reason || 'stop_loss'})`);
410
485
  }
411
486
  stats.winRate = stats.trades > 0 ? ((stats.wins / stats.trades) * 100).toFixed(1) : '0.0';
487
+
488
+ // Check daily target
489
+ if (stats.pnl >= dailyTarget) {
490
+ stopReason = 'target';
491
+ addLog('success', `Daily target reached! +$${stats.pnl.toFixed(2)}`);
492
+ algoRunning = false;
493
+ if (hqxConnected) {
494
+ hqxServer.stopAlgo();
495
+ }
496
+ }
497
+
498
+ // Check max risk
499
+ if (stats.pnl <= -maxRisk) {
500
+ stopReason = 'risk';
501
+ addLog('error', `Max risk reached! -$${Math.abs(stats.pnl).toFixed(2)}`);
502
+ algoRunning = false;
503
+ if (hqxConnected) {
504
+ hqxServer.stopAlgo();
505
+ }
506
+ }
507
+
412
508
  displayUI();
413
509
  });
414
510
 
@@ -431,13 +527,14 @@ const launchAlgo = async (service, account, contract, numContracts) => {
431
527
  // Start algo
432
528
  if (hqxConnected) {
433
529
  addLog('info', 'Starting HQX Ultra-Scalping...');
530
+ addLog('info', `Target: $${dailyTarget.toFixed(2)} | Risk: $${maxRisk.toFixed(2)}`);
434
531
  hqxServer.startAlgo({
435
532
  accountId: account.accountId,
436
533
  contractId: contract.id || contract.contractId,
437
534
  symbol: symbol,
438
535
  contracts: numContracts,
439
- dailyTarget: 500, // Default daily target
440
- maxRisk: 200, // Default max risk
536
+ dailyTarget: dailyTarget,
537
+ maxRisk: maxRisk,
441
538
  propfirm: account.propfirm || 'projectx',
442
539
  propfirmToken: service.getToken ? service.getToken() : null
443
540
  });
@@ -450,14 +547,44 @@ const launchAlgo = async (service, account, contract, numContracts) => {
450
547
 
451
548
  displayUI();
452
549
 
453
- // Wait for X key to stop
454
- await waitForStopKey();
550
+ // Wait for X key OR auto-stop (target/risk reached)
551
+ await new Promise((resolve) => {
552
+ // Check for auto-stop every 500ms
553
+ const checkInterval = setInterval(() => {
554
+ if (!algoRunning || stopReason) {
555
+ clearInterval(checkInterval);
556
+ if (process.stdin.isTTY && process.stdin.isRaw) {
557
+ process.stdin.setRawMode(false);
558
+ }
559
+ resolve();
560
+ }
561
+ }, 500);
562
+
563
+ // Also listen for X key
564
+ if (process.stdin.isTTY) {
565
+ readline.emitKeypressEvents(process.stdin);
566
+ process.stdin.setRawMode(true);
567
+
568
+ const onKeypress = (str, key) => {
569
+ if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
570
+ clearInterval(checkInterval);
571
+ process.stdin.setRawMode(false);
572
+ process.stdin.removeListener('keypress', onKeypress);
573
+ resolve();
574
+ }
575
+ };
576
+
577
+ process.stdin.on('keypress', onKeypress);
578
+ }
579
+ });
455
580
 
456
581
  // Stop algo
457
- addLog('warning', 'Stopping algo...');
458
- displayUI();
582
+ if (!stopReason) {
583
+ addLog('warning', 'Stopping algo...');
584
+ displayUI();
585
+ }
459
586
 
460
- if (hqxConnected) {
587
+ if (hqxConnected && algoRunning) {
461
588
  hqxServer.stopAlgo();
462
589
  }
463
590
 
@@ -465,18 +592,27 @@ const launchAlgo = async (service, account, contract, numContracts) => {
465
592
  algoRunning = false;
466
593
 
467
594
  console.log();
468
- console.log(chalk.green(' [OK] Algo stopped successfully'));
595
+ if (stopReason === 'target') {
596
+ console.log(chalk.green.bold(' [OK] Daily target reached! Algo stopped.'));
597
+ } else if (stopReason === 'risk') {
598
+ console.log(chalk.red.bold(' [X] Max risk reached! Algo stopped.'));
599
+ } else {
600
+ console.log(chalk.yellow(' [OK] Algo stopped by user'));
601
+ }
469
602
  console.log();
470
603
 
471
604
  // Final stats
472
605
  console.log(chalk.gray(getSeparator()));
473
606
  console.log(chalk.white.bold(' Session Summary'));
474
607
  console.log(chalk.gray(getSeparator()));
608
+ console.log(chalk.white(` Daily Target: ${chalk.green('$' + dailyTarget.toFixed(2))}`));
609
+ console.log(chalk.white(` Max Risk: ${chalk.red('$' + maxRisk.toFixed(2))}`));
610
+ console.log(chalk.white(` Final P&L: ${stats.pnl >= 0 ? chalk.green('+$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2))}`));
611
+ console.log(chalk.gray(getSeparator()));
475
612
  console.log(chalk.white(` Total Trades: ${chalk.cyan(stats.trades)}`));
476
613
  console.log(chalk.white(` Wins: ${chalk.green(stats.wins)}`));
477
614
  console.log(chalk.white(` Losses: ${chalk.red(stats.losses)}`));
478
615
  console.log(chalk.white(` Win Rate: ${chalk.yellow(stats.winRate + '%')}`));
479
- console.log(chalk.white(` Total P&L: ${stats.pnl >= 0 ? chalk.green('+$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2))}`));
480
616
  console.log(chalk.gray(getSeparator()));
481
617
  console.log();
482
618
 
@@ -1032,11 +1168,40 @@ const launchCopyTrading = async (config) => {
1032
1168
  }
1033
1169
  }, 2000); // Check every 2 seconds
1034
1170
 
1035
- // Wait for stop key
1036
- await waitForStopKey();
1171
+ // Wait for X key OR auto-stop (target/risk reached)
1172
+ await new Promise((resolve) => {
1173
+ // Check for auto-stop every 500ms
1174
+ const checkInterval = setInterval(() => {
1175
+ if (!isRunning || stopReason) {
1176
+ clearInterval(checkInterval);
1177
+ clearInterval(monitorInterval);
1178
+ if (process.stdin.isTTY && process.stdin.isRaw) {
1179
+ process.stdin.setRawMode(false);
1180
+ }
1181
+ resolve();
1182
+ }
1183
+ }, 500);
1184
+
1185
+ // Also listen for X key
1186
+ if (process.stdin.isTTY) {
1187
+ readline.emitKeypressEvents(process.stdin);
1188
+ process.stdin.setRawMode(true);
1189
+
1190
+ const onKeypress = (str, key) => {
1191
+ if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
1192
+ clearInterval(checkInterval);
1193
+ clearInterval(monitorInterval);
1194
+ process.stdin.setRawMode(false);
1195
+ process.stdin.removeListener('keypress', onKeypress);
1196
+ resolve();
1197
+ }
1198
+ };
1199
+
1200
+ process.stdin.on('keypress', onKeypress);
1201
+ }
1202
+ });
1037
1203
 
1038
1204
  // Cleanup
1039
- clearInterval(monitorInterval);
1040
1205
  isRunning = false;
1041
1206
 
1042
1207
  console.log();