hedgequantx 2.9.195 → 2.9.196

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.9.195",
3
+ "version": "2.9.196",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -492,48 +492,92 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
492
492
  clearInterval(pnlInterval);
493
493
  clearInterval(liveLogInterval);
494
494
 
495
- // Flatten any open position before stopping
496
- // Get fresh position from service to avoid stale/corrupted values
495
+ // ============================================================
496
+ // EMERGENCY FLATTEN - ULTRA SOLID
497
+ // 1. Cancel ALL pending orders
498
+ // 2. Exit position via Rithmic API
499
+ // 3. Fallback: Market order to flatten
500
+ // 4. Verify position is flat
501
+ // ============================================================
502
+
503
+ const accountId = account.rithmicAccountId || account.accountId;
504
+ const exchange = contract.exchange || 'CME';
505
+
506
+ ui.addLog('system', 'Stopping algo - cancelling orders...');
507
+ sessionLogger.log('EXIT', 'Emergency flatten initiated');
508
+
509
+ // STEP 1: Cancel ALL pending orders
510
+ try {
511
+ if (service.cancelAllOrders) {
512
+ await service.cancelAllOrders(accountId);
513
+ ui.addLog('system', 'All pending orders cancelled');
514
+ sessionLogger.log('EXIT', 'All orders cancelled');
515
+ }
516
+ } catch (e) {
517
+ sessionLogger.log('EXIT', `Cancel orders error: ${e.message}`);
518
+ }
519
+
520
+ // Wait for cancellations to process
521
+ await new Promise(r => setTimeout(r, 1000));
522
+
523
+ // STEP 2: Try Rithmic's native ExitPosition first
524
+ try {
525
+ if (service.exitPosition) {
526
+ ui.addLog('system', 'Exiting position via Rithmic...');
527
+ const exitResult = await service.exitPosition(accountId, symbolCode, exchange);
528
+ if (exitResult.success) {
529
+ ui.addLog('system', 'Exit position command sent');
530
+ sessionLogger.log('EXIT', 'ExitPosition sent to Rithmic');
531
+ }
532
+ }
533
+ } catch (e) {
534
+ sessionLogger.log('EXIT', `ExitPosition error: ${e.message}`);
535
+ }
536
+
537
+ // Wait for exit to process
538
+ await new Promise(r => setTimeout(r, 2000));
539
+
540
+ // STEP 3: Verify position and flatten with market order if needed
497
541
  let positionToFlatten = 0;
