hedgequantx 2.9.247 → 2.9.248
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
|
@@ -215,32 +215,34 @@ const handleShowOrdersResponse = (service, data) => {
|
|
|
215
215
|
|
|
216
216
|
/**
|
|
217
217
|
* Handle new order response (template 313)
|
|
218
|
+
* rpCode[0] = '0' means accepted, anything else = rejected by gateway
|
|
218
219
|
*/
|
|
219
220
|
const handleNewOrderResponse = (service, data) => {
|
|
220
221
|
try {
|
|
221
222
|
const res = proto.decode('ResponseNewOrder', data);
|
|
222
|
-
debug('New order response:', JSON.stringify(res));
|
|
223
223
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
224
|
+
const isAccepted = res.rpCode?.[0] === '0';
|
|
225
|
+
const rejectReason = res.rpCode?.[1] || res.rqHandlerRpCode?.[1] || 'Unknown';
|
|
226
|
+
|
|
227
|
+
// Log rejection details for debugging
|
|
228
|
+
if (!isAccepted) {
|
|
229
|
+
console.log('[Rithmic] Order rejected:', rejectReason, '| rpCode:', res.rpCode, '| rqHandlerRpCode:', res.rqHandlerRpCode);
|
|
230
|
+
}
|
|
231
|
+
|
|
227
232
|
const order = {
|
|
228
|
-
basketId: res.basketId ||
|
|
229
|
-
accountId: res.accountId,
|
|
233
|
+
basketId: res.basketId || null,
|
|
230
234
|
symbol: res.symbol,
|
|
231
235
|
exchange: res.exchange || 'CME',
|
|
232
|
-
|
|
233
|
-
|
|
236
|
+
notifyType: isAccepted ? 1 : 6, // 1=STATUS (accepted), 6=REJECT
|
|
237
|
+
status: isAccepted ? 2 : 6,
|
|
238
|
+
text: isAccepted ? null : rejectReason,
|
|
234
239
|
rpCode: res.rpCode,
|
|
235
|
-
rqHandlerRpCode: res.rqHandlerRpCode, // Also include request handler response code
|
|
236
240
|
userMsg: res.userMsg,
|
|
237
241
|
};
|
|
238
242
|
service.emit('orderNotification', order);
|
|
239
|
-
|
|
240
|
-
// Also emit specific event
|
|
241
243
|
service.emit('newOrderResponse', res);
|
|
242
244
|
} catch (e) {
|
|
243
|
-
|
|
245
|
+
console.error('Error decoding ResponseNewOrder:', e.message);
|
|
244
246
|
}
|
|
245
247
|
};
|
|
246
248
|
|
|
@@ -369,12 +371,31 @@ const handleAccountRmsInfo = (service, data) => {
|
|
|
369
371
|
|
|
370
372
|
/**
|
|
371
373
|
* Handle exchange order notification (fills/trades)
|
|
372
|
-
* NotifyType:
|
|
374
|
+
* NotifyType: 1=STATUS, 2=MODIFY, 3=CANCEL, 4=TRIGGER, 5=FILL, 6=REJECT
|
|
373
375
|
*/
|
|
374
376
|
const handleExchangeNotification = (service, data) => {
|
|
375
377
|
try {
|
|
376
378
|
const res = proto.decode('ExchangeOrderNotification', data);
|
|
377
|
-
debug('Exchange notification:', res.notifyType, res.symbol);
|
|
379
|
+
debug('Exchange notification:', res.notifyType, res.symbol, res.status);
|
|
380
|
+
|
|
381
|
+
// Emit orderNotification for placeOrder listener
|
|
382
|
+
// This is critical for order tracking
|
|
383
|
+
const orderNotif = {
|
|
384
|
+
basketId: res.basketId,
|
|
385
|
+
accountId: res.accountId,
|
|
386
|
+
symbol: res.symbol,
|
|
387
|
+
exchange: res.exchange || 'CME',
|
|
388
|
+
notifyType: res.notifyType, // 1=STATUS, 5=FILL, 6=REJECT
|
|
389
|
+
status: res.notifyType === 5 ? 3 : res.notifyType === 6 ? 6 : 2,
|
|
390
|
+
fillPrice: res.fillPrice ? parseFloat(res.fillPrice) : null,
|
|
391
|
+
fillSize: res.fillSize ? parseInt(res.fillSize) : null,
|
|
392
|
+
avgFillPrice: res.avgFillPrice ? parseFloat(res.avgFillPrice) : null,
|
|
393
|
+
totalFillSize: res.totalFillSize ? parseInt(res.totalFillSize) : null,
|
|
394
|
+
confirmedSize: res.confirmedSize ? parseInt(res.confirmedSize) : null,
|
|
395
|
+
text: res.text || res.reportText,
|
|
396
|
+
userMsg: res.userTag ? [res.userTag] : null,
|
|
397
|
+
};
|
|
398
|
+
service.emit('orderNotification', orderNotif);
|
|
378
399
|
|
|
379
400
|
// notifyType 5 = FILL (trade executed)
|
|
380
401
|
if (res.notifyType === 5 && res.fillPrice && res.fillSize) {
|
|
@@ -125,72 +125,83 @@ const placeOrder = async (service, orderData) => {
|
|
|
125
125
|
}, ORDER_TIMEOUTS.PLACE);
|
|
126
126
|
|
|
127
127
|
const onNotification = (order) => {
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
order.symbol === orderData.symbol;
|
|
128
|
+
// Match by symbol
|
|
129
|
+
if (order.symbol !== orderData.symbol) return;
|
|
131
130
|
|
|
132
|
-
|
|
131
|
+
const notifyType = order.notifyType;
|
|
132
|
+
|
|
133
|
+
// FILL (notifyType 5) - Order executed
|
|
134
|
+
if (notifyType === 5) {
|
|
133
135
|
clearTimeout(timeout);
|
|
134
136
|
service.removeListener('orderNotification', onNotification);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
orderTag,
|
|
167
|
-
rpCode,
|
|
168
|
-
rqHandlerRpCode: rqCode,
|
|
169
|
-
});
|
|
137
|
+
resolve({
|
|
138
|
+
success: true,
|
|
139
|
+
orderId: order.basketId,
|
|
140
|
+
status: 3,
|
|
141
|
+
fillPrice: order.avgFillPrice || order.fillPrice || orderData.price,
|
|
142
|
+
filledQty: order.totalFillSize || order.fillSize || orderData.size,
|
|
143
|
+
orderTag,
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// REJECT (notifyType 6) - Order rejected
|
|
149
|
+
if (notifyType === 6) {
|
|
150
|
+
clearTimeout(timeout);
|
|
151
|
+
service.removeListener('orderNotification', onNotification);
|
|
152
|
+
resolve({
|
|
153
|
+
success: false,
|
|
154
|
+
error: order.text || 'Order rejected',
|
|
155
|
+
orderId: order.basketId,
|
|
156
|
+
orderTag,
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// STATUS (notifyType 1) with basketId - Order accepted by gateway
|
|
162
|
+
if (notifyType === 1 && order.basketId) {
|
|
163
|
+
// For market orders, wait a bit more for fill notification
|
|
164
|
+
// For limit orders, this is success (working)
|
|
165
|
+
if (orderData.type === 2) {
|
|
166
|
+
// Market order - don't resolve yet, wait for fill
|
|
167
|
+
return;
|
|
170
168
|
}
|
|
169
|
+
clearTimeout(timeout);
|
|
170
|
+
service.removeListener('orderNotification', onNotification);
|
|
171
|
+
resolve({
|
|
172
|
+
success: true,
|
|
173
|
+
orderId: order.basketId,
|
|
174
|
+
status: 2,
|
|
175
|
+
fillPrice: orderData.price,
|
|
176
|
+
filledQty: 0,
|
|
177
|
+
orderTag,
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
171
180
|
}
|
|
172
181
|
};
|
|
173
182
|
|
|
174
183
|
service.on('orderNotification', onNotification);
|
|
175
184
|
|
|
176
|
-
// HFT: Inline order request construction (no try-catch in hot path)
|
|
177
185
|
const exchange = orderData.exchange || 'CME';
|
|
178
186
|
|
|
179
|
-
// Get trade route
|
|
187
|
+
// CRITICAL: Get trade route - orders WILL BE REJECTED without it
|
|
180
188
|
let tradeRoute = null;
|
|
181
189
|
const routes = service.tradeRoutes;
|
|
182
190
|
if (routes && routes.size > 0) {
|
|
183
191
|
const routeInfo = routes.get(exchange);
|
|
184
|
-
tradeRoute = routeInfo
|
|
185
|
-
routes.values().next().value?.tradeRoute || null;
|
|
192
|
+
tradeRoute = routeInfo?.tradeRoute || routes.values().next().value?.tradeRoute;
|
|
186
193
|
}
|
|
187
194
|
|
|
188
|
-
//
|
|
195
|
+
// FAIL FAST if no trade route
|
|
189
196
|
if (!tradeRoute) {
|
|
190
|
-
|
|
197
|
+
clearTimeout(timeout);
|
|
198
|
+
service.removeListener('orderNotification', onNotification);
|
|
199
|
+
console.log('[Orders] ERROR: No trade route for', exchange);
|
|
200
|
+
resolve({ success: false, error: `No trade route for ${exchange}. Login may be incomplete.`, orderTag });
|
|
201
|
+
return;
|
|
191
202
|
}
|
|
192
203
|
|
|
193
|
-
//
|
|
204
|
+
// Build order request
|
|
194
205
|
ORDER_REQUEST_TEMPLATE.userMsg[0] = orderTag;
|
|
195
206
|
ORDER_REQUEST_TEMPLATE.fcmId = service.loginInfo.fcmId;
|
|
196
207
|
ORDER_REQUEST_TEMPLATE.ibId = service.loginInfo.ibId;
|