hedgequantx 1.2.56 → 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.
- package/package.json +1 -1
- package/src/pages/algo.js +301 -31
package/package.json
CHANGED
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:
|
|
225
|
-
console.log(chalk.white(` Symbol:
|
|
226
|
-
console.log(chalk.white(` 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 =
|
|
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('
|
|
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:
|
|
440
|
-
maxRisk:
|
|
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
|
|
454
|
-
await
|
|
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
|
-
|
|
458
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -580,8 +693,52 @@ const copyTradingMenu = async () => {
|
|
|
580
693
|
return;
|
|
581
694
|
}
|
|
582
695
|
|
|
583
|
-
// Step 1:
|
|
584
|
-
console.log(chalk.cyan.bold(' Step 1:
|
|
696
|
+
// Step 1: Risk Management Settings
|
|
697
|
+
console.log(chalk.cyan.bold(' Step 1: Risk Management'));
|
|
698
|
+
console.log(chalk.gray(' Set your daily target and maximum risk to auto-stop copy trading.'));
|
|
699
|
+
console.log();
|
|
700
|
+
|
|
701
|
+
const { dailyTarget } = await inquirer.prompt([
|
|
702
|
+
{
|
|
703
|
+
type: 'input',
|
|
704
|
+
name: 'dailyTarget',
|
|
705
|
+
message: chalk.white.bold('Daily Target ($):'),
|
|
706
|
+
default: '500',
|
|
707
|
+
validate: (input) => {
|
|
708
|
+
const num = parseFloat(input);
|
|
709
|
+
if (isNaN(num) || num <= 0) {
|
|
710
|
+
return 'Please enter a valid amount greater than 0';
|
|
711
|
+
}
|
|
712
|
+
return true;
|
|
713
|
+
},
|
|
714
|
+
filter: (input) => parseFloat(input)
|
|
715
|
+
}
|
|
716
|
+
]);
|
|
717
|
+
|
|
718
|
+
const { maxRisk } = await inquirer.prompt([
|
|
719
|
+
{
|
|
720
|
+
type: 'input',
|
|
721
|
+
name: 'maxRisk',
|
|
722
|
+
message: chalk.white.bold('Max Risk ($):'),
|
|
723
|
+
default: '200',
|
|
724
|
+
validate: (input) => {
|
|
725
|
+
const num = parseFloat(input);
|
|
726
|
+
if (isNaN(num) || num <= 0) {
|
|
727
|
+
return 'Please enter a valid amount greater than 0';
|
|
728
|
+
}
|
|
729
|
+
return true;
|
|
730
|
+
},
|
|
731
|
+
filter: (input) => parseFloat(input)
|
|
732
|
+
}
|
|
733
|
+
]);
|
|
734
|
+
|
|
735
|
+
console.log();
|
|
736
|
+
console.log(chalk.gray(' Daily Target: ') + chalk.green('$' + dailyTarget.toFixed(2)));
|
|
737
|
+
console.log(chalk.gray(' Max Risk: ') + chalk.red('$' + maxRisk.toFixed(2)));
|
|
738
|
+
console.log();
|
|
739
|
+
|
|
740
|
+
// Step 2: Select Lead Account
|
|
741
|
+
console.log(chalk.cyan.bold(' Step 2: Select Lead Account'));
|
|
585
742
|
console.log(chalk.gray(' The lead account is the master account whose trades will be copied.'));
|
|
586
743
|
console.log();
|
|
587
744
|
|
|
@@ -605,9 +762,9 @@ const copyTradingMenu = async () => {
|
|
|
605
762
|
|
|
606
763
|
if (leadAccount === 'back') return;
|
|
607
764
|
|
|
608
|
-
// Step
|
|
765
|
+
// Step 3: Select Follower Account
|
|
609
766
|
console.log();
|
|
610
|
-
console.log(chalk.cyan.bold(' Step
|
|
767
|
+
console.log(chalk.cyan.bold(' Step 3: Select Follower Account'));
|
|
611
768
|
console.log(chalk.gray(' The follower account will copy trades from the lead account.'));
|
|
612
769
|
console.log();
|
|
613
770
|
|
|
@@ -633,9 +790,9 @@ const copyTradingMenu = async () => {
|
|
|
633
790
|
|
|
634
791
|
if (followerAccount === 'back') return;
|
|
635
792
|
|
|
636
|
-
// Step
|
|
793
|
+
// Step 4: Select Lead Symbol
|
|
637
794
|
console.log();
|
|
638
|
-
console.log(chalk.cyan.bold(' Step
|
|
795
|
+
console.log(chalk.cyan.bold(' Step 4: Configure Lead Symbol'));
|
|
639
796
|
console.log();
|
|
640
797
|
|
|
641
798
|
const { leadSymbol } = await inquirer.prompt([
|
|
@@ -669,9 +826,9 @@ const copyTradingMenu = async () => {
|
|
|
669
826
|
}
|
|
670
827
|
]);
|
|
671
828
|
|
|
672
|
-
// Step
|
|
829
|
+
// Step 5: Select Follower Symbol
|
|
673
830
|
console.log();
|
|
674
|
-
console.log(chalk.cyan.bold(' Step
|
|
831
|
+
console.log(chalk.cyan.bold(' Step 5: Configure Follower Symbol'));
|
|
675
832
|
console.log();
|
|
676
833
|
|
|
677
834
|
const { followerSymbol } = await inquirer.prompt([
|
|
@@ -711,6 +868,10 @@ const copyTradingMenu = async () => {
|
|
|
711
868
|
console.log(chalk.white.bold(' Copy Trading Configuration'));
|
|
712
869
|
console.log(chalk.gray(getSeparator()));
|
|
713
870
|
console.log();
|
|
871
|
+
console.log(chalk.white(' RISK MANAGEMENT'));
|
|
872
|
+
console.log(chalk.white(` Daily Target: ${chalk.green('$' + dailyTarget.toFixed(2))}`));
|
|
873
|
+
console.log(chalk.white(` Max Risk: ${chalk.red('$' + maxRisk.toFixed(2))}`));
|
|
874
|
+
console.log();
|
|
714
875
|
console.log(chalk.white(' LEAD ACCOUNT'));
|
|
715
876
|
console.log(chalk.white(` Account: ${chalk.cyan(leadAccount.accountName || leadAccount.name)}`));
|
|
716
877
|
console.log(chalk.white(` PropFirm: ${chalk.magenta(leadAccount.propfirm)}`));
|
|
@@ -743,6 +904,8 @@ const copyTradingMenu = async () => {
|
|
|
743
904
|
|
|
744
905
|
// Launch Copy Trading
|
|
745
906
|
await launchCopyTrading({
|
|
907
|
+
dailyTarget,
|
|
908
|
+
maxRisk,
|
|
746
909
|
lead: {
|
|
747
910
|
account: leadAccount,
|
|
748
911
|
symbol: leadSymbol,
|
|
@@ -762,22 +925,24 @@ const copyTradingMenu = async () => {
|
|
|
762
925
|
* Launch Copy Trading
|
|
763
926
|
*/
|
|
764
927
|
const launchCopyTrading = async (config) => {
|
|
765
|
-
const { lead, follower } = config;
|
|
928
|
+
const { lead, follower, dailyTarget, maxRisk } = config;
|
|
766
929
|
|
|
767
930
|
console.log();
|
|
768
931
|
console.log(chalk.green.bold(' [>] Launching Copy Trading...'));
|
|
769
932
|
console.log();
|
|
770
933
|
|
|
771
934
|
let isRunning = true;
|
|
935
|
+
let stopReason = null;
|
|
772
936
|
let lastLeadPosition = null;
|
|
773
937
|
const logs = [];
|
|
774
|
-
const MAX_LOGS =
|
|
938
|
+
const MAX_LOGS = 12;
|
|
775
939
|
|
|
776
940
|
const stats = {
|
|
777
941
|
copiedTrades: 0,
|
|
778
942
|
leadTrades: 0,
|
|
779
943
|
followerTrades: 0,
|
|
780
|
-
errors: 0
|
|
944
|
+
errors: 0,
|
|
945
|
+
pnl: 0
|
|
781
946
|
};
|
|
782
947
|
|
|
783
948
|
const addLog = (type, message) => {
|
|
@@ -796,6 +961,15 @@ const launchCopyTrading = async (config) => {
|
|
|
796
961
|
console.log(chalk.gray(getSeparator()));
|
|
797
962
|
console.log();
|
|
798
963
|
|
|
964
|
+
// Risk Management
|
|
965
|
+
console.log(chalk.white.bold(' RISK MANAGEMENT'));
|
|
966
|
+
const targetProgress = Math.min(100, (stats.pnl / dailyTarget) * 100);
|
|
967
|
+
const riskProgress = Math.min(100, (Math.abs(Math.min(0, stats.pnl)) / maxRisk) * 100);
|
|
968
|
+
console.log(chalk.white(` Target: ${chalk.green('$' + dailyTarget.toFixed(2))} | Progress: ${targetProgress >= 100 ? chalk.green.bold(targetProgress.toFixed(1) + '%') : chalk.yellow(targetProgress.toFixed(1) + '%')}`));
|
|
969
|
+
console.log(chalk.white(` Risk: ${chalk.red('$' + maxRisk.toFixed(2))} | Used: ${riskProgress >= 100 ? chalk.red.bold(riskProgress.toFixed(1) + '%') : chalk.cyan(riskProgress.toFixed(1) + '%')}`));
|
|
970
|
+
console.log(chalk.white(` P&L: ${stats.pnl >= 0 ? chalk.green('+$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2))}`));
|
|
971
|
+
console.log();
|
|
972
|
+
|
|
799
973
|
// Lead info
|
|
800
974
|
console.log(chalk.white.bold(' LEAD'));
|
|
801
975
|
console.log(chalk.white(` ${chalk.cyan(lead.account.accountName)} @ ${chalk.magenta(lead.account.propfirm)}`));
|
|
@@ -858,6 +1032,63 @@ const launchCopyTrading = async (config) => {
|
|
|
858
1032
|
if (!isRunning) return;
|
|
859
1033
|
|
|
860
1034
|
try {
|
|
1035
|
+
// Get follower positions for P&L tracking
|
|
1036
|
+
const followerPositions = await follower.service.getPositions(follower.account.rithmicAccountId || follower.account.accountId);
|
|
1037
|
+
|
|
1038
|
+
if (followerPositions.success && followerPositions.positions) {
|
|
1039
|
+
const followerPos = followerPositions.positions.find(p =>
|
|
1040
|
+
p.symbol === follower.symbol.value ||
|
|
1041
|
+
p.symbol?.includes(follower.symbol.searchText)
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1044
|
+
// Update P&L from follower position
|
|
1045
|
+
if (followerPos && typeof followerPos.unrealizedPnl === 'number') {
|
|
1046
|
+
stats.pnl = followerPos.unrealizedPnl;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Check if daily target reached
|
|
1051
|
+
if (stats.pnl >= dailyTarget) {
|
|
1052
|
+
isRunning = false;
|
|
1053
|
+
stopReason = 'target';
|
|
1054
|
+
addLog('success', `Daily target reached! +$${stats.pnl.toFixed(2)}`);
|
|
1055
|
+
|
|
1056
|
+
// Close follower position
|
|
1057
|
+
try {
|
|
1058
|
+
await follower.service.closePosition(
|
|
1059
|
+
follower.account.rithmicAccountId || follower.account.accountId,
|
|
1060
|
+
follower.symbol.value
|
|
1061
|
+
);
|
|
1062
|
+
addLog('info', 'Follower position closed');
|
|
1063
|
+
} catch (e) {
|
|
1064
|
+
// Position may already be closed
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
displayUI();
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Check if max risk reached
|
|
1072
|
+
if (stats.pnl <= -maxRisk) {
|
|
1073
|
+
isRunning = false;
|
|
1074
|
+
stopReason = 'risk';
|
|
1075
|
+
addLog('error', `Max risk reached! -$${Math.abs(stats.pnl).toFixed(2)}`);
|
|
1076
|
+
|
|
1077
|
+
// Close follower position
|
|
1078
|
+
try {
|
|
1079
|
+
await follower.service.closePosition(
|
|
1080
|
+
follower.account.rithmicAccountId || follower.account.accountId,
|
|
1081
|
+
follower.symbol.value
|
|
1082
|
+
);
|
|
1083
|
+
addLog('info', 'Follower position closed');
|
|
1084
|
+
} catch (e) {
|
|
1085
|
+
// Position may already be closed
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
displayUI();
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
861
1092
|
// Get lead positions
|
|
862
1093
|
const leadPositions = await lead.service.getPositions(lead.account.rithmicAccountId || lead.account.accountId);
|
|
863
1094
|
|
|
@@ -914,19 +1145,58 @@ const launchCopyTrading = async (config) => {
|
|
|
914
1145
|
}
|
|
915
1146
|
}, 2000); // Check every 2 seconds
|
|
916
1147
|
|
|
917
|
-
// Wait for stop
|
|
918
|
-
await
|
|
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
|
+
});
|
|
919
1180
|
|
|
920
1181
|
// Cleanup
|
|
921
1182
|
isRunning = false;
|
|
922
|
-
clearInterval(monitorInterval);
|
|
923
1183
|
|
|
924
1184
|
console.log();
|
|
925
|
-
|
|
1185
|
+
if (stopReason === 'target') {
|
|
1186
|
+
console.log(chalk.green.bold(' [OK] Daily target reached! Copy trading stopped.'));
|
|
1187
|
+
} else if (stopReason === 'risk') {
|
|
1188
|
+
console.log(chalk.red.bold(' [X] Max risk reached! Copy trading stopped.'));
|
|
1189
|
+
} else {
|
|
1190
|
+
console.log(chalk.yellow(' [OK] Copy trading stopped by user'));
|
|
1191
|
+
}
|
|
926
1192
|
console.log();
|
|
927
1193
|
console.log(chalk.gray(getSeparator()));
|
|
928
1194
|
console.log(chalk.white.bold(' Session Summary'));
|
|
929
1195
|
console.log(chalk.gray(getSeparator()));
|
|
1196
|
+
console.log(chalk.white(` Daily Target: ${chalk.green('$' + dailyTarget.toFixed(2))}`));
|
|
1197
|
+
console.log(chalk.white(` Max Risk: ${chalk.red('$' + maxRisk.toFixed(2))}`));
|
|
1198
|
+
console.log(chalk.white(` Final P&L: ${stats.pnl >= 0 ? chalk.green('+$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2))}`));
|
|
1199
|
+
console.log(chalk.gray(getSeparator()));
|
|
930
1200
|
console.log(chalk.white(` Lead Trades: ${chalk.cyan(stats.leadTrades)}`));
|
|
931
1201
|
console.log(chalk.white(` Copied Trades: ${chalk.green(stats.copiedTrades)}`));
|
|
932
1202
|
console.log(chalk.white(` Errors: ${chalk.red(stats.errors)}`));
|