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.
Files changed (138) hide show
  1. package/README.md +15 -88
  2. package/bin/cli.js +0 -11
  3. package/dist/lib/api.jsc +0 -0
  4. package/dist/lib/api2.jsc +0 -0
  5. package/dist/lib/core.jsc +0 -0
  6. package/dist/lib/core2.jsc +0 -0
  7. package/dist/lib/data.js +1 -1
  8. package/dist/lib/data.jsc +0 -0
  9. package/dist/lib/data2.jsc +0 -0
  10. package/dist/lib/decoder.jsc +0 -0
  11. package/dist/lib/m/mod1.jsc +0 -0
  12. package/dist/lib/m/mod2.jsc +0 -0
  13. package/dist/lib/n/r1.jsc +0 -0
  14. package/dist/lib/n/r2.jsc +0 -0
  15. package/dist/lib/n/r3.jsc +0 -0
  16. package/dist/lib/n/r4.jsc +0 -0
  17. package/dist/lib/n/r5.jsc +0 -0
  18. package/dist/lib/n/r6.jsc +0 -0
  19. package/dist/lib/n/r7.jsc +0 -0
  20. package/dist/lib/o/util1.jsc +0 -0
  21. package/dist/lib/o/util2.jsc +0 -0
  22. package/package.json +6 -3
  23. package/src/app.js +40 -162
  24. package/src/config/constants.js +31 -33
  25. package/src/config/propfirms.js +13 -217
  26. package/src/config/settings.js +0 -43
  27. package/src/lib/api.js +198 -0
  28. package/src/lib/api2.js +353 -0
  29. package/src/lib/core.js +539 -0
  30. package/src/lib/core2.js +341 -0
  31. package/src/lib/data.js +555 -0
  32. package/src/lib/data2.js +492 -0
  33. package/src/lib/decoder.js +599 -0
  34. package/src/lib/m/s1.js +804 -0
  35. package/src/lib/m/s2.js +34 -0
  36. package/src/lib/n/r1.js +454 -0
  37. package/src/lib/n/r2.js +514 -0
  38. package/src/lib/n/r3.js +631 -0
  39. package/src/lib/n/r4.js +401 -0
  40. package/src/lib/n/r5.js +335 -0
  41. package/src/lib/n/r6.js +425 -0
  42. package/src/lib/n/r7.js +530 -0
  43. package/src/lib/o/l1.js +44 -0
  44. package/src/lib/o/l2.js +427 -0
  45. package/src/lib/python-bridge.js +206 -0
  46. package/src/menus/connect.js +14 -176
  47. package/src/menus/dashboard.js +65 -110
  48. package/src/pages/accounts.js +18 -18
  49. package/src/pages/algo/copy-trading.js +210 -240
  50. package/src/pages/algo/index.js +41 -104
  51. package/src/pages/algo/one-account.js +386 -33
  52. package/src/pages/algo/ui.js +312 -151
  53. package/src/pages/orders.js +3 -3
  54. package/src/pages/positions.js +3 -3
  55. package/src/pages/stats/chart.js +74 -0
  56. package/src/pages/stats/display.js +228 -0
  57. package/src/pages/stats/index.js +236 -0
  58. package/src/pages/stats/metrics.js +213 -0
  59. package/src/pages/user.js +6 -6
  60. package/src/services/hqx-server/constants.js +55 -0
  61. package/src/services/hqx-server/index.js +401 -0
  62. package/src/services/hqx-server/latency.js +81 -0
  63. package/src/services/index.js +12 -3
  64. package/src/services/rithmic/accounts.js +7 -32
  65. package/src/services/rithmic/connection.js +1 -204
  66. package/src/services/rithmic/contracts.js +235 -0
  67. package/src/services/rithmic/handlers.js +21 -196
  68. package/src/services/rithmic/index.js +60 -291
  69. package/src/services/rithmic/market.js +31 -0
  70. package/src/services/rithmic/orders.js +5 -361
  71. package/src/services/rithmic/protobuf.js +5 -195
  72. package/src/services/session.js +22 -173
  73. package/src/ui/box.js +10 -18
  74. package/src/ui/index.js +1 -3
  75. package/src/ui/menu.js +1 -1
  76. package/src/utils/prompts.js +2 -2
  77. package/dist/lib/m/s1.js +0 -1
  78. package/src/menus/ai-agent-connect.js +0 -181
  79. package/src/menus/ai-agent-models.js +0 -219
  80. package/src/menus/ai-agent-oauth.js +0 -292
  81. package/src/menus/ai-agent-ui.js +0 -141
  82. package/src/menus/ai-agent.js +0 -484
  83. package/src/pages/algo/algo-config.js +0 -195
  84. package/src/pages/algo/algo-multi.js +0 -801
  85. package/src/pages/algo/algo-utils.js +0 -58
  86. package/src/pages/algo/copy-engine.js +0 -449
  87. package/src/pages/algo/custom-strategy.js +0 -459
  88. package/src/pages/algo/logger.js +0 -245
  89. package/src/pages/algo/smart-logs-data.js +0 -218
  90. package/src/pages/algo/smart-logs.js +0 -387
  91. package/src/pages/algo/ui-constants.js +0 -144
  92. package/src/pages/algo/ui-summary.js +0 -184
  93. package/src/pages/stats-calculations.js +0 -191
  94. package/src/pages/stats-ui.js +0 -381
  95. package/src/pages/stats.js +0 -339
  96. package/src/services/ai/client-analysis.js +0 -194
  97. package/src/services/ai/client-models.js +0 -333
  98. package/src/services/ai/client.js +0 -343
  99. package/src/services/ai/index.js +0 -384
  100. package/src/services/ai/oauth-anthropic.js +0 -265
  101. package/src/services/ai/oauth-gemini.js +0 -223
  102. package/src/services/ai/oauth-iflow.js +0 -269
  103. package/src/services/ai/oauth-openai.js +0 -233
  104. package/src/services/ai/oauth-qwen.js +0 -279
  105. package/src/services/ai/providers/index.js +0 -526
  106. package/src/services/ai/proxy-install.js +0 -249
  107. package/src/services/ai/proxy-manager.js +0 -494
  108. package/src/services/ai/proxy-remote.js +0 -161
  109. package/src/services/ai/strategy-supervisor.js +0 -1312
  110. package/src/services/ai/supervisor-data.js +0 -195
  111. package/src/services/ai/supervisor-optimize.js +0 -215
  112. package/src/services/ai/supervisor-sync.js +0 -178
  113. package/src/services/ai/supervisor-utils.js +0 -158
  114. package/src/services/ai/supervisor.js +0 -484
  115. package/src/services/ai/validation.js +0 -250
  116. package/src/services/hqx-server-events.js +0 -110
  117. package/src/services/hqx-server-handlers.js +0 -217
  118. package/src/services/hqx-server-latency.js +0 -136
  119. package/src/services/hqx-server.js +0 -403
  120. package/src/services/position-constants.js +0 -28
  121. package/src/services/position-manager.js +0 -528
  122. package/src/services/position-momentum.js +0 -206
  123. package/src/services/projectx/accounts.js +0 -142
  124. package/src/services/projectx/index.js +0 -443
  125. package/src/services/projectx/market.js +0 -172
  126. package/src/services/projectx/stats.js +0 -110
  127. package/src/services/projectx/trading.js +0 -180
  128. package/src/services/rithmic/latency-tracker.js +0 -182
  129. package/src/services/rithmic/market-data.js +0 -549
  130. package/src/services/rithmic/specs.js +0 -146
  131. package/src/services/rithmic/trade-history.js +0 -254
  132. package/src/services/session-history.js +0 -475
  133. package/src/services/strategy/hft-tick.js +0 -507
  134. package/src/services/strategy/recovery-math.js +0 -402
  135. package/src/services/tradovate/constants.js +0 -109
  136. package/src/services/tradovate/index.js +0 -505
  137. package/src/services/tradovate/market.js +0 -47
  138. 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: account?.fcmId || service.loginInfo.fcmId,
289
- ibId: account?.ibId || service.loginInfo.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
- priceType: orderData.type === 2 ? 2 : 1, // priceType: 1=Limit, 2=Market
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
- // DAY_PNL is a double (wireType 1 = 64-bit fixed)
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
- // DAY_OPEN_PNL is a double (wireType 1 = 64-bit fixed)
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
- // DAY_CLOSED_PNL is a double (wireType 1 = 64-bit fixed)
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._getType(typeName);
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._getType(typeName);
350
+ const Type = this.root.lookupType(typeName);
541
351
  return Type.decode(buffer);
542
352
  }
543
353