hedgequantx 2.9.248 → 2.9.249
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
|
@@ -118,6 +118,32 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
|
|
|
118
118
|
}
|
|
119
119
|
ui.addLog('system', 'Connecting to market data...');
|
|
120
120
|
|
|
121
|
+
// Listen for position updates from Rithmic (external closes, manual trades)
|
|
122
|
+
const accId = account.rithmicAccountId || account.accountId;
|
|
123
|
+
service.on('positionUpdate', (pos) => {
|
|
124
|
+
// Match by account and symbol
|
|
125
|
+
const posSymbol = pos.contractId || pos.symbol || '';
|
|
126
|
+
const matchesSymbol = posSymbol.includes(contract.name) || posSymbol.includes(contractId) ||
|
|
127
|
+
posSymbol === symbolCode || contractId.includes(posSymbol);
|
|
128
|
+
const matchesAccount = pos.accountId === accId || pos.accountId === account.accountId;
|
|
129
|
+
|
|
130
|
+
if (matchesSymbol && matchesAccount) {
|
|
131
|
+
const qty = parseInt(pos.quantity) || 0;
|
|
132
|
+
if (!isNaN(qty) && Math.abs(qty) < 1000 && qty !== currentPosition) {
|
|
133
|
+
const oldPos = currentPosition;
|
|
134
|
+
currentPosition = qty;
|
|
135
|
+
if (qty === 0 && oldPos !== 0) {
|
|
136
|
+
ui.addLog('trade', `Position closed externally (was ${oldPos})`);
|
|
137
|
+
sessionLogger.log('POSITION', `External close: ${oldPos} -> 0`);
|
|
138
|
+
pendingOrder = false; // Reset pending order flag
|
|
139
|
+
} else if (qty !== 0 && oldPos === 0) {
|
|
140
|
+
ui.addLog('trade', `Position opened externally: ${qty}`);
|
|
141
|
+
sessionLogger.log('POSITION', `External open: 0 -> ${qty}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
121
147
|
strategy.on('signal', async (signal) => {
|
|
122
148
|
const dir = signal.direction?.toUpperCase() || 'UNKNOWN';
|
|
123
149
|
const signalLog = smartLogs.getSignalLog(dir, symbolCode, (signal.confidence || 0) * 100, strategyName);
|
|
@@ -45,6 +45,9 @@ class DaemonProxyService extends EventEmitter {
|
|
|
45
45
|
|
|
46
46
|
/** @type {Object|null} Credentials for direct mode */
|
|
47
47
|
this.credentials = null;
|
|
48
|
+
|
|
49
|
+
/** @type {Map<string, Object>} Cached P&L data for daemon mode */
|
|
50
|
+
this._pnlCache = new Map();
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
/**
|
|
@@ -192,7 +195,20 @@ class DaemonProxyService extends EventEmitter {
|
|
|
192
195
|
|
|
193
196
|
client.on('orderUpdate', (data) => this.emit('orderUpdate', data));
|
|
194
197
|
client.on('positionUpdate', (data) => this.emit('positionUpdate', data));
|
|
195
|
-
|
|
198
|
+
|
|
199
|
+
// Cache P&L data for sync getAccountPnL calls
|
|
200
|
+
client.on('pnlUpdate', (data) => {
|
|
201
|
+
if (data && data.accountId) {
|
|
202
|
+
this._pnlCache.set(data.accountId, {
|
|
203
|
+
pnl: data.dayPnl ?? data.pnl ?? null,
|
|
204
|
+
openPnl: data.openPositionPnl ?? data.openPnl ?? null,
|
|
205
|
+
closedPnl: data.closedPositionPnl ?? data.closedPnl ?? null,
|
|
206
|
+
balance: data.accountBalance ?? data.balance ?? null,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
this.emit('pnlUpdate', data);
|
|
210
|
+
});
|
|
211
|
+
|
|
196
212
|
client.on('fill', (data) => this.emit('fill', data));
|
|
197
213
|
client.on('marketData', (data) => this.emit('marketData', data));
|
|
198
214
|
client.on('rithmicDisconnected', (data) => this.emit('disconnected', data));
|
|
@@ -248,8 +264,15 @@ class DaemonProxyService extends EventEmitter {
|
|
|
248
264
|
if (!this._backend) return { pnl: null, openPnl: null, closedPnl: null, balance: null };
|
|
249
265
|
|
|
250
266
|
if (this._mode === 'daemon') {
|
|
251
|
-
//
|
|
252
|
-
|
|
267
|
+
// Return cached P&L from pnlUpdate events
|
|
268
|
+
const cached = this._pnlCache.get(accountId);
|
|
269
|
+
if (cached) return cached;
|
|
270
|
+
|
|
271
|
+
// Try without account ID (some events don't include it)
|
|
272
|
+
if (this._pnlCache.size === 1) {
|
|
273
|
+
return this._pnlCache.values().next().value;
|
|
274
|
+
}
|
|
275
|
+
|
|
253
276
|
return { pnl: null, openPnl: null, closedPnl: null, balance: null };
|
|
254
277
|
}
|
|
255
278
|
return this._backend.getAccountPnL(accountId);
|
|
@@ -222,11 +222,33 @@ const handleNewOrderResponse = (service, data) => {
|
|
|
222
222
|
const res = proto.decode('ResponseNewOrder', data);
|
|
223
223
|
|
|
224
224
|
const isAccepted = res.rpCode?.[0] === '0';
|
|
225
|
-
const rejectReason = res.rpCode?.[1] || res.rqHandlerRpCode?.[1] || 'Unknown';
|
|
226
225
|
|
|
227
|
-
//
|
|
226
|
+
// Build rejection reason from rpCode array
|
|
227
|
+
// rpCode is typically ['code', 'message'] or just ['code']
|
|
228
|
+
let rejectReason = null;
|
|
228
229
|
if (!isAccepted) {
|
|
229
|
-
|
|
230
|
+
// Try to get the error message from rpCode[1] or rqHandlerRpCode[1]
|
|
231
|
+
const rpMsg = res.rpCode?.slice(1).join(' ') || '';
|
|
232
|
+
const rqMsg = res.rqHandlerRpCode?.slice(1).join(' ') || '';
|
|
233
|
+
rejectReason = rpMsg || rqMsg || null;
|
|
234
|
+
|
|
235
|
+
// If no message, interpret the code
|
|
236
|
+
if (!rejectReason && res.rpCode?.[0]) {
|
|
237
|
+
const codeMap = {
|
|
238
|
+
'1': 'Invalid request',
|
|
239
|
+
'2': 'Invalid account',
|
|
240
|
+
'3': 'Invalid symbol',
|
|
241
|
+
'4': 'Invalid quantity',
|
|
242
|
+
'5': 'Insufficient buying power',
|
|
243
|
+
'6': 'Market closed',
|
|
244
|
+
'7': 'Order rejected by risk system',
|
|
245
|
+
'8': 'Position limit exceeded',
|
|
246
|
+
'9': 'Rate limit exceeded',
|
|
247
|
+
};
|
|
248
|
+
rejectReason = codeMap[res.rpCode[0]] || `Gateway rejected (code: ${res.rpCode[0]})`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log('[Rithmic] Gateway rejection:', rejectReason, '| rpCode:', res.rpCode, '| rqHandlerRpCode:', res.rqHandlerRpCode);
|
|
230
252
|
}
|
|
231
253
|
|
|
232
254
|
const order = {
|
|
@@ -235,7 +257,7 @@ const handleNewOrderResponse = (service, data) => {
|
|
|
235
257
|
exchange: res.exchange || 'CME',
|
|
236
258
|
notifyType: isAccepted ? 1 : 6, // 1=STATUS (accepted), 6=REJECT
|
|
237
259
|
status: isAccepted ? 2 : 6,
|
|
238
|
-
text:
|
|
260
|
+
text: rejectReason,
|
|
239
261
|
rpCode: res.rpCode,
|
|
240
262
|
userMsg: res.userMsg,
|
|
241
263
|
};
|
|
@@ -378,6 +400,25 @@ const handleExchangeNotification = (service, data) => {
|
|
|
378
400
|
const res = proto.decode('ExchangeOrderNotification', data);
|
|
379
401
|
debug('Exchange notification:', res.notifyType, res.symbol, res.status);
|
|
380
402
|
|
|
403
|
+
// Build rejection text from available fields
|
|
404
|
+
// Priority: text > reportText > remarks > status code interpretation
|
|
405
|
+
let rejectText = res.text || res.reportText || res.remarks || null;
|
|
406
|
+
if (!rejectText && res.notifyType === 6) {
|
|
407
|
+
// Map common Rithmic status codes to human-readable messages
|
|
408
|
+
const statusMap = {
|
|
409
|
+
'1': 'Order pending',
|
|
410
|
+
'2': 'Order working',
|
|
411
|
+
'3': 'Order filled',
|
|
412
|
+
'4': 'Order partially filled',
|
|
413
|
+
'5': 'Order rejected by exchange',
|
|
414
|
+
'6': 'Order cancelled',
|
|
415
|
+
'7': 'Order expired',
|
|
416
|
+
'8': 'Order suspended',
|
|
417
|
+
};
|
|
418
|
+
rejectText = statusMap[res.status] || `Exchange rejected (code: ${res.status || 'unknown'})`;
|
|
419
|
+
console.log('[Rithmic] Exchange rejection:', rejectText, '| status:', res.status, '| symbol:', res.symbol);
|
|
420
|
+
}
|
|
421
|
+
|
|
381
422
|
// Emit orderNotification for placeOrder listener
|
|
382
423
|
// This is critical for order tracking
|
|
383
424
|
const orderNotif = {
|
|
@@ -392,7 +433,7 @@ const handleExchangeNotification = (service, data) => {
|
|
|
392
433
|
avgFillPrice: res.avgFillPrice ? parseFloat(res.avgFillPrice) : null,
|
|
393
434
|
totalFillSize: res.totalFillSize ? parseInt(res.totalFillSize) : null,
|
|
394
435
|
confirmedSize: res.confirmedSize ? parseInt(res.confirmedSize) : null,
|
|
395
|
-
text:
|
|
436
|
+
text: rejectText,
|
|
396
437
|
userMsg: res.userTag ? [res.userTag] : null,
|
|
397
438
|
};
|
|
398
439
|
service.emit('orderNotification', orderNotif);
|