hedgequantx 2.6.40 → 2.6.42

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.6.40",
3
+ "version": "2.6.42",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -362,6 +362,33 @@ const launchAlgo = async (service, account, contract, config) => {
362
362
  });
363
363
  }
364
364
 
365
+ // ═══════════════════════════════════════════════════════════════════════════
366
+ // REAL-TIME P&L VIA WEBSOCKET (like R Trader)
367
+ // Listen to pnlUpdate events from Rithmic PNL_PLANT WebSocket
368
+ // ═══════════════════════════════════════════════════════════════════════════
369
+ const rithmicAccountId = account.rithmicAccountId || account.accountId;
370
+
371
+ service.on('pnlUpdate', (pnlData) => {
372
+ // Only update for our account
373
+ if (pnlData.accountId !== rithmicAccountId) return;
374
+
375
+ // Calculate P&L exactly like R Trader: openPositionPnl + closedPositionPnl
376
+ const openPnl = parseFloat(pnlData.openPositionPnl || 0);
377
+ const closedPnl = parseFloat(pnlData.closedPositionPnl || 0);
378
+
379
+ // R Trader "Today's P&L" = unrealized + realized
380
+ stats.pnl = openPnl + closedPnl;
381
+ });
382
+
383
+ // Also listen to position updates for real-time position tracking
384
+ service.on('positionUpdate', (pos) => {
385
+ if (!pos || pos.accountId !== rithmicAccountId) return;
386
+ if (pos.symbol !== symbolName && !pos.symbol?.includes(symbolName.replace(/[A-Z]\d+$/, ''))) return;
387
+
388
+ // Update current position from WebSocket
389
+ currentPosition = pos.quantity || 0;
390
+ });
391
+
365
392
  // Initialize AI Strategy Supervisor - agents observe, learn & optimize
366
393
  // Only if user enabled AI in config
367
394
  let aiAgentCount = 0;
