hedgequantx 2.6.162 → 2.7.0
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/README.md +15 -88
- package/bin/cli.js +0 -11
- package/dist/lib/api.jsc +0 -0
- package/dist/lib/api2.jsc +0 -0
- package/dist/lib/core.jsc +0 -0
- package/dist/lib/core2.jsc +0 -0
- package/dist/lib/data.js +1 -1
- package/dist/lib/data.jsc +0 -0
- package/dist/lib/data2.jsc +0 -0
- package/dist/lib/decoder.jsc +0 -0
- package/dist/lib/m/mod1.jsc +0 -0
- package/dist/lib/m/mod2.jsc +0 -0
- package/dist/lib/n/r1.jsc +0 -0
- package/dist/lib/n/r2.jsc +0 -0
- package/dist/lib/n/r3.jsc +0 -0
- package/dist/lib/n/r4.jsc +0 -0
- package/dist/lib/n/r5.jsc +0 -0
- package/dist/lib/n/r6.jsc +0 -0
- package/dist/lib/n/r7.jsc +0 -0
- package/dist/lib/o/util1.jsc +0 -0
- package/dist/lib/o/util2.jsc +0 -0
- package/package.json +6 -3
- package/src/app.js +40 -162
- package/src/config/constants.js +31 -33
- package/src/config/propfirms.js +13 -217
- package/src/config/settings.js +0 -43
- package/src/lib/api.js +198 -0
- package/src/lib/api2.js +353 -0
- package/src/lib/core.js +539 -0
- package/src/lib/core2.js +341 -0
- package/src/lib/data.js +555 -0
- package/src/lib/data2.js +492 -0
- package/src/lib/decoder.js +599 -0
- package/src/lib/m/s1.js +804 -0
- package/src/lib/m/s2.js +34 -0
- package/src/lib/n/r1.js +454 -0
- package/src/lib/n/r2.js +514 -0
- package/src/lib/n/r3.js +631 -0
- package/src/lib/n/r4.js +401 -0
- package/src/lib/n/r5.js +335 -0
- package/src/lib/n/r6.js +425 -0
- package/src/lib/n/r7.js +530 -0
- package/src/lib/o/l1.js +44 -0
- package/src/lib/o/l2.js +427 -0
- package/src/lib/python-bridge.js +206 -0
- package/src/menus/connect.js +14 -176
- package/src/menus/dashboard.js +65 -110
- package/src/pages/accounts.js +18 -18
- package/src/pages/algo/copy-trading.js +210 -240
- package/src/pages/algo/index.js +41 -104
- package/src/pages/algo/one-account.js +386 -33
- package/src/pages/algo/ui.js +312 -151
- package/src/pages/orders.js +3 -3
- package/src/pages/positions.js +3 -3
- package/src/pages/stats/chart.js +74 -0
- package/src/pages/stats/display.js +228 -0
- package/src/pages/stats/index.js +236 -0
- package/src/pages/stats/metrics.js +213 -0
- package/src/pages/user.js +6 -6
- package/src/services/hqx-server/constants.js +55 -0
- package/src/services/hqx-server/index.js +401 -0
- package/src/services/hqx-server/latency.js +81 -0
- package/src/services/index.js +12 -3
- package/src/services/rithmic/accounts.js +7 -32
- package/src/services/rithmic/connection.js +1 -204
- package/src/services/rithmic/contracts.js +235 -0
- package/src/services/rithmic/handlers.js +21 -196
- package/src/services/rithmic/index.js +60 -291
- package/src/services/rithmic/market.js +31 -0
- package/src/services/rithmic/orders.js +5 -361
- package/src/services/rithmic/protobuf.js +5 -195
- package/src/services/session.js +22 -173
- package/src/ui/box.js +10 -18
- package/src/ui/index.js +1 -3
- package/src/ui/menu.js +1 -1
- package/src/utils/prompts.js +2 -2
- package/dist/lib/m/s1.js +0 -1
- package/src/menus/ai-agent-connect.js +0 -181
- package/src/menus/ai-agent-models.js +0 -219
- package/src/menus/ai-agent-oauth.js +0 -292
- package/src/menus/ai-agent-ui.js +0 -141
- package/src/menus/ai-agent.js +0 -484
- package/src/pages/algo/algo-config.js +0 -195
- package/src/pages/algo/algo-multi.js +0 -801
- package/src/pages/algo/algo-utils.js +0 -58
- package/src/pages/algo/copy-engine.js +0 -449
- package/src/pages/algo/custom-strategy.js +0 -459
- package/src/pages/algo/logger.js +0 -245
- package/src/pages/algo/smart-logs-data.js +0 -218
- package/src/pages/algo/smart-logs.js +0 -387
- package/src/pages/algo/ui-constants.js +0 -144
- package/src/pages/algo/ui-summary.js +0 -184
- package/src/pages/stats-calculations.js +0 -191
- package/src/pages/stats-ui.js +0 -381
- package/src/pages/stats.js +0 -339
- package/src/services/ai/client-analysis.js +0 -194
- package/src/services/ai/client-models.js +0 -333
- package/src/services/ai/client.js +0 -343
- package/src/services/ai/index.js +0 -384
- package/src/services/ai/oauth-anthropic.js +0 -265
- package/src/services/ai/oauth-gemini.js +0 -223
- package/src/services/ai/oauth-iflow.js +0 -269
- package/src/services/ai/oauth-openai.js +0 -233
- package/src/services/ai/oauth-qwen.js +0 -279
- package/src/services/ai/providers/index.js +0 -526
- package/src/services/ai/proxy-install.js +0 -249
- package/src/services/ai/proxy-manager.js +0 -494
- package/src/services/ai/proxy-remote.js +0 -161
- package/src/services/ai/strategy-supervisor.js +0 -1312
- package/src/services/ai/supervisor-data.js +0 -195
- package/src/services/ai/supervisor-optimize.js +0 -215
- package/src/services/ai/supervisor-sync.js +0 -178
- package/src/services/ai/supervisor-utils.js +0 -158
- package/src/services/ai/supervisor.js +0 -484
- package/src/services/ai/validation.js +0 -250
- package/src/services/hqx-server-events.js +0 -110
- package/src/services/hqx-server-handlers.js +0 -217
- package/src/services/hqx-server-latency.js +0 -136
- package/src/services/hqx-server.js +0 -403
- package/src/services/position-constants.js +0 -28
- package/src/services/position-manager.js +0 -528
- package/src/services/position-momentum.js +0 -206
- package/src/services/projectx/accounts.js +0 -142
- package/src/services/projectx/index.js +0 -443
- package/src/services/projectx/market.js +0 -172
- package/src/services/projectx/stats.js +0 -110
- package/src/services/projectx/trading.js +0 -180
- package/src/services/rithmic/latency-tracker.js +0 -182
- package/src/services/rithmic/market-data.js +0 -549
- package/src/services/rithmic/specs.js +0 -146
- package/src/services/rithmic/trade-history.js +0 -254
- package/src/services/session-history.js +0 -475
- package/src/services/strategy/hft-tick.js +0 -507
- package/src/services/strategy/recovery-math.js +0 -402
- package/src/services/tradovate/constants.js +0 -109
- package/src/services/tradovate/index.js +0 -505
- package/src/services/tradovate/market.js +0 -47
- package/src/services/tradovate/websocket.js +0 -97
|
@@ -1,263 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Rithmic Orders Module
|
|
3
3
|
* Order placement, cancellation, and history
|
|
4
|
-
*
|
|
5
|
-
* FAST SCALPING: fastEntry() and fastExit() for ultra-low latency execution
|
|
6
|
-
* Target: < 5ms local processing (network latency separate)
|
|
7
|
-
*
|
|
8
|
-
* OPTIMIZATIONS:
|
|
9
|
-
* - Pre-allocated order template objects
|
|
10
|
-
* - Fast orderTag generation (no Date.now in hot path)
|
|
11
|
-
* - Direct proto encoding with cached types
|
|
12
|
-
* - Minimal object creation
|
|
13
4
|
*/
|
|
14
5
|
|
|
15
6
|
const { REQ } = require('./constants');
|
|
16
|
-
const { proto } = require('./protobuf');
|
|
17
|
-
const { LatencyTracker } = require('./handlers');
|
|
18
|
-
const { performance } = require('perf_hooks');
|
|
19
|
-
const { logger } = require('../../utils/logger');
|
|
20
|
-
|
|
21
|
-
const log = logger.scope('RithmicOrders');
|
|
22
|
-
|
|
23
|
-
// Debug mode - use no-op function when disabled for zero overhead
|
|
24
|
-
const DEBUG = process.env.HQX_DEBUG === '1';
|
|
25
|
-
const debug = DEBUG ? (...args) => console.log('[Rithmic:Orders]', ...args) : () => {};
|
|
26
|
-
|
|
27
|
-
// ==================== FAST ORDER TAG ====================
|
|
28
|
-
// Pre-generate prefix once at module load (not per-order)
|
|
29
|
-
const ORDER_TAG_PREFIX = `HQX${process.pid}-`;
|
|
30
|
-
let orderIdCounter = 0;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Ultra-fast order tag generation
|
|
34
|
-
* Avoids Date.now() and string interpolation in hot path
|
|
35
|
-
* @returns {string}
|
|
36
|
-
*/
|
|
37
|
-
const generateOrderTag = () => ORDER_TAG_PREFIX + (++orderIdCounter);
|
|
38
|
-
|
|
39
|
-
// ==================== PRE-ALLOCATED ORDER TEMPLATES ====================
|
|
40
|
-
// Reusable order object to minimize GC pressure
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Order object pool for zero-allocation hot path
|
|
44
|
-
*/
|
|
45
|
-
const OrderPool = {
|
|
46
|
-
// Pre-allocated order template
|
|
47
|
-
_template: {
|
|
48
|
-
templateId: REQ.NEW_ORDER,
|
|
49
|
-
userMsg: [''],
|
|
50
|
-
userTag: '', // Our order tag - returned in RithmicOrderNotification
|
|
51
|
-
fcmId: '',
|
|
52
|
-
ibId: '',
|
|
53
|
-
accountId: '',
|
|
54
|
-
symbol: '',
|
|
55
|
-
exchange: 'CME',
|
|
56
|
-
quantity: 0,
|
|
57
|
-
transactionType: 1,
|
|
58
|
-
duration: 1,
|
|
59
|
-
priceType: 2, // priceType 2 = MARKET order
|
|
60
|
-
manualOrAuto: 2,
|
|
61
|
-
tradeRoute: '', // Required by Rithmic API - fetched from RequestTradeRoutes
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Get order object with values filled in
|
|
66
|
-
* Reuses same object to avoid allocation
|
|
67
|
-
* @param {string} orderTag - Unique order tag
|
|
68
|
-
* @param {Object} loginInfo - { fcmId, ibId }
|
|
69
|
-
* @param {Object} orderData - { accountId, symbol, exchange, size, side, tradeRoute }
|
|
70
|
-
*/
|
|
71
|
-
fill(orderTag, loginInfo, orderData) {
|
|
72
|
-
const o = this._template;
|
|
73
|
-
o.userMsg[0] = orderTag;
|
|
74
|
-
o.userTag = orderTag; // Set userTag for notification tracking
|
|
75
|
-
o.fcmId = loginInfo.fcmId;
|
|
76
|
-
o.ibId = loginInfo.ibId;
|
|
77
|
-
o.accountId = orderData.accountId;
|
|
78
|
-
o.symbol = orderData.symbol;
|
|
79
|
-
o.exchange = orderData.exchange || 'CME';
|
|
80
|
-
o.quantity = orderData.size;
|
|
81
|
-
o.transactionType = orderData.side === 0 ? 1 : 2;
|
|
82
|
-
o.tradeRoute = orderData.tradeRoute || ''; // From API via service.getTradeRoute()
|
|
83
|
-
return o;
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Ultra-fast market order entry - HOT PATH
|
|
89
|
-
* NO SL/TP, NO await confirmation, fire-and-forget
|
|
90
|
-
* Target latency: < 5ms local processing
|
|
91
|
-
*
|
|
92
|
-
* OPTIMIZATIONS:
|
|
93
|
-
* - Reuses pre-allocated order object
|
|
94
|
-
* - Fast orderTag (no Date.now)
|
|
95
|
-
* - Uses fastEncode for cached protobuf type
|
|
96
|
-
* - Minimal branching
|
|
97
|
-
*
|
|
98
|
-
* @param {RithmicService} service - The Rithmic service instance
|
|
99
|
-
* @param {Object} orderData - { accountId, symbol, exchange, size, side }
|
|
100
|
-
* @returns {{ success: boolean, orderTag: string, entryTime: number, latencyMs: number }}
|
|
101
|
-
*/
|
|
102
|
-
const fastEntry = (service, orderData) => {
|
|
103
|
-
const startTime = performance.now();
|
|
104
|
-
const orderTag = generateOrderTag();
|
|
105
|
-
const entryTime = Date.now();
|
|
106
|
-
|
|
107
|
-
// Fast connection check
|
|
108
|
-
if (!service.orderConn?.isConnected || !service.loginInfo) {
|
|
109
|
-
return {
|
|
110
|
-
success: false,
|
|
111
|
-
error: 'Not connected',
|
|
112
|
-
orderTag,
|
|
113
|
-
entryTime,
|
|
114
|
-
latencyMs: performance.now() - startTime,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
// FIXED: Use account-specific fcmId/ibId if available (some prop firms have different IDs per account)
|
|
120
|
-
const account = service.accounts?.find(a =>
|
|
121
|
-
a.accountId === orderData.accountId || a.rithmicAccountId === orderData.accountId
|
|
122
|
-
);
|
|
123
|
-
const effectiveLoginInfo = {
|
|
124
|
-
fcmId: account?.fcmId || service.loginInfo.fcmId,
|
|
125
|
-
ibId: account?.ibId || service.loginInfo.ibId,
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// Get trade route from API (required by Rithmic)
|
|
129
|
-
const exchange = orderData.exchange || 'CME';
|
|
130
|
-
const tradeRoute = service.getTradeRoute?.(exchange);
|
|
131
|
-
if (!tradeRoute) {
|
|
132
|
-
return {
|
|
133
|
-
success: false,
|
|
134
|
-
error: `No trade route for exchange ${exchange}`,
|
|
135
|
-
orderTag,
|
|
136
|
-
entryTime,
|
|
137
|
-
latencyMs: performance.now() - startTime,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// OPTIMIZED: Use pre-allocated order object
|
|
142
|
-
const orderWithRoute = { ...orderData, tradeRoute };
|
|
143
|
-
const order = OrderPool.fill(orderTag, effectiveLoginInfo, orderWithRoute);
|
|
144
|
-
|
|
145
|
-
// Debug only - UI handles display via events
|
|
146
|
-
debug('ORDER Sending:', orderTag, orderData.side === 0 ? 'BUY' : 'SELL', orderData.size, 'x', orderData.symbol, 'acct:', orderData.accountId, 'route:', tradeRoute);
|
|
147
|
-
|
|
148
|
-
// OPTIMIZED: Use fastEncode with cached type
|
|
149
|
-
const buffer = proto.fastEncode('RequestNewOrder', order);
|
|
150
|
-
|
|
151
|
-
// ULTRA-OPTIMIZED: Try direct socket write first, fallback to fastSend
|
|
152
|
-
const sent = service.orderConn.ultraSend
|
|
153
|
-
? service.orderConn.ultraSend(buffer)
|
|
154
|
-
: (service.orderConn.fastSend(buffer), true);
|
|
155
|
-
|
|
156
|
-
if (!sent) {
|
|
157
|
-
service.orderConn.fastSend(buffer);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
debug('ORDER Sent to Rithmic:', orderTag, 'buffer:', buffer.length, 'bytes');
|
|
161
|
-
|
|
162
|
-
// Track for round-trip latency measurement
|
|
163
|
-
LatencyTracker.recordEntry(orderTag, entryTime);
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
success: true,
|
|
167
|
-
orderTag,
|
|
168
|
-
entryTime,
|
|
169
|
-
latencyMs: performance.now() - startTime,
|
|
170
|
-
};
|
|
171
|
-
} catch (error) {
|
|
172
|
-
return {
|
|
173
|
-
success: false,
|
|
174
|
-
error: error.message,
|
|
175
|
-
orderTag,
|
|
176
|
-
entryTime,
|
|
177
|
-
latencyMs: performance.now() - startTime,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Ultra-fast market exit - for position closing
|
|
184
|
-
* Fire-and-forget like fastEntry
|
|
185
|
-
* Same optimizations as fastEntry
|
|
186
|
-
*
|
|
187
|
-
* @param {RithmicService} service - The Rithmic service instance
|
|
188
|
-
* @param {Object} orderData - { accountId, symbol, exchange, size, side }
|
|
189
|
-
* @returns {{ success: boolean, orderTag: string, exitTime: number, latencyMs: number }}
|
|
190
|
-
*/
|
|
191
|
-
const fastExit = (service, orderData) => {
|
|
192
|
-
const startTime = performance.now();
|
|
193
|
-
const orderTag = generateOrderTag();
|
|
194
|
-
const exitTime = Date.now();
|
|
195
|
-
|
|
196
|
-
if (!service.orderConn?.isConnected || !service.loginInfo) {
|
|
197
|
-
return {
|
|
198
|
-
success: false,
|
|
199
|
-
error: 'Not connected',
|
|
200
|
-
orderTag,
|
|
201
|
-
exitTime,
|
|
202
|
-
latencyMs: performance.now() - startTime,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
// FIXED: Use account-specific fcmId/ibId if available
|
|
208
|
-
const account = service.accounts?.find(a =>
|
|
209
|
-
a.accountId === orderData.accountId || a.rithmicAccountId === orderData.accountId
|
|
210
|
-
);
|
|
211
|
-
const effectiveLoginInfo = {
|
|
212
|
-
fcmId: account?.fcmId || service.loginInfo.fcmId,
|
|
213
|
-
ibId: account?.ibId || service.loginInfo.ibId,
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
// Get trade route from API (required by Rithmic)
|
|
217
|
-
const exchange = orderData.exchange || 'CME';
|
|
218
|
-
const tradeRoute = service.getTradeRoute?.(exchange);
|
|
219
|
-
if (!tradeRoute) {
|
|
220
|
-
return {
|
|
221
|
-
success: false,
|
|
222
|
-
error: `No trade route for exchange ${exchange}`,
|
|
223
|
-
orderTag,
|
|
224
|
-
exitTime,
|
|
225
|
-
latencyMs: performance.now() - startTime,
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// OPTIMIZED: Use pre-allocated order object
|
|
230
|
-
const orderWithRoute = { ...orderData, tradeRoute };
|
|
231
|
-
const order = OrderPool.fill(orderTag, effectiveLoginInfo, orderWithRoute);
|
|
232
|
-
|
|
233
|
-
// OPTIMIZED: Use fastEncode with cached type
|
|
234
|
-
const buffer = proto.fastEncode('RequestNewOrder', order);
|
|
235
|
-
|
|
236
|
-
// ULTRA-OPTIMIZED: Try direct socket write first, fallback to fastSend
|
|
237
|
-
const sent = service.orderConn.ultraSend
|
|
238
|
-
? service.orderConn.ultraSend(buffer)
|
|
239
|
-
: (service.orderConn.fastSend(buffer), true);
|
|
240
|
-
|
|
241
|
-
if (!sent) {
|
|
242
|
-
service.orderConn.fastSend(buffer);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
success: true,
|
|
247
|
-
orderTag,
|
|
248
|
-
exitTime,
|
|
249
|
-
latencyMs: performance.now() - startTime,
|
|
250
|
-
};
|
|
251
|
-
} catch (error) {
|
|
252
|
-
return {
|
|
253
|
-
success: false,
|
|
254
|
-
error: error.message,
|
|
255
|
-
orderTag,
|
|
256
|
-
exitTime,
|
|
257
|
-
latencyMs: performance.now() - startTime,
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
7
|
|
|
262
8
|
/**
|
|
263
9
|
* Place order via ORDER_PLANT
|
|
@@ -270,32 +16,19 @@ const placeOrder = async (service, orderData) => {
|
|
|
270
16
|
}
|
|
271
17
|
|
|
272
18
|
try {
|
|
273
|
-
// FIXED: Use account-specific fcmId/ibId if available
|
|
274
|
-
const account = service.accounts?.find(a =>
|
|
275
|
-
a.accountId === orderData.accountId || a.rithmicAccountId === orderData.accountId
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
// Get trade route from API (required by Rithmic)
|
|
279
|
-
const exchange = orderData.exchange || 'CME';
|
|
280
|
-
const tradeRoute = service.getTradeRoute?.(exchange);
|
|
281
|
-
if (!tradeRoute) {
|
|
282
|
-
return { success: false, error: `No trade route for exchange ${exchange}` };
|
|
283
|
-
}
|
|
284
|
-
|
|
285
19
|
service.orderConn.send('RequestNewOrder', {
|
|
286
20
|
templateId: REQ.NEW_ORDER,
|
|
287
21
|
userMsg: ['HQX'],
|
|
288
|
-
fcmId:
|
|
289
|
-
ibId:
|
|
22
|
+
fcmId: service.loginInfo.fcmId,
|
|
23
|
+
ibId: service.loginInfo.ibId,
|
|
290
24
|
accountId: orderData.accountId,
|
|
291
25
|
symbol: orderData.symbol,
|
|
292
|
-
exchange: exchange,
|
|
26
|
+
exchange: orderData.exchange || 'CME',
|
|
293
27
|
quantity: orderData.size,
|
|
294
28
|
transactionType: orderData.side === 0 ? 1 : 2, // 1=Buy, 2=Sell
|
|
295
29
|
duration: 1, // DAY
|
|
296
|
-
|
|
30
|
+
orderType: orderData.type === 2 ? 1 : 2, // 1=Market, 2=Limit
|
|
297
31
|
price: orderData.price || 0,
|
|
298
|
-
tradeRoute: tradeRoute, // Required by Rithmic API - fetched from RequestTradeRoutes
|
|
299
32
|
});
|
|
300
33
|
|
|
301
34
|
return { success: true, message: 'Order submitted' };
|
|
@@ -450,99 +183,10 @@ const closePosition = async (service, accountId, symbol) => {
|
|
|
450
183
|
});
|
|
451
184
|
};
|
|
452
185
|
|
|
453
|
-
/**
|
|
454
|
-
* Cancel all open orders for an account
|
|
455
|
-
* @param {RithmicService} service - The Rithmic service instance
|
|
456
|
-
* @param {string} accountId - Account ID
|
|
457
|
-
*/
|
|
458
|
-
const cancelAllOrders = async (service, accountId) => {
|
|
459
|
-
if (!service.orderConn || !service.loginInfo) {
|
|
460
|
-
return { success: false, error: 'Not connected' };
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
try {
|
|
464
|
-
// Use RequestCancelAllOrders template
|
|
465
|
-
service.orderConn.send('RequestCancelAllOrders', {
|
|
466
|
-
templateId: 346, // CANCEL_ALL_ORDERS
|
|
467
|
-
userMsg: ['HQX-STOP'],
|
|
468
|
-
fcmId: service.loginInfo.fcmId,
|
|
469
|
-
ibId: service.loginInfo.ibId,
|
|
470
|
-
accountId: accountId,
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
log.info('Cancel all orders sent', { accountId });
|
|
474
|
-
return { success: true };
|
|
475
|
-
} catch (error) {
|
|
476
|
-
log.error('Cancel all orders failed', { error: error.message });
|
|
477
|
-
return { success: false, error: error.message };
|
|
478
|
-
}
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Flatten all positions for an account (close all positions)
|
|
483
|
-
* @param {RithmicService} service - The Rithmic service instance
|
|
484
|
-
* @param {string} accountId - Account ID
|
|
485
|
-
*/
|
|
486
|
-
const flattenAll = async (service, accountId) => {
|
|
487
|
-
const results = [];
|
|
488
|
-
const positions = Array.from(service.positions.values());
|
|
489
|
-
const accountPositions = positions.filter(p => p.accountId === accountId && p.quantity !== 0);
|
|
490
|
-
|
|
491
|
-
log.info('Flattening all positions', { accountId, count: accountPositions.length });
|
|
492
|
-
|
|
493
|
-
for (const position of accountPositions) {
|
|
494
|
-
try {
|
|
495
|
-
const result = await placeOrder(service, {
|
|
496
|
-
accountId,
|
|
497
|
-
symbol: position.symbol,
|
|
498
|
-
exchange: position.exchange,
|
|
499
|
-
size: Math.abs(position.quantity),
|
|
500
|
-
side: position.quantity > 0 ? 1 : 0, // Sell if long, Buy if short
|
|
501
|
-
type: 2, // Market
|
|
502
|
-
});
|
|
503
|
-
results.push({ symbol: position.symbol, ...result });
|
|
504
|
-
} catch (error) {
|
|
505
|
-
results.push({ symbol: position.symbol, success: false, error: error.message });
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
return { success: true, results };
|
|
510
|
-
};
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* Emergency stop - Cancel all orders and flatten all positions
|
|
514
|
-
* @param {RithmicService} service - The Rithmic service instance
|
|
515
|
-
* @param {string} accountId - Account ID
|
|
516
|
-
*/
|
|
517
|
-
const emergencyStop = async (service, accountId) => {
|
|
518
|
-
log.warn('EMERGENCY STOP initiated', { accountId });
|
|
519
|
-
|
|
520
|
-
// 1. Cancel all orders first
|
|
521
|
-
const cancelResult = await cancelAllOrders(service, accountId);
|
|
522
|
-
|
|
523
|
-
// 2. Wait a moment for cancellations to process
|
|
524
|
-
await new Promise(r => setTimeout(r, 500));
|
|
525
|
-
|
|
526
|
-
// 3. Flatten all positions
|
|
527
|
-
const flattenResult = await flattenAll(service, accountId);
|
|
528
|
-
|
|
529
|
-
return {
|
|
530
|
-
success: cancelResult.success && flattenResult.success,
|
|
531
|
-
cancelled: cancelResult,
|
|
532
|
-
flattened: flattenResult,
|
|
533
|
-
};
|
|
534
|
-
};
|
|
535
|
-
|
|
536
186
|
module.exports = {
|
|
537
187
|
placeOrder,
|
|
538
188
|
cancelOrder,
|
|
539
|
-
cancelAllOrders,
|
|
540
189
|
getOrders,
|
|
541
190
|
getOrderHistory,
|
|
542
|
-
closePosition
|
|
543
|
-
flattenAll,
|
|
544
|
-
emergencyStop,
|
|
545
|
-
// Fast scalping - ultra-low latency
|
|
546
|
-
fastEntry,
|
|
547
|
-
fastExit,
|
|
191
|
+
closePosition
|
|
548
192
|
};
|
|
@@ -7,74 +7,6 @@ const protobuf = require('protobufjs');
|
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const { PROTO_FILES } = require('./constants');
|
|
9
9
|
|
|
10
|
-
// ==================== BUFFER POOL ====================
|
|
11
|
-
// Pre-allocated buffer pool for zero-allocation hot path
|
|
12
|
-
// Avoids GC pressure during high-frequency trading
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* High-performance buffer pool for zero-allocation encoding
|
|
16
|
-
* Uses ring buffer pattern for O(1) acquire/release
|
|
17
|
-
*/
|
|
18
|
-
class BufferPool {
|
|
19
|
-
constructor(poolSize = 16, bufferSize = 512) {
|
|
20
|
-
this._pool = new Array(poolSize);
|
|
21
|
-
this._available = new Array(poolSize);
|
|
22
|
-
this._size = poolSize;
|
|
23
|
-
this._bufferSize = bufferSize;
|
|
24
|
-
this._head = 0;
|
|
25
|
-
this._tail = 0;
|
|
26
|
-
this._count = poolSize;
|
|
27
|
-
|
|
28
|
-
// Pre-allocate all buffers
|
|
29
|
-
for (let i = 0; i < poolSize; i++) {
|
|
30
|
-
this._pool[i] = Buffer.allocUnsafe(bufferSize);
|
|
31
|
-
this._available[i] = i;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Acquire a buffer from the pool
|
|
37
|
-
* @returns {Buffer|null} Buffer or null if pool exhausted
|
|
38
|
-
*/
|
|
39
|
-
acquire() {
|
|
40
|
-
if (this._count === 0) {
|
|
41
|
-
// Pool exhausted - allocate new (fallback)
|
|
42
|
-
return Buffer.allocUnsafe(this._bufferSize);
|
|
43
|
-
}
|
|
44
|
-
const idx = this._available[this._head];
|
|
45
|
-
this._head = (this._head + 1) % this._size;
|
|
46
|
-
this._count--;
|
|
47
|
-
return this._pool[idx];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Release a buffer back to pool
|
|
52
|
-
* Only releases buffers that belong to the pool
|
|
53
|
-
* @param {Buffer} buffer
|
|
54
|
-
*/
|
|
55
|
-
release(buffer) {
|
|
56
|
-
// Find if this buffer is from our pool
|
|
57
|
-
const idx = this._pool.indexOf(buffer);
|
|
58
|
-
if (idx !== -1 && this._count < this._size) {
|
|
59
|
-
this._available[this._tail] = idx;
|
|
60
|
-
this._tail = (this._tail + 1) % this._size;
|
|
61
|
-
this._count++;
|
|
62
|
-
}
|
|
63
|
-
// If not from pool, let GC handle it
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Get pool stats
|
|
68
|
-
*/
|
|
69
|
-
getStats() {
|
|
70
|
-
return {
|
|
71
|
-
size: this._size,
|
|
72
|
-
available: this._count,
|
|
73
|
-
bufferSize: this._bufferSize,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
10
|
// PnL field IDs (Rithmic uses very large field IDs)
|
|
79
11
|
const PNL_FIELDS = {
|
|
80
12
|
TEMPLATE_ID: 154467,
|
|
@@ -344,34 +276,13 @@ function decodeInstrumentPnL(buffer) {
|
|
|
344
276
|
[result.closedPositionPnl, offset] = readLengthDelimited(buffer, offset);
|
|
345
277
|
break;
|
|
346
278
|
case INSTRUMENT_PNL_FIELDS.DAY_PNL:
|
|
347
|
-
|
|
348
|
-
if (wireType === 1) {
|
|
349
|
-
result.dayPnl = buffer.readDoubleLE(offset);
|
|
350
|
-
offset += 8;
|
|
351
|
-
} else {
|
|
352
|
-
// Fallback: try string
|
|
353
|
-
[result.dayPnl, offset] = readLengthDelimited(buffer, offset);
|
|
354
|
-
}
|
|
279
|
+
[result.dayPnl, offset] = readLengthDelimited(buffer, offset);
|
|
355
280
|
break;
|
|
356
281
|
case INSTRUMENT_PNL_FIELDS.DAY_OPEN_PNL:
|
|
357
|
-
|
|
358
|
-
if (wireType === 1) {
|
|
359
|
-
result.dayOpenPnl = buffer.readDoubleLE(offset);
|
|
360
|
-
offset += 8;
|
|
361
|
-
} else {
|
|
362
|
-
// Fallback: try string
|
|
363
|
-
[result.dayOpenPnl, offset] = readLengthDelimited(buffer, offset);
|
|
364
|
-
}
|
|
282
|
+
[result.dayOpenPnl, offset] = readLengthDelimited(buffer, offset);
|
|
365
283
|
break;
|
|
366
284
|
case INSTRUMENT_PNL_FIELDS.DAY_CLOSED_PNL:
|
|
367
|
-
|
|
368
|
-
if (wireType === 1) {
|
|
369
|
-
result.dayClosedPnl = buffer.readDoubleLE(offset);
|
|
370
|
-
offset += 8;
|
|
371
|
-
} else {
|
|
372
|
-
// Fallback: try string
|
|
373
|
-
[result.dayClosedPnl, offset] = readLengthDelimited(buffer, offset);
|
|
374
|
-
}
|
|
285
|
+
[result.dayClosedPnl, offset] = readLengthDelimited(buffer, offset);
|
|
375
286
|
break;
|
|
376
287
|
case INSTRUMENT_PNL_FIELDS.SSBOE:
|
|
377
288
|
[result.ssboe, offset] = readVarint(buffer, offset);
|
|
@@ -392,34 +303,16 @@ function decodeInstrumentPnL(buffer) {
|
|
|
392
303
|
|
|
393
304
|
/**
|
|
394
305
|
* Protobuf Handler class
|
|
395
|
-
* OPTIMIZED: Pre-compile types, cache encoders, buffer pooling
|
|
396
|
-
*
|
|
397
|
-
* ULTRA-LOW LATENCY FEATURES:
|
|
398
|
-
* - Pre-allocated buffer pool (zero allocation in hot path)
|
|
399
|
-
* - Reusable protobuf Writer
|
|
400
|
-
* - Cached compiled message types
|
|
401
|
-
* - Direct buffer encoding without intermediate objects
|
|
402
306
|
*/
|
|
403
307
|
class ProtobufHandler {
|
|
404
308
|
constructor() {
|
|
405
309
|
this.root = null;
|
|
406
310
|
this.loaded = false;
|
|
407
311
|
this.protoPath = path.join(__dirname, 'proto');
|
|
408
|
-
|
|
409
|
-
// OPTIMIZATION: Cache compiled types for hot path
|
|
410
|
-
this._typeCache = new Map();
|
|
411
|
-
this._encoderCache = new Map();
|
|
412
|
-
|
|
413
|
-
// OPTIMIZATION: Pre-allocated buffer pool for zero-allocation encoding
|
|
414
|
-
this._bufferPool = new BufferPool(16, 512); // 16 buffers of 512 bytes each
|
|
415
|
-
|
|
416
|
-
// OPTIMIZATION: Reusable protobuf Writer instance
|
|
417
|
-
this._writer = null;
|
|
418
312
|
}
|
|
419
313
|
|
|
420
314
|
/**
|
|
421
315
|
* Load all proto files
|
|
422
|
-
* Call once at startup, not per-connection
|
|
423
316
|
*/
|
|
424
317
|
async load() {
|
|
425
318
|
if (this.loaded) return;
|
|
@@ -435,109 +328,26 @@ class ProtobufHandler {
|
|
|
435
328
|
}
|
|
436
329
|
|
|
437
330
|
this.loaded = true;
|
|
438
|
-
|
|
439
|
-
// Pre-compile frequently used types
|
|
440
|
-
this._precompileTypes();
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Pre-compile frequently used message types
|
|
445
|
-
* @private
|
|
446
|
-
*/
|
|
447
|
-
_precompileTypes() {
|
|
448
|
-
const hotTypes = [
|
|
449
|
-
'RequestNewOrder',
|
|
450
|
-
'RequestCancelOrder',
|
|
451
|
-
'RequestHeartbeat',
|
|
452
|
-
'RequestLogin',
|
|
453
|
-
'ResponseLogin',
|
|
454
|
-
'RithmicOrderNotification',
|
|
455
|
-
'ExchangeOrderNotification',
|
|
456
|
-
];
|
|
457
|
-
|
|
458
|
-
for (const typeName of hotTypes) {
|
|
459
|
-
try {
|
|
460
|
-
const Type = this.root.lookupType(typeName);
|
|
461
|
-
this._typeCache.set(typeName, Type);
|
|
462
|
-
} catch (e) {
|
|
463
|
-
// Type may not exist in all proto files
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Get cached type or lookup
|
|
470
|
-
* @private
|
|
471
|
-
*/
|
|
472
|
-
_getType(typeName) {
|
|
473
|
-
let Type = this._typeCache.get(typeName);
|
|
474
|
-
if (!Type) {
|
|
475
|
-
Type = this.root.lookupType(typeName);
|
|
476
|
-
this._typeCache.set(typeName, Type);
|
|
477
|
-
}
|
|
478
|
-
return Type;
|
|
479
331
|
}
|
|
480
332
|
|
|
481
333
|
/**
|
|
482
334
|
* Encode a message to Buffer
|
|
483
|
-
* OPTIMIZED: Uses cached type lookup
|
|
484
335
|
*/
|
|
485
336
|
encode(typeName, data) {
|
|
486
337
|
if (!this.root) throw new Error('Proto not loaded');
|
|
487
338
|
|
|
488
|
-
const Type = this.
|
|
339
|
+
const Type = this.root.lookupType(typeName);
|
|
489
340
|
const msg = Type.create(data);
|
|
490
341
|
return Buffer.from(Type.encode(msg).finish());
|
|
491
342
|
}
|
|
492
343
|
|
|
493
|
-
/**
|
|
494
|
-
* Fast encode for hot path - uses buffer pool and reuses writer
|
|
495
|
-
* ULTRA-LOW LATENCY: Zero allocation in typical case
|
|
496
|
-
*
|
|
497
|
-
* @param {string} typeName
|
|
498
|
-
* @param {Object} data
|
|
499
|
-
* @returns {Buffer}
|
|
500
|
-
*/
|
|
501
|
-
fastEncode(typeName, data) {
|
|
502
|
-
const Type = this._typeCache.get(typeName);
|
|
503
|
-
if (!Type) return this.encode(typeName, data);
|
|
504
|
-
|
|
505
|
-
// OPTIMIZATION: Create message without validation (faster)
|
|
506
|
-
const msg = Type.fromObject(data);
|
|
507
|
-
|
|
508
|
-
// OPTIMIZATION: Get length first to check if pool buffer fits
|
|
509
|
-
const len = Type.encode(msg).len;
|
|
510
|
-
|
|
511
|
-
if (len <= 512) {
|
|
512
|
-
// Use pooled buffer for small messages (typical orders are ~100-200 bytes)
|
|
513
|
-
const poolBuf = this._bufferPool.acquire();
|
|
514
|
-
const writer = protobuf.Writer.create();
|
|
515
|
-
Type.encode(msg, writer);
|
|
516
|
-
const encoded = writer.finish();
|
|
517
|
-
|
|
518
|
-
// Copy to pooled buffer and return a slice
|
|
519
|
-
encoded.copy(poolBuf, 0, 0, len);
|
|
520
|
-
|
|
521
|
-
// Return a NEW buffer (copy) because pooled buffer will be reused
|
|
522
|
-
// This is still faster than allocating fresh each time due to copy being optimized
|
|
523
|
-
const result = Buffer.allocUnsafe(len);
|
|
524
|
-
poolBuf.copy(result, 0, 0, len);
|
|
525
|
-
this._bufferPool.release(poolBuf);
|
|
526
|
-
return result;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// Large message - use standard path
|
|
530
|
-
return Buffer.from(Type.encode(msg).finish());
|
|
531
|
-
}
|
|
532
|
-
|
|
533
344
|
/**
|
|
534
345
|
* Decode a Buffer to object
|
|
535
|
-
* OPTIMIZED: Uses cached type lookup
|
|
536
346
|
*/
|
|
537
347
|
decode(typeName, buffer) {
|
|
538
348
|
if (!this.root) throw new Error('Proto not loaded');
|
|
539
349
|
|
|
540
|
-
const Type = this.
|
|
350
|
+
const Type = this.root.lookupType(typeName);
|
|
541
351
|
return Type.decode(buffer);
|
|
542
352
|
}
|
|
543
353
|
|