hedgequantx 2.6.162 → 2.6.163

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.
@@ -1,264 +1,14 @@
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
7
  const { logger } = require('../../utils/logger');
8
+ const { fastEntry, fastExit, getEffectiveLoginInfo } = require('./orders-fast');
20
9
 
21
10
  const log = logger.scope('RithmicOrders');
22
11
 
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
-
262
12
  /**
263
13
  * Place order via ORDER_PLANT
264
14
  * @param {RithmicService} service - The Rithmic service instance
@@ -0,0 +1,403 @@
1
+ /**
2
+ * Rithmic Protobuf Manual Decoders
3
+ * @module services/rithmic/proto-decoders
4
+ *
5
+ * Hand-optimized decoders for Rithmic's large field ID messages.
6
+ * Faster than generic protobuf parsing for hot path.
7
+ */
8
+
9
+ // PnL field IDs (Rithmic uses very large field IDs)
10
+ const PNL_FIELDS = {
11
+ TEMPLATE_ID: 154467,
12
+ IS_SNAPSHOT: 110121,
13
+ FCM_ID: 154013,
14
+ IB_ID: 154014,
15
+ ACCOUNT_ID: 154008,
16
+ ACCOUNT_BALANCE: 156970,
17
+ CASH_ON_HAND: 156971,
18
+ MARGIN_BALANCE: 156977,
19
+ MIN_ACCOUNT_BALANCE: 156968,
20
+ OPEN_POSITION_PNL: 156961,
21
+ CLOSED_POSITION_PNL: 156963,
22
+ DAY_PNL: 157956,
23
+ DAY_OPEN_PNL: 157954,
24
+ DAY_CLOSED_PNL: 157955,
25
+ AVAILABLE_BUYING_POWER: 157015,
26
+ SSBOE: 150100,
27
+ USECS: 150101,
28
+ };
29
+
30
+ // Symbol/Contract field IDs
31
+ const SYMBOL_FIELDS = {
32
+ TEMPLATE_ID: 154467,
33
+ RP_CODE: 132766,
34
+ EXCHANGE: 110101,
35
+ PRODUCT_CODE: 110102,
36
+ PRODUCT_NAME: 110103,
37
+ SYMBOL: 110100,
38
+ TRADING_SYMBOL: 157095,
39
+ DESCRIPTION: 110114,
40
+ USER_MSG: 132760,
41
+ };
42
+
43
+ // Instrument PnL Position Update field IDs
44
+ const INSTRUMENT_PNL_FIELDS = {
45
+ TEMPLATE_ID: 154467,
46
+ IS_SNAPSHOT: 110121,
47
+ FCM_ID: 154013,
48
+ IB_ID: 154014,
49
+ ACCOUNT_ID: 154008,
50
+ SYMBOL: 110100,
51
+ EXCHANGE: 110101,
52
+ PRODUCT_CODE: 100749,
53
+ INSTRUMENT_TYPE: 110116,
54
+ FILL_BUY_QTY: 154041,
55
+ FILL_SELL_QTY: 154042,
56
+ ORDER_BUY_QTY: 154037,
57
+ ORDER_SELL_QTY: 154038,
58
+ BUY_QTY: 154260,
59
+ SELL_QTY: 154261,
60
+ AVG_OPEN_FILL_PRICE: 154434,
61
+ DAY_OPEN_PNL: 157954,
62
+ DAY_CLOSED_PNL: 157955,
63
+ DAY_PNL: 157956,
64
+ OPEN_POSITION_PNL: 156961,
65
+ OPEN_POSITION_QUANTITY: 156962,
66
+ CLOSED_POSITION_PNL: 156963,
67
+ CLOSED_POSITION_QUANTITY: 156964,
68
+ NET_QUANTITY: 156967,
69
+ SSBOE: 150100,
70
+ USECS: 150101,
71
+ };
72
+
73
+ /** Read a varint from buffer */
74
+ function readVarint(buffer, offset) {
75
+ let result = BigInt(0);
76
+ let shift = BigInt(0);
77
+ let pos = offset;
78
+
79
+ while (pos < buffer.length) {
80
+ const byte = buffer[pos++];
81
+ result |= BigInt(byte & 0x7f) << shift;
82
+ if ((byte & 0x80) === 0) {
83
+ return [Number(result), pos];
84
+ }
85
+ shift += BigInt(7);
86
+ if (shift > BigInt(63)) throw new Error('Varint too large');
87
+ }
88
+ throw new Error('Incomplete varint');
89
+ }
90
+
91
+ /** Read a length-delimited field (string/bytes) */
92
+ function readLengthDelimited(buffer, offset) {
93
+ const [length, newOffset] = readVarint(buffer, offset);
94
+ const value = buffer.slice(newOffset, newOffset + length).toString('utf8');
95
+ return [value, newOffset + length];
96
+ }
97
+
98
+ /** Skip a field based on wire type */
99
+ function skipField(buffer, offset, wireType) {
100
+ switch (wireType) {
101
+ case 0:
102
+ const [, newOffset] = readVarint(buffer, offset);
103
+ return newOffset;
104
+ case 1:
105
+ return offset + 8;
106
+ case 2:
107
+ const [length, lenOffset] = readVarint(buffer, offset);
108
+ return lenOffset + length;
109
+ case 5:
110
+ return offset + 4;
111
+ default:
112
+ throw new Error(`Unknown wire type: ${wireType}`);
113
+ }
114
+ }
115
+
116
+ /** Manually decode AccountPnL from raw bytes */
117
+ function decodeAccountPnL(buffer) {
118
+ const result = {};
119
+ let offset = 0;
120
+
121
+ while (offset < buffer.length) {
122
+ try {
123
+ const [tag, tagOffset] = readVarint(buffer, offset);
124
+ const wireType = tag & 0x7;
125
+ const fieldNumber = tag >>> 3;
126
+ offset = tagOffset;
127
+
128
+ switch (fieldNumber) {
129
+ case PNL_FIELDS.TEMPLATE_ID:
130
+ [result.templateId, offset] = readVarint(buffer, offset);
131
+ break;
132
+ case PNL_FIELDS.IS_SNAPSHOT:
133
+ const [isSnap, snapOffset] = readVarint(buffer, offset);
134
+ result.isSnapshot = isSnap !== 0;
135
+ offset = snapOffset;
136
+ break;
137
+ case PNL_FIELDS.FCM_ID:
138
+ [result.fcmId, offset] = readLengthDelimited(buffer, offset);
139
+ break;
140
+ case PNL_FIELDS.IB_ID:
141
+ [result.ibId, offset] = readLengthDelimited(buffer, offset);
142
+ break;
143
+ case PNL_FIELDS.ACCOUNT_ID:
144
+ [result.accountId, offset] = readLengthDelimited(buffer, offset);
145
+ break;
146
+ case PNL_FIELDS.ACCOUNT_BALANCE:
147
+ [result.accountBalance, offset] = readLengthDelimited(buffer, offset);
148
+ break;
149
+ case PNL_FIELDS.CASH_ON_HAND:
150
+ [result.cashOnHand, offset] = readLengthDelimited(buffer, offset);
151
+ break;
152
+ case PNL_FIELDS.MARGIN_BALANCE:
153
+ [result.marginBalance, offset] = readLengthDelimited(buffer, offset);
154
+ break;
155
+ case PNL_FIELDS.MIN_ACCOUNT_BALANCE:
156
+ [result.minAccountBalance, offset] = readLengthDelimited(buffer, offset);
157
+ break;
158
+ case PNL_FIELDS.OPEN_POSITION_PNL:
159
+ [result.openPositionPnl, offset] = readLengthDelimited(buffer, offset);
160
+ break;
161
+ case PNL_FIELDS.CLOSED_POSITION_PNL:
162
+ [result.closedPositionPnl, offset] = readLengthDelimited(buffer, offset);
163
+ break;
164
+ case PNL_FIELDS.DAY_PNL:
165
+ [result.dayPnl, offset] = readLengthDelimited(buffer, offset);
166
+ break;
167
+ case PNL_FIELDS.DAY_OPEN_PNL:
168
+ [result.dayOpenPnl, offset] = readLengthDelimited(buffer, offset);
169
+ break;
170
+ case PNL_FIELDS.DAY_CLOSED_PNL:
171
+ [result.dayClosedPnl, offset] = readLengthDelimited(buffer, offset);
172
+ break;
173
+ case PNL_FIELDS.AVAILABLE_BUYING_POWER:
174
+ [result.availableBuyingPower, offset] = readLengthDelimited(buffer, offset);
175
+ break;
176
+ case PNL_FIELDS.SSBOE:
177
+ [result.ssboe, offset] = readVarint(buffer, offset);
178
+ break;
179
+ case PNL_FIELDS.USECS:
180
+ [result.usecs, offset] = readVarint(buffer, offset);
181
+ break;
182
+ default:
183
+ offset = skipField(buffer, offset, wireType);
184
+ }
185
+ } catch (error) {
186
+ break;
187
+ }
188
+ }
189
+ return result;
190
+ }
191
+
192
+ /** Manually decode InstrumentPnLPositionUpdate from raw bytes */
193
+ function decodeInstrumentPnL(buffer) {
194
+ const result = {};
195
+ let offset = 0;
196
+
197
+ while (offset < buffer.length) {
198
+ try {
199
+ const [tag, tagOffset] = readVarint(buffer, offset);
200
+ const wireType = tag & 0x7;
201
+ const fieldNumber = tag >>> 3;
202
+ offset = tagOffset;
203
+
204
+ switch (fieldNumber) {
205
+ case INSTRUMENT_PNL_FIELDS.TEMPLATE_ID:
206
+ [result.templateId, offset] = readVarint(buffer, offset);
207
+ break;
208
+ case INSTRUMENT_PNL_FIELDS.IS_SNAPSHOT:
209
+ const [isSnap, snapOffset] = readVarint(buffer, offset);
210
+ result.isSnapshot = isSnap !== 0;
211
+ offset = snapOffset;
212
+ break;
213
+ case INSTRUMENT_PNL_FIELDS.FCM_ID:
214
+ [result.fcmId, offset] = readLengthDelimited(buffer, offset);
215
+ break;
216
+ case INSTRUMENT_PNL_FIELDS.IB_ID:
217
+ [result.ibId, offset] = readLengthDelimited(buffer, offset);
218
+ break;
219
+ case INSTRUMENT_PNL_FIELDS.ACCOUNT_ID:
220
+ [result.accountId, offset] = readLengthDelimited(buffer, offset);
221
+ break;
222
+ case INSTRUMENT_PNL_FIELDS.SYMBOL:
223
+ [result.symbol, offset] = readLengthDelimited(buffer, offset);
224
+ break;
225
+ case INSTRUMENT_PNL_FIELDS.EXCHANGE:
226
+ [result.exchange, offset] = readLengthDelimited(buffer, offset);
227
+ break;
228
+ case INSTRUMENT_PNL_FIELDS.PRODUCT_CODE:
229
+ [result.productCode, offset] = readLengthDelimited(buffer, offset);
230
+ break;
231
+ case INSTRUMENT_PNL_FIELDS.BUY_QTY:
232
+ [result.buyQty, offset] = readVarint(buffer, offset);
233
+ break;
234
+ case INSTRUMENT_PNL_FIELDS.SELL_QTY:
235
+ [result.sellQty, offset] = readVarint(buffer, offset);
236
+ break;
237
+ case INSTRUMENT_PNL_FIELDS.FILL_BUY_QTY:
238
+ [result.fillBuyQty, offset] = readVarint(buffer, offset);
239
+ break;
240
+ case INSTRUMENT_PNL_FIELDS.FILL_SELL_QTY:
241
+ [result.fillSellQty, offset] = readVarint(buffer, offset);
242
+ break;
243
+ case INSTRUMENT_PNL_FIELDS.NET_QUANTITY:
244
+ [result.netQuantity, offset] = readVarint(buffer, offset);
245
+ break;
246
+ case INSTRUMENT_PNL_FIELDS.OPEN_POSITION_QUANTITY:
247
+ [result.openPositionQuantity, offset] = readVarint(buffer, offset);
248
+ break;
249
+ case INSTRUMENT_PNL_FIELDS.AVG_OPEN_FILL_PRICE:
250
+ if (wireType === 1) {
251
+ result.avgOpenFillPrice = buffer.readDoubleLE(offset);
252
+ offset += 8;
253
+ } else {
254
+ offset = skipField(buffer, offset, wireType);
255
+ }
256
+ break;
257
+ case INSTRUMENT_PNL_FIELDS.OPEN_POSITION_PNL:
258
+ [result.openPositionPnl, offset] = readLengthDelimited(buffer, offset);
259
+ break;
260
+ case INSTRUMENT_PNL_FIELDS.CLOSED_POSITION_PNL:
261
+ [result.closedPositionPnl, offset] = readLengthDelimited(buffer, offset);
262
+ break;
263
+ case INSTRUMENT_PNL_FIELDS.DAY_PNL:
264
+ if (wireType === 1) {
265
+ result.dayPnl = buffer.readDoubleLE(offset);
266
+ offset += 8;
267
+ } else {
268
+ [result.dayPnl, offset] = readLengthDelimited(buffer, offset);
269
+ }
270
+ break;
271
+ case INSTRUMENT_PNL_FIELDS.DAY_OPEN_PNL:
272
+ if (wireType === 1) {
273
+ result.dayOpenPnl = buffer.readDoubleLE(offset);
274
+ offset += 8;
275
+ } else {
276
+ [result.dayOpenPnl, offset] = readLengthDelimited(buffer, offset);
277
+ }
278
+ break;
279
+ case INSTRUMENT_PNL_FIELDS.DAY_CLOSED_PNL:
280
+ if (wireType === 1) {
281
+ result.dayClosedPnl = buffer.readDoubleLE(offset);
282
+ offset += 8;
283
+ } else {
284
+ [result.dayClosedPnl, offset] = readLengthDelimited(buffer, offset);
285
+ }
286
+ break;
287
+ case INSTRUMENT_PNL_FIELDS.SSBOE:
288
+ [result.ssboe, offset] = readVarint(buffer, offset);
289
+ break;
290
+ case INSTRUMENT_PNL_FIELDS.USECS:
291
+ [result.usecs, offset] = readVarint(buffer, offset);
292
+ break;
293
+ default:
294
+ offset = skipField(buffer, offset, wireType);
295
+ }
296
+ } catch (error) {
297
+ break;
298
+ }
299
+ }
300
+ return result;
301
+ }
302
+
303
+ /** Decode ResponseProductCodes (template 112) */
304
+ function decodeProductCodes(buffer) {
305
+ const result = { rpCode: [] };
306
+ let offset = 0;
307
+
308
+ while (offset < buffer.length) {
309
+ try {
310
+ const [tag, tagOffset] = readVarint(buffer, offset);
311
+ const wireType = tag & 0x7;
312
+ const fieldNumber = tag >>> 3;
313
+ offset = tagOffset;
314
+
315
+ switch (fieldNumber) {
316
+ case SYMBOL_FIELDS.TEMPLATE_ID:
317
+ [result.templateId, offset] = readVarint(buffer, offset);
318
+ break;
319
+ case SYMBOL_FIELDS.RP_CODE:
320
+ let rpCode;
321
+ [rpCode, offset] = readLengthDelimited(buffer, offset);
322
+ result.rpCode.push(rpCode);
323
+ break;
324
+ case SYMBOL_FIELDS.EXCHANGE:
325
+ [result.exchange, offset] = readLengthDelimited(buffer, offset);
326
+ break;
327
+ case SYMBOL_FIELDS.PRODUCT_CODE:
328
+ [result.productCode, offset] = readLengthDelimited(buffer, offset);
329
+ break;
330
+ case SYMBOL_FIELDS.PRODUCT_NAME:
331
+ [result.productName, offset] = readLengthDelimited(buffer, offset);
332
+ break;
333
+ case SYMBOL_FIELDS.USER_MSG:
334
+ [result.userMsg, offset] = readLengthDelimited(buffer, offset);
335
+ break;
336
+ default:
337
+ offset = skipField(buffer, offset, wireType);
338
+ }
339
+ } catch (error) {
340
+ break;
341
+ }
342
+ }
343
+ return result;
344
+ }
345
+
346
+ /** Decode ResponseFrontMonthContract (template 114) */
347
+ function decodeFrontMonthContract(buffer) {
348
+ const result = { rpCode: [] };
349
+ let offset = 0;
350
+
351
+ while (offset < buffer.length) {
352
+ try {
353
+ const [tag, tagOffset] = readVarint(buffer, offset);
354
+ const wireType = tag & 0x7;
355
+ const fieldNumber = tag >>> 3;
356
+ offset = tagOffset;
357
+
358
+ switch (fieldNumber) {
359
+ case SYMBOL_FIELDS.TEMPLATE_ID:
360
+ [result.templateId, offset] = readVarint(buffer, offset);
361
+ break;
362
+ case SYMBOL_FIELDS.RP_CODE:
363
+ let rpCode;
364
+ [rpCode, offset] = readLengthDelimited(buffer, offset);
365
+ result.rpCode.push(rpCode);
366
+ break;
367
+ case SYMBOL_FIELDS.SYMBOL:
368
+ [result.symbol, offset] = readLengthDelimited(buffer, offset);
369
+ break;
370
+ case SYMBOL_FIELDS.EXCHANGE:
371
+ [result.exchange, offset] = readLengthDelimited(buffer, offset);
372
+ break;
373
+ case SYMBOL_FIELDS.TRADING_SYMBOL:
374
+ [result.tradingSymbol, offset] = readLengthDelimited(buffer, offset);
375
+ break;
376
+ case SYMBOL_FIELDS.DESCRIPTION:
377
+ [result.description, offset] = readLengthDelimited(buffer, offset);
378
+ break;
379
+ case SYMBOL_FIELDS.USER_MSG:
380
+ [result.userMsg, offset] = readLengthDelimited(buffer, offset);
381
+ break;
382
+ default:
383
+ offset = skipField(buffer, offset, wireType);
384
+ }
385
+ } catch (error) {
386
+ break;
387
+ }
388
+ }
389
+ return result;
390
+ }
391
+
392
+ module.exports = {
393
+ PNL_FIELDS,
394
+ SYMBOL_FIELDS,
395
+ INSTRUMENT_PNL_FIELDS,
396
+ readVarint,
397
+ readLengthDelimited,
398
+ skipField,
399
+ decodeAccountPnL,
400
+ decodeInstrumentPnL,
401
+ decodeProductCodes,
402
+ decodeFrontMonthContract,
403
+ };