hedgequantx 2.6.37 → 2.6.38

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.37",
3
+ "version": "2.6.38",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -135,6 +135,7 @@ class RithmicService extends EventEmitter {
135
135
  this.completedTrades = []; // Store filled orders for trade history
136
136
  this.user = null;
137
137
  this.credentials = null;
138
+ this.tradeRoutes = new Map(); // exchange -> tradeRoute (from API)
138
139
 
139
140
  // Cache
140
141
  this._contractsCache = null;
@@ -175,6 +176,14 @@ class RithmicService extends EventEmitter {
175
176
  this.loginInfo = data;
176
177
  this.user = { userName: username, fcmId: data.fcmId, ibId: data.ibId };
177
178
 
179
+ // Fetch trade routes first (needed for orders)
180
+ try {
181
+ await this._fetchTradeRoutes();
182
+ log.debug('Fetched trade routes', { count: this.tradeRoutes.size });
183
+ } catch (err) {
184
+ log.warn('Failed to fetch trade routes', { error: err.message });
185
+ }
186
+
178
187
  // Fetch accounts
179
188
  try {
180
189
  await fetchAccounts(this);
@@ -183,6 +192,19 @@ class RithmicService extends EventEmitter {
183
192
  log.warn('Failed to fetch accounts', { error: err.message });
184
193
  }
185
194
 
195
+ // Subscribe to order updates (required to receive fill notifications)
196
+ try {
197
+ this.orderConn.send('RequestSubscribeForOrderUpdates', {
198
+ templateId: REQ.ORDER_UPDATES,
199
+ userMsg: ['HQX'],
200
+ fcmId: data.fcmId,
201
+ ibId: data.ibId,
202
+ });
203
+ log.debug('Subscribed to order updates');
204
+ } catch (err) {
205
+ log.warn('Failed to subscribe to order updates', { error: err.message });
206
+ }
207
+
186
208
  // Track open entries for P&L calculation
187
209
  this._openEntries = new Map(); // key: accountId:symbol, value: {side, qty, price}
188
210
 
@@ -381,6 +403,64 @@ class RithmicService extends EventEmitter {
381
403
  }
382
404
  }
383
405
 
406
+ // ==================== TRADE ROUTES ====================
407
+
408
+ /**
409
+ * Fetch trade routes from Rithmic API
410
+ * Trade routes are required for order submission
411
+ * @private
412
+ * @returns {Promise<void>}
413
+ */
414
+ async _fetchTradeRoutes() {
415
+ if (!this.orderConn || !this.loginInfo) {
416
+ return;
417
+ }
418
+
419
+ return new Promise((resolve) => {
420
+ const timeout = setTimeout(() => {
421
+ log.debug('Trade routes fetch timeout');
422
+ resolve();
423
+ }, 5000);
424
+
425
+ const onTradeRoute = (res) => {
426
+ if (res.tradeRoute && res.exchange) {
427
+ // Store trade route by exchange (from API)
428
+ this.tradeRoutes.set(res.exchange, res.tradeRoute);
429
+ log.debug('Trade route received', { exchange: res.exchange, route: res.tradeRoute });
430
+ }
431
+ if (res.rpCode?.[0] === '0' && !res.tradeRoute) {
432
+ // End of trade routes
433
+ clearTimeout(timeout);
434
+ this.removeListener('tradeRoutes', onTradeRoute);
435
+ resolve();
436
+ }
437
+ };
438
+
439
+ this.on('tradeRoutes', onTradeRoute);
440
+
441
+ try {
442
+ this.orderConn.send('RequestTradeRoutes', {
443
+ templateId: REQ.TRADE_ROUTES,
444
+ userMsg: ['HQX'],
445
+ subscribeForUpdates: true, // Required by Rithmic API
446
+ });
447
+ } catch (e) {
448
+ clearTimeout(timeout);
449
+ this.removeListener('tradeRoutes', onTradeRoute);
450
+ resolve();
451
+ }
452
+ });
453
+ }
454
+
455
+ /**
456
+ * Get trade route for an exchange
457
+ * @param {string} exchange - Exchange name (e.g., 'CME', 'NYMEX')
458
+ * @returns {string|null} Trade route from API, or null if not available
459
+ */
460
+ getTradeRoute(exchange) {
461
+ return this.tradeRoutes.get(exchange) || null;
462
+ }
463
+
384
464
  // ==================== DELEGATED METHODS ====================
385
465
 
386
466
  async getTradingAccounts() { return getTradingAccounts(this); }
@@ -48,13 +48,17 @@ const OrderPool = {
48
48
  quantity: 0,
49
49
  transactionType: 1,
50
50
  duration: 1,
51
- priceType: 2, // FIXED: was 'orderType: 1' - priceType 2 = MARKET order
51
+ priceType: 2, // priceType 2 = MARKET order
52
52
  manualOrAuto: 2,
53
+ tradeRoute: '', // Required by Rithmic API - fetched from RequestTradeRoutes
53
54
  },
54
55
 
55
56
  /**
56
57
  * Get order object with values filled in
57
58
  * Reuses same object to avoid allocation
59
+ * @param {string} orderTag - Unique order tag
60
+ * @param {Object} loginInfo - { fcmId, ibId }
61
+ * @param {Object} orderData - { accountId, symbol, exchange, size, side, tradeRoute }
58
62
  */
59
63
  fill(orderTag, loginInfo, orderData) {
60
64
  const o = this._template;
@@ -66,6 +70,7 @@ const OrderPool = {
66
70
  o.exchange = orderData.exchange || 'CME';
67
71
  o.quantity = orderData.size;
68
72
  o.transactionType = orderData.side === 0 ? 1 : 2;
73
+ o.tradeRoute = orderData.tradeRoute || ''; // From API via service.getTradeRoute()
69
74
  return o;
70
75
  }
71
76
  };
@@ -111,11 +116,25 @@ const fastEntry = (service, orderData) => {
111
116
  ibId: account?.ibId || service.loginInfo.ibId,
112
117
  };
113
118
 
119
+ // Get trade route from API (required by Rithmic)
120
+ const exchange = orderData.exchange || 'CME';
121
+ const tradeRoute = service.getTradeRoute?.(exchange);
122
+ if (!tradeRoute) {
123
+ return {
124
+ success: false,
125
+ error: `No trade route for exchange ${exchange}`,
126
+ orderTag,
127
+ entryTime,
128
+ latencyMs: performance.now() - startTime,
129
+ };
130
+ }
131
+
114
132
  // OPTIMIZED: Use pre-allocated order object
115
- const order = OrderPool.fill(orderTag, effectiveLoginInfo, orderData);
133
+ const orderWithRoute = { ...orderData, tradeRoute };
134
+ const order = OrderPool.fill(orderTag, effectiveLoginInfo, orderWithRoute);
116
135
 
117
136
  // DEBUG: Log order details
118
- console.log(`[ORDER] Sending: ${orderTag} | ${orderData.side === 0 ? 'BUY' : 'SELL'} ${orderData.size}x ${orderData.symbol} | acct=${orderData.accountId} | fcm=${effectiveLoginInfo.fcmId} ib=${effectiveLoginInfo.ibId}`);
137
+ console.log(`[ORDER] Sending: ${orderTag} | ${orderData.side === 0 ? 'BUY' : 'SELL'} ${orderData.size}x ${orderData.symbol} | acct=${orderData.accountId} | route=${tradeRoute}`);
119
138
 
120
139
  // OPTIMIZED: Use fastEncode with cached type
121
140
  const buffer = proto.fastEncode('RequestNewOrder', order);
@@ -185,8 +204,22 @@ const fastExit = (service, orderData) => {
185
204
  ibId: account?.ibId || service.loginInfo.ibId,
186
205
  };
187
206
 
207
+ // Get trade route from API (required by Rithmic)
208
+ const exchange = orderData.exchange || 'CME';
209
+ const tradeRoute = service.getTradeRoute?.(exchange);
210
+ if (!tradeRoute) {
211
+ return {
212
+ success: false,
213
+ error: `No trade route for exchange ${exchange}`,
214
+ orderTag,
215
+ exitTime,
216
+ latencyMs: performance.now() - startTime,
217
+ };
218
+ }
219
+
188
220
  // OPTIMIZED: Use pre-allocated order object
189
- const order = OrderPool.fill(orderTag, effectiveLoginInfo, orderData);
221
+ const orderWithRoute = { ...orderData, tradeRoute };
222
+ const order = OrderPool.fill(orderTag, effectiveLoginInfo, orderWithRoute);
190
223
 
191
224
  // OPTIMIZED: Use fastEncode with cached type
192
225
  const buffer = proto.fastEncode('RequestNewOrder', order);
@@ -233,6 +266,13 @@ const placeOrder = async (service, orderData) => {
233
266
  a.accountId === orderData.accountId || a.rithmicAccountId === orderData.accountId
234
267
  );
235
268
 
269
+ // Get trade route from API (required by Rithmic)
270
+ const exchange = orderData.exchange || 'CME';
271
+ const tradeRoute = service.getTradeRoute?.(exchange);
272
+ if (!tradeRoute) {
273
+ return { success: false, error: `No trade route for exchange ${exchange}` };
274
+ }
275
+
236
276
  service.orderConn.send('RequestNewOrder', {
237
277
  templateId: REQ.NEW_ORDER,
238
278
  userMsg: ['HQX'],
@@ -240,12 +280,13 @@ const placeOrder = async (service, orderData) => {
240
280
  ibId: account?.ibId || service.loginInfo.ibId,
241
281
  accountId: orderData.accountId,
242
282
  symbol: orderData.symbol,
243
- exchange: orderData.exchange || 'CME',
283
+ exchange: exchange,
244
284
  quantity: orderData.size,
245
285
  transactionType: orderData.side === 0 ? 1 : 2, // 1=Buy, 2=Sell
246
286
  duration: 1, // DAY
247
- priceType: orderData.type === 2 ? 2 : 1, // FIXED: was 'orderType' with inverted logic. priceType: 1=Limit, 2=Market
287
+ priceType: orderData.type === 2 ? 2 : 1, // priceType: 1=Limit, 2=Market
248
288
  price: orderData.price || 0,
289
+ tradeRoute: tradeRoute, // Required by Rithmic API - fetched from RequestTradeRoutes
249
290
  });
250
291
 
251
292
  return { success: true, message: 'Order submitted' };