hedgequantx 2.9.246 → 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 +1 -1
- package/src/pages/algo/ui.js +23 -33
- package/src/services/rithmic/handlers.js +35 -14
- package/src/services/rithmic/orders.js +57 -46
package/package.json
CHANGED
package/src/pages/algo/ui.js
CHANGED
|
@@ -77,47 +77,37 @@ const center = (text, width) => {
|
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
79
|
* Fit text to exact width (truncate or pad)
|
|
80
|
-
* Ensures output is EXACTLY width characters
|
|
80
|
+
* Ensures output is EXACTLY width visible characters
|
|
81
81
|
*/
|
|
82
82
|
const fitToWidth = (text, width) => {
|
|
83
83
|
const plain = stripAnsi(text);
|
|
84
84
|
|
|
85
|
-
if (plain.length
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
85
|
+
if (plain.length <= width) {
|
|
86
|
+
// Pad to exact width
|
|
87
|
+
return text + ' '.repeat(width - plain.length);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Need to truncate to (width - 2) visible chars, then add ".."
|
|
91
|
+
const targetLen = width - 2;
|
|
92
|
+
let visibleCount = 0;
|
|
93
|
+
let cutIndex = 0;
|
|
94
|
+
let i = 0;
|
|
95
|
+
|
|
96
|
+
while (i < text.length && visibleCount < targetLen) {
|
|
97
|
+
if (text[i] === '\x1B') {
|
|
98
|
+
// Skip entire ANSI sequence
|
|
99
|
+
while (i < text.length && text[i] !== 'm') {
|
|
100
|
+
i++;
|
|
102
101
|
}
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const truncated = text.substring(0, cutIndex);
|
|
108
|
-
const truncatedPlain = stripAnsi(truncated);
|
|
109
|
-
const remaining = width - truncatedPlain.length;
|
|
110
|
-
|
|
111
|
-
if (remaining >= 2) {
|
|
112
|
-
return truncated + '..' + ' '.repeat(remaining - 2);
|
|
113
|
-
} else if (remaining > 0) {
|
|
114
|
-
return truncated + ' '.repeat(remaining);
|
|
102
|
+
i++; // skip 'm'
|
|
103
|
+
} else {
|
|
104
|
+
visibleCount++;
|
|
105
|
+
i++;
|
|
115
106
|
}
|
|
116
|
-
|
|
107
|
+
cutIndex = i;
|
|
117
108
|
}
|
|
118
109
|
|
|
119
|
-
|
|
120
|
-
return text + ' '.repeat(width - plain.length);
|
|
110
|
+
return text.substring(0, cutIndex) + '..';
|
|
121
111
|
};
|
|
122
112
|
|
|
123
113
|
/**
|
|
@@ -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;
|