-- Fix Zero P&L Trades (CORRECTED VERSION) -- This script recalculates P&L for trades that were incorrectly recorded as $0.00 -- IMPORTANT: positionSizeUSD already includes leverage, so we must divide by leverage -- to get the actual capital at risk, then multiply by price change % -- Created: 2025-11-03 -- Backup: backup_before_pnl_fix_20251103_091248.sql -- First, let's see what we're fixing SELECT id, symbol, direction, ROUND("entryPrice"::numeric, 2) as entry, ROUND("exitPrice"::numeric, 2) as exit, "positionSizeUSD", leverage, "realizedPnL" as current_pnl, "exitReason" FROM "Trade" WHERE "realizedPnL" = 0 AND "exitReason" IS NOT NULL AND "exitPrice" IS NOT NULL AND "exitOrderTx" IN ('ON_CHAIN_ORDER', 'UNKNOWN_CLOSURE') ORDER BY "entryTime" DESC; -- CORRECT P&L Formula: -- 1. actualCapital = positionSizeUSD / leverage (remove leverage to get base capital) -- 2. priceChange% = (exitPrice - entryPrice) / entryPrice * 100 -- 3. accountReturn% = priceChange% * leverage (leverage amplifies returns) -- 4. realizedPnL = actualCapital * (accountReturn% / 100) -- -- Simplified: realizedPnL = (positionSizeUSD / leverage) * (priceChange% * leverage) / 100 -- = positionSizeUSD * priceChange% / 100 -- (leverage cancels out!) UPDATE "Trade" SET "realizedPnL" = CASE WHEN direction = 'long' THEN -- Long: profit when exit > entry ("positionSizeUSD" * ((("exitPrice" - "entryPrice") / "entryPrice") * 100)) / 100 WHEN direction = 'short' THEN -- Short: profit when exit < entry ("positionSizeUSD" * ((("entryPrice" - "exitPrice") / "entryPrice") * 100)) / 100 ELSE 0 END, "realizedPnLPercent" = CASE WHEN direction = 'long' THEN ((("exitPrice" - "entryPrice") / "entryPrice") * 100) WHEN direction = 'short' THEN ((("entryPrice" - "exitPrice") / "entryPrice") * 100) ELSE 0 END, "updatedAt" = NOW() WHERE "realizedPnL" = 0 AND "exitReason" IS NOT NULL AND "exitPrice" IS NOT NULL AND "exitOrderTx" IN ('ON_CHAIN_ORDER', 'UNKNOWN_CLOSURE'); -- Show the results after fix SELECT id, symbol, direction, ROUND("entryPrice"::numeric, 4) as entry, ROUND("exitPrice"::numeric, 4) as exit, ROUND("positionSizeUSD"::numeric, 2) as notional, leverage, ROUND(("positionSizeUSD" / leverage)::numeric, 2) as capital, ROUND("realizedPnL"::numeric, 2) as fixed_pnl, ROUND("realizedPnLPercent"::numeric, 2) as pnl_pct, "exitReason" FROM "Trade" WHERE "exitOrderTx" IN ('ON_CHAIN_ORDER', 'UNKNOWN_CLOSURE') AND "exitReason" IS NOT NULL ORDER BY "entryTime" DESC LIMIT 15; -- Show new total P&L SELECT COUNT(*) as total_trades, SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) as wins, SUM(CASE WHEN "realizedPnL" <= 0 THEN 1 ELSE 0 END) as losses, ROUND(SUM("realizedPnL")::numeric, 2) as total_pnl, ROUND(AVG(CASE WHEN "realizedPnL" > 0 THEN "realizedPnL" END)::numeric, 2) as avg_win, ROUND(AVG(CASE WHEN "realizedPnL" <= 0 THEN "realizedPnL" END)::numeric, 2) as avg_loss FROM "Trade" WHERE "entryTime" >= NOW() - INTERVAL '7 days' AND "exitReason" IS NOT NULL;