hedgequantx 2.9.164 → 2.9.166
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
|
@@ -83,12 +83,18 @@ const fetchAllFrontMonths = (service) => {
|
|
|
83
83
|
throw new Error('TICKER_PLANT not connected');
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
const tickerState = service.tickerConn.connectionState;
|
|
87
|
+
const tickerConnected = service.tickerConn.isConnected;
|
|
88
|
+
log.debug('fetchAllFrontMonths starting', { tickerState, tickerConnected });
|
|
89
|
+
|
|
86
90
|
return new Promise((resolve) => {
|
|
87
91
|
const contracts = new Map();
|
|
88
92
|
const productsToCheck = new Map();
|
|
93
|
+
let msgCount = 0;
|
|
89
94
|
|
|
90
95
|
// Handler for ProductCodes responses
|
|
91
96
|
const productHandler = (msg) => {
|
|
97
|
+
msgCount++;
|
|
92
98
|
if (msg.templateId !== 112) return;
|
|
93
99
|
|
|
94
100
|
const decoded = decodeProductCodes(msg.data);
|
|
@@ -112,6 +118,7 @@ const fetchAllFrontMonths = (service) => {
|
|
|
112
118
|
|
|
113
119
|
// Handler for FrontMonth responses
|
|
114
120
|
const frontMonthHandler = (msg) => {
|
|
121
|
+
msgCount++;
|
|
115
122
|
if (msg.templateId !== 114) return;
|
|
116
123
|
|
|
117
124
|
const decoded = decodeFrontMonthContract(msg.data);
|
|
@@ -128,23 +135,36 @@ const fetchAllFrontMonths = (service) => {
|
|
|
128
135
|
service.tickerConn.on('message', frontMonthHandler);
|
|
129
136
|
|
|
130
137
|
// Request all product codes
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
log.debug('Sending RequestProductCodes');
|
|
139
|
+
try {
|
|
140
|
+
service.tickerConn.send('RequestProductCodes', {
|
|
141
|
+
templateId: 111,
|
|
142
|
+
userMsg: ['get-products'],
|
|
143
|
+
});
|
|
144
|
+
} catch (err) {
|
|
145
|
+
log.warn('Failed to send RequestProductCodes', { error: err.message });
|
|
146
|
+
}
|
|
135
147
|
|
|
136
148
|
// After timeout, request front months
|
|
137
149
|
setTimeout(() => {
|
|
138
150
|
service.tickerConn.removeListener('message', productHandler);
|
|
139
|
-
log.debug('Collected products', { count: productsToCheck.size });
|
|
151
|
+
log.debug('Collected products', { count: productsToCheck.size, totalMsgs: msgCount });
|
|
152
|
+
|
|
153
|
+
if (productsToCheck.size === 0) {
|
|
154
|
+
log.warn('No products collected - TICKER may not be working');
|
|
155
|
+
}
|
|
140
156
|
|
|
141
157
|
for (const product of productsToCheck.values()) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
158
|
+
try {
|
|
159
|
+
service.tickerConn.send('RequestFrontMonthContract', {
|
|
160
|
+
templateId: 113,
|
|
161
|
+
userMsg: [product.productCode],
|
|
162
|
+
symbol: product.productCode,
|
|
163
|
+
exchange: product.exchange,
|
|
164
|
+
});
|
|
165
|
+
} catch (err) {
|
|
166
|
+
log.warn('Failed to send RequestFrontMonthContract', { product: product.productCode, error: err.message });
|
|
167
|
+
}
|
|
148
168
|
}
|
|
149
169
|
|
|
150
170
|
// Collect results after timeout
|
|
@@ -171,7 +191,7 @@ const fetchAllFrontMonths = (service) => {
|
|
|
171
191
|
// Sort alphabetically by base symbol
|
|
172
192
|
results.sort((a, b) => a.baseSymbol.localeCompare(b.baseSymbol));
|
|
173
193
|
|
|
174
|
-
log.debug('Got contracts from API', { count: results.length });
|
|
194
|
+
log.debug('Got contracts from API', { count: results.length, totalMsgs: msgCount });
|
|
175
195
|
resolve(results);
|
|
176
196
|
}, TIMEOUTS.RITHMIC_PRODUCTS);
|
|
177
197
|
}, TIMEOUTS.RITHMIC_CONTRACTS);
|
|
@@ -342,22 +342,70 @@ class RithmicBrokerDaemon {
|
|
|
342
342
|
// Log service state for debugging
|
|
343
343
|
const hasCredentials = !!conn.service.credentials;
|
|
344
344
|
const hasTickerConn = !!conn.service.tickerConn;
|
|
345
|
-
|
|
345
|
+
const tickerState = conn.service.tickerConn?.connectionState;
|
|
346
|
+
log('DEBUG', 'getContracts request', { propfirm: payload.propfirmKey, hasCredentials, hasTickerConn, tickerState });
|
|
346
347
|
|
|
347
348
|
try {
|
|
348
349
|
const result = await conn.service.getContracts();
|
|
350
|
+
|
|
351
|
+
// Log detailed result
|
|
352
|
+
const tickerStateAfter = conn.service.tickerConn?.connectionState;
|
|
349
353
|
log('DEBUG', 'getContracts result', {
|
|
350
354
|
propfirm: payload.propfirmKey,
|
|
351
355
|
success: result.success,
|
|
352
356
|
count: result.contracts?.length || 0,
|
|
357
|
+
source: result.source,
|
|
358
|
+
tickerStateAfter,
|
|
353
359
|
error: result.error
|
|
354
360
|
});
|
|
361
|
+
|
|
362
|
+
// If no contracts found, return fallback list for common futures
|
|
363
|
+
if (!result.success || result.contracts?.length === 0) {
|
|
364
|
+
log('WARN', 'Using fallback contracts list');
|
|
365
|
+
const fallbackContracts = this._getFallbackContracts();
|
|
366
|
+
return { type: 'contracts', payload: { success: true, contracts: fallbackContracts, source: 'fallback' }, requestId };
|
|
367
|
+
}
|
|
368
|
+
|
|
355
369
|
return { type: 'contracts', payload: result, requestId };
|
|
356
370
|
} catch (err) {
|
|
357
371
|
log('ERROR', 'getContracts exception', { propfirm: payload.propfirmKey, error: err.message, stack: err.stack?.split('\n')[1] });
|
|
358
|
-
|
|
372
|
+
// Return fallback on error
|
|
373
|
+
log('WARN', 'Using fallback contracts due to error');
|
|
374
|
+
const fallbackContracts = this._getFallbackContracts();
|
|
375
|
+
return { type: 'contracts', payload: { success: true, contracts: fallbackContracts, source: 'fallback' }, requestId };
|
|
359
376
|
}
|
|
360
377
|
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Get fallback contracts list when TICKER_PLANT fails
|
|
381
|
+
* These are common futures that most prop firms support
|
|
382
|
+
*/
|
|
383
|
+
_getFallbackContracts() {
|
|
384
|
+
const now = new Date();
|
|
385
|
+
const month = now.getMonth();
|
|
386
|
+
const year = now.getFullYear();
|
|
387
|
+
|
|
388
|
+
// Determine front month code (H=Mar, M=Jun, U=Sep, Z=Dec for indices)
|
|
389
|
+
const monthCodes = ['H', 'H', 'H', 'M', 'M', 'M', 'U', 'U', 'U', 'Z', 'Z', 'Z'];
|
|
390
|
+
const frontMonth = monthCodes[month];
|
|
391
|
+
const yearCode = String(year).slice(-1);
|
|
392
|
+
const suffix = frontMonth + yearCode;
|
|
393
|
+
|
|
394
|
+
return [
|
|
395
|
+
{ symbol: `MNQ${suffix}`, baseSymbol: 'MNQ', name: 'Micro E-mini Nasdaq-100', exchange: 'CME', tickSize: 0.25 },
|
|
396
|
+
{ symbol: `MES${suffix}`, baseSymbol: 'MES', name: 'Micro E-mini S&P 500', exchange: 'CME', tickSize: 0.25 },
|
|
397
|
+
{ symbol: `NQ${suffix}`, baseSymbol: 'NQ', name: 'E-mini Nasdaq-100', exchange: 'CME', tickSize: 0.25 },
|
|
398
|
+
{ symbol: `ES${suffix}`, baseSymbol: 'ES', name: 'E-mini S&P 500', exchange: 'CME', tickSize: 0.25 },
|
|
399
|
+
{ symbol: `MCL${suffix}`, baseSymbol: 'MCL', name: 'Micro WTI Crude Oil', exchange: 'NYMEX', tickSize: 0.01 },
|
|
400
|
+
{ symbol: `MGC${suffix}`, baseSymbol: 'MGC', name: 'Micro Gold', exchange: 'COMEX', tickSize: 0.10 },
|
|
401
|
+
{ symbol: `M2K${suffix}`, baseSymbol: 'M2K', name: 'Micro E-mini Russell 2000', exchange: 'CME', tickSize: 0.10 },
|
|
402
|
+
{ symbol: `MYM${suffix}`, baseSymbol: 'MYM', name: 'Micro E-mini Dow', exchange: 'CBOT', tickSize: 0.50 },
|
|
403
|
+
{ symbol: `RTY${suffix}`, baseSymbol: 'RTY', name: 'E-mini Russell 2000', exchange: 'CME', tickSize: 0.10 },
|
|
404
|
+
{ symbol: `YM${suffix}`, baseSymbol: 'YM', name: 'E-mini Dow', exchange: 'CBOT', tickSize: 1.00 },
|
|
405
|
+
{ symbol: `CL${suffix}`, baseSymbol: 'CL', name: 'Crude Oil', exchange: 'NYMEX', tickSize: 0.01 },
|
|
406
|
+
{ symbol: `GC${suffix}`, baseSymbol: 'GC', name: 'Gold', exchange: 'COMEX', tickSize: 0.10 },
|
|
407
|
+
];
|
|
408
|
+
}
|
|
361
409
|
|
|
362
410
|
async _handleSearchContracts(payload, requestId) {
|
|
363
411
|
const conn = this.connections.get(payload.propfirmKey);
|
package/src/services/session.js
CHANGED
|
@@ -111,19 +111,38 @@ const connections = {
|
|
|
111
111
|
log.info('Connection added', { type, propfirm: propfirm || type });
|
|
112
112
|
},
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Sanitize account data to prevent corrupted data from being saved
|
|
116
|
+
*/
|
|
117
|
+
_sanitizeAccount(acc) {
|
|
118
|
+
if (!acc || typeof acc !== 'object' || !acc.accountId) return null;
|
|
119
|
+
return {
|
|
120
|
+
accountId: String(acc.accountId),
|
|
121
|
+
fcmId: acc.fcmId ? String(acc.fcmId) : undefined,
|
|
122
|
+
ibId: acc.ibId ? String(acc.ibId) : undefined,
|
|
123
|
+
accountName: acc.accountName ? String(acc.accountName) : undefined,
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
|
|
114
127
|
saveToStorage() {
|
|
115
128
|
// Load existing sessions to preserve AI agents
|
|
116
129
|
const existingSessions = storage.load();
|
|
117
130
|
const aiSessions = existingSessions.filter(s => s.type === 'ai');
|
|
118
131
|
|
|
119
132
|
// Build Rithmic sessions - INCLUDE accounts to avoid Rithmic API limit on restore
|
|
120
|
-
const rithmicSessions = this.services.map(conn =>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
133
|
+
const rithmicSessions = this.services.map(conn => {
|
|
134
|
+
// Sanitize accounts to prevent corrupted data
|
|
135
|
+
const rawAccounts = conn.service.accounts || [];
|
|
136
|
+
const accounts = rawAccounts.map(a => this._sanitizeAccount(a)).filter(Boolean);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
type: conn.type,
|
|
140
|
+
propfirm: conn.propfirm,
|
|
141
|
+
propfirmKey: conn.service.propfirmKey || conn.propfirmKey,
|
|
142
|
+
credentials: conn.service.credentials,
|
|
143
|
+
accounts, // CRITICAL: Cache sanitized accounts to avoid 2000 GetAccounts limit
|
|
144
|
+
};
|
|
145
|
+
});
|
|
127
146
|
|
|
128
147
|
// Merge: AI sessions + Rithmic sessions
|
|
129
148
|
storage.save([...aiSessions, ...rithmicSessions]);
|
|
@@ -200,8 +219,17 @@ const connections = {
|
|
|
200
219
|
if (type === 'rithmic' && session.credentials) {
|
|
201
220
|
const client = new RithmicBrokerClient(propfirmKey || 'apex_rithmic');
|
|
202
221
|
|
|
203
|
-
//
|
|
204
|
-
|
|
222
|
+
// Validate cached accounts before using
|
|
223
|
+
let validAccounts = null;
|
|
224
|
+
if (session.accounts && Array.isArray(session.accounts)) {
|
|
225
|
+
validAccounts = session.accounts
|
|
226
|
+
.map(a => this._sanitizeAccount(a))
|
|
227
|
+
.filter(Boolean);
|
|
228
|
+
if (validAccounts.length === 0) validAccounts = null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// CRITICAL: Pass validated cached accounts to avoid Rithmic's 2000 GetAccounts limit
|
|
232
|
+
const loginOptions = validAccounts ? { cachedAccounts: validAccounts } : {};
|
|
205
233
|
const result = await client.login(session.credentials.username, session.credentials.password, loginOptions);
|
|
206
234
|
|
|
207
235
|
if (result.success) {
|
|
@@ -212,7 +240,7 @@ const connections = {
|
|
|
212
240
|
propfirmKey,
|
|
213
241
|
connectedAt: new Date(),
|
|
214
242
|
});
|
|
215
|
-
log.debug('Rithmic session restored via broker', { hasCachedAccounts: !!
|
|
243
|
+
log.debug('Rithmic session restored via broker', { hasCachedAccounts: !!validAccounts, accountCount: validAccounts?.length || 0 });
|
|
216
244
|
}
|
|
217
245
|
}
|
|
218
246
|
},
|