@@ -579,9 +606,11 @@ const launchAlgo = async (service, account, contract, config) => {
579
606
  tps++;
580
607
  const latencyStart = Date.now();
581
608
 
582
- // Debug: log first tick to see structure
609
+ // Debug: log first tick and confirm data flow after 5s
583
610
  if (tickCount === 1) {
584
611
  algoLogger.info(ui, 'FIRST TICK', `price=${tick.price} bid=${tick.bid} ask=${tick.ask} vol=${tick.volume || tick.size || 0}`);
612
+ } else if (tickCount === 100) {
613
+ algoLogger.info(ui, 'DATA FLOWING', `100 ticks received - market data OK`);
585
614
  }
586
615
 
587
616
  // Feed tick to strategy
@@ -625,8 +654,8 @@ const launchAlgo = async (service, account, contract, config) => {
625
654
 
626
655
  stats.latency = Date.now() - latencyStart;
627
656
 
628
- // Heartbeat every 30 seconds - show strategy model values
629
- if (Date.now() - lastHeartbeat > 30000) {
657
+ // Heartbeat every 15 seconds - show strategy model values
658
+ if (Date.now() - lastHeartbeat > 15000) {
630
659
  // Try to get model values from strategy
631
660
  const modelValues = strategy.getModelValues?.() || strategy.getModelValues?.(contractId) || null;
632
661
 
@@ -638,10 +667,10 @@ const launchAlgo = async (service, account, contract, config) => {
638
667
  const mom = (modelValues.momentum || 0).toFixed(1);
639
668
  const signals = modelValues.signalCount || 0;
640
669
  const deltaStr = delta >= 0 ? `+${delta}` : `${delta}`;
641
- algoLogger.info(ui, strategyName, `OFI=${ofi} Z=${zscore} Mom=${mom} Δ=${deltaStr} | ${signals} signals | ${tps} t/30s`);
670
+ algoLogger.info(ui, strategyName, `OFI=${ofi} Z=${zscore} Mom=${mom} Δ=${deltaStr} | ${tps} t/15s | ${tickCount} total`);
642
671
  } else {
643
672
  // Fallback
644
- algoLogger.info(ui, 'SCANNING', `${tps} ticks/30s | ${tickCount} total | price=${tickData.price}`);
673
+ algoLogger.info(ui, 'SCANNING', `${tps} ticks/15s | ${tickCount} total | price=${tickData.price}`);
645
674
  }
646
675
  lastHeartbeat = Date.now();
647
676
  tps = 0;
@@ -714,16 +743,16 @@ const launchAlgo = async (service, account, contract, config) => {
714
743
  algoLogger.error(ui, 'CONNECTION ERROR', e.message.substring(0, 50));
715
744
  }
716
745
 
717
- // Poll account P&L and sync with real trades from API
746
+ // Poll account P&L and sync with real trades from API (fallback, WebSocket is primary)
718
747
  const pollPnL = async () => {
719
748
  try {
720
- // Get account P&L
749
+ // Get account P&L (fallback if WebSocket not updating)
721
750
  const accountResult = await service.getTradingAccounts();
722
751
  if (accountResult.success && accountResult.accounts) {
723
752
  const acc = accountResult.accounts.find(a => a.accountId === account.accountId);
724
- if (acc && acc.profitAndLoss !== undefined) {
725
- if (startingPnL === null) startingPnL = acc.profitAndLoss;
726
- stats.pnl = acc.profitAndLoss - startingPnL;
753
+ if (acc && acc.profitAndLoss !== undefined && acc.profitAndLoss !== null) {
754
+ // Use day P&L directly (same as R Trader)
755
+ stats.pnl = acc.profitAndLoss;
727
756
  }
728
757
  }
729
758
 
@@ -826,8 +855,9 @@ const launchAlgo = async (service, account, contract, config) => {
826
855
  };
827
856
 
828
857
  // Start polling and UI refresh
858
+ // UI refreshes every 250ms, P&L polling every 10s (WebSocket is primary for P&L)
829
859
  const refreshInterval = setInterval(() => { if (running) ui.render(stats); }, 250);
830
- const pnlInterval = setInterval(() => { if (running) pollPnL(); }, 2000);
860
+ const pnlInterval = setInterval(() => { if (running) pollPnL(); }, 10000);
831
861
  pollPnL(); // Initial poll
832
862
 
833
863
  // Keyboard handler
@@ -115,15 +115,22 @@ const getTradingAccounts = async (service) => {
115
115
  const closedPnL = pnlData.closedPositionPnl ? parseFloat(pnlData.closedPositionPnl) : null;
116
116
  const dayPnL = pnlData.dayPnl ? parseFloat(pnlData.dayPnl) : null;
117
117
 
118
+ // Calculate P&L like R Trader: openPositionPnl + closedPositionPnl
119
+ // This matches what R Trader shows as "Today's P&L"
120
+ const totalPnL = (openPnL !== null || closedPnL !== null)
121
+ ? (openPnL || 0) + (closedPnL || 0)
122
+ : null;
123
+
118
124
  return {
119
125
  accountId: hashAccountId(acc.accountId),
120
126
  rithmicAccountId: acc.accountId,
121
127
  accountName: acc.accountId, // Never expose real name - only account ID
122
128
  name: acc.accountId, // Never expose real name - only account ID
123
129
  balance: accountBalance,
124
- profitAndLoss: dayPnL !== null ? dayPnL : (openPnL !== null || closedPnL !== null ? (openPnL || 0) + (closedPnL || 0) : null),
130
+ profitAndLoss: totalPnL, // Same as R Trader: open + closed
125
131
  openPnL: openPnL,
126
- todayPnL: closedPnL,
132
+ closedPnL: closedPnL,
133
+ dayPnL: dayPnL, // Keep for reference
127
134
  status: 0,
128
135
  platform: 'Rithmic',
129
136
  propfirm: service.propfirm.name,