hedgequantx 2.9.253 → 2.9.254

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.253",
3
+ "version": "2.9.254",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -215,40 +215,53 @@ 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
+ * Rithmic sends TWO ResponseNewOrder messages:
220
+ * 1. First with basketId + rqHandlerRpCode: ["0"] = request handler accepted
221
+ * 2. Second with rpCode: ["0"] = gateway accepted
222
+ *
223
+ * We only care about messages with rpCode (gateway response)
224
+ * If rpCode is missing, it's just an ACK from request handler - ignore rejection logic
219
225
  */
220
226
  const handleNewOrderResponse = (service, data) => {
221
227
  try {
222
228
  const res = proto.decode('ResponseNewOrder', data);
223
229
 
224
- // Log full response for debugging
225
- console.log('[Rithmic] ResponseNewOrder:', JSON.stringify(res));
230
+ // If no rpCode, this is just a request handler ACK - emit but don't judge
231
+ if (!res.rpCode || res.rpCode.length === 0) {
232
+ // First message with basketId - just store it
233
+ if (res.basketId) {
234
+ const order = {
235
+ basketId: res.basketId,
236
+ symbol: res.symbol,
237
+ exchange: res.exchange || 'CME',
238
+ notifyType: 1, // STATUS - order acknowledged
239
+ status: 1, // Pending
240
+ text: null,
241
+ rpCode: res.rqHandlerRpCode,
242
+ userMsg: res.userMsg,
243
+ };
244
+ service.emit('orderNotification', order);
245
+ }
246
+ service.emit('newOrderResponse', res);
247
+ return;
248
+ }
226
249
 
227
- const isAccepted = res.rpCode?.[0] === '0';
250
+ // Second message with rpCode - this is the real gateway response
251
+ const isAccepted = res.rpCode[0] === '0';
228
252
 
229
- // Build rejection reason from rpCode array
230
253
  let rejectReason = null;
231
- if (!isAccepted && res.rpCode) {
232
- // rpCode can be ['0'] for success or ['code', 'message', ...] for errors
233
- // Join all parts after the code for the full message
254
+ if (!isAccepted) {
234
255
  const allCodes = Array.isArray(res.rpCode) ? res.rpCode : [res.rpCode];
235
- const rqCodes = Array.isArray(res.rqHandlerRpCode) ? res.rqHandlerRpCode : [];
236
-
237
- // Get message from rpCode (skip first element which is the code)
238
256
  const rpMsg = allCodes.slice(1).join(' ').trim();
239
- const rqMsg = rqCodes.slice(1).join(' ').trim();
240
257
 
241
- // Use the message if available
242
258
  if (rpMsg) {
243
259
  rejectReason = rpMsg;
244
- } else if (rqMsg) {
245
- rejectReason = rqMsg;
246
260
  } else {
247
- // Map numeric codes to messages
248
261
  const code = allCodes[0];
249
262
  const codeMap = {
250
263
  '1': 'Invalid request',
251
- '2': 'Invalid account',
264
+ '2': 'Invalid account',
252
265
  '3': 'Invalid symbol',
253
266
  '4': 'Invalid quantity',
254
267
  '5': 'Insufficient buying power',
@@ -259,8 +272,7 @@ const handleNewOrderResponse = (service, data) => {
259
272
  };
260
273
  rejectReason = codeMap[code] || `Gateway rejected (code: ${code})`;
261
274
  }
262
-
263
- console.log('[Rithmic] Order REJECTED:', rejectReason);
275
+ console.log('[Rithmic] Order REJECTED by gateway:', rejectReason);
264
276
  }
265
277
 
266
278
  const order = {
@@ -125,8 +125,12 @@ const placeOrder = async (service, orderData) => {
125
125
  }, ORDER_TIMEOUTS.PLACE);
126
126
 
127
127
  const onNotification = (order) => {
128
- // Match by symbol
129
- if (order.symbol !== orderData.symbol) return;
128
+ // Match by orderTag (userMsg) or symbol
129
+ const orderUserMsg = order.userMsg?.[0] || '';
130
+ const matchesTag = orderUserMsg === orderTag;
131
+ const matchesSymbol = order.symbol === orderData.symbol;
132
+
133
+ if (!matchesTag && !matchesSymbol) return;
130
134
 
131
135
  const notifyType = order.notifyType;
132
136
 
@@ -158,26 +162,28 @@ const placeOrder = async (service, orderData) => {
158
162
  return;
159
163
  }
160
164
 
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;
168
- }
165
+ // STATUS (notifyType 1) with rpCode=['0'] - Order accepted by gateway
166
+ // For market orders on Apex/Rithmic, the fill notification may not arrive
167
+ // via ORDER connection, so we consider gateway acceptance as success
168
+ if (notifyType === 1 && order.rpCode?.[0] === '0') {
169
169
  clearTimeout(timeout);
170
170
  service.removeListener('orderNotification', onNotification);
171
171
  resolve({
172
172
  success: true,
173
173
  orderId: order.basketId,
174
- status: 2,
175
- fillPrice: orderData.price,
176
- filledQty: 0,
174
+ status: orderData.type === 2 ? 3 : 2, // 3=filled for market, 2=working for limit
175
+ fillPrice: orderData.price || 0,
176
+ filledQty: orderData.type === 2 ? orderData.size : 0,
177
177
  orderTag,
178
178
  });
179
179
  return;
180
180
  }
181
+
182
+ // STATUS (notifyType 1) with basketId but no rpCode - order acknowledged, wait for more
183
+ if (notifyType === 1 && order.basketId && !order.rpCode) {
184
+ // Just an ACK, continue waiting
185
+ return;
186
+ }
181
187
  };
182
188
 
183
189
  service.on('orderNotification', onNotification);