hedgequantx 2.9.112 → 2.9.113
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/lib/data.js +78 -32
- package/src/pages/algo/algo-executor.js +5 -16
package/package.json
CHANGED
package/src/lib/data.js
CHANGED
|
@@ -171,11 +171,12 @@ class MarketDataFeed extends EventEmitter {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
/**
|
|
174
|
-
* Get historical
|
|
174
|
+
* Get historical 1-minute bars from HISTORY_PLANT
|
|
175
|
+
* Fetches raw ticks and aggregates them into 1-minute OHLCV bars
|
|
175
176
|
* @param {string} symbol - Symbol (e.g., 'MNQH6')
|
|
176
177
|
* @param {string} exchange - Exchange (default: 'CME')
|
|
177
|
-
* @param {number} barCount - Number of bars to
|
|
178
|
-
* @returns {Promise<Array>} Array of
|
|
178
|
+
* @param {number} barCount - Number of 1-min bars to return (default: 30)
|
|
179
|
+
* @returns {Promise<Array>} Array of 1-min bar objects {timestamp, open, high, low, close, volume}
|
|
179
180
|
*/
|
|
180
181
|
async getHistoricalBars(symbol, exchange = 'CME', barCount = 30) {
|
|
181
182
|
if (!this.credentials) {
|
|
@@ -193,51 +194,55 @@ class MarketDataFeed extends EventEmitter {
|
|
|
193
194
|
await historyConn.connect(historyConfig);
|
|
194
195
|
|
|
195
196
|
return new Promise((resolve, reject) => {
|
|
196
|
-
const
|
|
197
|
+
const ticks = [];
|
|
197
198
|
let isComplete = false;
|
|
198
199
|
|
|
200
|
+
// Request more ticks to cover barCount minutes (estimate ~1500 ticks/min for MNQ)
|
|
201
|
+
const ticksToRequest = Math.min(50000, barCount * 2000);
|
|
202
|
+
|
|
199
203
|
const timeout = setTimeout(() => {
|
|
200
204
|
if (!isComplete) {
|
|
201
205
|
historyConn.disconnect();
|
|
202
|
-
//
|
|
203
|
-
|
|
206
|
+
// Aggregate what we have and return
|
|
207
|
+
const aggregatedBars = this._aggregateTicksTo1MinBars(ticks, barCount);
|
|
208
|
+
resolve(aggregatedBars);
|
|
204
209
|
}
|
|
205
|
-
},
|
|
210
|
+
}, 20000);
|
|
206
211
|
|
|
207
|
-
// Listen for
|
|
212
|
+
// Listen for tick data (template 207)
|
|
208
213
|
historyConn.on('message', (msg) => {
|
|
209
214
|
if (msg.templateId === RES.TICK_BAR_REPLAY) {
|
|
210
215
|
try {
|
|
211
216
|
const data = proto.decode('ResponseTickBarReplay', msg.data);
|
|
212
|
-
const
|
|
217
|
+
const tickObj = data.toJSON ? data.toJSON() : data;
|
|
213
218
|
|
|
214
219
|
// Check for error response
|
|
215
|
-
if (
|
|
220
|
+
if (tickObj.rpCode && tickObj.rpCode[0] !== '0') {
|
|
216
221
|
clearTimeout(timeout);
|
|
217
222
|
isComplete = true;
|
|
218
223
|
historyConn.disconnect();
|
|
219
|
-
reject(new Error(`History request failed: ${
|
|
224
|
+
reject(new Error(`History request failed: ${tickObj.rpCode.join(' - ')}`));
|
|
220
225
|
return;
|
|
221
226
|
}
|
|
222
227
|
|
|
223
|
-
// Add
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
timestamp:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
low: Number(barObj.lowPrice) || Number(barObj.closePrice),
|
|
230
|
-
close: Number(barObj.closePrice),
|
|
231
|
-
volume: Number(barObj.volume) || 1,
|
|
228
|
+
// Add tick if it has price data
|
|
229
|
+
if (tickObj.closePrice !== undefined) {
|
|
230
|
+
ticks.push({
|
|
231
|
+
timestamp: tickObj.ssboe ? tickObj.ssboe * 1000 : Date.now(),
|
|
232
|
+
price: Number(tickObj.closePrice),
|
|
233
|
+
volume: Number(tickObj.volume) || 1,
|
|
232
234
|
});
|
|
233
235
|
}
|
|
234
236
|
|
|
235
237
|
// Check if this is the final response (rpCode = ['0'])
|
|
236
|
-
if (
|
|
238
|
+
if (tickObj.rpCode && tickObj.rpCode[0] === '0' && tickObj.closePrice === undefined) {
|
|
237
239
|
clearTimeout(timeout);
|
|
238
240
|
isComplete = true;
|
|
239
241
|
historyConn.disconnect();
|
|
240
|
-
|
|
242
|
+
|
|
243
|
+
// Aggregate ticks into 1-minute bars
|
|
244
|
+
const aggregatedBars = this._aggregateTicksTo1MinBars(ticks, barCount);
|
|
245
|
+
resolve(aggregatedBars);
|
|
241
246
|
}
|
|
242
247
|
} catch (e) {
|
|
243
248
|
this.emit('debug', `History decode error: ${e.message}`);
|
|
@@ -247,25 +252,22 @@ class MarketDataFeed extends EventEmitter {
|
|
|
247
252
|
|
|
248
253
|
// Login to HISTORY_PLANT
|
|
249
254
|
historyConn.once('loggedIn', () => {
|
|
250
|
-
// Request tick bar replay - limited count, most recent bars
|
|
251
|
-
// startIndex/finishIndex are required, but userMaxCount + direction=LAST
|
|
252
|
-
// will return only the most recent barCount bars
|
|
253
255
|
const now = Math.floor(Date.now() / 1000);
|
|
254
|
-
const startTime = now - (
|
|
256
|
+
const startTime = now - (barCount * 2 * 60); // Request 2x the time needed
|
|
255
257
|
|
|
256
258
|
historyConn.send('RequestTickBarReplay', {
|
|
257
259
|
templateId: REQ.TICK_BAR_REPLAY, // 206
|
|
258
260
|
userMsg: ['hqx-history'],
|
|
259
261
|
symbol: symbol,
|
|
260
262
|
exchange: exchange,
|
|
261
|
-
barType: 1, // TICK_BAR
|
|
263
|
+
barType: 1, // TICK_BAR (only option for raw ticks)
|
|
262
264
|
barSubType: 1, // REGULAR
|
|
263
265
|
barTypeSpecifier: '1', // 1 tick per bar
|
|
264
|
-
startIndex: startTime, //
|
|
265
|
-
finishIndex: now, //
|
|
266
|
-
userMaxCount:
|
|
267
|
-
direction: 2, // LAST (most recent
|
|
268
|
-
timeOrder: 1, // FORWARDS (
|
|
266
|
+
startIndex: startTime, // Start time
|
|
267
|
+
finishIndex: now, // End time
|
|
268
|
+
userMaxCount: ticksToRequest, // Request enough ticks
|
|
269
|
+
direction: 2, // LAST (most recent first)
|
|
270
|
+
timeOrder: 1, // FORWARDS (chronological order)
|
|
269
271
|
});
|
|
270
272
|
});
|
|
271
273
|
|
|
@@ -283,6 +285,50 @@ class MarketDataFeed extends EventEmitter {
|
|
|
283
285
|
}
|
|
284
286
|
}
|
|
285
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Aggregate raw ticks into 1-minute OHLCV bars
|
|
290
|
+
* @param {Array} ticks - Array of {timestamp, price, volume}
|
|
291
|
+
* @param {number} maxBars - Maximum number of bars to return
|
|
292
|
+
* @returns {Array} Array of 1-min bars {timestamp, open, high, low, close, volume}
|
|
293
|
+
*/
|
|
294
|
+
_aggregateTicksTo1MinBars(ticks, maxBars) {
|
|
295
|
+
if (!ticks || ticks.length === 0) return [];
|
|
296
|
+
|
|
297
|
+
// Sort ticks by timestamp (oldest first)
|
|
298
|
+
const sortedTicks = [...ticks].sort((a, b) => a.timestamp - b.timestamp);
|
|
299
|
+
|
|
300
|
+
// Group ticks by minute
|
|
301
|
+
const barMap = new Map();
|
|
302
|
+
const barIntervalMs = 60000; // 1 minute
|
|
303
|
+
|
|
304
|
+
for (const tick of sortedTicks) {
|
|
305
|
+
const barStartTime = Math.floor(tick.timestamp / barIntervalMs) * barIntervalMs;
|
|
306
|
+
|
|
307
|
+
if (!barMap.has(barStartTime)) {
|
|
308
|
+
barMap.set(barStartTime, {
|
|
309
|
+
timestamp: barStartTime,
|
|
310
|
+
open: tick.price,
|
|
311
|
+
high: tick.price,
|
|
312
|
+
low: tick.price,
|
|
313
|
+
close: tick.price,
|
|
314
|
+
volume: tick.volume,
|
|
315
|
+
});
|
|
316
|
+
} else {
|
|
317
|
+
const bar = barMap.get(barStartTime);
|
|
318
|
+
bar.high = Math.max(bar.high, tick.price);
|
|
319
|
+
bar.low = Math.min(bar.low, tick.price);
|
|
320
|
+
bar.close = tick.price;
|
|
321
|
+
bar.volume += tick.volume;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Convert to array and sort by timestamp
|
|
326
|
+
const bars = Array.from(barMap.values()).sort((a, b) => a.timestamp - b.timestamp);
|
|
327
|
+
|
|
328
|
+
// Return only the last maxBars bars
|
|
329
|
+
return bars.slice(-maxBars);
|
|
330
|
+
}
|
|
331
|
+
|
|
286
332
|
/**
|
|
287
333
|
* Handle incoming messages from TICKER_PLANT
|
|
288
334
|
*/
|
|
@@ -409,22 +409,11 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
|
|
|
409
409
|
await marketFeed.connect(rithmicCredentials);
|
|
410
410
|
await marketFeed.subscribe(symbolCode, contract.exchange || 'CME');
|
|
411
411
|
|
|
412
|
-
//
|
|
413
|
-
// Note: HISTORY_PLANT
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const historicalBars = await marketFeed.getHistoricalBars(symbolCode, contract.exchange || 'CME', 30);
|
|
418
|
-
if (historicalBars && historicalBars.length > 0) {
|
|
419
|
-
strategy.preloadBars(contractId, historicalBars);
|
|
420
|
-
ui.addLog('system', `Loaded ${historicalBars.length} bars - ready!`);
|
|
421
|
-
} else {
|
|
422
|
-
ui.addLog('system', 'No historical data - warming up with live bars...');
|
|
423
|
-
}
|
|
424
|
-
} catch (histErr) {
|
|
425
|
-
// HISTORY_PLANT not available (common on paper accounts)
|
|
426
|
-
ui.addLog('system', 'Historical data not available - warming up with live bars...');
|
|
427
|
-
}
|
|
412
|
+
// HQX-2B strategy warmup message
|
|
413
|
+
// Note: HISTORY_PLANT returns limited ticks (10k max = ~1 second of data)
|
|
414
|
+
// which is not enough for 1-min bar aggregation. Strategy warms up with live data.
|
|
415
|
+
if (strategyId === 'hqx-2b') {
|
|
416
|
+
ui.addLog('system', 'HQX-2B warming up - needs ~3 min for first signals...');
|
|
428
417
|
}
|
|
429
418
|
} catch (e) {
|
|
430
419
|
ui.addLog('error', `Failed to connect: ${e.message}`);
|