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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.9.185",
3
+ "version": "2.9.186",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -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
- const contracts = await fetchAllFrontMonths(service);
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: 'api' };
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 };