hedgequantx 2.6.83 → 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.83",
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,21 +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
- if (useFastPath && service && account.rithmicAccountId) {
1027
- try {
1028
- ui.addLog('warning', 'EMERGENCY STOP - Cancelling orders & flattening positions...');
1029
- const stopResult = await service.emergencyStop(account.rithmicAccountId);
1030
- if (stopResult.success) {
1031
- ui.addLog('success', 'All orders cancelled, positions flattened');
1032
- } else {
1033
- ui.addLog('error', 'Emergency stop partial - check positions manually');
1034
- }
1035
- } catch (e) {
1036
- ui.addLog('error', `Emergency stop failed: ${e.message}`);
1037
- }
1038
- }
1039
-
1040
1171
  // Stop Position Manager (fast path)
1041
1172
  if (positionManager) {
1042
1173
  positionManager.stop();