hedgequantx 2.9.190 → 2.9.192

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.190",
3
+ "version": "2.9.192",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -486,6 +486,35 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
486
486
  clearInterval(refreshInterval);
487
487
  clearInterval(pnlInterval);
488
488
  clearInterval(liveLogInterval);
489
+
490
+ // Flatten any open position before stopping
491
+ if (currentPosition !== 0) {
492
+ ui.addLog('system', `Flattening position: ${currentPosition > 0 ? 'LONG' : 'SHORT'} ${Math.abs(currentPosition)}`);
493
+ sessionLogger.log('EXIT', `Flattening position: ${currentPosition}`);
494
+ try {
495
+ const flattenResult = await service.placeOrder({
496
+ accountId: account.rithmicAccountId || account.accountId,
497
+ symbol: symbolCode,
498
+ exchange: contract.exchange || 'CME',
499
+ type: 2, // Market
500
+ side: currentPosition > 0 ? 1 : 0, // Sell if long, Buy if short
501
+ size: Math.abs(currentPosition)
502
+ });
503
+ if (flattenResult.success) {
504
+ ui.addLog('fill_' + (currentPosition > 0 ? 'sell' : 'buy'), `Position flattened @ market`);
505
+ sessionLogger.log('EXIT', `Position flattened successfully`);
506
+ } else {
507
+ ui.addLog('error', `Flatten failed: ${flattenResult.error}`);
508
+ sessionLogger.log('EXIT', `Flatten failed: ${flattenResult.error}`);
509
+ }
510
+ } catch (e) {
511
+ ui.addLog('error', `Flatten error: ${e.message}`);
512
+ sessionLogger.log('EXIT', `Flatten error: ${e.message}`);
513
+ }
514
+ // Wait for fill
515
+ await new Promise(r => setTimeout(r, 2000));
516
+ }
517
+
489
518
  await marketFeed.disconnect();
490
519
  if (cleanupKeys) cleanupKeys();
491
520
  ui.cleanup();
@@ -335,6 +335,47 @@ const getPositions = async (service) => {
335
335
  return { success: true, positions };
336
336
  };
337
337
 
