diff --git a/lib/trading/position-manager.ts b/lib/trading/position-manager.ts index c95752f..661e156 100644 --- a/lib/trading/position-manager.ts +++ b/lib/trading/position-manager.ts @@ -872,54 +872,30 @@ export class PositionManager { console.log(` ⚠️ PHANTOM TRADE: Setting P&L to 0 (size mismatch >50%)`) } - // CRITICAL FIX (Nov 20, 2025): Query Drift's ACTUAL P&L instead of calculating - // Previous bug: Calculated P&L from monitoring loop price (currentPrice) - // But actual fill price can differ significantly (36% error in real case) - // Solution: Query Drift's last known unrealizedPnL before position closed - // That unrealizedPnL IS the realizedPnL once position is gone + // CRITICAL FIX (Nov 26, 2025): Calculate P&L from actual entry/exit prices + // ALWAYS use entry price vs current price with the ACTUAL position size in USD + // DO NOT rely on Drift settledPnL - it's zero for closed positions + // DO NOT use token size - use the USD notional size from when position opened let totalRealizedPnL = 0 let runnerProfitPercent = 0 if (!wasPhantom) { - // Try to get actual P&L from Drift's position data stored in trade - // If position was just closed, we can use the last unrealized P&L - // Otherwise fall back to calculation (less accurate but better than nothing) - const driftService = await initializeDriftService() - const marketConfig = getMarketConfig(trade.symbol) + // Calculate profit percentage from entry to current price + runnerProfitPercent = this.calculateProfitPercent( + trade.entryPrice, + currentPrice, + trade.direction + ) - // Check if Drift has cached P&L data for this position - try { - const userAccount = driftService.getClient().getUserAccount() - if (userAccount) { - // Get the perp position (even if closed, might still have P&L data) - const position = userAccount.perpPositions.find((p: any) => - p.marketIndex === marketConfig.driftMarketIndex - ) - - if (position) { - // Use Drift's settled P&L if available - const settledPnL = Number(position.settledPnl || 0) / 1e6 - if (Math.abs(settledPnL) > 0.01) { - totalRealizedPnL = settledPnL - runnerProfitPercent = (totalRealizedPnL / sizeForPnL) * 100 - console.log(` ✅ Using Drift's actual P&L: $${totalRealizedPnL.toFixed(2)} (settled)`) - } - } - } - } catch (driftError) { - console.error('⚠️ Failed to query Drift P&L, falling back to calculation:', driftError) - } + // CRITICAL: Use USD notional size, NOT token size + // sizeForPnL is already in USD from above calculation + totalRealizedPnL = (sizeForPnL * runnerProfitPercent) / 100 - // Fallback: Calculate from price (less accurate) - if (totalRealizedPnL === 0) { - runnerProfitPercent = this.calculateProfitPercent( - trade.entryPrice, - currentPrice, - trade.direction - ) - totalRealizedPnL = (sizeForPnL * runnerProfitPercent) / 100 - console.log(` ⚠️ Using calculated P&L (fallback): ${runnerProfitPercent.toFixed(2)}% on $${sizeForPnL.toFixed(2)} = $${totalRealizedPnL.toFixed(2)}`) - } + console.log(` 💰 P&L calculation:`) + console.log(` Entry: $${trade.entryPrice.toFixed(4)} → Exit: $${currentPrice.toFixed(4)}`) + console.log(` Profit %: ${runnerProfitPercent.toFixed(3)}%`) + console.log(` Position size: $${sizeForPnL.toFixed(2)}`) + console.log(` Realized P&L: $${totalRealizedPnL.toFixed(2)}`) } else { console.log(` Phantom trade P&L: $0.00`) }