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
|
@@ -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, //
|
|
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
|
|
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} |
|
|
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
|
|
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:
|
|
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, //
|
|
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' };
|