hedgequantx 2.9.194 → 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.194",
3
+ "version": "2.9.196",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -395,7 +395,11 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
395
395
  return sym.includes(contract.name) || sym.includes(contractId);
396
396
  });
397
397
  if (pos && pos.quantity !== 0) {
398
- currentPosition = pos.quantity;
398
+ // Validate quantity is reasonable (prevent overflow values)
399
+ const qty = parseInt(pos.quantity);
400
+ if (!isNaN(qty) && Math.abs(qty) < 1000) {
401
+ currentPosition = qty;
402
+ }
399
403
  } else {
400
404
  currentPosition = 0;
401
405
  }
@@ -488,22 +492,92 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
488
492
  clearInterval(pnlInterval);
489
493
  clearInterval(liveLogInterval);
490
494
 
491
- // Flatten any open position before stopping
492
- if (currentPosition !== 0) {
493
- ui.addLog('system', `Flattening position: ${currentPosition > 0 ? 'LONG' : 'SHORT'} ${Math.abs(currentPosition)}`);
494
- sessionLogger.log('EXIT', `Flattening position: ${currentPosition}`);
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
541
+ let positionToFlatten = 0;
542
+ try {
543
+ const posResult = await service.getPositions(accountId);
544
+ if (posResult.success && posResult.positions) {
545
+ const pos = posResult.positions.find(p => {
546
+ const sym = p.contractId || p.symbol || '';
547
+ return sym.includes(symbolCode) || sym.includes(contract.name) || sym.includes(contract.baseSymbol);
548
+ });
549
+ if (pos && pos.quantity) {
550
+ const qty = parseInt(pos.quantity);
551
+ // Validate quantity is reasonable (not overflow - max 100 contracts)
552
+ if (!isNaN(qty) && qty !== 0 && Math.abs(qty) <= 100) {
553
+ positionToFlatten = qty;
554
+ }
555
+ }
556
+ }
557
+ } catch (e) {
558
+ sessionLogger.log('EXIT', `Get positions error: ${e.message}`);
559
+ }
560
+
561
+ // STEP 4: Market order flatten if position still open
562
+ if (positionToFlatten !== 0) {
563
+ const side = positionToFlatten > 0 ? 'LONG' : 'SHORT';
564
+ const size = Math.abs(positionToFlatten);
565
+ ui.addLog('system', `Flattening ${side} ${size} @ market...`);
566
+ sessionLogger.log('EXIT', `Market flatten: ${side} ${size}`);
567
+
495
568
  try {
496
569
  const flattenResult = await service.placeOrder({
497
- accountId: account.rithmicAccountId || account.accountId,
570
+ accountId: accountId,
498
571
  symbol: symbolCode,
499
- exchange: contract.exchange || 'CME',
500
- type: 2, // Market
501
- side: currentPosition > 0 ? 1 : 0, // Sell if long, Buy if short
502
- size: Math.abs(currentPosition)
572
+ exchange: exchange,
573
+ type: 2, // Market order
574
+ side: positionToFlatten > 0 ? 1 : 0, // Sell if long, Buy if short
575
+ size: size
503
576
  });
577
+
504
578
  if (flattenResult.success) {
505
- ui.addLog('fill_' + (currentPosition > 0 ? 'sell' : 'buy'), `Position flattened @ market`);
506
- sessionLogger.log('EXIT', `Position flattened successfully`);
579
+ ui.addLog('fill_' + (positionToFlatten > 0 ? 'sell' : 'buy'), `Position flattened @ market`);
580
+ sessionLogger.log('EXIT', 'Position flattened successfully');
507
581
  } else {
508
582
  ui.addLog('error', `Flatten failed: ${flattenResult.error}`);
509
583
  sessionLogger.log('EXIT', `Flatten failed: ${flattenResult.error}`);
@@ -512,8 +586,32 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
512
586
  ui.addLog('error', `Flatten error: ${e.message}`);
513
587
  sessionLogger.log('EXIT', `Flatten error: ${e.message}`);
514
588
  }
589
+
515
590
  // Wait for fill
516
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');
517
615
  }
518
616
 
519
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,