338
+ /**
339
+ * Fetch trade routes from ORDER_PLANT
340
+ * Routes are needed to place orders - Rithmic rejects orders without trade_route
341
+ * @param {RithmicService} service - The Rithmic service instance
342
+ */
343
+ const fetchTradeRoutes = async (service) => {
344
+ if (!service.orderConn || !service.loginInfo) {
345
+ debug('fetchTradeRoutes: no connection or loginInfo');
346
+ return;
347
+ }
348
+
349
+ // Initialize map if needed
350
+ if (!service.tradeRoutes) service.tradeRoutes = new Map();
351
+
352
+ return new Promise((resolve) => {
353
+ const timeout = setTimeout(() => {
354
+ debug('fetchTradeRoutes: timeout, found', service.tradeRoutes.size, 'routes');
355
+ resolve();
356
+ }, 5000);
357
+
358
+ service.once('tradeRoutesComplete', () => {
359
+ debug('fetchTradeRoutes: complete, found', service.tradeRoutes.size, 'routes');
360
+ clearTimeout(timeout);
361
+ resolve();
362
+ });
363
+
364
+ try {
365
+ debug('fetchTradeRoutes: sending RequestTradeRoutes');
366
+ service.orderConn.send('RequestTradeRoutes', {
367
+ templateId: REQ.TRADE_ROUTES,
368
+ userMsg: ['HQX'],
369
+ subscribeForUpdates: false,
370
+ });
371
+ } catch (e) {
372
+ debug('fetchTradeRoutes: error', e.message);
373
+ clearTimeout(timeout);
374
+ resolve();
375
+ }
376
+ });
377
+ };
378
+
338
379
  module.exports = {
339
380
  hashAccountId,
340
381
  fetchAccounts,
@@ -343,5 +384,6 @@ module.exports = {
343
384
  getTradingAccounts,
344
385
  requestPnLSnapshot,
345
386
  subscribePnLUpdates,
346
- getPositions
387
+ getPositions,
388
+ fetchTradeRoutes
347
389
  };
@@ -133,13 +133,41 @@ const handleAccountList = (service, data) => {
133
133
 
134
134
  /**
135
135
  * Handle trade routes response
136
+ * Stores trade routes in service.tradeRoutes Map keyed by exchange
136
137
  */
137
138
  const handleTradeRoutes = (service, data) => {
138
139
  try {
139
140
  const res = proto.decode('ResponseTradeRoutes', data);
141
+ debug('Trade routes response:', JSON.stringify(res));
142
+
143
+ // Store trade route if we have exchange and trade_route
144
+ if (res.exchange && res.tradeRoute) {
145
+ const routeInfo = {
146
+ fcmId: res.fcmId,
147
+ ibId: res.ibId,
148
+ exchange: res.exchange,
149
+ tradeRoute: res.tradeRoute,
150
+ status: res.status,
151
+ isDefault: res.isDefault || false,
152
+ };
153
+
154
+ // Use exchange as key, prefer default route
155
+ const existing = service.tradeRoutes.get(res.exchange);
156
+ if (!existing || res.isDefault) {
157
+ service.tradeRoutes.set(res.exchange, routeInfo);
158
+ debug('Stored trade route for', res.exchange, ':', res.tradeRoute, res.isDefault ? '(default)' : '');
159
+ }
160
+ }
161
+
162
+ // Signal completion when rpCode is '0'
163
+ if (res.rpCode?.[0] === '0') {
164
+ debug('Trade routes complete, total:', service.tradeRoutes.size);
165
+ service.emit('tradeRoutesComplete');
166
+ }
167
+
140
168
  service.emit('tradeRoutes', res);
141
169
  } catch (e) {
142
- // Ignore decode errors
170
+ debug('Error decoding trade routes:', e.message);
143
171
  }
144
172
  };
145
173
 
@@ -15,6 +15,7 @@ const {
15
15
  requestPnLSnapshot,
16
16
  subscribePnLUpdates,
17
17
  getPositions,
18
+ fetchTradeRoutes,
18
19
  } = require('./accounts');
19
20
  const { placeOrder, cancelOrder, getOrders, getOrderHistory, getOrderHistoryDates, getTradeHistoryFull, closePosition } = require('./orders');
20
21
  const { fillsToRoundTrips, calculateTradeStats } = require('./trades');
@@ -78,6 +79,9 @@ class RithmicService extends EventEmitter {
78
79
  this._contractsCache = null;
79
80
  this._contractsCacheTime = 0;
80
81
 
82
+ // Trade routes cache (keyed by exchange)
83
+ this.tradeRoutes = new Map();
84
+
81
85
  // Trades history (captured from ExchangeOrderNotification fills)
82
86
  this.trades = [];
83
87
  }
@@ -155,6 +159,14 @@ class RithmicService extends EventEmitter {
155
159
  log.warn('PnL connection failed', { error: err.message });
156
160
  }
157
161
 
162
+ // Fetch trade routes (required for order placement)
163
+ try {
164
+ await fetchTradeRoutes(this);
165
+ log.debug('Trade routes fetched', { count: this.tradeRoutes.size });
166
+ } catch (err) {
167
+ log.warn('Trade routes fetch failed', { error: err.message });
168
+ }
169
+
158
170
  // Get trading accounts (uses existing this.accounts, no new API call)
159
171
  const result = await getTradingAccounts(this);
160
172
  log.info('Login successful', { accounts: result.accounts.length });
@@ -472,6 +484,7 @@ class RithmicService extends EventEmitter {
472
484
  this.accounts = [];
473
485
  this.accountPnL.clear();
474
486
  this.positions.clear();
487
+ this.tradeRoutes.clear();
475
488
  this.orders = [];
476
489
  this.loginInfo = null;
477
490
  this.user = null;
@@ -75,6 +75,35 @@ const placeOrder = async (service, orderData) => {
75
75
  service.on('orderNotification', onNotification);
76
76
 
77
77
  try {
78
+ const exchange = orderData.exchange || 'CME';
79
+
80
+ // Get trade route from cache (fetched during login)
81
+ // Trade route is REQUIRED by Rithmic - orders rejected without it
82
+ let tradeRoute = null;
83
+ if (service.tradeRoutes && service.tradeRoutes.size > 0) {
84
+ const routeInfo = service.tradeRoutes.get(exchange);
85
+ if (routeInfo) {
86
+ tradeRoute = routeInfo.tradeRoute;
87
+ } else {
88
+ // Fallback: use first available route
89
+ const firstRoute = service.tradeRoutes.values().next().value;
90
+ if (firstRoute) {
91
+ tradeRoute = firstRoute.tradeRoute;
92
+ }
93
+ }
94
+ }
95
+
96
+ if (DEBUG) {
97
+ console.log('[ORDER] Trade route for', exchange, ':', tradeRoute);
98
+ }
99
+
100
+ if (!tradeRoute) {
101
+ // No trade route available - order will likely fail
102
+ if (DEBUG) {
103
+ console.log('[ORDER] WARNING: No trade route available, order may be rejected');
104
+ }
105
+ }
106
+
78
107
  const orderRequest = {
79
108
  templateId: REQ.NEW_ORDER,
80
109
  userMsg: [orderTag],
@@ -82,12 +111,13 @@ const placeOrder = async (service, orderData) => {
82
111
  ibId: service.loginInfo.ibId,
83
112
  accountId: orderData.accountId,
84
113
  symbol: orderData.symbol,
85
- exchange: orderData.exchange || 'CME',
114
+ exchange: exchange,
86
115
  quantity: orderData.size,
87
116
  transactionType: orderData.side === 0 ? 1 : 2, // 1=Buy, 2=Sell
88
117
  duration: 1, // DAY
89
118
  priceType: orderData.type === 2 ? 2 : 1, // 2=Market, 1=Limit
90
119
  price: orderData.price || 0,
120
+ tradeRoute: tradeRoute, // REQUIRED by Rithmic
91
121
  manualOrAuto: 2, // AUTO
92
122
  };
93
123