hedgequantx 1.2.55 → 1.2.57

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 +190 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "1.2.55",
3
+ "version": "1.2.57",
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
@@ -519,6 +519,55 @@ const copyTradingMenu = async () => {
519
519
  console.log(chalk.gray(getSeparator()));
520
520
  console.log();
521
521
 
522
+ // Check market status first
523
+ const marketSpinner = ora('Checking market status...').start();
524
+
525
+ // Use a simple market hours check
526
+ const now = new Date();
527
+ const utcDay = now.getUTCDay();
528
+ const utcHour = now.getUTCHours();
529
+ const isDST = (() => {
530
+ const jan = new Date(now.getFullYear(), 0, 1);
531
+ const jul = new Date(now.getFullYear(), 6, 1);
532
+ return now.getTimezoneOffset() < Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
533
+ })();
534
+ const ctOffset = isDST ? 5 : 6;
535
+ const ctHour = (utcHour - ctOffset + 24) % 24;
536
+ const ctDay = utcHour < ctOffset ? (utcDay + 6) % 7 : utcDay;
537
+
538
+ let marketClosed = false;
539
+ let marketMessage = '';
540
+
541
+ if (ctDay === 6) {
542
+ marketClosed = true;
543
+ marketMessage = 'Market closed (Saturday)';
544
+ } else if (ctDay === 0 && ctHour < 17) {
545
+ marketClosed = true;
546
+ marketMessage = 'Market opens Sunday 5:00 PM CT';
547
+ } else if (ctDay === 5 && ctHour >= 16) {
548
+ marketClosed = true;
549
+ marketMessage = 'Market closed (Friday after 4PM CT)';
550
+ } else if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) {
551
+ marketClosed = true;
552
+ marketMessage = 'Daily maintenance (4:00-5:00 PM CT)';
553
+ }
554
+
555
+ if (marketClosed) {
556
+ marketSpinner.fail('Market is CLOSED');
557
+ console.log();
558
+ console.log(chalk.red.bold(' [X] ' + marketMessage));
559
+ console.log();
560
+ console.log(chalk.gray(' Futures markets (CME) trading hours:'));
561
+ console.log(chalk.gray(' Sunday 5:00 PM CT - Friday 4:00 PM CT'));
562
+ console.log(chalk.gray(' Daily maintenance: 4:00 PM - 5:00 PM CT'));
563
+ console.log();
564
+ await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
565
+ return;
566
+ }
567
+
568
+ marketSpinner.succeed('Market is OPEN - Ready to trade!');
569
+ console.log();
570
+
522
571
  // Get all active accounts from all connections
523
572
  const allAccounts = await connections.getAllAccounts();
524
573
  const activeAccounts = allAccounts.filter(acc => acc.status === 0);
@@ -531,8 +580,52 @@ const copyTradingMenu = async () => {
531
580
  return;
532
581
  }
533
582
 
534
- // Step 1: Select Lead Account
535
- console.log(chalk.cyan.bold(' Step 1: Select Lead Account'));
583
+ // Step 1: Risk Management Settings
584
+ console.log(chalk.cyan.bold(' Step 1: Risk Management'));
585
+ console.log(chalk.gray(' Set your daily target and maximum risk to auto-stop copy trading.'));
586
+ console.log();
587
+
588
+ const { dailyTarget } = await inquirer.prompt([
589
+ {
590
+ type: 'input',
591
+ name: 'dailyTarget',
592
+ message: chalk.white.bold('Daily Target ($):'),
593
+ default: '500',
594
+ validate: (input) => {
595
+ const num = parseFloat(input);
596
+ if (isNaN(num) || num <= 0) {
597
+ return 'Please enter a valid amount greater than 0';
598
+ }
599
+ return true;
600
+ },
601
+ filter: (input) => parseFloat(input)
602
+ }
603
+ ]);
604
+
605
+ const { maxRisk } = await inquirer.prompt([
606
+ {
607
+ type: 'input',
608
+ name: 'maxRisk',
609
+ message: chalk.white.bold('Max Risk ($):'),
610
+ default: '200',
611
+ validate: (input) => {
612
+ const num = parseFloat(input);
613
+ if (isNaN(num) || num <= 0) {
614
+ return 'Please enter a valid amount greater than 0';
615
+ }
616
+ return true;
617
+ },
618
+ filter: (input) => parseFloat(input)
619
+ }
620
+ ]);
621
+
622
+ console.log();
623
+ console.log(chalk.gray(' Daily Target: ') + chalk.green('$' + dailyTarget.toFixed(2)));
624
+ console.log(chalk.gray(' Max Risk: ') + chalk.red('$' + maxRisk.toFixed(2)));
625
+ console.log();
626
+
627
+ // Step 2: Select Lead Account
628
+ console.log(chalk.cyan.bold(' Step 2: Select Lead Account'));
536
629
  console.log(chalk.gray(' The lead account is the master account whose trades will be copied.'));
