hedgequantx 1.8.33 → 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/index.js +175 -95
package/package.json
CHANGED
|
@@ -9,49 +9,12 @@ 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 } = require('./protobuf');
|
|
12
|
+
const { decodeFrontMonthContract, readVarint, readLengthDelimited, skipField } = require('./protobuf');
|
|
13
13
|
|
|
14
14
|
// Debug mode
|
|
15
15
|
const DEBUG = process.env.HQX_DEBUG === '1';
|
|
16
16
|
const debug = (...args) => DEBUG && console.log('[Rithmic:Service]', ...args);
|
|
17
17
|
|
|
18
|
-
// Base symbols for futures contracts
|
|
19
|
-
const BASE_SYMBOLS = {
|
|
20
|
-
// Equity Index (Quarterly: H, M, U, Z)
|
|
21
|
-
quarterly: [
|
|
22
|
-
{ base: 'ES', name: 'E-mini S&P 500', exchange: 'CME', category: 'Index' },
|
|
23
|
-
{ base: 'NQ', name: 'E-mini NASDAQ-100', exchange: 'CME', category: 'Index' },
|
|
24
|
-
{ base: 'YM', name: 'E-mini Dow Jones', exchange: 'CBOT', category: 'Index' },
|
|
25
|
-
{ base: 'RTY', name: 'E-mini Russell 2000', exchange: 'CME', category: 'Index' },
|
|
26
|
-
{ base: 'MES', name: 'Micro E-mini S&P 500', exchange: 'CME', category: 'Micro Index' },
|
|
27
|
-
{ base: 'MNQ', name: 'Micro E-mini NASDAQ-100', exchange: 'CME', category: 'Micro Index' },
|
|
28
|
-
{ base: 'MYM', name: 'Micro E-mini Dow Jones', exchange: 'CBOT', category: 'Micro Index' },
|
|
29
|
-
{ base: 'M2K', name: 'Micro E-mini Russell 2000', exchange: 'CME', category: 'Micro Index' },
|
|
30
|
-
// Currencies (Quarterly)
|
|
31
|
-
{ base: '6E', name: 'Euro FX', exchange: 'CME', category: 'Currency' },
|
|
32
|
-
{ base: 'M6E', name: 'Micro Euro FX', exchange: 'CME', category: 'Currency' },
|
|
33
|
-
{ base: '6B', name: 'British Pound', exchange: 'CME', category: 'Currency' },
|
|
34
|
-
{ base: '6J', name: 'Japanese Yen', exchange: 'CME', category: 'Currency' },
|
|
35
|
-
{ base: '6A', name: 'Australian Dollar', exchange: 'CME', category: 'Currency' },
|
|
36
|
-
{ base: '6C', name: 'Canadian Dollar', exchange: 'CME', category: 'Currency' },
|
|
37
|
-
// Bonds (Quarterly)
|
|
38
|
-
{ base: 'ZB', name: '30-Year T-Bond', exchange: 'CBOT', category: 'Bonds' },
|
|
39
|
-
{ base: 'ZN', name: '10-Year T-Note', exchange: 'CBOT', category: 'Bonds' },
|
|
40
|
-
{ base: 'ZF', name: '5-Year T-Note', exchange: 'CBOT', category: 'Bonds' },
|
|
41
|
-
{ base: 'ZT', name: '2-Year T-Note', exchange: 'CBOT', category: 'Bonds' },
|
|
42
|
-
],
|
|
43
|
-
// Energy & Metals (Monthly)
|
|
44
|
-
monthly: [
|
|
45
|
-
{ base: 'CL', name: 'Crude Oil WTI', exchange: 'NYMEX', category: 'Energy' },
|
|
46
|
-
{ base: 'MCL', name: 'Micro Crude Oil', exchange: 'NYMEX', category: 'Energy' },
|
|
47
|
-
{ base: 'NG', name: 'Natural Gas', exchange: 'NYMEX', category: 'Energy' },
|
|
48
|
-
{ base: 'GC', name: 'Gold', exchange: 'COMEX', category: 'Metals' },
|
|
49
|
-
{ base: 'MGC', name: 'Micro Gold', exchange: 'COMEX', category: 'Metals' },
|
|
50
|
-
{ base: 'SI', name: 'Silver', exchange: 'COMEX', category: 'Metals' },
|
|
51
|
-
{ base: 'HG', name: 'Copper', exchange: 'COMEX', category: 'Metals' },
|
|
52
|
-
],
|
|
53
|
-
};
|
|
54
|
-
|
|
55
18
|
// PropFirm configurations - NO FAKE DATA
|
|
56
19
|
const PROPFIRM_CONFIGS = {
|
|
57
20
|
'apex': { name: 'Apex Trader Funding', systemName: 'Apex', gateway: RITHMIC_ENDPOINTS.CHICAGO },
|
|
@@ -293,39 +256,31 @@ class RithmicService extends EventEmitter {
|
|
|
293
256
|
}
|
|
294
257
|
|
|
295
258
|
/**
|
|
296
|
-
* Get front month
|
|
259
|
+
* Get front month contracts for multiple symbols at once - batch request
|
|
297
260
|
*/
|
|
298
|
-
async
|
|
261
|
+
async getFrontMonthsBatch(products) {
|
|
299
262
|
if (!this.tickerConn) {
|
|
300
|
-
|
|
301
|
-
throw new Error('Not logged in - cannot fetch front month');
|
|
302
|
-
}
|
|
303
|
-
const connected = await this.connectTicker(this.credentials.username, this.credentials.password);
|
|
304
|
-
if (!connected) {
|
|
305
|
-
throw new Error('Failed to connect to TICKER_PLANT');
|
|
306
|
-
}
|
|
263
|
+
throw new Error('TICKER_PLANT not connected');
|
|
307
264
|
}
|
|
308
265
|
|
|
309
|
-
return new Promise((resolve
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
}, 10000);
|
|
266
|
+
return new Promise((resolve) => {
|
|
267
|
+
const results = new Map();
|
|
268
|
+
const pending = new Set(products.map(p => p.productCode));
|
|
313
269
|
|
|
314
270
|
const handler = (msg) => {
|
|
315
271
|
if (msg.templateId === 114) { // ResponseFrontMonthContract
|
|
316
272
|
const decoded = decodeFrontMonthContract(msg.data);
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
273
|
+
const baseSymbol = decoded.userMsg;
|
|
274
|
+
|
|
275
|
+
if (pending.has(baseSymbol)) {
|
|
276
|
+
pending.delete(baseSymbol);
|
|
320
277
|
|
|
321
|
-
if (decoded.rpCode[0] === '0') {
|
|
322
|
-
|
|
278
|
+
if (decoded.rpCode[0] === '0' && decoded.tradingSymbol) {
|
|
279
|
+
results.set(baseSymbol, {
|
|
280
|
+
symbol: decoded.tradingSymbol,
|
|
323
281
|
baseSymbol: baseSymbol,
|
|
324
|
-
|
|
325
|
-
exchange: decoded.exchange || exchange,
|
|
282
|
+
exchange: decoded.exchange,
|
|
326
283
|
});
|
|
327
|
-
} else {
|
|
328
|
-
reject(new Error(`API error for ${baseSymbol}: ${decoded.rpCode.join(' ')}`));
|
|
329
284
|
}
|
|
330
285
|
}
|
|
331
286
|
}
|
|
@@ -333,18 +288,166 @@ class RithmicService extends EventEmitter {
|
|
|
333
288
|
|
|
334
289
|
this.tickerConn.on('message', handler);
|
|
335
290
|
|
|
336
|
-
// Send
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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'],
|
|
342
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);
|
|
343
445
|
});
|
|
344
446
|
}
|
|
345
447
|
|
|
346
448
|
/**
|
|
347
|
-
* Get all available contracts - REAL DATA from Rithmic API
|
|
449
|
+
* Get all available contracts - 100% REAL DATA from Rithmic API
|
|
450
|
+
* Fetches product codes and front months directly from the API
|
|
348
451
|
*/
|
|
349
452
|
async getContracts() {
|
|
350
453
|
// Return cached if available and fresh (5 min cache)
|
|
@@ -366,37 +469,14 @@ class RithmicService extends EventEmitter {
|
|
|
366
469
|
}
|
|
367
470
|
}
|
|
368
471
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
debug(`Fetching front months for ${allSymbols.length} symbols...`);
|
|
373
|
-
|
|
374
|
-
// Fetch front months in parallel batches
|
|
375
|
-
const batchSize = 10;
|
|
376
|
-
for (let i = 0; i < allSymbols.length; i += batchSize) {
|
|
377
|
-
const batch = allSymbols.slice(i, i + batchSize);
|
|
378
|
-
const promises = batch.map(async (sym) => {
|
|
379
|
-
try {
|
|
380
|
-
const result = await this.getFrontMonth(sym.base, sym.exchange);
|
|
381
|
-
return {
|
|
382
|
-
symbol: result.symbol,
|
|
383
|
-
name: `${sym.name} (${result.symbol})`,
|
|
384
|
-
exchange: result.exchange,
|
|
385
|
-
category: sym.category,
|
|
386
|
-
baseSymbol: sym.base,
|
|
387
|
-
};
|
|
388
|
-
} catch (e) {
|
|
389
|
-
debug(`Failed to get front month for ${sym.base}: ${e.message}`);
|
|
390
|
-
return null;
|
|
391
|
-
}
|
|
392
|
-
});
|
|
472
|
+
// Increase max listeners to avoid warnings
|
|
473
|
+
this.tickerConn.setMaxListeners(5000);
|
|
393
474
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
475
|
+
debug('Fetching all tradeable contracts from Rithmic API...');
|
|
476
|
+
const contracts = await this.fetchAllFrontMonths();
|
|
397
477
|
|
|
398
478
|
if (contracts.length === 0) {
|
|
399
|
-
return { success: false, error: 'No contracts
|
|
479
|
+
return { success: false, error: 'No tradeable contracts found' };
|
|
400
480
|
}
|
|
401
481
|
|
|
402
482
|
// Cache the results
|