498
542
  try {
499
- const posResult = await service.getPositions(account.rithmicAccountId || account.accountId);
543
+ const posResult = await service.getPositions(accountId);
500
544
  if (posResult.success && posResult.positions) {
501
545
  const pos = posResult.positions.find(p => {
502
546
  const sym = p.contractId || p.symbol || '';
503
- return sym.includes(contract.name) || sym.includes(symbolCode);
547
+ return sym.includes(symbolCode) || sym.includes(contract.name) || sym.includes(contract.baseSymbol);
504
548
  });
505
549
  if (pos && pos.quantity) {
506
550
  const qty = parseInt(pos.quantity);
507
- // Validate quantity is reasonable (not overflow)
508
- if (!isNaN(qty) && Math.abs(qty) < 1000) {
551
+ // Validate quantity is reasonable (not overflow - max 100 contracts)
552
+ if (!isNaN(qty) && qty !== 0 && Math.abs(qty) <= 100) {
509
553
  positionToFlatten = qty;
510
554
  }
511
555
  }
512
556
  }
513
557
  } catch (e) {
514
- // Use tracked position as fallback, but validate it
515
- if (Math.abs(currentPosition) < 1000) {
516
- positionToFlatten = currentPosition;
517
- }
558
+ sessionLogger.log('EXIT', `Get positions error: ${e.message}`);
518
559
  }
519
560
 
561
+ // STEP 4: Market order flatten if position still open
520
562
  if (positionToFlatten !== 0) {
521
563
  const side = positionToFlatten > 0 ? 'LONG' : 'SHORT';
522
564
  const size = Math.abs(positionToFlatten);
523
- ui.addLog('system', `Flattening position: ${side} ${size}`);
524
- sessionLogger.log('EXIT', `Flattening position: ${side} ${size}`);
565
+ ui.addLog('system', `Flattening ${side} ${size} @ market...`);
566
+ sessionLogger.log('EXIT', `Market flatten: ${side} ${size}`);
567
+
525
568
  try {
526
569
  const flattenResult = await service.placeOrder({
527
- accountId: account.rithmicAccountId || account.accountId,
570
+ accountId: accountId,
528
571
  symbol: symbolCode,
529
- exchange: contract.exchange || 'CME',
530
- type: 2, // Market
572
+ exchange: exchange,
573
+ type: 2, // Market order
531
574
  side: positionToFlatten > 0 ? 1 : 0, // Sell if long, Buy if short
532
575
  size: size
533
576
  });
577
+
534
578
  if (flattenResult.success) {
535
579
  ui.addLog('fill_' + (positionToFlatten > 0 ? 'sell' : 'buy'), `Position flattened @ market`);
536
- sessionLogger.log('EXIT', `Position flattened successfully`);
580
+ sessionLogger.log('EXIT', 'Position flattened successfully');
537
581
  } else {
538
582
  ui.addLog('error', `Flatten failed: ${flattenResult.error}`);
539
583
  sessionLogger.log('EXIT', `Flatten failed: ${flattenResult.error}`);
@@ -542,8 +586,32 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
542
586
  ui.addLog('error', `Flatten error: ${e.message}`);
543
587
  sessionLogger.log('EXIT', `Flatten error: ${e.message}`);
544
588
  }
589
+
545
590
  // Wait for fill
546
591
  await new Promise(r => setTimeout(r, 2000));
592
+
593
+ // STEP 5: Final verification
594
+ try {
595
+ const verifyResult = await service.getPositions(accountId);
596
+ if (verifyResult.success && verifyResult.positions) {
597
+ const pos = verifyResult.positions.find(p => {
598
+ const sym = p.contractId || p.symbol || '';
599
+ return sym.includes(symbolCode);
600
+ });
601
+ if (pos && pos.quantity && Math.abs(parseInt(pos.quantity)) > 0 && Math.abs(parseInt(pos.quantity)) <= 100) {
602
+ ui.addLog('error', `WARNING: Position still open! Qty: ${pos.quantity}`);
603
+ sessionLogger.log('EXIT', `WARNING: Position NOT flat! Qty: ${pos.quantity}`);
604
+ } else {
605
+ ui.addLog('system', 'Position verified flat');
606
+ sessionLogger.log('EXIT', 'Position verified flat');
607
+ }
608
+ }
609
+ } catch (e) {
610
+ // Ignore verification errors
611
+ }
612
+ } else {
613
+ ui.addLog('system', 'No position to flatten');
614
+ sessionLogger.log('EXIT', 'No position to flatten');
547
615
  }
548
616
 
549
617
  await marketFeed.disconnect();
@@ -17,7 +17,7 @@ const {
17
17
  getPositions,
18
18
  fetchTradeRoutes,
19
19
  } = require('./accounts');
20
- const { placeOrder, cancelOrder, getOrders, getOrderHistory, getOrderHistoryDates, getTradeHistoryFull, closePosition } = require('./orders');
20
+ const { placeOrder, cancelOrder, cancelAllOrders, exitPosition, getOrders, getOrderHistory, getOrderHistoryDates, getTradeHistoryFull, closePosition } = require('./orders');
21
21
  const { fillsToRoundTrips, calculateTradeStats } = require('./trades');
22
22
  const { getContracts, searchContracts } = require('./contracts');
23
23
  const { handleAutoReconnect } = require('./reconnect');
@@ -320,6 +320,8 @@ class RithmicService extends EventEmitter {
320
320
  async getTradeHistoryFull(days) { return getTradeHistoryFull(this, days); }
321
321
  async placeOrder(orderData) { return placeOrder(this, orderData); }
322
322
  async cancelOrder(orderId) { return cancelOrder(this, orderId); }
323
+ async cancelAllOrders(accountId) { return cancelAllOrders(this, accountId); }
324
+ async exitPosition(accountId, symbol, exchange) { return exitPosition(this, accountId, symbol, exchange); }
323
325
  async closePosition(accountId, symbol) { return closePosition(this, accountId, symbol); }
324
326
  async getContracts() { return getContracts(this); }
325
327
  async searchContracts(searchText) { return searchContracts(this, searchText); }
@@ -473,9 +473,112 @@ const closePosition = async (service, accountId, symbol) => {
473
473
  });
474
474
  };
475
475
 
476
+ /**
477
+ * Cancel all orders for an account
478
+ * Uses RequestCancelAllOrders (template 346)
479
+ * @param {RithmicService} service - The Rithmic service instance
480
+ * @param {string} accountId - Account ID
481
+ */
482
+ const cancelAllOrders = async (service, accountId) => {
483
+ if (!service.orderConn || !service.loginInfo) {
484
+ return { success: false, error: 'Not connected' };
485
+ }
486
+
487
+ return new Promise((resolve) => {
488
+ const timeout = setTimeout(() => {
489
+ resolve({ success: true, message: 'Cancel all orders sent' });
490
+ }, 3000);
491
+
492
+ try {
493
+ service.orderConn.send('RequestCancelAllOrders', {
494
+ templateId: REQ.CANCEL_ALL_ORDERS,
495
+ userMsg: ['HQX-FLATTEN'],
496
+ fcmId: service.loginInfo.fcmId,
497
+ ibId: service.loginInfo.ibId,
498
+ accountId: accountId,
499
+ });
500
+
501
+ // Listen for response
502
+ const handler = (msg) => {
503
+ if (msg.templateId === 347) { // ResponseCancelAllOrders
504
+ clearTimeout(timeout);
505
+ service.orderConn.removeListener('message', handler);
506
+ resolve({ success: true, message: 'All orders cancelled' });
507
+ }
508
+ };
509
+ service.orderConn.on('message', handler);
510
+
511
+ } catch (error) {
512
+ clearTimeout(timeout);
513
+ resolve({ success: false, error: error.message });
514
+ }
515
+ });
516
+ };
517
+
518
+ /**
519
+ * Exit position using Rithmic's ExitPosition request
520
+ * Uses RequestExitPosition (template 3504)
521
+ * @param {RithmicService} service - The Rithmic service instance
522
+ * @param {string} accountId - Account ID
523
+ * @param {string} symbol - Symbol to exit
524
+ * @param {string} exchange - Exchange (default CME)
525
+ */
526
+ const exitPosition = async (service, accountId, symbol, exchange = 'CME') => {
527
+ if (!service.orderConn || !service.loginInfo) {
528
+ return { success: false, error: 'Not connected' };
529
+ }
530
+
531
+ // Get trade route
532
+ let tradeRoute = null;
533
+ if (service.tradeRoutes && service.tradeRoutes.size > 0) {
534
+ const routeInfo = service.tradeRoutes.get(exchange);
535
+ if (routeInfo) {
536
+ tradeRoute = routeInfo.tradeRoute;
537
+ } else {
538
+ const firstRoute = service.tradeRoutes.values().next().value;
539
+ if (firstRoute) tradeRoute = firstRoute.tradeRoute;
540
+ }
541
+ }
542
+
543
+ return new Promise((resolve) => {
544
+ const timeout = setTimeout(() => {
545
+ resolve({ success: true, message: 'Exit position sent' });
546
+ }, 5000);
547
+
548
+ try {
549
+ service.orderConn.send('RequestExitPosition', {
550
+ templateId: REQ.EXIT_POSITION,
551
+ userMsg: ['HQX-EXIT'],
552
+ fcmId: service.loginInfo.fcmId,
553
+ ibId: service.loginInfo.ibId,
554
+ accountId: accountId,
555
+ symbol: symbol,
556
+ exchange: exchange,
557
+ tradeRoute: tradeRoute,
558
+ });
559
+
560
+ // Listen for response
561
+ const handler = (msg) => {
562
+ if (msg.templateId === 3505) { // ResponseExitPosition
563
+ clearTimeout(timeout);
564
+ service.orderConn.removeListener('message', handler);
565
+ resolve({ success: true, message: 'Position exit sent' });
566
+ }
567
+ };
568
+ service.orderConn.on('message', handler);
569
+
570
+ } catch (error) {
571
+ clearTimeout(timeout);
572
+ resolve({ success: false, error: error.message });
573
+ }
574
+ });
575
+ };
576
+
476
577
  module.exports = {
477
578
  placeOrder,
478
579
  cancelOrder,
580
+ cancelAllOrders,
581
+ exitPosition,
479
582
  getOrders,
480
583
  getOrderHistory,
481
584
  getOrderHistoryDates,