hedgequantx 1.2.121 → 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 +165 -55
package/package.json
CHANGED
package/src/pages/algo.js
CHANGED
|
@@ -221,8 +221,54 @@ const selectSymbolMenu = async (service, account) => {
|
|
|
221
221
|
});
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
// Sort by
|
|
224
|
+
// Sort by category: E-mini indices first, then Micro E-mini, then others
|
|
225
|
+
const getSymbolPriority = (contract) => {
|
|
226
|
+
const name = (contract.name || contract.symbol || '').toUpperCase();
|
|
227
|
+
const desc = (contract.description || '').toLowerCase();
|
|
228
|
+
|
|
229
|
+
// E-mini indices (NQ, ES, YM, RTY) - highest priority
|
|
230
|
+
if (name.match(/^(NQ|ES|YM|RTY)[A-Z]\d/) && !name.startsWith('M')) {
|
|
231
|
+
if (name.startsWith('NQ')) return 10;
|
|
232
|
+
if (name.startsWith('ES')) return 11;
|
|
233
|
+
if (name.startsWith('YM')) return 12;
|
|
234
|
+
if (name.startsWith('RTY')) return 13;
|
|
235
|
+
return 15;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Micro E-mini indices (MNQ, MES, MYM, M2K)
|
|
239
|
+
if (name.match(/^(MNQ|MES|MYM|M2K)/)) {
|
|
240
|
+
if (name.startsWith('MNQ')) return 20;
|
|
241
|
+
if (name.startsWith('MES')) return 21;
|
|
242
|
+
if (name.startsWith('MYM')) return 22;
|
|
243
|
+
if (name.startsWith('M2K')) return 23;
|
|
244
|
+
return 25;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Energy (CL, MCL, NG)
|
|
248
|
+
if (name.match(/^(CL|MCL|NG|QG)/)) return 30;
|
|
249
|
+
|
|
250
|
+
// Metals (GC, MGC, SI)
|
|
251
|
+
if (name.match(/^(GC|MGC|SI|HG|PL)/)) return 40;
|
|
252
|
+
|
|
253
|
+
// Currencies (6E, 6B, etc)
|
|
254
|
+
if (name.match(/^(6E|6B|6J|6A|6C|M6E)/)) return 50;
|
|
255
|
+
|
|
256
|
+
// Treasuries (ZB, ZN, ZF, ZT)
|
|
257
|
+
if (name.match(/^(ZB|ZN|ZF|ZT)/)) return 60;
|
|
258
|
+
|
|
259
|
+
// Everything else
|
|
260
|
+
return 100;
|
|
261
|
+
};
|
|
262
|
+
|
|
225
263
|
symbolChoices.sort((a, b) => {
|
|
264
|
+
const priorityA = getSymbolPriority(a.value);
|
|
265
|
+
const priorityB = getSymbolPriority(b.value);
|
|
266
|
+
|
|
267
|
+
if (priorityA !== priorityB) {
|
|
268
|
+
return priorityA - priorityB;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Same priority - sort alphabetically
|
|
226
272
|
const aCode = a.value.name || a.value.symbol || '';
|
|
227
273
|
const bCode = b.value.name || b.value.symbol || '';
|
|
228
274
|
return aCode.localeCompare(bCode);
|
|
@@ -427,6 +473,7 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
427
473
|
success: chalk.green,
|
|
428
474
|
signal: chalk.yellow.bold,
|
|
429
475
|
trade: chalk.green.bold,
|
|
476
|
+
loss: chalk.magenta.bold,
|
|
430
477
|
error: chalk.red,
|
|
431
478
|
warning: chalk.yellow
|
|
432
479
|
};
|
|
@@ -453,10 +500,12 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
453
500
|
if (logs.length > MAX_LOGS) logs.shift(); // Remove oldest from top
|
|
454
501
|
};
|
|
455
502
|
|
|
456
|
-
// Print log
|
|
503
|
+
// Print log - just add to buffer, spinner interval will refresh display
|
|
504
|
+
// This prevents display flicker from multiple concurrent displayUI() calls
|
|
457
505
|
const printLog = (type, message) => {
|
|
458
506
|
addLog(type, message);
|
|
459
|
-
displayUI()
|
|
507
|
+
// Don't call displayUI() here - let the spinner interval handle it
|
|
508
|
+
// This prevents flickering when logs arrive rapidly
|
|
460
509
|
};
|
|
461
510
|
|
|
462
511
|
// Check market hours
|
|
@@ -481,19 +530,38 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
481
530
|
};
|
|
482
531
|
|
|
483
532
|
// Display full UI with logs (newest first at top)
|
|
484
|
-
// Use ANSI escape codes to avoid flickering
|
|
485
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
|
+
|
|
486
546
|
const displayUI = () => {
|
|
487
|
-
//
|
|
547
|
+
// Prevent concurrent draws
|
|
548
|
+
if (isDrawing) return;
|
|
549
|
+
isDrawing = true;
|
|
550
|
+
|
|
551
|
+
// Reset buffer
|
|
552
|
+
screenBuffer = '';
|
|
553
|
+
|
|
488
554
|
if (firstDraw) {
|
|
489
|
-
|
|
490
|
-
|
|
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
|
|
491
559
|
firstDraw = false;
|
|
492
|
-
} else {
|
|
493
|
-
// Move cursor to top-left without clearing (avoids flicker)
|
|
494
|
-
process.stdout.write('\x1B[H');
|
|
495
560
|
}
|
|
496
561
|
|
|
562
|
+
// Move cursor to home position
|
|
563
|
+
screenBuffer += '\x1B[H';
|
|
564
|
+
|
|
497
565
|
// Stats
|
|
498
566
|
const pnlColor = stats.pnl >= 0 ? chalk.green : chalk.red;
|
|
499
567
|
const pnlStr = (stats.pnl >= 0 ? '+$' : '-$') + Math.abs(stats.pnl).toFixed(2);
|
|
@@ -530,25 +598,25 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
530
598
|
return text + ' '.repeat(width - text.length);
|
|
531
599
|
};
|
|
532
600
|
|
|
533
|
-
|
|
534
|
-
|
|
601
|
+
printLine('');
|
|
602
|
+
printLine(chalk.cyan(TOP));
|
|
535
603
|
// Logo = 87 chars cyan + 9 chars yellow = 96 total
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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));
|
|
543
611
|
|
|
544
612
|
// Centered title
|
|
545
613
|
const title1 = `Prop Futures Algo Trading v${version}`;
|
|
546
|
-
|
|
547
|
-
|
|
614
|
+
printLine(chalk.cyan(V) + chalk.white(center(title1, W)) + chalk.cyan(V));
|
|
615
|
+
printLine(chalk.cyan(MID));
|
|
548
616
|
|
|
549
617
|
// Centered subtitle
|
|
550
618
|
const title2 = 'HQX Ultra-Scalping Algorithm';
|
|
551
|
-
|
|
619
|
+
printLine(chalk.cyan(V) + chalk.yellow(center(title2, W)) + chalk.cyan(V));
|
|
552
620
|
|
|
553
621
|
// Grid layout for metrics - 2 columns per row, 4 rows
|
|
554
622
|
// Row 1: Account | Symbol + Qty
|
|
@@ -598,15 +666,15 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
598
666
|
const GRID_BOT = '\u2560' + '\u2550'.repeat(colL) + '\u2567' + '\u2550'.repeat(colR) + '\u2563';
|
|
599
667
|
|
|
600
668
|
// Print grid
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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));
|
|
610
678
|
|
|
611
679
|
// Activity log header with spinner and centered date
|
|
612
680
|
spinnerFrame = (spinnerFrame + 1) % spinnerChars.length;
|
|
@@ -621,19 +689,42 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
621
689
|
const datePad = Math.max(0, Math.floor((midSpace - dateCentered.length) / 2));
|
|
622
690
|
const remainingPad = Math.max(0, midSpace - datePad - dateCentered.length);
|
|
623
691
|
const dateSection = ' '.repeat(datePad) + chalk.cyan(dateCentered) + ' '.repeat(remainingPad);
|
|
624
|
-
|
|
625
|
-
|
|
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, '');
|
|
697
|
+
|
|
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
|
+
};
|
|
626
718
|
|
|
627
|
-
// Logs
|
|
628
|
-
// Align with left border of rectangle above (║ = 1 char + 1 space)
|
|
719
|
+
// Logs inside the rectangle - newest first, max 50 lines
|
|
629
720
|
const MAX_VISIBLE_LOGS = 50;
|
|
630
|
-
console.log();
|
|
631
721
|
|
|
632
722
|
if (logs.length === 0) {
|
|
633
|
-
|
|
634
|
-
|
|
723
|
+
const emptyLine = ' Waiting for activity...';
|
|
724
|
+
bufferLine(chalk.cyan(V) + chalk.gray(fitToWidth(emptyLine, W)) + chalk.cyan(V));
|
|
725
|
+
// Fill remaining lines
|
|
635
726
|
for (let i = 0; i < MAX_VISIBLE_LOGS - 1; i++) {
|
|
636
|
-
|
|
727
|
+
bufferLine(chalk.cyan(V) + ' '.repeat(W) + chalk.cyan(V));
|
|
637
728
|
}
|
|
638
729
|
} else {
|
|
639
730
|
// Show newest first (reverse), limited to MAX_VISIBLE_LOGS
|
|
@@ -641,25 +732,33 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
641
732
|
reversedLogs.forEach(log => {
|
|
642
733
|
const color = typeColors[log.type] || chalk.white;
|
|
643
734
|
const icon = getIcon(log.type);
|
|
644
|
-
//
|
|
645
|
-
const
|
|
646
|
-
|
|
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));
|
|
647
740
|
});
|
|
648
741
|
// Fill remaining lines with empty to keep fixed height
|
|
649
742
|
for (let i = reversedLogs.length; i < MAX_VISIBLE_LOGS; i++) {
|
|
650
|
-
|
|
743
|
+
bufferLine(chalk.cyan(V) + ' '.repeat(W) + chalk.cyan(V));
|
|
651
744
|
}
|
|
652
745
|
}
|
|
653
|
-
|
|
654
|
-
|
|
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;
|
|
655
754
|
};
|
|
656
755
|
|
|
657
|
-
// Spinner interval to refresh UI
|
|
756
|
+
// Spinner interval to refresh UI - 250ms for stability
|
|
658
757
|
const spinnerInterval = setInterval(() => {
|
|
659
|
-
if (algoRunning) {
|
|
758
|
+
if (algoRunning && !isDrawing) {
|
|
660
759
|
displayUI();
|
|
661
760
|
}
|
|
662
|
-
},
|
|
761
|
+
}, 250);
|
|
663
762
|
|
|
664
763
|
// Connect to HQX Server
|
|
665
764
|
const spinner = ora('Authenticating with HQX Server...').start();
|
|
@@ -699,7 +798,7 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
699
798
|
// Setup event handlers - logs scroll down naturally
|
|
700
799
|
hqxServer.on('latency', (data) => {
|
|
701
800
|
latency = data.latency || 0;
|
|
702
|
-
displayUI()
|
|
801
|
+
// Don't call displayUI() - spinner interval will refresh
|
|
703
802
|
});
|
|
704
803
|
|
|
705
804
|
hqxServer.on('log', (data) => {
|
|
@@ -730,12 +829,13 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
730
829
|
printLog('trade', `Closed +$${data.pnl.toFixed(2)} (${data.reason || 'take_profit'})`);
|
|
731
830
|
} else {
|
|
732
831
|
stats.losses++;
|
|
733
|
-
printLog('
|
|
832
|
+
printLog('loss', `Closed -$${Math.abs(data.pnl).toFixed(2)} (${data.reason || 'stop_loss'})`);
|
|
734
833
|
}
|
|
735
834
|
stats.winRate = stats.trades > 0 ? ((stats.wins / stats.trades) * 100).toFixed(1) : '0.0';
|
|
736
835
|
|
|
737
836
|
// Print updated stats
|
|
738
|
-
|
|
837
|
+
const statsType = stats.pnl >= 0 ? 'info' : 'loss';
|
|
838
|
+
printLog(statsType, `Stats: Trades: ${stats.trades} | Wins: ${stats.wins} | P&L: $${stats.pnl.toFixed(2)}`);
|
|
739
839
|
|
|
740
840
|
// Check daily target
|
|
741
841
|
if (stats.pnl >= dailyTarget) {
|
|
@@ -759,8 +859,17 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
759
859
|
});
|
|
760
860
|
|
|
761
861
|
hqxServer.on('stats', (data) => {
|
|
762
|
-
|
|
763
|
-
|
|
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;
|
|
764
873
|
});
|
|
765
874
|
|
|
766
875
|
hqxServer.on('error', (data) => {
|
|
@@ -862,8 +971,9 @@ const launchAlgo = async (service, account, contract, numContracts, dailyTarget,
|
|
|
862
971
|
// Clear spinner interval
|
|
863
972
|
clearInterval(spinnerInterval);
|
|
864
973
|
|
|
865
|
-
//
|
|
866
|
-
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
|
|
867
977
|
|
|
868
978
|
// Stop algo
|
|
869
979
|
console.log();
|