hedgequantx 1.8.32 → 1.8.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "1.8.32",
3
+ "version": "1.8.34",
4
4
  "description": "Prop Futures Algo Trading CLI - Connect to Topstep, Alpha Futures, and other prop firms",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -58,6 +58,8 @@ const REQ = {
58
58
  SYSTEM_INFO: 16,
59
59
  HEARTBEAT: 18,
60
60
  MARKET_DATA: 100,
61
+ PRODUCT_CODES: 111,
62
+ FRONT_MONTH_CONTRACT: 113,
61
63
  LOGIN_INFO: 300,
62
64
  ACCOUNT_LIST: 302,
63
65
  ACCOUNT_RMS: 304,
@@ -84,6 +86,8 @@ const RES = {
84
86
  SYSTEM_INFO: 17,
85
87
  HEARTBEAT: 19,
86
88
  MARKET_DATA: 101,
89
+ PRODUCT_CODES: 112,
90
+ FRONT_MONTH_CONTRACT: 114,
87
91
  LOGIN_INFO: 301,
88
92
  ACCOUNT_LIST: 303,
89
93
  ACCOUNT_RMS: 305,
@@ -155,6 +159,10 @@ const PROTO_FILES = [
155
159
  'response_pnl_position_updates.proto',
156
160
  'account_pnl_position_update.proto',
157
161
  'instrument_pnl_position_update.proto',
162
+ 'request_product_codes.proto',
163
+ 'response_product_codes.proto',
164
+ 'request_front_month_contract.proto',
165
+ 'response_front_month_contract.proto',
158
166
  ];
159
167
 
160
168
  module.exports = {
@@ -9,6 +9,7 @@ const { RITHMIC_ENDPOINTS, RITHMIC_SYSTEMS } = require('./constants');
9
9
  const { createOrderHandler, createPnLHandler } = require('./handlers');
10
10
  const { fetchAccounts, getTradingAccounts, requestPnLSnapshot, subscribePnLUpdates, getPositions, hashAccountId } = require('./accounts');
11
11
  const { placeOrder, cancelOrder, getOrders, getOrderHistory, closePosition } = require('./orders');
12
+ const { decodeFrontMonthContract, readVarint, readLengthDelimited, skipField } = require('./protobuf');
12
13
 
13
14
  // Debug mode
14
15
  const DEBUG = process.env.HQX_DEBUG === '1';
@@ -45,6 +46,7 @@ class RithmicService extends EventEmitter {
45
46
  };
46
47
  this.orderConn = null;
47
48
  this.pnlConn = null;
49
+ this.tickerConn = null; // TICKER_PLANT for symbol lookup
48
50
  this.loginInfo = null;
49
51
  this.accounts = [];
50
52
  this.accountPnL = new Map();
@@ -52,6 +54,7 @@ class RithmicService extends EventEmitter {
52
54
  this.orders = [];
53
55
  this.user = null;
54
56
  this.credentials = null;
57
+ this.cachedContracts = null; // Cache contracts to avoid repeated API calls
55
58
  }
56
59
 
57
60
  /**
@@ -208,53 +211,282 @@ class RithmicService extends EventEmitter {
208
211
  }
209
212
 
210
213
  /**
211
- * Get contracts from Rithmic
212
- * TODO: Implement TICKER_PLANT connection to fetch real contracts
213
- * For now, returns common futures contracts that are available on Rithmic
214
+ * Connect to TICKER_PLANT for symbol lookup
215
+ */
216
+ async connectTicker(username, password) {
217
+ try {
218
+ this.tickerConn = new RithmicConnection();
219
+ const gateway = this.propfirm.gateway || RITHMIC_ENDPOINTS.CHICAGO;
220
+
221
+ const config = {
222
+ uri: gateway,
223
+ systemName: this.propfirm.systemName,
224
+ userId: username,
225
+ password: password,
226
+ appName: 'HQX-CLI',
227
+ appVersion: '1.0.0',
228
+ };
229
+
230
+ await this.tickerConn.connect(config);
231
+
232
+ return new Promise((resolve) => {
233
+ const timeout = setTimeout(() => {
234
+ debug('TICKER_PLANT login timeout');
235
+ resolve(false);
236
+ }, 10000);
237
+
238
+ this.tickerConn.once('loggedIn', () => {
239
+ clearTimeout(timeout);
240
+ debug('TICKER_PLANT connected');
241
+ resolve(true);
242
+ });
243
+
244
+ this.tickerConn.once('loginFailed', () => {
245
+ clearTimeout(timeout);
246
+ debug('TICKER_PLANT login failed');
247
+ resolve(false);
248
+ });
249
+
250
+ this.tickerConn.login('TICKER_PLANT');
251
+ });
252
+ } catch (e) {
253
+ debug('TICKER_PLANT connection error:', e.message);
254
+ return false;
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Get front month contracts for multiple symbols at once - batch request
260
+ */
261
+ async getFrontMonthsBatch(products) {
262
+ if (!this.tickerConn) {
263
+ throw new Error('TICKER_PLANT not connected');
264
+ }
265
+
266
+ return new Promise((resolve) => {
267
+ const results = new Map();
268
+ const pending = new Set(products.map(p => p.productCode));
269
+
270
+ const handler = (msg) => {
271
+ if (msg.templateId === 114) { // ResponseFrontMonthContract
272
+ const decoded = decodeFrontMonthContract(msg.data);
273
+ const baseSymbol = decoded.userMsg;
274
+
275
+ if (pending.has(baseSymbol)) {
276
+ pending.delete(baseSymbol);
277
+
278
+ if (decoded.rpCode[0] === '0' && decoded.tradingSymbol) {
279
+ results.set(baseSymbol, {
280
+ symbol: decoded.tradingSymbol,
281
+ baseSymbol: baseSymbol,
282
+ exchange: decoded.exchange,
283
+ });
284
+ }
285
+ }
286
+ }
287
+ };
288
+
289
+ this.tickerConn.on('message', handler);
290
+
291
+ // Send all requests
292
+ for (const product of products) {
293
+ this.tickerConn.send('RequestFrontMonthContract', {
294
+ templateId: 113,
295
+ userMsg: [product.productCode],
296
+ symbol: product.productCode,
297
+ exchange: product.exchange,
298
+ });
299
+ }
300
+
301
+ // Wait for responses (with timeout)
302
+ setTimeout(() => {
303
+ this.tickerConn.removeListener('message', handler);
304
+ resolve(Array.from(results.values()));
305
+ }, 5000);
306
+ });
307
+ }
308
+
309
+ /**
310
+ * Decode ProductCodes response (template 112) - field IDs from Rithmic API
311
+ */
312
+ decodeProductCodesResponse(buffer) {
313
+ const result = {};
314
+ let offset = 0;
315
+
316
+ while (offset < buffer.length) {
317
+ try {
318
+ const [tag, tagOffset] = readVarint(buffer, offset);
319
+ const wireType = tag & 0x7;
320
+ const fieldNumber = tag >>> 3;
321
+ offset = tagOffset;
322
+
323
+ if (wireType === 0) {
324
+ const [val, newOff] = readVarint(buffer, offset);
325
+ offset = newOff;
326
+ if (fieldNumber === 154467) result.templateId = val;
327
+ } else if (wireType === 2) {
328
+ const [val, newOff] = readLengthDelimited(buffer, offset);
329
+ offset = newOff;
330
+ if (fieldNumber === 110101) result.exchange = val; // exchange
331
+ if (fieldNumber === 100749) result.productCode = val; // product_code (base symbol)
332
+ if (fieldNumber === 100003) result.productName = val; // symbol_name
333
+ if (fieldNumber === 132760) result.userMsg = val; // user_msg
334
+ if (fieldNumber === 132764) result.rpCode = val; // rp_code
335
+ } else {
336
+ offset = skipField(buffer, offset, wireType);
337
+ }
338
+ } catch (e) { break; }
339
+ }
340
+
341
+ return result;
342
+ }
343
+
344
+ /**
345
+ * Get all tradeable futures from Rithmic API - 100% REAL DATA
346
+ * Sends RequestFrontMonthContract for known futures and gets real trading symbols
347
+ */
348
+ async fetchAllFrontMonths() {
349
+ if (!this.tickerConn) {
350
+ throw new Error('TICKER_PLANT not connected');
351
+ }
352
+
353
+ // Request front months for ALL products that might be futures
354
+ // The API will return the current trading symbol for each
355
+ return new Promise((resolve) => {
356
+ const contracts = new Map();
357
+ const productsToCheck = new Map(); // Will collect from ProductCodes
358
+
359
+ // Handler for ProductCodes responses
360
+ const productHandler = (msg) => {
361
+ if (msg.templateId === 112) {
362
+ const decoded = this.decodeProductCodesResponse(msg.data);
363
+ if (!decoded.productCode || !decoded.exchange) return;
364
+
365
+ // Only main futures exchanges
366
+ const validExchanges = ['CME', 'CBOT', 'NYMEX', 'COMEX', 'NYBOT', 'CFE'];
367
+ if (!validExchanges.includes(decoded.exchange)) return;
368
+
369
+ // Skip obvious non-futures
370
+ const name = (decoded.productName || '').toLowerCase();
371
+ if (name.includes('option')) return;
372
+ if (name.includes('swap')) return;
373
+ if (name.includes('spread')) return;
374
+
375
+ const key = `${decoded.productCode}:${decoded.exchange}`;
376
+ if (!productsToCheck.has(key)) {
377
+ productsToCheck.set(key, {
378
+ productCode: decoded.productCode,
379
+ productName: decoded.productName || decoded.productCode,
380
+ exchange: decoded.exchange,
381
+ });
382
+ }
383
+ }
384
+ };
385
+
386
+ // Handler for FrontMonth responses
387
+ const frontMonthHandler = (msg) => {
388
+ if (msg.templateId === 114) {
389
+ const decoded = decodeFrontMonthContract(msg.data);
390
+ if (decoded.rpCode[0] === '0' && decoded.tradingSymbol) {
391
+ contracts.set(decoded.userMsg, {
392
+ symbol: decoded.tradingSymbol,
393
+ baseSymbol: decoded.userMsg,
394
+ exchange: decoded.exchange,
395
+ });
396
+ }
397
+ }
398
+ };
399
+
400
+ this.tickerConn.on('message', productHandler);
401
+ this.tickerConn.on('message', frontMonthHandler);
402
+
403
+ // Step 1: Get all product codes
404
+ this.tickerConn.send('RequestProductCodes', {
405
+ templateId: 111,
406
+ userMsg: ['get-products'],
407
+ });
408
+
409
+ // After 5 seconds, request front months for all collected products
410
+ setTimeout(() => {
411
+ this.tickerConn.removeListener('message', productHandler);
412
+ debug(`Collected ${productsToCheck.size} products, requesting front months...`);
413
+
414
+ // Send front month requests for all products
415
+ for (const [key, product] of productsToCheck) {
416
+ this.tickerConn.send('RequestFrontMonthContract', {
417
+ templateId: 113,
418
+ userMsg: [product.productCode],
419
+ symbol: product.productCode,
420
+ exchange: product.exchange,
421
+ });
422
+ }
423
+
424
+ // After another 5 seconds, collect results
425
+ setTimeout(() => {
426
+ this.tickerConn.removeListener('message', frontMonthHandler);
427
+
428
+ // Merge with product names
429
+ const results = [];
430
+ for (const [baseSymbol, contract] of contracts) {
431
+ const productKey = `${baseSymbol}:${contract.exchange}`;
432
+ const product = productsToCheck.get(productKey);
433
+ results.push({
434
+ symbol: contract.symbol,
435
+ baseSymbol: baseSymbol,
436
+ name: product ? product.productName : baseSymbol,
437
+ exchange: contract.exchange,
438
+ });
439
+ }
440
+
441
+ debug(`Got ${results.length} tradeable contracts from API`);
442
+ resolve(results);
443
+ }, 8000);
444
+ }, 5000);
445
+ });
446
+ }
447
+
448
+ /**
449
+ * Get all available contracts - 100% REAL DATA from Rithmic API
450
+ * Fetches product codes and front months directly from the API
214
451
  */
215
452
  async getContracts() {
216
- // Calculate current front month dynamically
217
- const now = new Date();
218
- const month = now.getMonth();
219
- const year = now.getFullYear() % 100;
220
-
221
- // Quarterly months for index futures: H=Mar, M=Jun, U=Sep, Z=Dec
222
- const quarterlyMonths = ['H', 'M', 'U', 'Z'];
223
- const monthNames = ['Mar', 'Jun', 'Sep', 'Dec'];
224
- const quarterIndex = Math.floor(month / 3);
225
-
226
- // If past 15th of expiry month, use next quarter
227
- let frontMonth = quarterlyMonths[quarterIndex];
228
- let frontMonthName = monthNames[quarterIndex];
229
- let frontYear = year;
230
-
231
- if (now.getDate() > 15 && [2, 5, 8, 11].includes(month)) {
232
- const nextIdx = (quarterIndex + 1) % 4;
233
- frontMonth = quarterlyMonths[nextIdx];
234
- frontMonthName = monthNames[nextIdx];
235
- if (nextIdx === 0) frontYear++;
453
+ // Return cached if available and fresh (5 min cache)
454
+ if (this.cachedContracts && this.cachedContracts.timestamp > Date.now() - 300000) {
455
+ return { success: true, contracts: this.cachedContracts.data, source: 'cache' };
456
+ }
457
+
458
+ // Need credentials to fetch real data
459
+ if (!this.credentials) {
460
+ return { success: false, error: 'Not logged in - cannot fetch contracts from API' };
461
+ }
462
+
463
+ try {
464
+ // Connect to TICKER_PLANT if not connected
465
+ if (!this.tickerConn) {
466
+ const connected = await this.connectTicker(this.credentials.username, this.credentials.password);
467
+ if (!connected) {
468
+ return { success: false, error: 'Failed to connect to TICKER_PLANT' };
469
+ }
470
+ }
471
+
472
+ // Increase max listeners to avoid warnings
473
+ this.tickerConn.setMaxListeners(5000);
474
+
475
+ debug('Fetching all tradeable contracts from Rithmic API...');
476
+ const contracts = await this.fetchAllFrontMonths();
477
+
478
+ if (contracts.length === 0) {
479
+ return { success: false, error: 'No tradeable contracts found' };
480
+ }
481
+
482
+ // Cache the results
483
+ this.cachedContracts = { data: contracts, timestamp: Date.now() };
484
+ return { success: true, contracts, source: 'api' };
485
+
486
+ } catch (e) {
487
+ debug('getContracts error:', e.message);
488
+ return { success: false, error: e.message };
236
489
  }
237
-
238
- const y = frontYear; // e.g., 26 for 2026
239
- const fy = `${frontYear}`; // full year string
240
-
241
- const contracts = [
242
- { symbol: `ES${frontMonth}${y}`, name: `E-mini S&P 500 (${frontMonthName} ${fy})`, exchange: 'CME' },
243
- { symbol: `NQ${frontMonth}${y}`, name: `E-mini NASDAQ-100 (${frontMonthName} ${fy})`, exchange: 'CME' },
244
- { symbol: `MES${frontMonth}${y}`, name: `Micro E-mini S&P 500 (${frontMonthName} ${fy})`, exchange: 'CME' },
245
- { symbol: `MNQ${frontMonth}${y}`, name: `Micro E-mini NASDAQ-100 (${frontMonthName} ${fy})`, exchange: 'CME' },
246
- { symbol: `RTY${frontMonth}${y}`, name: `E-mini Russell 2000 (${frontMonthName} ${fy})`, exchange: 'CME' },
247
- { symbol: `M2K${frontMonth}${y}`, name: `Micro E-mini Russell 2000 (${frontMonthName} ${fy})`, exchange: 'CME' },
248
- { symbol: `YM${frontMonth}${y}`, name: `E-mini Dow Jones (${frontMonthName} ${fy})`, exchange: 'CBOT' },
249
- { symbol: `MYM${frontMonth}${y}`, name: `Micro E-mini Dow Jones (${frontMonthName} ${fy})`, exchange: 'CBOT' },
250
- { symbol: `CL${frontMonth}${y}`, name: `Crude Oil (${frontMonthName} ${fy})`, exchange: 'NYMEX' },
251
- { symbol: `MCL${frontMonth}${y}`, name: `Micro Crude Oil (${frontMonthName} ${fy})`, exchange: 'NYMEX' },
252
- { symbol: `GC${frontMonth}${y}`, name: `Gold (${frontMonthName} ${fy})`, exchange: 'COMEX' },
253
- { symbol: `MGC${frontMonth}${y}`, name: `Micro Gold (${frontMonthName} ${fy})`, exchange: 'COMEX' },
254
- { symbol: `SI${frontMonth}${y}`, name: `Silver (${frontMonthName} ${fy})`, exchange: 'COMEX' },
255
- { symbol: `NG${frontMonth}${y}`, name: `Natural Gas (${frontMonthName} ${fy})`, exchange: 'NYMEX' },
256
- ];
257
- return { success: true, contracts };
258
490
  }
259
491
 
260
492
  async searchContracts(searchText) {
@@ -296,6 +528,10 @@ class RithmicService extends EventEmitter {
296
528
  await this.pnlConn.disconnect();
297
529
  this.pnlConn = null;
298
530
  }
531
+ if (this.tickerConn) {
532
+ await this.tickerConn.disconnect();
533
+ this.tickerConn = null;
534
+ }
299
535
  this.accounts = [];
300
536
  this.accountPnL.clear();
301
537
  this.positions.clear();
@@ -303,6 +539,7 @@ class RithmicService extends EventEmitter {
303
539
  this.loginInfo = null;
304
540
  this.user = null;
305
541
  this.credentials = null;
542
+ this.cachedContracts = null;
306
543
  }
307
544
  }
308
545
 
@@ -0,0 +1,11 @@
1
+
2
+ package rti;
3
+
4
+ message RequestFrontMonthContract
5
+ {
6
+ required int32 template_id = 154467; // Template ID = 113
7
+ repeated string user_msg = 132760; // User message for tracking
8
+ optional string symbol = 110100; // Base symbol (e.g., "MNQ", "ES")
9
+ optional string exchange = 110101; // Exchange (e.g., "CME")
10
+ optional bool need_updates = 154352; // Request updates
11
+ }
@@ -0,0 +1,9 @@
1
+
2
+ package rti;
3
+
4
+ message RequestProductCodes
5
+ {
6
+ required int32 template_id = 154467; // Template ID = 111
7
+ repeated string user_msg = 132760; // User message for tracking
8
+ optional string exchange = 110101; // Exchange filter (optional)
9
+ }
@@ -0,0 +1,13 @@
1
+
2
+ package rti;
3
+
4
+ message ResponseFrontMonthContract
5
+ {
6
+ required int32 template_id = 154467; // Template ID = 114
7
+ repeated string rp_code = 132766; // Response code
8
+ repeated string user_msg = 132760; // Echo of user message
9
+ optional string symbol = 110100; // Full contract symbol (e.g., "MNQH5")
10
+ optional string exchange = 110101; // Exchange
11
+ optional string trading_symbol = 157095; // Trading symbol
12
+ optional string description = 110114; // Contract description
13
+ }
@@ -0,0 +1,12 @@
1
+
2
+ package rti;
3
+
4
+ message ResponseProductCodes
5
+ {
6
+ required int32 template_id = 154467; // Template ID = 112
7
+ repeated string rp_code = 132766; // Response code
8
+ repeated string user_msg = 132760; // Echo of user message
9
+ optional string exchange = 110101; // Exchange
10
+ optional string product_code = 110102; // Product code (e.g., "MNQ", "ES")
11
+ optional string product_name = 110103; // Product name
12
+ }
@@ -28,6 +28,19 @@ const PNL_FIELDS = {
28
28
  USECS: 150101,
29
29
  };
30
30
 
31
+ // Symbol/Contract field IDs (ResponseProductCodes, ResponseFrontMonthContract)
32
+ const SYMBOL_FIELDS = {
33
+ TEMPLATE_ID: 154467,
34
+ RP_CODE: 132766,
35
+ EXCHANGE: 110101,
36
+ PRODUCT_CODE: 110102, // Base symbol (ES, NQ, MNQ)
37
+ PRODUCT_NAME: 110103, // Product name
38
+ SYMBOL: 110100, // Full contract symbol (ESH26)
39
+ TRADING_SYMBOL: 157095, // Trading symbol
40
+ DESCRIPTION: 110114, // Contract description
41
+ USER_MSG: 132760,
42
+ };
43
+
31
44
  // Instrument PnL Position Update field IDs
32
45
  const INSTRUMENT_PNL_FIELDS = {
33
46
  TEMPLATE_ID: 154467,
@@ -377,6 +390,101 @@ class ProtobufHandler {
377
390
  }
378
391
  }
379
392
 
393
+ /**
394
+ * Decode ResponseProductCodes (template 112) - list of available symbols
395
+ */
396
+ function decodeProductCodes(buffer) {
397
+ const result = { rpCode: [] };
398
+ let offset = 0;
399
+
400
+ while (offset < buffer.length) {
401
+ try {
402
+ const [tag, tagOffset] = readVarint(buffer, offset);
403
+ const wireType = tag & 0x7;
404
+ const fieldNumber = tag >>> 3;
405
+ offset = tagOffset;
406
+
407
+ switch (fieldNumber) {
408
+ case SYMBOL_FIELDS.TEMPLATE_ID:
409
+ [result.templateId, offset] = readVarint(buffer, offset);
410
+ break;
411
+ case SYMBOL_FIELDS.RP_CODE:
412
+ let rpCode;
413
+ [rpCode, offset] = readLengthDelimited(buffer, offset);
414
+ result.rpCode.push(rpCode);
415
+ break;
416
+ case SYMBOL_FIELDS.EXCHANGE:
417
+ [result.exchange, offset] = readLengthDelimited(buffer, offset);
418
+ break;
419
+ case SYMBOL_FIELDS.PRODUCT_CODE:
420
+ [result.productCode, offset] = readLengthDelimited(buffer, offset);
421
+ break;
422
+ case SYMBOL_FIELDS.PRODUCT_NAME:
423
+ [result.productName, offset] = readLengthDelimited(buffer, offset);
424
+ break;
425
+ case SYMBOL_FIELDS.USER_MSG:
426
+ [result.userMsg, offset] = readLengthDelimited(buffer, offset);
427
+ break;
428
+ default:
429
+ offset = skipField(buffer, offset, wireType);
430
+ }
431
+ } catch (error) {
432
+ break;
433
+ }
434
+ }
435
+
436
+ return result;
437
+ }
438
+
439
+ /**
440
+ * Decode ResponseFrontMonthContract (template 114) - current tradeable contract
441
+ */
442
+ function decodeFrontMonthContract(buffer) {
443
+ const result = { rpCode: [] };
444
+ let offset = 0;
445
+
446
+ while (offset < buffer.length) {
447
+ try {
448
+ const [tag, tagOffset] = readVarint(buffer, offset);
449
+ const wireType = tag & 0x7;
450
+ const fieldNumber = tag >>> 3;
451
+ offset = tagOffset;
452
+
453
+ switch (fieldNumber) {
454
+ case SYMBOL_FIELDS.TEMPLATE_ID:
455
+ [result.templateId, offset] = readVarint(buffer, offset);
456
+ break;
457
+ case SYMBOL_FIELDS.RP_CODE:
458
+ let rpCode;
459
+ [rpCode, offset] = readLengthDelimited(buffer, offset);
460
+ result.rpCode.push(rpCode);
461
+ break;
462
+ case SYMBOL_FIELDS.SYMBOL:
463
+ [result.symbol, offset] = readLengthDelimited(buffer, offset);
464
+ break;
465
+ case SYMBOL_FIELDS.EXCHANGE:
466
+ [result.exchange, offset] = readLengthDelimited(buffer, offset);
467
+ break;
468
+ case SYMBOL_FIELDS.TRADING_SYMBOL:
469
+ [result.tradingSymbol, offset] = readLengthDelimited(buffer, offset);
470
+ break;
471
+ case SYMBOL_FIELDS.DESCRIPTION:
472
+ [result.description, offset] = readLengthDelimited(buffer, offset);
473
+ break;
474
+ case SYMBOL_FIELDS.USER_MSG:
475
+ [result.userMsg, offset] = readLengthDelimited(buffer, offset);
476
+ break;
477
+ default:
478
+ offset = skipField(buffer, offset, wireType);
479
+ }
480
+ } catch (error) {
481
+ break;
482
+ }
483
+ }
484
+
485
+ return result;
486
+ }
487
+
380
488
  // Singleton
381
489
  const proto = new ProtobufHandler();
382
490
 
@@ -384,6 +492,8 @@ module.exports = {
384
492
  proto,
385
493
  decodeAccountPnL,
386
494
  decodeInstrumentPnL,
495
+ decodeProductCodes,
496
+ decodeFrontMonthContract,
387
497
  readVarint,
388
498
  readLengthDelimited,
389
499
  skipField,