diff --git a/lib/trading/position-manager.ts b/lib/trading/position-manager.ts index 47e7863..4c2f815 100644 --- a/lib/trading/position-manager.ts +++ b/lib/trading/position-manager.ts @@ -782,10 +782,10 @@ export class PositionManager { // CRITICAL: Use trade state flags, not current price (on-chain orders filled in the past!) let exitReason: 'TP1' | 'TP2' | 'SL' | 'SOFT_SL' | 'HARD_SL' = 'SL' - // Include any previously realized profit (e.g., from TP1 partial close) - // CRITICAL: Get from original database value, not mutated in-memory value - const previouslyRealized = trade.realizedPnL - + // 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 runner P&L for this specific closure let runnerRealized = 0 let runnerProfitPercent = 0 if (!wasPhantom) { @@ -797,10 +797,13 @@ export class PositionManager { runnerRealized = (sizeForPnL * runnerProfitPercent) / 100 } - const totalRealizedPnL = previouslyRealized + runnerRealized - // DON'T mutate trade.realizedPnL here - causes compounding on re-detection! - // trade.realizedPnL = totalRealizedPnL ← REMOVED: Causes duplicate P&L bug - console.log(` Realized P&L calculation → Previous: $${previouslyRealized.toFixed(2)} | Runner: $${runnerRealized.toFixed(2)} (Δ${runnerProfitPercent.toFixed(2)}%) on $${sizeForPnL.toFixed(2)} | Total: $${totalRealizedPnL.toFixed(2)}`) + // For external closures, we DON'T know if TP1 already hit, so calculate full position P&L + // Database will have correct previouslyRealized if TP1 was hit + // For external closures, we DON'T know if TP1 already hit, so calculate full position P&L + // Database will have correct previouslyRealized if TP1 was hit + const totalRealizedPnL = runnerRealized + console.log(` External closure P&L → ${runnerProfitPercent.toFixed(2)}% on $${sizeForPnL.toFixed(2)} = $${totalRealizedPnL.toFixed(2)}`) + // Determine exit reason from trade state and P&L if (trade.tp2Hit) {