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
|
@@ -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
|
-
|
|
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
|
-
//
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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:
|
|
570
|
+
accountId: accountId,
|
|
498
571
|
symbol: symbolCode,
|
|
499
|
-
exchange:
|
|
500
|
-
type: 2, // Market
|
|
501
|
-
side:
|
|
502
|
-
size:
|
|
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_' + (
|
|
506
|
-
sessionLogger.log('EXIT',
|
|
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,
|