537
630
  console.log();
538
631
 
@@ -556,9 +649,9 @@ const copyTradingMenu = async () => {
556
649
 
557
650
  if (leadAccount === 'back') return;
558
651
 
559
- // Step 2: Select Follower Account
652
+ // Step 3: Select Follower Account
560
653
  console.log();
561
- console.log(chalk.cyan.bold(' Step 2: Select Follower Account'));
654
+ console.log(chalk.cyan.bold(' Step 3: Select Follower Account'));
562
655
  console.log(chalk.gray(' The follower account will copy trades from the lead account.'));
563
656
  console.log();
564
657
 
@@ -584,9 +677,9 @@ const copyTradingMenu = async () => {
584
677
 
585
678
  if (followerAccount === 'back') return;
586
679
 
587
- // Step 3: Select Lead Symbol
680
+ // Step 4: Select Lead Symbol
588
681
  console.log();
589
- console.log(chalk.cyan.bold(' Step 3: Configure Lead Symbol'));
682
+ console.log(chalk.cyan.bold(' Step 4: Configure Lead Symbol'));
590
683
  console.log();
591
684
 
592
685
  const { leadSymbol } = await inquirer.prompt([
@@ -620,9 +713,9 @@ const copyTradingMenu = async () => {
620
713
  }
621
714
  ]);
622
715
 
623
- // Step 4: Select Follower Symbol
716
+ // Step 5: Select Follower Symbol
624
717
  console.log();
625
- console.log(chalk.cyan.bold(' Step 4: Configure Follower Symbol'));
718
+ console.log(chalk.cyan.bold(' Step 5: Configure Follower Symbol'));
626
719
  console.log();
627
720
 
628
721
  const { followerSymbol } = await inquirer.prompt([
@@ -662,6 +755,10 @@ const copyTradingMenu = async () => {
662
755
  console.log(chalk.white.bold(' Copy Trading Configuration'));
663
756
  console.log(chalk.gray(getSeparator()));
664
757
  console.log();
758
+ console.log(chalk.white(' RISK MANAGEMENT'));
759
+ console.log(chalk.white(` Daily Target: ${chalk.green('$' + dailyTarget.toFixed(2))}`));
760
+ console.log(chalk.white(` Max Risk: ${chalk.red('$' + maxRisk.toFixed(2))}`));
761
+ console.log();
665
762
  console.log(chalk.white(' LEAD ACCOUNT'));
666
763
  console.log(chalk.white(` Account: ${chalk.cyan(leadAccount.accountName || leadAccount.name)}`));
667
764
  console.log(chalk.white(` PropFirm: ${chalk.magenta(leadAccount.propfirm)}`));
@@ -694,6 +791,8 @@ const copyTradingMenu = async () => {
694
791
 
695
792
  // Launch Copy Trading
696
793
  await launchCopyTrading({
794
+ dailyTarget,
795
+ maxRisk,
697
796
  lead: {
698
797
  account: leadAccount,
699
798
  symbol: leadSymbol,
@@ -713,22 +812,24 @@ const copyTradingMenu = async () => {
713
812
  * Launch Copy Trading
714
813
  */
715
814
  const launchCopyTrading = async (config) => {
716
- const { lead, follower } = config;
815
+ const { lead, follower, dailyTarget, maxRisk } = config;
717
816
 
718
817
  console.log();
719
818
  console.log(chalk.green.bold(' [>] Launching Copy Trading...'));
720
819
  console.log();
721
820
 
722
821
  let isRunning = true;
822
+ let stopReason = null;
723
823
  let lastLeadPosition = null;
724
824
  const logs = [];
725
- const MAX_LOGS = 15;
825
+ const MAX_LOGS = 12;
726
826
 
727
827
  const stats = {
728
828
  copiedTrades: 0,
729
829
  leadTrades: 0,
730
830
  followerTrades: 0,
731
- errors: 0
831
+ errors: 0,
832
+ pnl: 0
732
833
  };
733
834
 
734
835
  const addLog = (type, message) => {
@@ -747,6 +848,15 @@ const launchCopyTrading = async (config) => {
747
848
  console.log(chalk.gray(getSeparator()));
748
849
  console.log();
749
850
 
851
+ // Risk Management
852
+ console.log(chalk.white.bold(' RISK MANAGEMENT'));
853
+ const targetProgress = Math.min(100, (stats.pnl / dailyTarget) * 100);
854
+ const riskProgress = Math.min(100, (Math.abs(Math.min(0, stats.pnl)) / maxRisk) * 100);
855
+ console.log(chalk.white(` Target: ${chalk.green('$' + dailyTarget.toFixed(2))} | Progress: ${targetProgress >= 100 ? chalk.green.bold(targetProgress.toFixed(1) + '%') : chalk.yellow(targetProgress.toFixed(1) + '%')}`));
856
+ console.log(chalk.white(` Risk: ${chalk.red('$' + maxRisk.toFixed(2))} | Used: ${riskProgress >= 100 ? chalk.red.bold(riskProgress.toFixed(1) + '%') : chalk.cyan(riskProgress.toFixed(1) + '%')}`));
857
+ console.log(chalk.white(` P&L: ${stats.pnl >= 0 ? chalk.green('+$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2))}`));
858
+ console.log();
859
+
750
860
  // Lead info
751
861
  console.log(chalk.white.bold(' LEAD'));
752
862
  console.log(chalk.white(` ${chalk.cyan(lead.account.accountName)} @ ${chalk.magenta(lead.account.propfirm)}`));
@@ -809,6 +919,63 @@ const launchCopyTrading = async (config) => {
809
919
  if (!isRunning) return;
810
920
 
811
921
  try {
922
+ // Get follower positions for P&L tracking
923
+ const followerPositions = await follower.service.getPositions(follower.account.rithmicAccountId || follower.account.accountId);
924
+
925
+ if (followerPositions.success && followerPositions.positions) {
926
+ const followerPos = followerPositions.positions.find(p =>
927
+ p.symbol === follower.symbol.value ||
928
+ p.symbol?.includes(follower.symbol.searchText)
929
+ );
930
+
931
+ // Update P&L from follower position
932
+ if (followerPos && typeof followerPos.unrealizedPnl === 'number') {
933
+ stats.pnl = followerPos.unrealizedPnl;
934
+ }
935
+ }
936
+
937
+ // Check if daily target reached
938
+ if (stats.pnl >= dailyTarget) {
939
+ isRunning = false;
940
+ stopReason = 'target';
941
+ addLog('success', `Daily target reached! +$${stats.pnl.toFixed(2)}`);
942
+
943
+ // Close follower position
944
+ try {
945
+ await follower.service.closePosition(
946
+ follower.account.rithmicAccountId || follower.account.accountId,
947
+ follower.symbol.value
948
+ );
949
+ addLog('info', 'Follower position closed');
950
+ } catch (e) {
951
+ // Position may already be closed
952
+ }
953
+
954
+ displayUI();
955
+ return;
956
+ }
957
+
958
+ // Check if max risk reached
959
+ if (stats.pnl <= -maxRisk) {
960
+ isRunning = false;
961
+ stopReason = 'risk';
962
+ addLog('error', `Max risk reached! -$${Math.abs(stats.pnl).toFixed(2)}`);
963
+
964
+ // Close follower position
965
+ try {
966
+ await follower.service.closePosition(
967
+ follower.account.rithmicAccountId || follower.account.accountId,
968
+ follower.symbol.value
969
+ );
970
+ addLog('info', 'Follower position closed');
971
+ } catch (e) {
972
+ // Position may already be closed
973
+ }
974
+
975
+ displayUI();
976
+ return;
977
+ }
978
+
812
979
  // Get lead positions
813
980
  const leadPositions = await lead.service.getPositions(lead.account.rithmicAccountId || lead.account.accountId);
814
981
 
@@ -869,15 +1036,25 @@ const launchCopyTrading = async (config) => {
869
1036
  await waitForStopKey();
870
1037
 
871
1038
  // Cleanup
872
- isRunning = false;
873
1039
  clearInterval(monitorInterval);
1040
+ isRunning = false;
874
1041
 
875
1042
  console.log();
876
- console.log(chalk.green(' [OK] Copy trading stopped'));
1043
+ if (stopReason === 'target') {
1044
+ console.log(chalk.green.bold(' [OK] Daily target reached! Copy trading stopped.'));
1045
+ } else if (stopReason === 'risk') {
1046
+ console.log(chalk.red.bold(' [X] Max risk reached! Copy trading stopped.'));
1047
+ } else {
1048
+ console.log(chalk.yellow(' [OK] Copy trading stopped by user'));
1049
+ }
877
1050
  console.log();
878
1051
  console.log(chalk.gray(getSeparator()));
879
1052
  console.log(chalk.white.bold(' Session Summary'));
880
1053
  console.log(chalk.gray(getSeparator()));
1054
+ console.log(chalk.white(` Daily Target: ${chalk.green('$' + dailyTarget.toFixed(2))}`));
1055
+ console.log(chalk.white(` Max Risk: ${chalk.red('$' + maxRisk.toFixed(2))}`));
1056
+ console.log(chalk.white(` Final P&L: ${stats.pnl >= 0 ? chalk.green('+$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2))}`));
1057
+ console.log(chalk.gray(getSeparator()));
881
1058
  console.log(chalk.white(` Lead Trades: ${chalk.cyan(stats.leadTrades)}`));
882
1059
  console.log(chalk.white(` Copied Trades: ${chalk.green(stats.copiedTrades)}`));
883
1060
  console.log(chalk.white(` Errors: ${chalk.red(stats.errors)}`));