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.
- package/package.json +1 -1
- package/src/pages/algo.js +190 -13
package/package.json
CHANGED
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:
|
|
535
|
-
console.log(chalk.cyan.bold(' Step 1:
|
|
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
|
|
652
|
+
// Step 3: Select Follower Account
|
|
560
653
|
console.log();
|
|
561
|
-
console.log(chalk.cyan.bold(' Step
|
|
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
|
|
680
|
+
// Step 4: Select Lead Symbol
|
|
588
681
|
console.log();
|
|
589
|
-
console.log(chalk.cyan.bold(' Step
|
|
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
|
|
716
|
+
// Step 5: Select Follower Symbol
|
|
624
717
|
console.log();
|
|
625
|
-
console.log(chalk.cyan.bold(' Step
|
|
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 =
|
|
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
|
-
|
|
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)}`));
|