hedgequantx 2.6.88 → 2.6.90
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
|
@@ -399,84 +399,68 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
399
399
|
}
|
|
400
400
|
|
|
401
401
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
402
|
-
// REAL-TIME P&L VIA WEBSOCKET
|
|
403
|
-
//
|
|
402
|
+
// REAL-TIME P&L VIA WEBSOCKET
|
|
403
|
+
// - Rithmic: Uses EventEmitter (service.on) for real-time updates
|
|
404
|
+
// - ProjectX: Uses HTTP polling (handled in pollPnL function below)
|
|
404
405
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
405
406
|
const rithmicAccountId = account.rithmicAccountId || account.accountId;
|
|
407
|
+
const serviceHasEvents = typeof service.on === 'function';
|
|
406
408
|
|
|
407
|
-
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// Net Liquidation Value (balance + openPnl) - same as R Trader
|
|
436
|
-
if (pnlData.netLiquidation !== undefined) {
|
|
437
|
-
stats.netLiquidation = parseFloat(pnlData.netLiquidation);
|
|
438
|
-
} else if (stats.balance !== null) {
|
|
439
|
-
// Calculate if not provided directly
|
|
440
|
-
stats.netLiquidation = stats.balance + (stats.openPnl || 0);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// Total P&L = openPnl (from positionUpdate) + closedPnl (from pnlUpdate)
|
|
444
|
-
// This matches R Trader's "Today's P&L" calculation
|
|
445
|
-
stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
// Listen to position updates for real-time position tracking (like R Trader)
|
|
449
|
-
// INSTRUMENT_PNL_UPDATE (450) provides per-instrument P&L in real-time
|
|
450
|
-
service.on('positionUpdate', (pos) => {
|
|
451
|
-
if (!pos || pos.accountId !== rithmicAccountId) return;
|
|
452
|
-
if (pos.symbol !== symbolName && !pos.symbol?.includes(symbolName.replace(/[A-Z]\d+$/, ''))) return;
|
|
453
|
-
|
|
454
|
-
// Update position info (like R Trader Positions panel)
|
|
455
|
-
const wasFlat = stats.position === 0;
|
|
456
|
-
stats.position = pos.quantity || 0;
|
|
457
|
-
stats.entryPrice = pos.averagePrice || 0;
|
|
458
|
-
currentPosition = pos.quantity || 0;
|
|
459
|
-
|
|
460
|
-
// Track entry time when opening a new position
|
|
461
|
-
if (wasFlat && stats.position !== 0) {
|
|
462
|
-
stats.entryTime = Date.now();
|
|
463
|
-
} else if (stats.position === 0) {
|
|
464
|
-
stats.entryTime = null;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// CRITICAL: Update Open P&L from instrument-level data (real-time, same as R Trader)
|
|
468
|
-
// pos.openPnl comes from INSTRUMENT_PNL_UPDATE (450) - this is the unrealized P&L
|
|
469
|
-
if (pos.openPnl !== undefined && pos.openPnl !== null) {
|
|
470
|
-
stats.openPnl = pos.openPnl;
|
|
471
|
-
// Recalculate total P&L whenever Open P&L changes
|
|
472
|
-
// Total P&L = openPnl + closedPnl (same formula as R Trader)
|
|
409
|
+
if (serviceHasEvents) {
|
|
410
|
+
// RITHMIC ONLY: Real-time P&L via WebSocket
|
|
411
|
+
service.on('pnlUpdate', (pnlData) => {
|
|
412
|
+
// Only update for our account
|
|
413
|
+
if (pnlData.accountId !== rithmicAccountId) return;
|
|
414
|
+
|
|
415
|
+
// ACCOUNT_PNL_UPDATE (451) - All R Trader account-level metrics
|
|
416
|
+
if (pnlData.closedPositionPnl !== undefined) {
|
|
417
|
+
stats.closedPnl = parseFloat(pnlData.closedPositionPnl);
|
|
418
|
+
}
|
|
419
|
+
if (pnlData.accountBalance !== undefined) {
|
|
420
|
+
stats.balance = parseFloat(pnlData.accountBalance);
|
|
421
|
+
}
|
|
422
|
+
if (pnlData.availableBuyingPower !== undefined) {
|
|
423
|
+
stats.buyingPower = parseFloat(pnlData.availableBuyingPower);
|
|
424
|
+
}
|
|
425
|
+
if (pnlData.marginBalance !== undefined) {
|
|
426
|
+
stats.margin = parseFloat(pnlData.marginBalance);
|
|
427
|
+
}
|
|
428
|
+
if (pnlData.netLiquidation !== undefined) {
|
|
429
|
+
stats.netLiquidation = parseFloat(pnlData.netLiquidation);
|
|
430
|
+
} else if (stats.balance !== null) {
|
|
431
|
+
stats.netLiquidation = stats.balance + (stats.openPnl || 0);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Total P&L = openPnl + closedPnl (same as R Trader)
|
|
473
435
|
stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// RITHMIC ONLY: Real-time position updates
|
|
439
|
+
service.on('positionUpdate', (pos) => {
|
|
440
|
+
if (!pos || pos.accountId !== rithmicAccountId) return;
|
|
441
|
+
if (pos.symbol !== symbolName && !pos.symbol?.includes(symbolName.replace(/[A-Z]\d+$/, ''))) return;
|
|
442
|
+
|
|
443
|
+
const wasFlat = stats.position === 0;
|
|
444
|
+
stats.position = pos.quantity || 0;
|
|
445
|
+
stats.entryPrice = pos.averagePrice || 0;
|
|
446
|
+
currentPosition = pos.quantity || 0;
|
|
447
|
+
|
|
448
|
+
if (wasFlat && stats.position !== 0) {
|
|
449
|
+
stats.entryTime = Date.now();
|
|
450
|
+
} else if (stats.position === 0) {
|
|
451
|
+
stats.entryTime = null;
|
|
477
452
|
}
|
|
478
|
-
|
|
479
|
-
|
|
453
|
+
|
|
454
|
+
// Update Open P&L from INSTRUMENT_PNL_UPDATE (450)
|
|
455
|
+
if (pos.openPnl !== undefined && pos.openPnl !== null) {
|
|
456
|
+
stats.openPnl = pos.openPnl;
|
|
457
|
+
stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
|
|
458
|
+
if (stats.balance !== null) {
|
|
459
|
+
stats.netLiquidation = stats.balance + stats.openPnl;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
480
464
|
|
|
481
465
|
// Initialize AI Strategy Supervisor - agents observe, learn & optimize
|
|
482
466
|
// Only if user enabled AI in config
|
|
@@ -869,20 +853,32 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
869
853
|
algoLogger.error(ui, 'CONNECTION ERROR', e.message.substring(0, 50));
|
|
870
854
|
}
|
|
871
855
|
|
|
872
|
-
// Poll account P&L and sync with real trades from API
|
|
856
|
+
// Poll account P&L and sync with real trades from API
|
|
857
|
+
// For ProjectX: This is the PRIMARY source of P&L data (no WebSocket events)
|
|
858
|
+
// For Rithmic: This is a fallback (WebSocket events are primary)
|
|
873
859
|
const pollPnL = async () => {
|
|
874
860
|
try {
|
|
875
|
-
// Get account P&L
|
|
861
|
+
// Get account P&L
|
|
876
862
|
const accountResult = await service.getTradingAccounts();
|
|
877
863
|
if (accountResult.success && accountResult.accounts) {
|
|
878
864
|
const acc = accountResult.accounts.find(a => a.accountId === account.accountId);
|
|
879
|
-
if (acc
|
|
880
|
-
//
|
|
881
|
-
|
|
865
|
+
if (acc) {
|
|
866
|
+
// Total day P&L
|
|
867
|
+
if (acc.profitAndLoss !== undefined && acc.profitAndLoss !== null) {
|
|
868
|
+
stats.pnl = acc.profitAndLoss;
|
|
869
|
+
}
|
|
870
|
+
// Closed P&L (realized)
|
|
871
|
+
if (acc.realizedPnl !== undefined) {
|
|
872
|
+
stats.closedPnl = acc.realizedPnl;
|
|
873
|
+
}
|
|
874
|
+
// Balance
|
|
875
|
+
if (acc.balance !== undefined) {
|
|
876
|
+
stats.balance = acc.balance;
|
|
877
|
+
}
|
|
882
878
|
}
|
|
883
879
|
}
|
|
884
880
|
|
|
885
|
-
// Check positions - detect when position closes
|
|
881
|
+
// Check positions - get Open P&L and detect when position closes
|
|
886
882
|
const posResult = await service.getPositions(account.accountId);
|
|
887
883
|
if (posResult.success && posResult.positions) {
|
|
888
884
|
const pos = posResult.positions.find(p => {
|
|
@@ -892,6 +888,20 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
892
888
|
|
|
893
889
|
const newPositionQty = pos?.quantity || 0;
|
|
894
890
|
|
|
891
|
+
// Update Open P&L from position (unrealized P&L)
|
|
892
|
+
if (pos && pos.unrealizedPnl !== undefined) {
|
|
893
|
+
stats.openPnl = pos.unrealizedPnl;
|
|
894
|
+
} else if (pos && pos.profitAndLoss !== undefined) {
|
|
895
|
+
stats.openPnl = pos.profitAndLoss;
|
|
896
|
+
} else if (newPositionQty === 0) {
|
|
897
|
+
stats.openPnl = 0; // Flat = no open P&L
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Recalculate total P&L if we have components
|
|
901
|
+
if (stats.openPnl !== null && stats.closedPnl !== null) {
|
|
902
|
+
stats.pnl = stats.openPnl + stats.closedPnl;
|
|
903
|
+
}
|
|
904
|
+
|
|
895
905
|
// Position just closed - cancel remaining orders and log result
|
|
896
906
|
if (lastPositionQty !== 0 && newPositionQty === 0) {
|
|
897
907
|
// Cancel all open orders to prevent new positions
|
|
@@ -244,7 +244,11 @@ function getMarketBiasLog(direction, delta, ofi) {
|
|
|
244
244
|
* Get a momentum log
|
|
245
245
|
*/
|
|
246
246
|
function getMomentumLog(momentum, zscore) {
|
|
247
|
-
|
|
247
|
+
// Handle undefined/NaN values
|
|
248
|
+
const mom = typeof momentum === 'number' && !isNaN(momentum) ? momentum : 0;
|
|
249
|
+
const z = typeof zscore === 'number' && !isNaN(zscore) ? zscore : 0;
|
|
250
|
+
|
|
251
|
+
const isUp = mom > 0;
|
|
248
252
|
const pool = isUp ? MOMENTUM_UP_MESSAGES : MOMENTUM_DOWN_MESSAGES;
|
|
249
253
|
const category = isUp ? 'mom_up' : 'mom_down';
|
|
250
254
|
const message = getVariedMessage(category, pool, isUp ? 'Momentum up' : 'Momentum down');
|
|
@@ -252,7 +256,7 @@ function getMomentumLog(momentum, zscore) {
|
|
|
252
256
|
|
|
253
257
|
return {
|
|
254
258
|
message: `${arrow} ${message}`,
|
|
255
|
-
details: `Mom=${
|
|
259
|
+
details: `Mom=${mom.toFixed(1)} | Z=${z.toFixed(2)}`,
|
|
256
260
|
};
|
|
257
261
|
}
|
|
258
262
|
|
|
@@ -260,7 +264,10 @@ function getMomentumLog(momentum, zscore) {
|
|
|
260
264
|
* Get a mean reversion log
|
|
261
265
|
*/
|
|
262
266
|
function getMeanReversionLog(zscore) {
|
|
263
|
-
|
|
267
|
+
// Handle undefined/NaN values
|
|
268
|
+
const z = typeof zscore === 'number' && !isNaN(zscore) ? zscore : 0;
|
|
269
|
+
|
|
270
|
+
const isOverbought = z > 0;
|
|
264
271
|
const pool = isOverbought ? OVERBOUGHT_MESSAGES : OVERSOLD_MESSAGES;
|
|
265
272
|
const category = isOverbought ? 'mr_overbought' : 'mr_oversold';
|
|
266
273
|
const message = getVariedMessage(category, pool, isOverbought ? 'Overbought' : 'Oversold');
|
|
@@ -268,7 +275,7 @@ function getMeanReversionLog(zscore) {
|
|
|
268
275
|
|
|
269
276
|
return {
|
|
270
277
|
message: `${arrow} ${message}`,
|
|
271
|
-
details: `Z-Score: ${
|
|
278
|
+
details: `Z-Score: ${z.toFixed(2)} | Looking for ${isOverbought ? 'SHORT' : 'LONG'}`,
|
|
272
279
|
};
|
|
273
280
|
}
|
|
274
281
|
|