hedgequantx 2.7.2 → 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
|
@@ -214,8 +214,7 @@ const renderHQXScore = (data) => {
|
|
|
214
214
|
* Render data source notice
|
|
215
215
|
*/
|
|
216
216
|
const renderNotice = () => {
|
|
217
|
-
|
|
218
|
-
console.log(chalk.gray(' Note: Rithmic API provides balance/P&L only. Trade history not available.'));
|
|
217
|
+
// No notice needed - all data comes from Rithmic API
|
|
219
218
|
};
|
|
220
219
|
|
|
221
220
|
module.exports = {
|
package/src/pages/stats/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const ora = require('ora');
|
|
|
11
11
|
|
|
12
12
|
const { connections } = require('../../services');
|
|
13
13
|
const { prompts } = require('../../utils');
|
|
14
|
+
const { displayBanner } = require('../../ui');
|
|
14
15
|
const { aggregateStats, calculateDerivedMetrics, calculateQuantMetrics, calculateHQXScore } = require('./metrics');
|
|
15
16
|
const { renderOverview, renderPnLMetrics, renderQuantMetrics, renderTradesHistory, renderHQXScore, renderNotice } = require('./display');
|
|
16
17
|
const { renderEquityCurve } = require('./chart');
|
|
@@ -196,6 +197,8 @@ const showStats = async (service) => {
|
|
|
196
197
|
const accountData = await aggregateAccountData(activeAccounts);
|
|
197
198
|
|
|
198
199
|
spinner.succeed('Stats loaded');
|
|
200
|
+
console.clear();
|
|
201
|
+
displayBanner();
|
|
199
202
|
console.log();
|
|
200
203
|
|
|
201
204
|
// Calculate stats from API data
|
|
@@ -36,10 +36,10 @@ const createOrderHandler = (service) => {
|
|
|
36
36
|
handleShowOrdersResponse(service, data);
|
|
37
37
|
break;
|
|
38
38
|
case STREAM.EXCHANGE_NOTIFICATION:
|
|
39
|
-
service
|
|
39
|
+
handleExchangeNotification(service, data);
|
|
40
40
|
break;
|
|
41
41
|
case STREAM.ORDER_NOTIFICATION:
|
|
42
|
-
service
|
|
42
|
+
handleOrderNotification(service, data);
|
|
43
43
|
break;
|
|
44
44
|
}
|
|
45
45
|
};
|
|
@@ -211,6 +211,93 @@ const handleInstrumentPnLUpdate = (service, data) => {
|
|
|
211
211
|
}
|
|
212
212
|
};
|
|
213
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Handle exchange order notification (fills/trades)
|
|
216
|
+
* NotifyType: 5 = FILL
|
|
217
|
+
*/
|
|
218
|
+
const handleExchangeNotification = (service, data) => {
|
|
219
|
+
try {
|
|
220
|
+
const res = proto.decode('ExchangeOrderNotification', data);
|
|
221
|
+
debug('Exchange notification:', res.notifyType, res.symbol);
|
|
222
|
+
|
|
223
|
+
// notifyType 5 = FILL (trade executed)
|
|
224
|
+
if (res.notifyType === 5 && res.fillPrice && res.fillSize) {
|
|
225
|
+
const trade = {
|
|
226
|
+
id: res.fillId || `${Date.now()}-${res.basketId}`,
|
|
227
|
+
accountId: res.accountId,
|
|
228
|
+
symbol: res.symbol,
|
|
229
|
+
exchange: res.exchange || 'CME',
|
|
230
|
+
side: res.transactionType, // 1=BUY, 2=SELL
|
|
231
|
+
price: parseFloat(res.fillPrice),
|
|
232
|
+
size: parseInt(res.fillSize),
|
|
233
|
+
fillTime: res.fillTime,
|
|
234
|
+
fillDate: res.fillDate,
|
|
235
|
+
basketId: res.basketId,
|
|
236
|
+
avgFillPrice: parseFloat(res.avgFillPrice || res.fillPrice),
|
|
237
|
+
totalFillSize: parseInt(res.totalFillSize || res.fillSize),
|
|
238
|
+
timestamp: Date.now(),
|
|
239
|
+
ssboe: res.ssboe,
|
|
240
|
+
usecs: res.usecs,
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
debug('Trade (fill) captured:', trade.symbol, trade.side === 1 ? 'BUY' : 'SELL', trade.size, '@', trade.price);
|
|
244
|
+
|
|
245
|
+
// Store in trades history
|
|
246
|
+
if (!service.trades) service.trades = [];
|
|
247
|
+
service.trades.push(trade);
|
|
248
|
+
|
|
249
|
+
// Keep max 1000 trades in memory
|
|
250
|
+
if (service.trades.length > 1000) {
|
|
251
|
+
service.trades = service.trades.slice(-1000);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
service.emit('trade', trade);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
service.emit('exchangeNotification', res);
|
|
258
|
+
} catch (e) {
|
|
259
|
+
debug('Error decoding ExchangeOrderNotification:', e.message);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Handle Rithmic order notification
|
|
265
|
+
*/
|
|
266
|
+
const handleOrderNotification = (service, data) => {
|
|
267
|
+
try {
|
|
268
|
+
const res = proto.decode('RithmicOrderNotification', data);
|
|
269
|
+
debug('Order notification:', res.notifyType, res.symbol, res.status);
|
|
270
|
+
|
|
271
|
+
// Track order status changes
|
|
272
|
+
if (res.basketId) {
|
|
273
|
+
const order = {
|
|
274
|
+
basketId: res.basketId,
|
|
275
|
+
accountId: res.accountId,
|
|
276
|
+
symbol: res.symbol,
|
|
277
|
+
exchange: res.exchange || 'CME',
|
|
278
|
+
side: res.transactionType,
|
|
279
|
+
quantity: res.quantity,
|
|
280
|
+
price: res.price,
|
|
281
|
+
status: res.status,
|
|
282
|
+
notifyType: res.notifyType,
|
|
283
|
+
avgFillPrice: res.avgFillPrice,
|
|
284
|
+
totalFillSize: res.totalFillSize,
|
|
285
|
+
totalUnfilledSize: res.totalUnfilledSize,
|
|
286
|
+
timestamp: Date.now(),
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
service.emit('orderNotification', order);
|
|
290
|
+
|
|
291
|
+
// If order is complete (notifyType 15), calculate P&L
|
|
292
|
+
if (res.notifyType === 15 && res.avgFillPrice) {
|
|
293
|
+
debug('Order complete:', res.basketId, 'avg fill:', res.avgFillPrice);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
} catch (e) {
|
|
297
|
+
debug('Error decoding RithmicOrderNotification:', e.message);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
214
301
|
module.exports = {
|
|
215
302
|
createOrderHandler,
|
|
216
303
|
createPnLHandler
|
|
@@ -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');
|
|
@@ -73,6 +73,9 @@ class RithmicService extends EventEmitter {
|
|
|
73
73
|
// Cache
|
|
74
74
|
this._contractsCache = null;
|
|
75
75
|
this._contractsCacheTime = 0;
|
|
76
|
+
|
|
77
|
+
// Trades history (captured from ExchangeOrderNotification fills)
|
|
78
|
+
this.trades = [];
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
// ==================== AUTH ====================
|
|
@@ -226,18 +229,160 @@ class RithmicService extends EventEmitter {
|
|
|
226
229
|
async getPositions() { return getPositions(this); }
|
|
227
230
|
async getOrders() { return getOrders(this); }
|
|
228
231
|
async getOrderHistory(date) { return getOrderHistory(this, date); }
|
|
232
|
+
async getOrderHistoryDates() { return getOrderHistoryDates(this); }
|
|
233
|
+
async getTradeHistoryFull(days) { return getTradeHistoryFull(this, days); }
|
|
229
234
|
async placeOrder(orderData) { return placeOrder(this, orderData); }
|
|
230
235
|
async cancelOrder(orderId) { return cancelOrder(this, orderId); }
|
|
231
236
|
async closePosition(accountId, symbol) { return closePosition(this, accountId, symbol); }
|
|
232
237
|
async getContracts() { return getContracts(this); }
|
|
233
238
|
async searchContracts(searchText) { return searchContracts(this, searchText); }
|
|
234
239
|
|
|
235
|
-
// ====================
|
|
240
|
+
// ==================== STATS & HISTORY ====================
|
|
236
241
|
|
|
237
242
|
async getUser() { return this.user; }
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get trade history from Rithmic API
|
|
246
|
+
* @param {string} accountId - Optional account filter
|
|
247
|
+
* @param {number} days - Number of days to look back (default 30)
|
|
248
|
+
*/
|
|
249
|
+
async getTradeHistory(accountId, days = 30) {
|
|
250
|
+
// Fetch from API
|
|
251
|
+
const result = await getTradeHistoryFull(this, days);
|
|
252
|
+
|
|
253
|
+
if (!result.success) {
|
|
254
|
+
return { success: false, trades: [] };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let trades = result.trades || [];
|
|
258
|
+
|
|
259
|
+
// Filter by account if specified
|
|
260
|
+
if (accountId) {
|
|
261
|
+
trades = trades.filter(t => t.accountId === accountId);
|
|
262
|
+
}
|
|
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
|
+
|
|
270
|
+
// Sort by timestamp descending (newest first)
|
|
271
|
+
trades.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
|
|
272
|
+
|
|
273
|
+
return { success: true, trades };
|
|
274
|
+
}
|
|
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
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Get lifetime stats calculated from trade history
|
|
296
|
+
*/
|
|
297
|
+
async getLifetimeStats(accountId) {
|
|
298
|
+
const { trades } = await this.getTradeHistory(accountId, 365);
|
|
299
|
+
|
|
300
|
+
if (trades.length === 0) {
|
|
301
|
+
return { success: true, stats: null };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Calculate stats from trades
|
|
305
|
+
let totalTrades = trades.length;
|
|
306
|
+
let winningTrades = 0;
|
|
307
|
+
let losingTrades = 0;
|
|
308
|
+
let totalProfit = 0;
|
|
309
|
+
let totalLoss = 0;
|
|
310
|
+
let longTrades = 0;
|
|
311
|
+
let shortTrades = 0;
|
|
312
|
+
let totalVolume = 0;
|
|
313
|
+
|
|
314
|
+
// Group fills by basketId to calculate P&L per trade
|
|
315
|
+
const tradeGroups = new Map();
|
|
316
|
+
for (const trade of trades) {
|
|
317
|
+
const key = trade.basketId || trade.id;
|
|
318
|
+
if (!tradeGroups.has(key)) {
|
|
319
|
+
tradeGroups.set(key, []);
|
|
320
|
+
}
|
|
321
|
+
tradeGroups.get(key).push(trade);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
for (const [, fills] of tradeGroups) {
|
|
325
|
+
const firstFill = fills[0];
|
|
326
|
+
totalVolume += fills.reduce((sum, f) => sum + f.size, 0);
|
|
327
|
+
|
|
328
|
+
if (firstFill.side === 1) longTrades++;
|
|
329
|
+
else if (firstFill.side === 2) shortTrades++;
|
|
330
|
+
|
|
331
|
+
// P&L calculation requires entry/exit matching which needs position tracking
|
|
332
|
+
// For now, count trades
|
|
333
|
+
totalTrades = tradeGroups.size;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const stats = {
|
|
337
|
+
totalTrades,
|
|
338
|
+
winningTrades,
|
|
339
|
+
losingTrades,
|
|
340
|
+
winRate: totalTrades > 0 ? ((winningTrades / totalTrades) * 100).toFixed(2) : 0,
|
|
341
|
+
totalProfit,
|
|
342
|
+
totalLoss,
|
|
343
|
+
netPnL: totalProfit - totalLoss,
|
|
344
|
+
longTrades,
|
|
345
|
+
shortTrades,
|
|
346
|
+
totalVolume,
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
return { success: true, stats };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Get daily stats from trade history
|
|
354
|
+
*/
|
|
355
|
+
async getDailyStats(accountId, days = 30) {
|
|
356
|
+
const { trades } = await this.getTradeHistory(accountId, days);
|
|
357
|
+
|
|
358
|
+
// Group by date
|
|
359
|
+
const dailyStats = new Map();
|
|
360
|
+
|
|
361
|
+
for (const trade of trades) {
|
|
362
|
+
const date = new Date(trade.timestamp).toISOString().slice(0, 10);
|
|
363
|
+
|
|
364
|
+
if (!dailyStats.has(date)) {
|
|
365
|
+
dailyStats.set(date, {
|
|
366
|
+
date,
|
|
367
|
+
trades: 0,
|
|
368
|
+
volume: 0,
|
|
369
|
+
buys: 0,
|
|
370
|
+
sells: 0,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const day = dailyStats.get(date);
|
|
375
|
+
day.trades++;
|
|
376
|
+
day.volume += trade.size;
|
|
377
|
+
if (trade.side === 1) day.buys++;
|
|
378
|
+
else if (trade.side === 2) day.sells++;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Convert to array and sort by date
|
|
382
|
+
const stats = Array.from(dailyStats.values()).sort((a, b) => b.date.localeCompare(a.date));
|
|
383
|
+
|
|
384
|
+
return { success: true, stats };
|
|
385
|
+
}
|
|
241
386
|
|
|
242
387
|
async getMarketStatus() {
|
|
243
388
|
const status = this.checkMarketHours();
|
|
@@ -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
|
-
},
|
|
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('
|
|
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
|
-
},
|
|
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
|
};
|