hedgequantx 2.6.84 → 2.6.85

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.6.84",
3
+ "version": "2.6.85",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -986,17 +986,163 @@ const launchAlgo = async (service, account, contract, config) => {
986
986
  const pnlInterval = setInterval(() => { if (running) pollPnL(); }, 10000);
987
987
  pollPnL(); // Initial poll
988
988
 
989
+ // ═══════════════════════════════════════════════════════════════════════════
990
+ // ULTRA SOLID EMERGENCY STOP FUNCTION
991
+ // Called when X is pressed - MUST cancel all orders and flatten positions
992
+ // ═══════════════════════════════════════════════════════════════════════════
993
+ const emergencyStopAll = async () => {
994
+ const accountId = account.rithmicAccountId || account.accountId;
995
+ const MAX_RETRIES = 3;
996
+ const TIMEOUT_MS = 5000;
997
+
998
+ ui.addLog('warning', '████ EMERGENCY STOP INITIATED ████');
999
+ ui.render(stats);
1000
+
1001
+ // Helper: run with timeout
1002
+ const withTimeout = (promise, ms) => Promise.race([
1003
+ promise,
1004
+ new Promise((_, reject) => setTimeout(() => reject(new Error('TIMEOUT')), ms))
1005
+ ]);
1006
+
1007
+ // ─────────────────────────────────────────────────────────────────────────
1008
+ // STEP 1: CANCEL ALL ORDERS (with retries)
1009
+ // ─────────────────────────────────────────────────────────────────────────
1010
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
1011
+ try {
1012
+ ui.addLog('info', `[${attempt}/${MAX_RETRIES}] Cancelling all orders...`);
1013
+ ui.render(stats);
1014
+
1015
+ if (typeof service.cancelAllOrders === 'function') {
1016
+ await withTimeout(service.cancelAllOrders(accountId), TIMEOUT_MS);
1017
+ ui.addLog('success', 'All orders cancelled');
1018
+ ui.render(stats);
1019
+ break;
1020
+ } else {
1021
+ ui.addLog('info', 'No cancelAllOrders function - skipping');
1022
+ break;
1023
+ }
1024
+ } catch (e) {
1025
+ ui.addLog('error', `Cancel orders attempt ${attempt} failed: ${e.message}`);
1026
+ ui.render(stats);
1027
+ if (attempt === MAX_RETRIES) {
1028
+ ui.addLog('error', 'FAILED TO CANCEL ORDERS AFTER 3 ATTEMPTS');
1029
+ }
1030
+ await new Promise(r => setTimeout(r, 500)); // Wait before retry
1031
+ }
1032
+ }
1033
+
1034
+ // ─────────────────────────────────────────────────────────────────────────
1035
+ // STEP 2: FLATTEN POSITION (with retries)
1036
+ // ─────────────────────────────────────────────────────────────────────────
1037
+ const posQty = Math.abs(currentPosition || stats.position || 0);
1038
+
1039
+ if (posQty > 0) {
1040
+ const closeSide = (currentPosition || stats.position) > 0 ? 1 : 0;
1041
+ const sideStr = closeSide === 1 ? 'SELL' : 'BUY';
1042
+
1043
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
1044
+ try {
1045
+ ui.addLog('info', `[${attempt}/${MAX_RETRIES}] Flattening ${posQty}x position (${sideStr})...`);
1046
+ ui.render(stats);
1047
+
1048
+ // Method 1: Rithmic emergencyStop (best)
1049
+ if (typeof service.emergencyStop === 'function') {
1050
+ const result = await withTimeout(service.emergencyStop(accountId), TIMEOUT_MS);
1051
+ if (result.success) {
1052
+ ui.addLog('success', 'Position FLATTENED (Rithmic emergency stop)');
1053
+ ui.render(stats);
1054
+ break;
1055
+ }
1056
+ }
1057
+
1058
+ // Method 2: Market order to close
1059
+ if (typeof service.placeOrder === 'function') {
1060
+ await withTimeout(service.placeOrder({
1061
+ accountId: account.accountId,
1062
+ contractId: contractId,
1063
+ type: 2, // Market order
1064
+ side: closeSide,
1065
+ size: posQty
1066
+ }), TIMEOUT_MS);
1067
+ ui.addLog('success', `Position FLATTENED (market ${sideStr} ${posQty}x)`);
1068
+ ui.render(stats);
1069
+ break;
1070
+ }
1071
+
1072
+ // Method 3: Rithmic fastExit
1073
+ if (typeof service.fastExit === 'function') {
1074
+ await withTimeout(service.fastExit({
1075
+ accountId: accountId,
1076
+ symbol: symbolName,
1077
+ exchange: contract.exchange || 'CME',
1078
+ size: posQty,
1079
+ side: closeSide
1080
+ }), TIMEOUT_MS);
1081
+ ui.addLog('success', `Position FLATTENED (fast exit ${sideStr} ${posQty}x)`);
1082
+ ui.render(stats);
1083
+ break;
1084
+ }
1085
+
1086
+ } catch (e) {
1087
+ ui.addLog('error', `Flatten attempt ${attempt} failed: ${e.message}`);
1088
+ ui.render(stats);
1089
+ if (attempt === MAX_RETRIES) {
1090
+ ui.addLog('error', '████ FAILED TO FLATTEN - CHECK MANUALLY! ████');
1091
+ }
1092
+ await new Promise(r => setTimeout(r, 500));
1093
+ }
1094
+ }
1095
+ } else {
1096
+ ui.addLog('success', 'No position to flatten - clean exit');
1097
+ ui.render(stats);
1098
+ }
1099
+
1100
+ // ─────────────────────────────────────────────────────────────────────────
1101
+ // STEP 3: VERIFY (optional - check position is actually flat)
1102
+ // ─────────────────────────────────────────────────────────────────────────
1103
+ try {
1104
+ if (typeof service.getPositions === 'function') {
1105
+ const posResult = await withTimeout(service.getPositions(account.accountId), 3000);
1106
+ if (posResult.success && posResult.positions) {
1107
+ const stillOpen = posResult.positions.find(p => {
1108
+ const sym = p.contractId || p.symbol || '';
1109
+ return (sym.includes(symbolName) || sym.includes(contractId)) && p.quantity !== 0;
1110
+ });
1111
+ if (stillOpen) {
1112
+ ui.addLog('error', `████ POSITION STILL OPEN: ${stillOpen.quantity}x ████`);
1113
+ ui.addLog('error', 'CLOSE MANUALLY IN R TRADER!');
1114
+ } else {
1115
+ ui.addLog('success', 'VERIFIED: Position is flat');
1116
+ }
1117
+ }
1118
+ }
1119
+ } catch (e) {
1120
+ // Verification failed, but main stop was attempted
1121
+ ui.addLog('warning', 'Could not verify position - check manually');
1122
+ }
1123
+
1124
+ ui.addLog('info', '════════════════════════════════════');
1125
+ ui.render(stats);
1126
+ };
1127
+
989
1128
  // Keyboard handler
