diff --git a/lib/trading/position-manager.ts b/lib/trading/position-manager.ts index 9797ae6..5f7ca8a 100644 --- a/lib/trading/position-manager.ts +++ b/lib/trading/position-manager.ts @@ -775,8 +775,59 @@ export class PositionManager { } } + // CRITICAL FIX (Nov 20, 2025): Check if price hit TP2 BEFORE external closure detection + // This activates trailing stop even if position fully closes before we detect TP2 + if (trade.tp1Hit && !trade.tp2Hit && !trade.closingInProgress) { + const reachedTP2 = this.shouldTakeProfit2(currentPrice, trade) + if (reachedTP2) { + // Calculate profit percent for logging + const profitPercent = this.calculateProfitPercent( + trade.entryPrice, + currentPrice, + trade.direction + ) + + console.log(`🎊 TP2 PRICE REACHED: ${trade.symbol} at ${profitPercent.toFixed(2)}%`) + console.log(` Activating trailing stop for runner protection`) + trade.tp2Hit = true + trade.trailingStopActive = true + + // Initialize peak price for trailing if not set + if (trade.peakPrice === 0 || + (trade.direction === 'long' && currentPrice > trade.peakPrice) || + (trade.direction === 'short' && currentPrice < trade.peakPrice)) { + trade.peakPrice = currentPrice + } + + // Save state + await this.saveTradeState(trade) + } + } + if ((position === null || position.size === 0) && !trade.closingInProgress) { + // CRITICAL FIX (Nov 20, 2025): If TP1 already hit, this is RUNNER closure + // We should have been monitoring with trailing stop active + // Check if we should have had trailing stop protection + if (trade.tp1Hit && !trade.tp2Hit) { + console.log(`⚠️ RUNNER CLOSED EXTERNALLY: ${trade.symbol}`) + console.log(` TP1 hit: true, TP2 hit: false`) + console.log(` This runner should have had trailing stop protection!`) + console.log(` Likely cause: Monitoring detected full closure before TP2 price check`) + + // Check if price reached TP2 - if so, trailing should have been active + const reachedTP2 = trade.direction === 'long' + ? currentPrice >= (trade.tp2Price || 0) + : currentPrice <= (trade.tp2Price || 0) + + if (reachedTP2) { + console.log(` ⚠️ Price reached TP2 ($${trade.tp2Price?.toFixed(4)}) but tp2Hit was false!`) + console.log(` Trailing stop should have been active but wasn't`) + } else { + console.log(` Runner hit SL before reaching TP2 ($${trade.tp2Price?.toFixed(4)})`) + } + } + // CRITICAL: Use original position size for P&L calculation on external closures // trade.currentSize may already be 0 if on-chain orders closed the position before // Position Manager detected it, causing zero P&L bug @@ -818,11 +869,30 @@ export class PositionManager { const totalRealizedPnL = runnerRealized console.log(` External closure P&L → ${runnerProfitPercent.toFixed(2)}% on $${sizeForPnL.toFixed(2)} = $${totalRealizedPnL.toFixed(2)}`) - // Determine exit reason from P&L percentage + // Determine exit reason from P&L percentage and trade state // Use actual profit percent to determine what order filled let exitReason: 'TP1' | 'TP2' | 'SL' | 'SOFT_SL' | 'HARD_SL' = 'SL' - if (runnerProfitPercent > 0.3) { + // CRITICAL (Nov 20, 2025): Check if trailing stop was active + // If so, this is a trailing stop exit, not regular SL + if (trade.tp2Hit && trade.trailingStopActive) { + console.log(` 🏃 Runner closed with TRAILING STOP active`) + console.log(` Peak price: $${trade.peakPrice.toFixed(4)}, Current: $${currentPrice.toFixed(4)}`) + + // Check if price dropped from peak (trailing stop hit) + const isPullback = trade.direction === 'long' + ? currentPrice < trade.peakPrice * 0.99 // More than 1% below peak + : currentPrice > trade.peakPrice * 1.01 // More than 1% above peak + + if (isPullback) { + exitReason = 'SL' // Trailing stop counts as SL + console.log(` ✅ Confirmed: Trailing stop hit (pulled back from peak)`) + } else { + // Very close to peak - might be emergency close or manual + exitReason = 'TP2' // Give credit for reaching runner profit target + console.log(` ✅ Closed near peak - counting as TP2`) + } + } else if (runnerProfitPercent > 0.3) { // Positive profit - was a TP order if (runnerProfitPercent >= 1.2) { // Large profit (>1.2%) - TP2 range