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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/pages/algo.js +301 -31
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "1.2.56",
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
 
@@ -580,8 +693,52 @@ const copyTradingMenu = async () => {
580
693
  return;
581
694
  }
582
695
 
583
- // Step 1: Select Lead Account
584
- console.log(chalk.cyan.bold(' Step 1: Select Lead Account'));
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 2: Select Follower Account
765
+ // Step 3: Select Follower Account
609
766
  console.log();
610
- console.log(chalk.cyan.bold(' Step 2: Select Follower Account'));
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 3: Select Lead Symbol
793
+ // Step 4: Select Lead Symbol
637
794
  console.log();
638
- console.log(chalk.cyan.bold(' Step 3: Configure Lead Symbol'));
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 4: Select Follower Symbol
829
+ // Step 5: Select Follower Symbol
673
830
  console.log();
674
- console.log(chalk.cyan.bold(' Step 4: Configure Follower Symbol'));
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 = 15;
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 key
918
- 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
+ });
919
1180
 
920
1181
  // Cleanup
921
1182
  isRunning = false;
922
- clearInterval(monitorInterval);
923
1183
 
924
1184
  console.log();
925
- console.log(chalk.green(' [OK] Copy trading stopped'));
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)}`));