critical: Fix P&L calculation to use Drift's actual settledPnl
- Query userAccount.perpPositions[].settledPnl from Drift SDK - Eliminates 36% calculation errors from stale monitoring prices - Real incident: Database -$101.68 vs Drift -$138.35 actual (Nov 20) - Fallback to price calculation if Drift query fails - Added initializeDriftService import to position-manager.ts - Detailed logging: '✅ Using Drift's actual P&L' or '⚠️ fallback' - Files: lib/trading/position-manager.ts lines 7, 854-900
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* Tracks active trades and manages automatic exits
|
||||
*/
|
||||
|
||||
import { getDriftService } from '../drift/client'
|
||||
import { getDriftService, initializeDriftService } from '../drift/client'
|
||||
import { closePosition } from '../drift/orders'
|
||||
import { getPythPriceMonitor, PriceUpdate } from '../pyth/price-monitor'
|
||||
import { getMergedConfig, TradingConfig, getMarketConfig } from '../../config/trading'
|
||||
@@ -851,23 +851,57 @@ export class PositionManager {
|
||||
console.log(` ⚠️ PHANTOM TRADE: Setting P&L to 0 (size mismatch >50%)`)
|
||||
}
|
||||
|
||||
// CRITICAL: Calculate P&L for THIS close only, do NOT add previouslyRealized
|
||||
// Previous bug: Added trade.realizedPnL which could be from prior detection cycles
|
||||
// This caused 10x P&L inflation when same trade detected multiple times
|
||||
// FIX: Calculate ONLY the P&L for this specific closure
|
||||
let runnerRealized = 0
|
||||
// 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
|
||||
let totalRealizedPnL = 0
|
||||
let runnerProfitPercent = 0
|
||||
if (!wasPhantom) {
|
||||
runnerProfitPercent = this.calculateProfitPercent(
|
||||
trade.entryPrice,
|
||||
currentPrice,
|
||||
trade.direction
|
||||
)
|
||||
runnerRealized = (sizeForPnL * runnerProfitPercent) / 100
|
||||
}
|
||||
|
||||
const totalRealizedPnL = runnerRealized
|
||||
console.log(` External closure P&L → ${runnerProfitPercent.toFixed(2)}% on $${sizeForPnL.toFixed(2)} = $${totalRealizedPnL.toFixed(2)}`)
|
||||
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)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)}`)
|
||||
}
|
||||
} else {
|
||||
console.log(` Phantom trade P&L: $0.00`)
|
||||
}
|
||||
|
||||
// Determine exit reason from P&L percentage and trade state
|
||||
// Use actual profit percent to determine what order filled
|
||||
|
||||
Reference in New Issue
Block a user