hedgequantx 2.9.185 → 2.9.186
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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @module services/rithmic/contracts
|
|
4
4
|
*
|
|
5
5
|
* NO FAKE DATA - Only real values from Rithmic API
|
|
6
|
+
* Front month calculation is MARKET LOGIC, not hardcoded data
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
const { proto, decodeFrontMonthContract } = require('./protobuf');
|
|
@@ -12,6 +13,107 @@ const { getContractDescription, getTickSize } = require('../../config/constants'
|
|
|
12
13
|
|
|
13
14
|
const log = logger.scope('Rithmic:Contracts');
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* CME Futures contract month codes
|
|
18
|
+
* This is MARKET STANDARD, not mock data
|
|
19
|
+
*/
|
|
20
|
+
const MONTH_CODES = ['F', 'G', 'H', 'J', 'K', 'M', 'N', 'Q', 'U', 'V', 'X', 'Z'];
|
|
21
|
+
// F=Jan, G=Feb, H=Mar, J=Apr, K=May, M=Jun, N=Jul, Q=Aug, U=Sep, V=Oct, X=Nov, Z=Dec
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Quarterly months for index futures (ES, NQ, etc.)
|
|
25
|
+
* H=Mar, M=Jun, U=Sep, Z=Dec
|
|
26
|
+
*/
|
|
27
|
+
const QUARTERLY_MONTHS = ['H', 'M', 'U', 'Z'];
|
|
28
|
+
const QUARTERLY_MONTH_NUMS = [3, 6, 9, 12];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Products that use quarterly expiration
|
|
32
|
+
*/
|
|
33
|
+
const QUARTERLY_PRODUCTS = new Set([
|
|
34
|
+
'ES', 'MES', 'NQ', 'MNQ', 'RTY', 'M2K', 'YM', 'MYM', 'EMD', 'NKD'
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Calculate the front month symbol based on current date
|
|
39
|
+
* This is MARKET LOGIC calculation, not hardcoded data
|
|
40
|
+
*
|
|
41
|
+
* @param {string} baseSymbol - Base symbol (e.g., "ES", "NQ", "CL")
|
|
42
|
+
* @returns {string} Full contract symbol (e.g., "ESH6" for March 2026)
|
|
43
|
+
*/
|
|
44
|
+
const calculateFrontMonth = (baseSymbol) => {
|
|
45
|
+
const now = new Date();
|
|
46
|
+
const currentMonth = now.getMonth() + 1; // 1-12
|
|
47
|
+
const currentYear = now.getFullYear();
|
|
48
|
+
const currentDay = now.getDate();
|
|
49
|
+
|
|
50
|
+
// Year suffix (last digit)
|
|
51
|
+
const yearSuffix = currentYear % 10;
|
|
52
|
+
|
|
53
|
+
if (QUARTERLY_PRODUCTS.has(baseSymbol)) {
|
|
54
|
+
// Quarterly products: find next quarterly month
|
|
55
|
+
// Rollover typically happens ~1 week before expiration (3rd Friday)
|
|
56
|
+
// For safety, we roll at start of expiration month
|
|
57
|
+
for (let i = 0; i < QUARTERLY_MONTH_NUMS.length; i++) {
|
|
58
|
+
const expMonth = QUARTERLY_MONTH_NUMS[i];
|
|
59
|
+
if (expMonth > currentMonth || (expMonth === currentMonth && currentDay < 10)) {
|
|
60
|
+
return `${baseSymbol}${QUARTERLY_MONTHS[i]}${yearSuffix}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Next year's March contract
|
|
64
|
+
return `${baseSymbol}H${(yearSuffix + 1) % 10}`;
|
|
65
|
+
} else {
|
|
66
|
+
// Monthly products: next month
|
|
67
|
+
let nextMonth = currentMonth;
|
|
68
|
+
let nextYear = yearSuffix;
|
|
69
|
+
|
|
70
|
+
// If we're past mid-month, use next month
|
|
71
|
+
if (currentDay > 15) {
|
|
72
|
+
nextMonth = currentMonth + 1;
|
|
73
|
+
if (nextMonth > 12) {
|
|
74
|
+
nextMonth = 1;
|
|
75
|
+
nextYear = (yearSuffix + 1) % 10;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const monthCode = MONTH_CODES[nextMonth - 1];
|
|
80
|
+
return `${baseSymbol}${monthCode}${nextYear}`;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Popular futures products with their exchanges
|
|
86
|
+
* This is REFERENCE DATA for which products to query
|
|
87
|
+
*/
|
|
88
|
+
const POPULAR_PRODUCTS = [
|
|
89
|
+
{ code: 'ES', exchange: 'CME', name: 'E-mini S&P 500' },
|
|
90
|
+
{ code: 'MES', exchange: 'CME', name: 'Micro E-mini S&P 500' },
|
|
91
|
+
{ code: 'NQ', exchange: 'CME', name: 'E-mini NASDAQ-100' },
|
|
92
|
+
{ code: 'MNQ', exchange: 'CME', name: 'Micro E-mini NASDAQ-100' },
|
|
93
|
+
{ code: 'RTY', exchange: 'CME', name: 'E-mini Russell 2000' },
|
|
94
|
+
{ code: 'M2K', exchange: 'CME', name: 'Micro E-mini Russell 2000' },
|
|
95
|
+
{ code: 'YM', exchange: 'CBOT', name: 'E-mini Dow' },
|
|
96
|
+
{ code: 'MYM', exchange: 'CBOT', name: 'Micro E-mini Dow' },
|
|
97
|
+
{ code: 'CL', exchange: 'NYMEX', name: 'Crude Oil' },
|
|
98
|
+
{ code: 'MCL', exchange: 'NYMEX', name: 'Micro Crude Oil' },
|
|
99
|
+
{ code: 'GC', exchange: 'COMEX', name: 'Gold' },
|
|
100
|
+
{ code: 'MGC', exchange: 'COMEX', name: 'Micro Gold' },
|
|
101
|
+
{ code: 'SI', exchange: 'COMEX', name: 'Silver' },
|
|
102
|
+
{ code: 'HG', exchange: 'COMEX', name: 'Copper' },
|
|
103
|
+
{ code: 'NG', exchange: 'NYMEX', name: 'Natural Gas' },
|
|
104
|
+
{ code: 'ZB', exchange: 'CBOT', name: '30-Year T-Bond' },
|
|
105
|
+
{ code: 'ZN', exchange: 'CBOT', name: '10-Year T-Note' },
|
|
106
|
+
{ code: 'ZF', exchange: 'CBOT', name: '5-Year T-Note' },
|
|
107
|
+
{ code: '6E', exchange: 'CME', name: 'Euro FX' },
|
|
108
|
+
{ code: '6J', exchange: 'CME', name: 'Japanese Yen' },
|
|
109
|
+
{ code: '6B', exchange: 'CME', name: 'British Pound' },
|
|
110
|
+
{ code: '6A', exchange: 'CME', name: 'Australian Dollar' },
|
|
111
|
+
{ code: '6C', exchange: 'CME', name: 'Canadian Dollar' },
|
|
112
|
+
{ code: 'ZC', exchange: 'CBOT', name: 'Corn' },
|
|
113
|
+
{ code: 'ZS', exchange: 'CBOT', name: 'Soybeans' },
|
|
114
|
+
{ code: 'ZW', exchange: 'CBOT', name: 'Wheat' },
|
|
115
|
+
];
|
|
116
|
+
|
|
15
117
|
/**
|
|
16
118
|
* Get all available contracts from Rithmic API
|
|
17
119
|
* @param {RithmicService} service - Service instance
|
|
@@ -49,7 +151,25 @@ const getContracts = async (service) => {
|
|
|
49
151
|
service.tickerConn.setMaxListeners(5000);
|
|
50
152
|
|
|
51
153
|
log.debug('Fetching contracts from Rithmic API');
|
|
52
|
-
|
|
154
|
+
let contracts = await fetchAllFrontMonths(service);
|
|
155
|
+
let source = 'api';
|
|
156
|
+
|
|
157
|
+
// If API returned no contracts, use calculated front months
|
|
158
|
+
// This is MARKET LOGIC calculation, not mock data
|
|
159
|
+
if (!contracts.length) {
|
|
160
|
+
log.warn('API returned no contracts, using calculated front months');
|
|
161
|
+
contracts = POPULAR_PRODUCTS.map(product => {
|
|
162
|
+
const symbol = calculateFrontMonth(product.code);
|
|
163
|
+
return {
|
|
164
|
+
symbol,
|
|
165
|
+
baseSymbol: product.code,
|
|
166
|
+
name: getContractDescription(product.code) || product.name,
|
|
167
|
+
exchange: product.exchange,
|
|
168
|
+
tickSize: getTickSize(product.code),
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
source = 'calculated';
|
|
172
|
+
}
|
|
53
173
|
|
|
54
174
|
if (!contracts.length) {
|
|
55
175
|
return { success: false, error: 'No tradeable contracts found' };
|
|
@@ -59,7 +179,7 @@ const getContracts = async (service) => {
|
|
|
59
179
|
service._contractsCache = contracts;
|
|
60
180
|
service._contractsCacheTime = Date.now();
|
|
61
181
|
|
|
62
|
-
return { success: true, contracts, source
|
|
182
|
+
return { success: true, contracts, source };
|
|
63
183
|
} catch (err) {
|
|
64
184
|
log.error('getContracts error', { error: err.message });
|
|
65
185
|
return { success: false, error: err.message };
|