hedgequantx 1.2.57 → 1.2.58

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 +161 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "1.2.57",
3
+ "version": "1.2.58",
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 = {
@@ -300,6 +343,15 @@ const launchAlgo = async (service, account, contract, numContracts) => {
300
343
  console.log(chalk.white(` Contracts: ${chalk.cyan(numContracts)}`));
301
344
  console.log(chalk.white(` Mode: ${hqxConnected ? chalk.green('LIVE') : chalk.yellow('OFFLINE')}`));
302
345
  console.log(chalk.gray(getSeparator()));
346
+
347
+ // Risk Management
348
+ console.log();
349
+ const targetProgress = Math.min(100, Math.max(0, (stats.pnl / dailyTarget) * 100));
350
+ const riskProgress = Math.min(100, (Math.abs(Math.min(0, stats.pnl)) / maxRisk) * 100);
351
+ console.log(chalk.white(' Target: ') + chalk.green('$' + dailyTarget.toFixed(2)) +
352
+ chalk.gray(' | Progress: ') + (targetProgress >= 100 ? chalk.green.bold(targetProgress.toFixed(1) + '%') : chalk.yellow(targetProgress.toFixed(1) + '%')));
353
+ console.log(chalk.white(' Risk: ') + chalk.red('$' + maxRisk.toFixed(2)) +
354
+ chalk.gray(' | Used: ') + (riskProgress >= 100 ? chalk.red.bold(riskProgress.toFixed(1) + '%') : chalk.cyan(riskProgress.toFixed(1) + '%')));
303
355
 
304
356
  // Stats bar
305
357
  console.log();
@@ -308,7 +360,7 @@ const launchAlgo = async (service, account, contract, numContracts) => {
308
360
  chalk.gray(' | Wins: ') + chalk.green(stats.wins) +
309
361
  chalk.gray(' | Losses: ') + chalk.red(stats.losses) +
310
362
  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)))
363
+ chalk.gray(' | P&L: ') + (stats.pnl >= 0 ? chalk.green('+$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2)))
312
364
  );
313
365
  console.log();
314
366
 
@@ -409,6 +461,27 @@ const launchAlgo = async (service, account, contract, numContracts) => {
409
461
  addLog('trade', `Closed -$${Math.abs(data.pnl).toFixed(2)} (${data.reason || 'stop_loss'})`);
410
462
  }
411
463
  stats.winRate = stats.trades > 0 ? ((stats.wins / stats.trades) * 100).toFixed(1) : '0.0';
464
+
465
+ // Check daily target
466
+ if (stats.pnl >= dailyTarget) {
467
+ stopReason = 'target';
468
+ addLog('success', `Daily target reached! +$${stats.pnl.toFixed(2)}`);
469
+ algoRunning = false;
470
+ if (hqxConnected) {
471
+ hqxServer.stopAlgo();
472
+ }
473
+ }
474
+
475
+ // Check max risk
476
+ if (stats.pnl <= -maxRisk) {
477
+ stopReason = 'risk';
478
+ addLog('error', `Max risk reached! -$${Math.abs(stats.pnl).toFixed(2)}`);
479
+ algoRunning = false;
480
+ if (hqxConnected) {
481
+ hqxServer.stopAlgo();
482
+ }
483
+ }
484
+
412
485
  displayUI();
413
486
  });
414
487
 
