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 +1 -1
- package/src/services/rithmic/constants.js +8 -0
- package/src/services/rithmic/index.js +281 -44
- package/src/services/rithmic/proto/request_front_month_contract.proto +11 -0
- package/src/services/rithmic/proto/request_product_codes.proto +9 -0
- package/src/services/rithmic/proto/response_front_month_contract.proto +13 -0
- package/src/services/rithmic/proto/response_product_codes.proto +12 -0
- package/src/services/rithmic/protobuf.js +110 -0
package/package.json
CHANGED
|
@@ -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
|
-
*
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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,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,
|