From 4996bc2aaddf5ba56c29a71f854c611366b79d2c Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Fri, 7 Nov 2025 14:53:03 +0100 Subject: [PATCH] Fix SHORT position P&L calculation bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL BUG FIX: SHORT positions were calculating P&L with inverted logic, causing profits to be recorded as losses and vice versa. Problem Example: - SHORT at $156.58, exit at $154.66 (price dropped $1.92) - Should be +~$25 profit - Was recorded as -$499.23 LOSS Root Cause: Old formula: profitPercent = (exit - entry) / entry * (side === 'long' ? 1 : -1) This multiplied the LONG formula by -1 for shorts, but then applied it to full notional instead of properly accounting for direction. Fix: - LONG: priceDiff = (exit - entry) โ†’ profit when price rises - SHORT: priceDiff = (entry - exit) โ†’ profit when price falls - profitPercent = priceDiff / entry * 100 - Proper leverage calculation: realizedPnL = collateral * profitPercent * leverage This fixes both dry-run and live close position calculations in lib/drift/orders.ts Impact: All SHORT trades since bot launch have incorrect P&L in database. Future trades will calculate correctly. --- lib/drift/orders.ts | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/drift/orders.ts b/lib/drift/orders.ts index 458e9ff..ccd09e8 100644 --- a/lib/drift/orders.ts +++ b/lib/drift/orders.ts @@ -527,10 +527,18 @@ export async function closePosition( console.log('๐Ÿงช DRY RUN MODE: Simulating close order (not executing on blockchain)') // Calculate realized P&L with leverage (default 10x in dry run) - const profitPercent = ((oraclePrice - position.entryPrice) / position.entryPrice) * 100 * (position.side === 'long' ? 1 : -1) + // For LONG: profit when exit > entry โ†’ (exit - entry) / entry + // For SHORT: profit when exit < entry โ†’ (entry - exit) / entry + const priceDiff = position.side === 'long' + ? (oraclePrice - position.entryPrice) // Long: profit when price rises + : (position.entryPrice - oraclePrice) // Short: profit when price falls + + const profitPercent = (priceDiff / position.entryPrice) * 100 const closedNotional = sizeToClose * oraclePrice - const realizedPnL = (closedNotional * profitPercent) / 100 - const accountPnLPercent = profitPercent * 10 // display using default leverage + const leverage = 10 + const collateral = closedNotional / leverage + const realizedPnL = collateral * (profitPercent / 100) * leverage + const accountPnLPercent = profitPercent * leverage const mockTxSig = `DRY_RUN_CLOSE_${Date.now()}_${Math.random().toString(36).substring(7)}` @@ -578,8 +586,13 @@ export async function closePosition( console.log('โœ… Transaction confirmed on-chain') // Calculate realized P&L with leverage - // CRITICAL: P&L must account for leverage and be calculated on USD notional, not base asset size - const profitPercent = ((oraclePrice - position.entryPrice) / position.entryPrice) * 100 * (position.side === 'long' ? 1 : -1) + // For LONG: profit when exit > entry โ†’ (exit - entry) / entry + // For SHORT: profit when exit < entry โ†’ (entry - exit) / entry + const priceDiff = position.side === 'long' + ? (oraclePrice - position.entryPrice) // Long: profit when price rises + : (position.entryPrice - oraclePrice) // Short: profit when price falls + + const profitPercent = (priceDiff / position.entryPrice) * 100 // Get leverage from user account (defaults to 10x if not found) let leverage = 10 @@ -593,9 +606,10 @@ export async function closePosition( console.log('โš ๏ธ Could not determine leverage from account, using 10x default') } - // Calculate closed notional value (USD) + // Calculate closed notional value (USD) and actual P&L with leverage const closedNotional = sizeToClose * oraclePrice - const realizedPnL = (closedNotional * profitPercent) / 100 + const collateral = closedNotional / leverage + const realizedPnL = collateral * (profitPercent / 100) * leverage // Leveraged P&L const accountPnLPercent = profitPercent * leverage console.log(`๐Ÿ’ฐ Close details:`)