@@ -431,13 +504,14 @@ const launchAlgo = async (service, account, contract, numContracts) => {
431
504
  // Start algo
432
505
  if (hqxConnected) {
433
506
  addLog('info', 'Starting HQX Ultra-Scalping...');
507
+ addLog('info', `Target: $${dailyTarget.toFixed(2)} | Risk: $${maxRisk.toFixed(2)}`);
434
508
  hqxServer.startAlgo({
435
509
  accountId: account.accountId,
436
510
  contractId: contract.id || contract.contractId,
437
511
  symbol: symbol,
438
512
  contracts: numContracts,
439
- dailyTarget: 500, // Default daily target
440
- maxRisk: 200, // Default max risk
513
+ dailyTarget: dailyTarget,
514
+ maxRisk: maxRisk,
441
515
  propfirm: account.propfirm || 'projectx',
442
516
  propfirmToken: service.getToken ? service.getToken() : null
443
517
  });
@@ -450,14 +524,44 @@ const launchAlgo = async (service, account, contract, numContracts) => {
450
524
 
451
525
  displayUI();
452
526
 
453
- // Wait for X key to stop
454
- await waitForStopKey();
527
+ // Wait for X key OR auto-stop (target/risk reached)
528
+ await new Promise((resolve) => {
529
+ // Check for auto-stop every 500ms
530
+ const checkInterval = setInterval(() => {
531
+ if (!algoRunning || stopReason) {
532
+ clearInterval(checkInterval);
533
+ if (process.stdin.isTTY && process.stdin.isRaw) {
534
+ process.stdin.setRawMode(false);
535
+ }
536
+ resolve();
537
+ }
538
+ }, 500);
539
+
540
+ // Also listen for X key
541
+ if (process.stdin.isTTY) {
542
+ readline.emitKeypressEvents(process.stdin);
543
+ process.stdin.setRawMode(true);
544
+
545
+ const onKeypress = (str, key) => {
546
+ if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
547
+ clearInterval(checkInterval);
548
+ process.stdin.setRawMode(false);
549
+ process.stdin.removeListener('keypress', onKeypress);
550
+ resolve();
551
+ }
552
+ };
553
+
554
+ process.stdin.on('keypress', onKeypress);
555
+ }
556
+ });
455
557
 
456
558
  // Stop algo
457
- addLog('warning', 'Stopping algo...');
458
- displayUI();
559
+ if (!stopReason) {
560
+ addLog('warning', 'Stopping algo...');
561
+ displayUI();
562
+ }
459
563
 
460
- if (hqxConnected) {
564
+ if (hqxConnected && algoRunning) {
461
565
  hqxServer.stopAlgo();
462
566
  }
463
567
 
@@ -465,18 +569,27 @@ const launchAlgo = async (service, account, contract, numContracts) => {
465
569
  algoRunning = false;
466
570
 
467
571
  console.log();
468
- console.log(chalk.green(' [OK] Algo stopped successfully'));
572
+ if (stopReason === 'target') {
573
+ console.log(chalk.green.bold(' [OK] Daily target reached! Algo stopped.'));
574
+ } else if (stopReason === 'risk') {
575
+ console.log(chalk.red.bold(' [X] Max risk reached! Algo stopped.'));
576
+ } else {
577
+ console.log(chalk.yellow(' [OK] Algo stopped by user'));
578
+ }
469
579
  console.log();
470
580
 
471
581
  // Final stats
472
582
  console.log(chalk.gray(getSeparator()));
473
583
  console.log(chalk.white.bold(' Session Summary'));
474
584
  console.log(chalk.gray(getSeparator()));
585
+ console.log(chalk.white(` Daily Target: ${chalk.green('$' + dailyTarget.toFixed(2))}`));
586
+ console.log(chalk.white(` Max Risk: ${chalk.red('$' + maxRisk.toFixed(2))}`));
587
+ console.log(chalk.white(` Final P&L: ${stats.pnl >= 0 ? chalk.green('+$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2))}`));
588
+ console.log(chalk.gray(getSeparator()));
475
589
  console.log(chalk.white(` Total Trades: ${chalk.cyan(stats.trades)}`));
476
590
  console.log(chalk.white(` Wins: ${chalk.green(stats.wins)}`));
477
591
  console.log(chalk.white(` Losses: ${chalk.red(stats.losses)}`));
478
592
  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
593
  console.log(chalk.gray(getSeparator()));
481
594
  console.log();
482
595
 
@@ -1032,11 +1145,40 @@ const launchCopyTrading = async (config) => {
1032
1145
  }
1033
1146
  }, 2000); // Check every 2 seconds
1034
1147
 
1035
- // Wait for stop key
1036
- await waitForStopKey();
1148
+ // Wait for X key OR auto-stop (target/risk reached)
1149
+ await new Promise((resolve) => {
1150
+ // Check for auto-stop every 500ms
1151
+ const checkInterval = setInterval(() => {
1152
+ if (!isRunning || stopReason) {
1153
+ clearInterval(checkInterval);
1154
+ clearInterval(monitorInterval);
1155
+ if (process.stdin.isTTY && process.stdin.isRaw) {
1156
+ process.stdin.setRawMode(false);
1157
+ }
1158
+ resolve();
1159
+ }
1160
+ }, 500);
1161
+
1162
+ // Also listen for X key
1163
+ if (process.stdin.isTTY) {
1164
+ readline.emitKeypressEvents(process.stdin);
1165
+ process.stdin.setRawMode(true);
1166
+
1167
+ const onKeypress = (str, key) => {
1168
+ if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
1169
+ clearInterval(checkInterval);
1170
+ clearInterval(monitorInterval);
1171
+ process.stdin.setRawMode(false);
1172
+ process.stdin.removeListener('keypress', onKeypress);
1173
+ resolve();
1174
+ }
1175
+ };
1176
+
1177
+ process.stdin.on('keypress', onKeypress);
1178
+ }
1179
+ });
1037
1180
 
1038
1181
  // Cleanup
1039
- clearInterval(monitorInterval);
1040
1182
  isRunning = false;
1041
1183
 
1042
1184
  console.log();