1129
+ let emergencyStopInProgress = false;
1130
+
990
1131
  const setupKeyHandler = () => {
991
1132
  if (!process.stdin.isTTY) return;
992
1133
  readline.emitKeypressEvents(process.stdin);
993
1134
  process.stdin.setRawMode(true);
994
1135
  process.stdin.resume();
995
1136
 
996
- const onKey = (str, key) => {
1137
+ const onKey = async (str, key) => {
997
1138
  if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
1139
+ if (emergencyStopInProgress) return; // Prevent double-trigger
1140
+ emergencyStopInProgress = true;
998
1141
  running = false;
999
1142
  stopReason = 'manual';
1143
+
1144
+ // Run emergency stop IMMEDIATELY on keypress (don't wait for main loop)
1145
+ await emergencyStopAll();
1000
1146
  }
1001
1147
  };
1002
1148
  process.stdin.on('keypress', onKey);
@@ -1022,53 +1168,6 @@ const launchAlgo = async (service, account, contract, config) => {
1022
1168
  clearInterval(refreshInterval);
1023
1169
  clearInterval(pnlInterval);
1024
1170
 
1025
- // EMERGENCY STOP: Cancel all orders and flatten all positions
1026
- // Works for BOTH Rithmic (fast path) and ProjectX (slow path)
1027
- ui.addLog('warning', 'STOPPING - Cancelling orders & flattening positions...');
1028
- ui.render(stats); // Force render to show message
1029
-
1030
- try {
1031
- const accountId = account.rithmicAccountId || account.accountId;
1032
-
1033
- // 1. Cancel ALL open orders first
1034
- if (typeof service.cancelAllOrders === 'function') {
1035
- await service.cancelAllOrders(accountId);
1036
- ui.addLog('info', 'All orders cancelled');
1037
- }
1038
-
1039
- // 2. Flatten position if we have one
1040
- if (currentPosition !== 0 || stats.position !== 0) {
1041
- const posQty = Math.abs(currentPosition || stats.position);
1042
- const closeSide = (currentPosition || stats.position) > 0 ? 1 : 0; // 1=Sell to close long, 0=Buy to close short
1043
-
1044
- // Try emergency stop first (Rithmic)
1045
- if (typeof service.emergencyStop === 'function') {
1046
- const stopResult = await service.emergencyStop(accountId);
1047
- if (stopResult.success) {
1048
- ui.addLog('success', 'Position flattened (emergency stop)');
1049
- }
1050
- }
1051
- // Fallback: place market order to close (ProjectX)
1052
- else if (typeof service.placeOrder === 'function') {
1053
- await service.placeOrder({
1054
- accountId: account.accountId,
1055
- contractId: contractId,
1056
- type: 2, // Market order
1057
- side: closeSide,
1058
- size: posQty
1059
- });
1060
- ui.addLog('success', `Position flattened (market order ${posQty}x)`);
1061
- }
1062
- } else {
1063
- ui.addLog('success', 'No open position - clean exit');
1064
- }
1065
- } catch (e) {
1066
- ui.addLog('error', `Emergency stop error: ${e.message}`);
1067
- ui.addLog('warning', 'CHECK POSITIONS MANUALLY!');
1068
- }
1069
-
1070
- ui.render(stats); // Force render to show final status
1071
-
1072
1171
  // Stop Position Manager (fast path)
1073
1172
  if (positionManager) {
1074
1173
  positionManager.stop();