hedgequantx 2.7.3 → 2.7.5
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/menus/dashboard.js +21 -12
- package/src/services/rithmic/index.js +38 -5
- package/src/services/rithmic/orders.js +124 -4
package/package.json
CHANGED
package/src/menus/dashboard.js
CHANGED
|
@@ -43,7 +43,7 @@ const dashboardMenu = async (service) => {
|
|
|
43
43
|
console.log(makeLine(propfirmText, 'center'));
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// Stats bar with
|
|
46
|
+
// Stats bar with aligned columns
|
|
47
47
|
const statsInfo = getCachedStats();
|
|
48
48
|
if (statsInfo) {
|
|
49
49
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
@@ -56,18 +56,27 @@ const dashboardMenu = async (service) => {
|
|
|
56
56
|
const agentDisplay = agentCount > 0 ? `${agentCount} connected` : 'disconnected';
|
|
57
57
|
const agentColor = agentCount > 0 ? chalk.green : chalk.red;
|
|
58
58
|
|
|
59
|
-
//
|
|
59
|
+
// Fixed width columns for alignment (3 columns now)
|
|
60
60
|
const icon = chalk.yellow('✔ ');
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
icon + chalk.white(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
const colWidth = Math.floor(W / 3);
|
|
62
|
+
|
|
63
|
+
const formatCol = (label, value, valueColor = chalk.white) => {
|
|
64
|
+
const text = `${label}: ${value}`;
|
|
65
|
+
const padding = colWidth - text.length - 2; // -2 for icon
|
|
66
|
+
return icon + chalk.white(label + ': ') + valueColor(value) + ' '.repeat(Math.max(0, padding));
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const col1 = formatCol('Accounts', String(statsInfo.accounts));
|
|
70
|
+
const col2 = formatCol('Balance', balStr, balColor);
|
|
71
|
+
const col3 = formatCol('AI Agents', agentDisplay, agentColor);
|
|
72
|
+
|
|
73
|
+
const statsLine = col1 + col2 + col3;
|
|
74
|
+
const statsPlainLen = statsLine.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
75
|
+
const totalPad = W - statsPlainLen;
|
|
76
|
+
const leftPad = Math.floor(totalPad / 2);
|
|
77
|
+
const rightPad = totalPad - leftPad;
|
|
78
|
+
|
|
79
|
+
console.log(chalk.cyan('║') + ' '.repeat(Math.max(0, leftPad)) + statsLine + ' '.repeat(Math.max(0, rightPad)) + chalk.cyan('║'));
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
@@ -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
|
|
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
|
-
|
|
249
|
-
|
|
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
|
-
},
|
|
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
|
};
|