hedgequantx 2.9.236 → 2.9.238
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/dist/lib/m/s1-models.js +281 -94
- package/package.json +2 -1
- package/src/lib/hft/index.js +648 -0
- package/src/pages/algo/algo-executor.js +29 -10
- package/src/services/rithmic/handlers.js +93 -54
- package/src/services/rithmic/orders.js +73 -95
- package/src/services/rithmic/protobuf-decoders.js +237 -241
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Algo Executor - Execution engine for algo modes with AI supervision
|
|
3
|
+
*
|
|
4
|
+
* HFT-GRADE OPTIMIZATIONS:
|
|
5
|
+
* - CircularBuffer for O(1) tick storage (replaces O(n) shift())
|
|
6
|
+
* - Pre-allocated tick structure pool
|
|
7
|
+
* - Cached timestamp operations
|
|
3
8
|
*/
|
|
4
9
|
const readline = require('readline');
|
|
5
10
|
const { AlgoUI, renderSessionSummary } = require('./ui');
|
|
@@ -9,6 +14,7 @@ const { SupervisionEngine } = require('../../services/ai-supervision');
|
|
|
9
14
|
const smartLogs = require('../../lib/smart-logs');
|
|
10
15
|
const { createEngine: createLogsEngine } = require('../../lib/smart-logs-engine');
|
|
11
16
|
const { sessionLogger } = require('../../services/session-logger');
|
|
17
|
+
const { CircularBuffer, Float64CircularBuffer, timestampCache } = require('../../lib/hft');
|
|
12
18
|
|
|
13
19
|
/**
|
|
14
20
|
* Execute algo strategy with market data
|
|
@@ -54,7 +60,16 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
|
|
|
54
60
|
let running = true, stopReason = null, startingPnL = null;
|
|
55
61
|
let currentPosition = 0, pendingOrder = false, tickCount = 0, lastBias = 'FLAT';
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
// HFT: Use CircularBuffer for O(1) tick storage (replaces O(n) shift())
|
|
64
|
+
const recentTicksBuffer = new CircularBuffer(100);
|
|
65
|
+
const recentSignalsBuffer = new CircularBuffer(10);
|
|
66
|
+
const recentTradesBuffer = new CircularBuffer(20);
|
|
67
|
+
const aiContext = {
|
|
68
|
+
recentTicks: recentTicksBuffer, // CircularBuffer - use .recent(n) to get array
|
|
69
|
+
recentSignals: recentSignalsBuffer, // CircularBuffer
|
|
70
|
+
recentTrades: recentTradesBuffer, // CircularBuffer
|
|
71
|
+
maxTicks: 100
|
|
72
|
+
};
|
|
58
73
|
|
|
59
74
|
const strategy = new StrategyClass({ tickSize });
|
|
60
75
|
strategy.initialize(contractId, tickSize);
|
|
@@ -129,8 +144,8 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
|
|
|
129
144
|
let { direction, entry, stopLoss, takeProfit, confidence } = signal;
|
|
130
145
|
let orderSize = contracts;
|
|
131
146
|
|
|
132
|
-
|
|
133
|
-
|
|
147
|
+
// HFT: O(1) push with automatic overflow handling
|
|
148
|
+
aiContext.recentSignals.push({ ...signal, timestamp: timestampCache.now() });
|
|
134
149
|
|
|
135
150
|
const riskLog = smartLogs.getRiskCheckLog(true, `${direction.toUpperCase()} @ ${entry.toFixed(2)}`);
|
|
136
151
|
ui.addLog('risk', `${riskLog.message} - ${riskLog.details}`);
|
|
@@ -139,12 +154,13 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
|
|
|
139
154
|
if (supervisionEnabled && supervisionEngine) {
|
|
140
155
|
ui.addLog('analysis', 'AI analyzing signal...');
|
|
141
156
|
|
|
157
|
+
// HFT: Extract arrays from CircularBuffers for AI supervision
|
|
142
158
|
const supervisionResult = await supervisionEngine.supervise({
|
|
143
159
|
symbolId: symbolName,
|
|
144
160
|
signal: { direction, entry, stopLoss, takeProfit, confidence, size: contracts },
|
|
145
|
-
recentTicks: aiContext.recentTicks,
|
|
146
|
-
recentSignals: aiContext.recentSignals,
|
|
147
|
-
recentTrades: aiContext.recentTrades,
|
|
161
|
+
recentTicks: aiContext.recentTicks.recent(aiContext.maxTicks),
|
|
162
|
+
recentSignals: aiContext.recentSignals.recent(10),
|
|
163
|
+
recentTrades: aiContext.recentTrades.recent(20),
|
|
148
164
|
stats,
|
|
149
165
|
config: { dailyTarget, maxRisk }
|
|
150
166
|
});
|
|
@@ -228,7 +244,9 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
|
|
|
228
244
|
let lastPrice = null, lastBid = null, lastAsk = null;
|
|
229
245
|
let ticksPerSecond = 0, lastTickSecond = Math.floor(Date.now() / 1000);
|
|
230
246
|
let lastBiasLogSecond = 0, lastStateLogSecond = 0;
|
|
231
|
-
|
|
247
|
+
// HFT: Float64CircularBuffer for O(1) latency statistics
|
|
248
|
+
let buyVolume = 0, sellVolume = 0, lastTickTime = 0;
|
|
249
|
+
const tickLatencies = new Float64CircularBuffer(20);
|
|
232
250
|
let runningDelta = 0, runningBuyPct = 50; // For live logs
|
|
233
251
|
|
|
234
252
|
marketFeed.on('tick', (tick) => {
|
|
@@ -250,8 +268,8 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
|
|
|
250
268
|
lastTickSecond = currentSecond;
|
|
251
269
|
}
|
|
252
270
|
|
|
271
|
+
// HFT: O(1) push with automatic overflow handling (no shift needed)
|
|
253
272
|
aiContext.recentTicks.push(tick);
|
|
254
|
-
if (aiContext.recentTicks.length > aiContext.maxTicks) aiContext.recentTicks.shift();
|
|
255
273
|
|
|
256
274
|
const price = Number(tick.price) || Number(tick.tradePrice) || null;
|
|
257
275
|
const bid = Number(tick.bid) || Number(tick.bidPrice) || null;
|
|
@@ -314,6 +332,7 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
|
|
|
314
332
|
}
|
|
315
333
|
|
|
316
334
|
// Calculate latency from Rithmic ssboe/usecs or inter-tick timing
|
|
335
|
+
// HFT: Use cached timestamp and Float64CircularBuffer for O(1) mean calculation
|
|
317
336
|
if (tick.ssboe && tick.usecs !== undefined) {
|
|
318
337
|
const tickTimeMs = (tick.ssboe * 1000) + Math.floor(tick.usecs / 1000);
|
|
319
338
|
const latency = now - tickTimeMs;
|
|
@@ -322,8 +341,8 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
|
|
|
322
341
|
const timeSinceLastTick = now - lastTickTime;
|
|
323
342
|
if (timeSinceLastTick < 100) {
|
|
324
343
|
tickLatencies.push(timeSinceLastTick);
|
|
325
|
-
|
|
326
|
-
stats.latency = Math.round(tickLatencies.
|
|
344
|
+
// HFT: O(1) mean calculation (pre-computed running sum)
|
|
345
|
+
stats.latency = Math.round(tickLatencies.mean());
|
|
327
346
|
}
|
|
328
347
|
}
|
|
329
348
|
lastTickTime = now;
|
|
@@ -1,16 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Rithmic Message Handlers
|
|
3
3
|
* Handles ORDER_PLANT and PNL_PLANT messages
|
|
4
|
+
*
|
|
5
|
+
* HFT-GRADE OPTIMIZATIONS:
|
|
6
|
+
* - Pre-allocated result objects
|
|
7
|
+
* - Minimal object creation in hot paths
|
|
8
|
+
* - No debug logging in production
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
11
|
const { proto, decodeAccountPnL, decodeInstrumentPnL } = require('./protobuf');
|
|
7
12
|
const { RES, STREAM } = require('./constants');
|
|
8
13
|
const { sanitizeQuantity } = require('./protobuf-utils');
|
|
9
14
|
|
|
10
|
-
// Debug
|
|
11
|
-
// Use session logs instead for debugging
|
|
15
|
+
// HFT: Debug completely disabled - no function call overhead
|
|
12
16
|
const DEBUG = false;
|
|
13
|
-
const debug = (...args) => {};
|
|
17
|
+
const debug = DEBUG ? (...args) => {} : () => {};
|
|
18
|
+
|
|
19
|
+
// HFT: Pre-allocated objects for hot path handlers
|
|
20
|
+
const _pnlDataTemplate = {
|
|
21
|
+
accountBalance: 0,
|
|
22
|
+
cashOnHand: 0,
|
|
23
|
+
marginBalance: 0,
|
|
24
|
+
openPositionPnl: 0,
|
|
25
|
+
closedPositionPnl: 0,
|
|
26
|
+
dayPnl: 0,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const _positionTemplate = {
|
|
30
|
+
accountId: '',
|
|
31
|
+
symbol: '',
|
|
32
|
+
exchange: 'CME',
|
|
33
|
+
quantity: 0,
|
|
34
|
+
averagePrice: 0,
|
|
35
|
+
openPnl: 0,
|
|
36
|
+
closedPnl: 0,
|
|
37
|
+
dayPnl: 0,
|
|
38
|
+
isSnapshot: false,
|
|
39
|
+
};
|
|
14
40
|
|
|
15
41
|
/**
|
|
16
42
|
* Create ORDER_PLANT message handler
|
|
@@ -219,71 +245,84 @@ const handleNewOrderResponse = (service, data) => {
|
|
|
219
245
|
|
|
220
246
|
/**
|
|
221
247
|
* Handle account PnL update
|
|
248
|
+
* HFT: Optimized for minimal allocations
|
|
222
249
|
*/
|
|
223
250
|
const handleAccountPnLUpdate = (service, data) => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
251
|
+
const pnl = decodeAccountPnL(data);
|
|
252
|
+
|
|
253
|
+
if (pnl.accountId) {
|
|
254
|
+
// HFT: Reuse cached object from Map or create once
|
|
255
|
+
let pnlData = service.accountPnL.get(pnl.accountId);
|
|
256
|
+
if (!pnlData) {
|
|
257
|
+
pnlData = {
|
|
258
|
+
accountBalance: 0,
|
|
259
|
+
cashOnHand: 0,
|
|
260
|
+
marginBalance: 0,
|
|
261
|
+
openPositionPnl: 0,
|
|
262
|
+
closedPositionPnl: 0,
|
|
263
|
+
dayPnl: 0,
|
|
236
264
|
};
|
|
237
|
-
debug('Storing PNL for account:', pnl.accountId, pnlData);
|
|
238
265
|
service.accountPnL.set(pnl.accountId, pnlData);
|
|
239
|
-
service.emit('pnlUpdate', pnl);
|
|
240
|
-
} else {
|
|
241
|
-
debug('No accountId in PNL response');
|
|
242
266
|
}
|
|
243
|
-
|
|
244
|
-
|
|
267
|
+
|
|
268
|
+
// HFT: Mutate existing object instead of creating new
|
|
269
|
+
pnlData.accountBalance = parseFloat(pnl.accountBalance || 0);
|
|
270
|
+
pnlData.cashOnHand = parseFloat(pnl.cashOnHand || 0);
|
|
271
|
+
pnlData.marginBalance = parseFloat(pnl.marginBalance || 0);
|
|
272
|
+
pnlData.openPositionPnl = parseFloat(pnl.openPositionPnl || 0);
|
|
273
|
+
pnlData.closedPositionPnl = parseFloat(pnl.closedPositionPnl || 0);
|
|
274
|
+
pnlData.dayPnl = parseFloat(pnl.dayPnl || 0);
|
|
275
|
+
|
|
276
|
+
service.emit('pnlUpdate', pnl);
|
|
245
277
|
}
|
|
246
278
|
};
|
|
247
279
|
|
|
248
280
|
/**
|
|
249
281
|
* Handle instrument PnL update (positions)
|
|
282
|
+
* HFT: Optimized for minimal allocations - reuses position objects
|
|
250
283
|
*/
|
|
251
284
|
const handleInstrumentPnLUpdate = (service, data) => {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
285
|
+
const pos = decodeInstrumentPnL(data);
|
|
286
|
+
|
|
287
|
+
if (pos.symbol && pos.accountId) {
|
|
288
|
+
const key = `${pos.accountId}:${pos.symbol}:${pos.exchange || 'CME'}`;
|
|
289
|
+
|
|
290
|
+
// CRITICAL: Sanitize quantity to prevent overflow (18446744073709552000 bug)
|
|
291
|
+
const rawQty = pos.netQuantity || pos.openPositionQuantity || ((pos.buyQty || 0) - (pos.sellQty || 0));
|
|
292
|
+
const netQty = sanitizeQuantity(rawQty);
|
|
293
|
+
|
|
294
|
+
if (netQty !== 0) {
|
|
295
|
+
// HFT: Reuse existing position object or create once
|
|
296
|
+
let position = service.positions.get(key);
|
|
297
|
+
if (!position) {
|
|
298
|
+
position = {
|
|
299
|
+
accountId: '',
|
|
300
|
+
symbol: '',
|
|
301
|
+
exchange: 'CME',
|
|
302
|
+
quantity: 0,
|
|
303
|
+
averagePrice: 0,
|
|
304
|
+
openPnl: 0,
|
|
305
|
+
closedPnl: 0,
|
|
306
|
+
dayPnl: 0,
|
|
307
|
+
isSnapshot: false,
|
|
308
|
+
};
|
|
309
|
+
service.positions.set(key, position);
|
|
275
310
|
}
|
|
276
311
|
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
312
|
+
// HFT: Mutate existing object
|
|
313
|
+
position.accountId = pos.accountId;
|
|
314
|
+
position.symbol = pos.symbol;
|
|
315
|
+
position.exchange = pos.exchange || 'CME';
|
|
316
|
+
position.quantity = netQty;
|
|
317
|
+
position.averagePrice = pos.avgOpenFillPrice || 0;
|
|
318
|
+
position.openPnl = parseFloat(pos.openPositionPnl || pos.dayOpenPnl || 0);
|
|
319
|
+
position.closedPnl = parseFloat(pos.closedPositionPnl || pos.dayClosedPnl || 0);
|
|
320
|
+
position.dayPnl = parseFloat(pos.dayPnl || 0);
|
|
321
|
+
position.isSnapshot = pos.isSnapshot || false;
|
|
322
|
+
|
|
323
|
+
service.emit('positionUpdate', position);
|
|
324
|
+
} else {
|
|
325
|
+
service.positions.delete(key);
|
|
287
326
|
}
|
|
288
327
|
}
|
|
289
328
|
};
|
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
* Rithmic Orders Module
|
|
3
3
|
* Order placement, cancellation, and history
|
|
4
4
|
*
|
|
5
|
+
* HFT-GRADE OPTIMIZATIONS:
|
|
6
|
+
* - Pre-allocated order request template
|
|
7
|
+
* - Cached timestamp for order tags
|
|
8
|
+
* - Zero console.log in production path
|
|
9
|
+
*
|
|
5
10
|
* @module services/rithmic/orders
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
13
|
const { REQ } = require('./constants');
|
|
9
14
|
const { sanitizeQuantity, MAX_SAFE_QUANTITY } = require('./protobuf-utils');
|
|
10
15
|
|
|
11
|
-
// Debug mode -
|
|
16
|
+
// HFT: Debug mode completely disabled - no conditional checks in hot path
|
|
12
17
|
const DEBUG = false;
|
|
13
18
|
|
|
14
19
|
// Order status constants
|
|
@@ -64,37 +69,44 @@ function validateOrderData(orderData) {
|
|
|
64
69
|
return { valid: true };
|
|
65
70
|
}
|
|
66
71
|
|
|
72
|
+
// HFT: Pre-allocated order request template to avoid object creation in hot path
|
|
73
|
+
const ORDER_REQUEST_TEMPLATE = {
|
|
74
|
+
templateId: REQ.NEW_ORDER,
|
|
75
|
+
userMsg: [''],
|
|
76
|
+
fcmId: '',
|
|
77
|
+
ibId: '',
|
|
78
|
+
accountId: '',
|
|
79
|
+
symbol: '',
|
|
80
|
+
exchange: 'CME',
|
|
81
|
+
quantity: 0,
|
|
82
|
+
transactionType: 1,
|
|
83
|
+
duration: 1,
|
|
84
|
+
priceType: 1,
|
|
85
|
+
price: 0,
|
|
86
|
+
tradeRoute: null,
|
|
87
|
+
manualOrAuto: 2,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// HFT: Monotonic counter for order tags (faster than Date.now())
|
|
91
|
+
let orderTagCounter = Date.now();
|
|
92
|
+
|
|
67
93
|
/**
|
|
68
94
|
* Place order via ORDER_PLANT and wait for confirmation
|
|
95
|
+
* HFT: Optimized for minimal latency
|
|
69
96
|
* @param {RithmicService} service - The Rithmic service instance
|
|
70
97
|
* @param {Object} orderData - Order parameters
|
|
71
98
|
* @returns {Promise<{success: boolean, orderId?: string, error?: string}>}
|
|
72
99
|
*/
|
|
73
100
|
const placeOrder = async (service, orderData) => {
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
hasLoginInfo: !!service.loginInfo
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Connection validation
|
|
88
|
-
if (!service.orderConn) {
|
|
89
|
-
return { success: false, error: 'ORDER_PLANT not connected' };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (!service.loginInfo) {
|
|
93
|
-
return { success: false, error: 'Not logged in - missing loginInfo' };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (connState !== 'LOGGED_IN') {
|
|
97
|
-
return { success: false, error: `ORDER_PLANT not logged in (state: ${connState})` };
|
|
101
|
+
// HFT: Fast connection validation (no intermediate variables)
|
|
102
|
+
if (!service.orderConn || !service.loginInfo ||
|
|
103
|
+
service.orderConn.connectionState !== 'LOGGED_IN') {
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
error: !service.orderConn ? 'ORDER_PLANT not connected' :
|
|
107
|
+
!service.loginInfo ? 'Not logged in' :
|
|
108
|
+
`ORDER_PLANT not logged in (state: ${service.orderConn.connectionState})`
|
|
109
|
+
};
|
|
98
110
|
}
|
|
99
111
|
|
|
100
112
|
// Validate order data
|
|
@@ -103,8 +115,8 @@ const placeOrder = async (service, orderData) => {
|
|
|
103
115
|
return { success: false, error: validation.error };
|
|
104
116
|
}
|
|
105
117
|
|
|
106
|
-
//
|
|
107
|
-
const orderTag = `HQX-${
|
|
118
|
+
// HFT: Use monotonic counter for order tag (faster than Date.now())
|
|
119
|
+
const orderTag = `HQX-${++orderTagCounter}`;
|
|
108
120
|
|
|
109
121
|
return new Promise((resolve) => {
|
|
110
122
|
const timeout = setTimeout(() => {
|
|
@@ -113,8 +125,7 @@ const placeOrder = async (service, orderData) => {
|
|
|
113
125
|
}, ORDER_TIMEOUTS.PLACE);
|
|
114
126
|
|
|
115
127
|
const onNotification = (order) => {
|
|
116
|
-
//
|
|
117
|
-
// This prevents race conditions when multiple orders for same symbol
|
|
128
|
+
// HFT: Fast match by orderTag first (most specific)
|
|
118
129
|
const orderMatches = (order.userMsg && order.userMsg.includes(orderTag)) ||
|
|
119
130
|
order.symbol === orderData.symbol;
|
|
120
131
|
|
|
@@ -122,89 +133,56 @@ const placeOrder = async (service, orderData) => {
|
|
|
122
133
|
clearTimeout(timeout);
|
|
123
134
|
service.removeListener('orderNotification', onNotification);
|
|
124
135
|
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
136
|
+
// HFT: Combined status check (2=Working, 3=Filled, 15=Complete)
|
|
137
|
+
const status = order.status;
|
|
138
|
+
if (status === 2 || status === 3 || order.notifyType === 15) {
|
|
128
139
|
resolve({
|
|
129
140
|
success: true,
|
|
130
141
|
orderId: order.basketId,
|
|
131
|
-
status
|
|
142
|
+
status,
|
|
132
143
|
fillPrice: order.avgFillPrice || orderData.price,
|
|
133
144
|
filledQty: order.totalFillSize || orderData.size,
|
|
134
|
-
orderTag
|
|
145
|
+
orderTag,
|
|
135
146
|
});
|
|
136
|
-
} else if (
|
|
137
|
-
// Status 5 = Rejected, 6 = Cancelled
|
|
147
|
+
} else if (status === 5 || status === 6) {
|
|
138
148
|
resolve({
|
|
139
149
|
success: false,
|
|
140
|
-
error: `Order rejected: status ${
|
|
150
|
+
error: `Order rejected: status ${status}`,
|
|
141
151
|
orderId: order.basketId,
|
|
142
|
-
orderTag
|
|
152
|
+
orderTag,
|
|
143
153
|
});
|
|
144
154
|
}
|
|
145
|
-
// Keep listening for other statuses
|
|
146
155
|
}
|
|
147
156
|
};
|
|
148
157
|
|
|
149
158
|
service.on('orderNotification', onNotification);
|
|
150
159
|
|
|
151
|
-
try
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
} else {
|
|
162
|
-
// Fallback: use first available route
|
|
163
|
-
const firstRoute = service.tradeRoutes.values().next().value;
|
|
164
|
-
if (firstRoute) {
|
|
165
|
-
tradeRoute = firstRoute.tradeRoute;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (DEBUG) {
|
|
171
|
-
console.log('[ORDER] Trade route for', exchange, ':', tradeRoute);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (!tradeRoute) {
|
|
175
|
-
// No trade route available - order will likely fail
|
|
176
|
-
if (DEBUG) {
|
|
177
|
-
console.log('[ORDER] WARNING: No trade route available, order may be rejected');
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const orderRequest = {
|
|
182
|
-
templateId: REQ.NEW_ORDER,
|
|
183
|
-
userMsg: [orderTag],
|
|
184
|
-
fcmId: service.loginInfo.fcmId,
|
|
185
|
-
ibId: service.loginInfo.ibId,
|
|
186
|
-
accountId: orderData.accountId,
|
|
187
|
-
symbol: orderData.symbol,
|
|
188
|
-
exchange: exchange,
|
|
189
|
-
quantity: sanitizeQuantity(orderData.size),
|
|
190
|
-
transactionType: orderData.side === 0 ? 1 : 2, // 1=Buy, 2=Sell
|
|
191
|
-
duration: 1, // DAY
|
|
192
|
-
priceType: orderData.type === 2 ? 2 : 1, // 2=Market, 1=Limit
|
|
193
|
-
price: orderData.price || 0,
|
|
194
|
-
tradeRoute: tradeRoute, // REQUIRED by Rithmic
|
|
195
|
-
manualOrAuto: 2, // AUTO
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
if (DEBUG) {
|
|
199
|
-
console.log('[ORDER] Sending RequestNewOrder:', JSON.stringify(orderRequest));
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
service.orderConn.send('RequestNewOrder', orderRequest);
|
|
203
|
-
} catch (error) {
|
|
204
|
-
clearTimeout(timeout);
|
|
205
|
-
service.removeListener('orderNotification', onNotification);
|
|
206
|
-
resolve({ success: false, error: error.message });
|
|
160
|
+
// HFT: Inline order request construction (no try-catch in hot path)
|
|
161
|
+
const exchange = orderData.exchange || 'CME';
|
|
162
|
+
|
|
163
|
+
// Get trade route from cache
|
|
164
|
+
let tradeRoute = null;
|
|
165
|
+
const routes = service.tradeRoutes;
|
|
166
|
+
if (routes && routes.size > 0) {
|
|
167
|
+
const routeInfo = routes.get(exchange);
|
|
168
|
+
tradeRoute = routeInfo ? routeInfo.tradeRoute :
|
|
169
|
+
routes.values().next().value?.tradeRoute || null;
|
|
207
170
|
}
|
|
171
|
+
|
|
172
|
+
// HFT: Reuse template and mutate (faster than object spread)
|
|
173
|
+
ORDER_REQUEST_TEMPLATE.userMsg[0] = orderTag;
|
|
174
|
+
ORDER_REQUEST_TEMPLATE.fcmId = service.loginInfo.fcmId;
|
|
175
|
+
ORDER_REQUEST_TEMPLATE.ibId = service.loginInfo.ibId;
|
|
176
|
+
ORDER_REQUEST_TEMPLATE.accountId = orderData.accountId;
|
|
177
|
+
ORDER_REQUEST_TEMPLATE.symbol = orderData.symbol;
|
|
178
|
+
ORDER_REQUEST_TEMPLATE.exchange = exchange;
|
|
179
|
+
ORDER_REQUEST_TEMPLATE.quantity = sanitizeQuantity(orderData.size);
|
|
180
|
+
ORDER_REQUEST_TEMPLATE.transactionType = orderData.side === 0 ? 1 : 2;
|
|
181
|
+
ORDER_REQUEST_TEMPLATE.priceType = orderData.type === 2 ? 2 : 1;
|
|
182
|
+
ORDER_REQUEST_TEMPLATE.price = orderData.price || 0;
|
|
183
|
+
ORDER_REQUEST_TEMPLATE.tradeRoute = tradeRoute;
|
|
184
|
+
|
|
185
|
+
service.orderConn.send('RequestNewOrder', ORDER_REQUEST_TEMPLATE);
|
|
208
186
|
});
|
|
209
187
|
};
|
|
210
188
|
|