hedgequantx 1.2.122 → 1.2.125
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 +118 -54
package/package.json
CHANGED
package/src/pages/algo.js
CHANGED
|
@@ -473,6 +473,7 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
473
473
|
success: chalk.green,
|
|
474
474
|
signal: chalk.yellow.bold,
|
|
475
475
|
trade: chalk.green.bold,
|
|
476
|
+
loss: chalk.magenta.bold,
|
|
476
477
|
error: chalk.red,
|
|
477
478
|
warning: chalk.yellow
|
|
478
479
|
};
|
|
@@ -499,10 +500,12 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
499
500
|
if (logs.length > MAX_LOGS) logs.shift(); // Remove oldest from top
|
|
500
501
|
};
|
|
501
502
|
|
|
502
|
-
// Print log
|
|
503
|
+
// Print log - just add to buffer, spinner interval will refresh display
|
|
504
|
+
// This prevents display flicker from multiple concurrent displayUI() calls
|
|
503
505
|
const printLog = (type, message) => {
|
|
504
506
|
addLog(type, message);
|
|
505
|
-
displayUI()
|
|
507
|
+
// Don't call displayUI() here - let the spinner interval handle it
|
|
508
|
+
// This prevents flickering when logs arrive rapidly
|
|
506
509
|
};
|
|
507
510
|
|
|
508
511
|
// Check market hours
|
|
@@ -527,19 +530,38 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
527
530
|
};
|
|
528
531
|
|
|
529
532
|
// Display full UI with logs (newest first at top)
|
|
530
|
-
// Use ANSI escape codes to avoid flickering
|
|
531
533
|
let firstDraw = true;
|
|
534
|
+
let isDrawing = false; // Mutex to prevent concurrent draws
|
|
535
|
+
|
|
536
|
+
// Build entire screen as a single string buffer to write atomically
|
|
537
|
+
let screenBuffer = '';
|
|
538
|
+
|
|
539
|
+
const bufferLine = (text) => {
|
|
540
|
+
screenBuffer += text + '\x1B[K\n'; // Add text + clear to EOL + newline
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
// Legacy function for compatibility
|
|
544
|
+
const printLine = bufferLine;
|
|
545
|
+
|
|
532
546
|
const displayUI = () => {
|
|
533
|
-
//
|
|
547
|
+
// Prevent concurrent draws
|
|
548
|
+
if (isDrawing) return;
|
|
549
|
+
isDrawing = true;
|
|
550
|
+
|
|
551
|
+
// Reset buffer
|
|
552
|
+
screenBuffer = '';
|
|
553
|
+
|
|
534
554
|
if (firstDraw) {
|
|
535
|
-
|
|
536
|
-
|
|
555
|
+
// Switch to alternate screen buffer - isolates our display
|
|
556
|
+
screenBuffer += '\x1B[?1049h'; // Enter alternate screen
|
|
557
|
+
screenBuffer += '\x1B[?25l'; // Hide cursor
|
|
558
|
+
screenBuffer += '\x1B[2J'; // Clear screen
|
|
537
559
|
firstDraw = false;
|
|
538
|
-
} else {
|
|
539
|
-
// Move cursor to top-left without clearing (avoids flicker)
|
|
540
|
-
process.stdout.write('\x1B[H');
|
|
541
560
|
}
|
|
542
561
|
|
|
562
|
+
// Move cursor to home position
|
|
563
|
+
screenBuffer += '\x1B[H';
|
|
564
|
+
|
|
543
565
|
// Stats
|
|
544
566
|
const pnlColor = stats.pnl >= 0 ? chalk.green : chalk.red;
|
|
545
567
|
const pnlStr = (stats.pnl >= 0 ? '+$' : '-$') + Math.abs(stats.pnl).toFixed(2);
|
|
@@ -576,25 +598,25 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
576
598
|
return text + ' '.repeat(width - text.length);
|
|
577
599
|
};
|
|
578
600
|
|
|
579
|
-
|
|
580
|
-
|
|
601
|
+
printLine('');
|
|
602
|
+
printLine(chalk.cyan(TOP));
|
|
581
603
|
// Logo = 87 chars cyan + 9 chars yellow = 96 total
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
604
|
+
printLine(chalk.cyan(V) + chalk.cyan(' ██╗ ██╗███████╗██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗████████╗') + chalk.yellow('██╗ ██╗') + ' ' + chalk.cyan(V));
|
|
605
|
+
printLine(chalk.cyan(V) + chalk.cyan(' ██║ ██║██╔════╝██╔══██╗██╔════╝ ██╔════╝██╔═══██╗██║ ██║██╔══██╗████╗ ██║╚══██╔══╝') + chalk.yellow('╚██╗██╔╝') + ' ' + chalk.cyan(V));
|
|
606
|
+
printLine(chalk.cyan(V) + chalk.cyan(' ███████║█████╗ ██║ ██║██║ ███╗█████╗ ██║ ██║██║ ██║███████║██╔██╗ ██║ ██║ ') + chalk.yellow(' ╚███╔╝ ') + ' ' + chalk.cyan(V));
|
|
607
|
+
printLine(chalk.cyan(V) + chalk.cyan(' ██╔══██║██╔══╝ ██║ ██║██║ ██║██╔══╝ ██║▄▄ ██║██║ ██║██╔══██║██║╚██╗██║ ██║ ') + chalk.yellow(' ██╔██╗ ') + ' ' + chalk.cyan(V));
|
|
608
|
+
printLine(chalk.cyan(V) + chalk.cyan(' ██║ ██║███████╗██████╔╝╚██████╔╝███████╗╚██████╔╝╚██████╔╝██║ ██║██║ ╚████║ ██║ ') + chalk.yellow('██╔╝ ██╗') + ' ' + chalk.cyan(V));
|
|
609
|
+
printLine(chalk.cyan(V) + chalk.cyan(' ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ') + chalk.yellow('╚═╝ ╚═╝') + ' ' + chalk.cyan(V));
|
|
610
|
+
printLine(chalk.cyan(MID));
|
|
589
611
|
|
|
590
612
|
// Centered title
|
|
591
613
|
const title1 = `Prop Futures Algo Trading v${version}`;
|
|
592
|
-
|
|
593
|
-
|
|
614
|
+
printLine(chalk.cyan(V) + chalk.white(center(title1, W)) + chalk.cyan(V));
|
|
615
|
+
printLine(chalk.cyan(MID));
|
|
594
616
|
|
|
595
617
|
// Centered subtitle
|
|
596
618
|
const title2 = 'HQX Ultra-Scalping Algorithm';
|
|
597
|
-
|
|
619
|
+
printLine(chalk.cyan(V) + chalk.yellow(center(title2, W)) + chalk.cyan(V));
|
|
598
620
|
|
|
599
621
|
// Grid layout for metrics - 2 columns per row, 4 rows
|
|
600
622
|
// Row 1: Account | Symbol + Qty
|
|
@@ -644,15 +666,15 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
644
666
|
const GRID_BOT = '\u2560' + '\u2550'.repeat(colL) + '\u2567' + '\u2550'.repeat(colR) + '\u2563';
|
|
645
667
|
|
|
646
668
|
// Print grid
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
669
|
+
printLine(chalk.cyan(GRID_TOP));
|
|
670
|
+
printLine(chalk.cyan(V) + r1c1.padded + chalk.cyan(VS) + r1c2 + chalk.cyan(V));
|
|
671
|
+
printLine(chalk.cyan(GRID_MID));
|
|
672
|
+
printLine(chalk.cyan(V) + r2c1.padded + chalk.cyan(VS) + r2c2.padded + chalk.cyan(V));
|
|
673
|
+
printLine(chalk.cyan(GRID_MID));
|
|
674
|
+
printLine(chalk.cyan(V) + r3c1.padded + chalk.cyan(VS) + r3c2.padded + chalk.cyan(V));
|
|
675
|
+
printLine(chalk.cyan(GRID_MID));
|
|
676
|
+
printLine(chalk.cyan(V) + r4c1 + chalk.cyan(VS) + r4c2.padded + chalk.cyan(V));
|
|
677
|
+
printLine(chalk.cyan(GRID_BOT));
|
|
656
678
|
|
|
657
679
|
// Activity log header with spinner and centered date
|
|
658
680
|
spinnerFrame = (spinnerFrame + 1) % spinnerChars.length;
|
|
@@ -667,19 +689,42 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
667
689
|
const datePad = Math.max(0, Math.floor((midSpace - dateCentered.length) / 2));
|
|
668
690
|
const remainingPad = Math.max(0, midSpace - datePad - dateCentered.length);
|
|
669
691
|
const dateSection = ' '.repeat(datePad) + chalk.cyan(dateCentered) + ' '.repeat(remainingPad);
|
|
670
|
-
|
|
671
|
-
|
|
692
|
+
bufferLine(chalk.cyan(V) + chalk.white(actLeft) + dateSection + chalk.yellow(actRight) + chalk.cyan(V));
|
|
693
|
+
bufferLine(chalk.cyan(MID));
|
|
694
|
+
|
|
695
|
+
// Helper to strip ANSI codes for length calculation
|
|
696
|
+
const stripAnsi = (str) => str.replace(/\x1B\[[0-9;]*m/g, '');
|
|
672
697
|
|
|
673
|
-
//
|
|
674
|
-
|
|
698
|
+
// Helper to truncate and pad text to exact width W
|
|
699
|
+
const fitToWidth = (text, width) => {
|
|
700
|
+
const plainText = stripAnsi(text);
|
|
701
|
+
if (plainText.length > width) {
|
|
702
|
+
// Truncate - find where to cut in original string
|
|
703
|
+
let count = 0;
|
|
704
|
+
let cutIndex = 0;
|
|
705
|
+
for (let i = 0; i < text.length && count < width - 3; i++) {
|
|
706
|
+
if (text[i] === '\x1B') {
|
|
707
|
+
// Skip ANSI sequence
|
|
708
|
+
while (i < text.length && text[i] !== 'm') i++;
|
|
709
|
+
} else {
|
|
710
|
+
count++;
|
|
711
|
+
cutIndex = i + 1;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return text.substring(0, cutIndex) + '...';
|
|
715
|
+
}
|
|
716
|
+
return text + ' '.repeat(width - plainText.length);
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
// Logs inside the rectangle - newest first, max 50 lines
|
|
675
720
|
const MAX_VISIBLE_LOGS = 50;
|
|
676
|
-
console.log();
|
|
677
721
|
|
|
678
722
|
if (logs.length === 0) {
|
|
679
|
-
|
|
680
|
-
|
|
723
|
+
const emptyLine = ' Waiting for activity...';
|
|
724
|
+
bufferLine(chalk.cyan(V) + chalk.gray(fitToWidth(emptyLine, W)) + chalk.cyan(V));
|
|
725
|
+
// Fill remaining lines
|
|
681
726
|
for (let i = 0; i < MAX_VISIBLE_LOGS - 1; i++) {
|
|
682
|
-
|
|
727
|
+
bufferLine(chalk.cyan(V) + ' '.repeat(W) + chalk.cyan(V));
|
|
683
728
|
}
|
|
684
729
|
} else {
|
|
685
730
|
// Show newest first (reverse), limited to MAX_VISIBLE_LOGS
|
|
@@ -687,25 +732,33 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
687
732
|
reversedLogs.forEach(log => {
|
|
688
733
|
const color = typeColors[log.type] || chalk.white;
|
|
689
734
|
const icon = getIcon(log.type);
|
|
690
|
-
//
|
|
691
|
-
const
|
|
692
|
-
|
|
735
|
+
// Build log line content (plain text, no color yet)
|
|
736
|
+
const logContent = ` [${log.timestamp}] ${icon} ${log.message}`;
|
|
737
|
+
// Fit to width then apply color
|
|
738
|
+
const fitted = fitToWidth(logContent, W);
|
|
739
|
+
bufferLine(chalk.cyan(V) + color(fitted) + chalk.cyan(V));
|
|
693
740
|
});
|
|
694
741
|
// Fill remaining lines with empty to keep fixed height
|
|
695
742
|
for (let i = reversedLogs.length; i < MAX_VISIBLE_LOGS; i++) {
|
|
696
|
-
|
|
743
|
+
bufferLine(chalk.cyan(V) + ' '.repeat(W) + chalk.cyan(V));
|
|
697
744
|
}
|
|
698
745
|
}
|
|
699
|
-
|
|
700
|
-
|
|
746
|
+
|
|
747
|
+
// Bottom border to close the rectangle
|
|
748
|
+
bufferLine(chalk.cyan(BOT));
|
|
749
|
+
|
|
750
|
+
// Write entire buffer atomically
|
|
751
|
+
process.stdout.write(screenBuffer);
|
|
752
|
+
|
|
753
|
+
isDrawing = false;
|
|
701
754
|
};
|
|
702
755
|
|
|
703
|
-
// Spinner interval to refresh UI
|
|
756
|
+
// Spinner interval to refresh UI - 250ms for stability
|
|
704
757
|
const spinnerInterval = setInterval(() => {
|
|
705
|
-
if (algoRunning) {
|
|
758
|
+
if (algoRunning && !isDrawing) {
|
|
706
759
|
displayUI();
|
|
707
760
|
}
|
|
708
|
-
},
|
|
761
|
+
}, 250);
|
|
709
762
|
|
|
710
763
|
// Connect to HQX Server
|
|
711
764
|
const spinner = ora('Authenticating with HQX Server...').start();
|
|
@@ -745,7 +798,7 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
745
798
|
// Setup event handlers - logs scroll down naturally
|
|
746
799
|
hqxServer.on('latency', (data) => {
|
|
747
800
|
latency = data.latency || 0;
|
|
748
|
-
displayUI()
|
|
801
|
+
// Don't call displayUI() - spinner interval will refresh
|
|
749
802
|
});
|
|
750
803
|
|
|
751
804
|
hqxServer.on('log', (data) => {
|
|
@@ -776,12 +829,13 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
776
829
|
printLog('trade', `Closed +$${data.pnl.toFixed(2)} (${data.reason || 'take_profit'})`);
|
|
777
830
|
} else {
|
|
778
831
|
stats.losses++;
|
|
779
|
-
printLog('
|
|
832
|
+
printLog('loss', `Closed -$${Math.abs(data.pnl).toFixed(2)} (${data.reason || 'stop_loss'})`);
|
|
780
833
|
}
|
|
781
834
|
stats.winRate = stats.trades > 0 ? ((stats.wins / stats.trades) * 100).toFixed(1) : '0.0';
|
|
782
835
|
|
|
783
836
|
// Print updated stats
|
|
784
|
-
|
|
837
|
+
const statsType = stats.pnl >= 0 ? 'info' : 'loss';
|
|
838
|
+
printLog(statsType, `Stats: Trades: ${stats.trades} | Wins: ${stats.wins} | P&L: $${stats.pnl.toFixed(2)}`);
|
|
785
839
|
|
|
786
840
|
// Check daily target
|
|
787
841
|
if (stats.pnl >= dailyTarget) {
|
|
@@ -805,8 +859,17 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
805
859
|
});
|
|
806
860
|
|
|
807
861
|
hqxServer.on('stats', (data) => {
|
|
808
|
-
|
|
809
|
-
|
|
862
|
+
// Update stats from server
|
|
863
|
+
stats.trades = data.trades || stats.trades;
|
|
864
|
+
stats.wins = data.wins || stats.wins;
|
|
865
|
+
stats.losses = data.losses || stats.losses;
|
|
866
|
+
stats.signals = data.signals || stats.signals;
|
|
867
|
+
stats.winRate = data.winRate || stats.winRate;
|
|
868
|
+
|
|
869
|
+
// P&L = realized P&L + unrealized P&L from open position
|
|
870
|
+
const realizedPnl = data.pnl || 0;
|
|
871
|
+
const unrealizedPnl = data.position?.pnl || 0;
|
|
872
|
+
stats.pnl = realizedPnl + unrealizedPnl;
|
|
810
873
|
});
|
|
811
874
|
|
|
812
875
|
hqxServer.on('error', (data) => {
|
|
@@ -908,8 +971,9 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
908
971
|
// Clear spinner interval
|
|
909
972
|
clearInterval(spinnerInterval);
|
|
910
973
|
|
|
911
|
-
//
|
|
912
|
-
process.stdout.write('\x1B[?
|
|
974
|
+
// Exit alternate screen buffer and show cursor
|
|
975
|
+
process.stdout.write('\x1B[?1049l'); // Exit alternate screen
|
|
976
|
+
process.stdout.write('\x1B[?25h'); // Show cursor
|
|
913
977
|
|
|
914
978
|
// Stop algo
|
|
915
979
|
console.log();
|