hedgequantx 2.7.3 → 2.7.4

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.7.3",
3
+ "version": "2.7.4",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -16,7 +16,7 @@ const {
16
16
  subscribePnLUpdates,
17
17
  getPositions,
18
18
  } = require('./accounts');
19
- const { placeOrder, cancelOrder, getOrders, getOrderHistory, closePosition } = require('./orders');
19
+ const { placeOrder, cancelOrder, getOrders, getOrderHistory, getOrderHistoryDates, getTradeHistoryFull, closePosition } = require('./orders');
20
20
  const { getContracts, searchContracts } = require('./contracts');
21
21
  const { TIMEOUTS } = require('../../config/settings');
22
22
  const { logger } = require('../../utils/logger');
@@ -229,6 +229,8 @@ class RithmicService extends EventEmitter {
229
229
  async getPositions() { return getPositions(this); }
230
230
  async getOrders() { return getOrders(this); }
231
231
  async getOrderHistory(date) { return getOrderHistory(this, date); }
232
+ async getOrderHistoryDates() { return getOrderHistoryDates(this); }
233
+ async getTradeHistoryFull(days) { return getTradeHistoryFull(this, days); }
232
234
  async placeOrder(orderData) { return placeOrder(this, orderData); }
233
235
  async cancelOrder(orderId) { return cancelOrder(this, orderId); }
234
236
  async closePosition(accountId, symbol) { return closePosition(this, accountId, symbol); }
@@ -240,24 +242,55 @@ class RithmicService extends EventEmitter {
240
242
  async getUser() { return this.user; }
241
243
 
242
244
  /**
243
- * Get trade history from captured fills
245
+ * Get trade history from Rithmic API
244
246
  * @param {string} accountId - Optional account filter
245
247
  * @param {number} days - Number of days to look back (default 30)
246
248
  */
247
249
  async getTradeHistory(accountId, days = 30) {
248
- const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
249
- let trades = this.trades.filter(t => t.timestamp >= cutoff);
250
+ // Fetch from API
251
+ const result = await getTradeHistoryFull(this, days);
250
252
 
253
+ if (!result.success) {
254
+ return { success: false, trades: [] };
255
+ }
256
+
257
+ let trades = result.trades || [];
258
+
259
+ // Filter by account if specified
251
260
  if (accountId) {
252
261
  trades = trades.filter(t => t.accountId === accountId);
253
262
  }
254
263
 
264
+ // Add timestamp from fillDate/fillTime if not present
265
+ trades = trades.map(t => ({
266
+ ...t,
267
+ timestamp: t.timestamp || this._parseDateTime(t.fillDate, t.fillTime),
268
+ }));
269
+
255
270
  // Sort by timestamp descending (newest first)
256
- trades.sort((a, b) => b.timestamp - a.timestamp);
271
+ trades.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
257
272
 
258
273
  return { success: true, trades };
259
274
  }
260
275
 
276
+ /**
277
+ * Parse Rithmic date/time to timestamp
278
+ * @private
279
+ */
280
+ _parseDateTime(dateStr, timeStr) {
281
+ if (!dateStr) return Date.now();
282
+ try {
283
+ // dateStr format: YYYYMMDD, timeStr format: HH:MM:SS
284
+ const year = dateStr.slice(0, 4);
285
+ const month = dateStr.slice(4, 6);
286
+ const day = dateStr.slice(6, 8);
287
+ const time = timeStr || '00:00:00';
288
+ return new Date(`${year}-${month}-${day}T${time}Z`).getTime();
289
+ } catch (e) {
290
+ return Date.now();
291
+ }
292
+ }
293
+
261
294
  /**
262
295
  * Get lifetime stats calculated from trade history
263
296
  */
@@ -119,9 +119,57 @@ const getOrders = async (service) => {
119
119
  };
120
120
 
121
121
  /**
122
- * Get order history
122
+ * Get available order history dates
123
+ * @param {RithmicService} service - The Rithmic service instance
124
+ * @returns {Promise<{success: boolean, dates: string[]}>}
125
+ */
126
+ const getOrderHistoryDates = async (service) => {
127
+ if (!service.orderConn || !service.loginInfo) {
128
+ return { success: false, dates: [] };
129
+ }
130
+
131
+ return new Promise((resolve) => {
132
+ const dates = [];
133
+ const timeout = setTimeout(() => {
134
+ resolve({ success: true, dates });
135
+ }, 5000);
136
+
137
+ const handler = (msg) => {
138
+ if (msg.templateId === 319 && msg.date) {
139
+ // ResponseShowOrderHistoryDates returns dates array
140
+ if (Array.isArray(msg.date)) {
141
+ dates.push(...msg.date);
142
+ } else {
143
+ dates.push(msg.date);
144
+ }
145
+ }
146
+ if (msg.templateId === 319 && msg.rpCode && msg.rpCode[0] === '0') {
147
+ clearTimeout(timeout);
148
+ service.orderConn.removeListener('message', handler);
149
+ resolve({ success: true, dates });
150
+ }
151
+ };
152
+
153
+ service.orderConn.on('message', handler);
154
+
155
+ try {
156
+ service.orderConn.send('RequestShowOrderHistoryDates', {
157
+ templateId: REQ.SHOW_ORDER_HISTORY_DATES,
158
+ userMsg: ['HQX'],
159
+ });
160
+ } catch (e) {
161
+ clearTimeout(timeout);
162
+ service.orderConn.removeListener('message', handler);
163
+ resolve({ success: false, error: e.message, dates: [] });
164
+ }
165
+ });
166
+ };
167
+
168
+ /**
169
+ * Get order history for a specific date
123
170
  * @param {RithmicService} service - The Rithmic service instance
124
171
  * @param {string} date - Date in YYYYMMDD format
172
+ * @returns {Promise<{success: boolean, orders: Array}>}
125
173
  */
126
174
  const getOrderHistory = async (service, date) => {
127
175
  if (!service.orderConn || !service.loginInfo) {
@@ -133,12 +181,46 @@ const getOrderHistory = async (service, date) => {
133
181
  return new Promise((resolve) => {
134
182
  const orders = [];
135
183
  const timeout = setTimeout(() => {
184
+ service.orderConn.removeListener('message', handler);
136
185
  resolve({ success: true, orders });
137
- }, 3000);
186
+ }, 10000);
187
+
188
+ const handler = (msg) => {
189
+ // ExchangeOrderNotification (352) contains order/fill details
190
+ if (msg.templateId === 352 && msg.data) {
191
+ try {
192
+ const notification = service.orderConn.proto.decode('ExchangeOrderNotification', msg.data);
193
+ if (notification && notification.symbol) {
194
+ orders.push({
195
+ id: notification.fillId || notification.basketId || `${Date.now()}`,
196
+ accountId: notification.accountId,
197
+ symbol: notification.symbol,
198
+ exchange: notification.exchange || 'CME',
199
+ side: notification.transactionType, // 1=BUY, 2=SELL
200
+ quantity: notification.quantity,
201
+ price: notification.price,
202
+ fillPrice: notification.fillPrice,
203
+ fillSize: notification.fillSize,
204
+ fillTime: notification.fillTime,
205
+ fillDate: notification.fillDate,
206
+ avgFillPrice: notification.avgFillPrice,
207
+ totalFillSize: notification.totalFillSize,
208
+ status: notification.status,
209
+ notifyType: notification.notifyType,
210
+ isSnapshot: notification.isSnapshot,
211
+ });
212
+ }
213
+ } catch (e) {
214
+ // Ignore decode errors
215
+ }
216
+ }
217
+ };
218
+
219
+ service.orderConn.on('message', handler);
138
220
 
139
221
  try {
140
222
  for (const acc of service.accounts) {
141
- service.orderConn.send('RequestShowOrderHistorySummary', {
223
+ service.orderConn.send('RequestShowOrderHistory', {
142
224
  templateId: REQ.SHOW_ORDER_HISTORY,
143
225
  userMsg: ['HQX'],
144
226
  fcmId: acc.fcmId || service.loginInfo.fcmId,
@@ -148,17 +230,53 @@ const getOrderHistory = async (service, date) => {
148
230
  });
149
231
  }
150
232
 
233
+ // Wait for responses
151
234
  setTimeout(() => {
152
235
  clearTimeout(timeout);
236
+ service.orderConn.removeListener('message', handler);
153
237
  resolve({ success: true, orders });
154
- }, 2000);
238
+ }, 5000);
155
239
  } catch (e) {
156
240
  clearTimeout(timeout);
241
+ service.orderConn.removeListener('message', handler);
157
242
  resolve({ success: false, error: e.message, orders: [] });
158
243
  }
159
244
  });
160
245
  };
161
246
 
247
+ /**
248
+ * Get full trade history for multiple dates
249
+ * @param {RithmicService} service - The Rithmic service instance
250
+ * @param {number} days - Number of days to fetch (default 30)
251
+ * @returns {Promise<{success: boolean, trades: Array}>}
252
+ */
253
+ const getTradeHistoryFull = async (service, days = 30) => {
254
+ if (!service.orderConn || !service.loginInfo) {
255
+ return { success: false, trades: [] };
256
+ }
257
+
258
+ // Get available dates
259
+ const { dates } = await getOrderHistoryDates(service);
260
+ if (!dates || dates.length === 0) {
261
+ return { success: true, trades: [] };
262
+ }
263
+
264
+ // Sort dates descending and limit to requested days
265
+ const sortedDates = dates.sort((a, b) => b.localeCompare(a)).slice(0, days);
266
+
267
+ const allTrades = [];
268
+
269
+ // Fetch history for each date
270
+ for (const date of sortedDates) {
271
+ const { orders } = await getOrderHistory(service, date);
272
+ // Filter only fills (notifyType 5)
273
+ const fills = orders.filter(o => o.notifyType === 5 || o.fillPrice);
274
+ allTrades.push(...fills);
275
+ }
276
+
277
+ return { success: true, trades: allTrades };
278
+ };
279
+
162
280
  /**
163
281
  * Close position (market order to flatten)
164
282
  * @param {RithmicService} service - The Rithmic service instance
@@ -188,5 +306,7 @@ module.exports = {
188
306
  cancelOrder,
189
307
  getOrders,
190
308
  getOrderHistory,
309
+ getOrderHistoryDates,
310
+ getTradeHistoryFull,
191
311
  closePosition
192
312
  };