diff --git a/.env b/.env index 791c2ab..eedb4a5 100644 --- a/.env +++ b/.env @@ -388,7 +388,7 @@ NEW_RELIC_LICENSE_KEY= # - PHASE_2_COMPLETE_REPORT.md - Feature summary USE_TRAILING_STOP=true -TRAILING_STOP_PERCENT=0.3 +TRAILING_STOP_PERCENT=0.5 TRAILING_STOP_ACTIVATION=0.4 MIN_QUALITY_SCORE=60 SOLANA_ENABLED=true @@ -411,5 +411,5 @@ TRAILING_STOP_MIN_PERCENT=0.25 TRAILING_STOP_MAX_PERCENT=0.9 USE_PERCENTAGE_SIZE=false -BREAKEVEN_TRIGGER_PERCENT=0 +BREAKEVEN_TRIGGER_PERCENT=0.4 ATR_MULTIPLIER_FOR_TP2=2 \ No newline at end of file diff --git a/app/api/trading/execute/route.ts b/app/api/trading/execute/route.ts index 186dea5..e5ee998 100644 --- a/app/api/trading/execute/route.ts +++ b/app/api/trading/execute/route.ts @@ -620,6 +620,9 @@ export async function POST(request: NextRequest): Promise { tp2Price: tp2Price, emergencyStopPrice: emergencyStopPrice, currentSize: positionSizeUSD, + originalPositionSize: positionSizeUSD, // Store original size for P&L + takeProfitPrice1: tp1Price, + takeProfitPrice2: tp2Price, tp1Hit: false, tp2Hit: false, slMovedToBreakeven: false, diff --git a/app/api/trading/test-db/route.ts b/app/api/trading/test-db/route.ts index 9a236f1..db294b7 100644 --- a/app/api/trading/test-db/route.ts +++ b/app/api/trading/test-db/route.ts @@ -170,6 +170,9 @@ export async function POST(request: NextRequest): Promise { + console.log(`๐Ÿ‘ค Processing manual closure for ${trade.symbol}`) + + // Determine exit reason based on price levels + let exitReason: 'TP1' | 'TP2' | 'SL' | 'SOFT_SL' | 'HARD_SL' | 'manual' | 'emergency' = 'manual' + const profitPercent = this.calculateProfitPercent(trade.entryPrice, currentPrice, trade.direction) + + // Check if price is at TP2 or SL levels + const isAtTP2 = this.isPriceAtTarget(currentPrice, trade.takeProfitPrice2 || 0) + const isAtSL = this.isPriceAtTarget(currentPrice, trade.stopLossPrice || 0) + + if (isAtTP2 && trade.tp1Hit) { + exitReason = 'TP2' + console.log(`โœ… Manual closure was TP2 (price at target)`) + } else if (isAtSL) { + exitReason = 'SL' + console.log(`๐Ÿ›‘ Manual closure was SL (price at target)`) + } else { + console.log(`๐Ÿ‘ค Manual closure confirmed (price not at any target)`) + console.log(` Current: $${currentPrice.toFixed(4)}, TP1: $${trade.takeProfitPrice1?.toFixed(4)}, TP2: $${trade.takeProfitPrice2?.toFixed(4)}, SL: $${trade.stopLossPrice?.toFixed(4)}`) + } + + // CRITICAL: Calculate P&L using originalPositionSize for accuracy + const realizedPnL = (trade.originalPositionSize * profitPercent) / 100 + console.log(`๐Ÿ’ฐ Manual close P&L: ${profitPercent.toFixed(2)}% on $${trade.originalPositionSize.toFixed(2)} = $${realizedPnL.toFixed(2)}`) + + // Remove from monitoring FIRST (prevent race conditions) + this.activeTrades.delete(trade.id) + + // Update database + try { + await updateTradeExit({ + positionId: trade.positionId, + exitPrice: currentPrice, + exitReason, + realizedPnL, + exitOrderTx: 'Manual closure detected', + holdTimeSeconds: Math.floor((Date.now() - trade.entryTime) / 1000), + maxDrawdown: Math.abs(Math.min(0, trade.maxAdverseExcursion)), + maxGain: Math.max(0, trade.maxFavorableExcursion), + maxFavorableExcursion: trade.maxFavorableExcursion, + maxAdverseExcursion: trade.maxAdverseExcursion, + maxFavorablePrice: trade.maxFavorablePrice, + maxAdversePrice: trade.maxAdversePrice, + }) + + console.log(`โœ… Manual closure recorded: ${trade.symbol} ${exitReason} P&L: $${realizedPnL.toFixed(2)}`) + + // Send Telegram notification + await sendPositionClosedNotification({ + symbol: trade.symbol, + direction: trade.direction, + entryPrice: trade.entryPrice, + exitPrice: currentPrice, + positionSize: trade.originalPositionSize, + realizedPnL, + exitReason, + holdTimeSeconds: Math.floor((Date.now() - trade.entryTime) / 1000), + maxGain: Math.max(0, trade.maxFavorableExcursion), + maxDrawdown: Math.abs(Math.min(0, trade.maxAdverseExcursion)), + }) + } catch (error) { + console.error('โŒ Failed to save manual closure:', error) + } + + if (this.activeTrades.size === 0) { + this.stopMonitoring() + } + } + /** * Add a new trade to monitor */ @@ -295,15 +379,17 @@ export class PositionManager { private async handleExternalClosure(trade: ActiveTrade, reason: string): Promise { console.log(`๐Ÿงน Handling external closure: ${trade.symbol} (${reason})`) - // Calculate approximate P&L using last known price + // CRITICAL: Calculate P&L using originalPositionSize for accuracy + // currentSize may be stale if Drift propagation was interrupted const profitPercent = this.calculateProfitPercent( trade.entryPrice, trade.lastPrice, trade.direction ) - const estimatedPnL = (trade.currentSize * profitPercent) / 100 + const sizeForPnL = trade.originalPositionSize // Use original, not currentSize + const estimatedPnL = (sizeForPnL * profitPercent) / 100 - console.log(`๐Ÿ’ฐ Estimated P&L: ${profitPercent.toFixed(2)}% โ†’ $${estimatedPnL.toFixed(2)}`) + console.log(`๐Ÿ’ฐ Estimated P&L: ${profitPercent.toFixed(2)}% on $${sizeForPnL.toFixed(2)} โ†’ $${estimatedPnL.toFixed(2)}`) // Remove from monitoring FIRST to prevent race conditions const tradeId = trade.id @@ -507,8 +593,21 @@ export class PositionManager { const reductionPercent = ((trackedSizeUSD - positionSizeUSD) / trade.positionSize) * 100 if (!trade.tp1Hit && reductionPercent >= (this.config.takeProfit1SizePercent * 0.8)) { - // TP1 fired (should be ~75% reduction) - console.log(`๐ŸŽฏ TP1 detected as filled! Reduction: ${reductionPercent.toFixed(1)}%`) + // CRITICAL: Validate price is actually at TP1 before marking as TP1 hit + const isPriceAtTP1 = this.isPriceAtTarget(currentPrice, trade.takeProfitPrice1 || 0, 0.002) + + if (!isPriceAtTP1) { + console.log(`โš ๏ธ Size reduction detected (${reductionPercent.toFixed(1)}%) but price NOT at TP1`) + console.log(` Current: $${currentPrice.toFixed(4)}, TP1: $${trade.takeProfitPrice1?.toFixed(4) || 'N/A'}`) + console.log(` This is likely a MANUAL CLOSE or external order, not TP1`) + + // Handle as external closure with proper exit reason detection + await this.handleManualClosure(trade, currentPrice, positionSizeUSD) + return + } + + // TP1 fired (price validated at target) + console.log(`๐ŸŽฏ TP1 detected as filled! Reduction: ${reductionPercent.toFixed(1)}%, price at TP1 target`) trade.tp1Hit = true trade.currentSize = positionSizeUSD @@ -660,9 +759,9 @@ export class PositionManager { // Position Manager detected it, causing zero P&L bug // HOWEVER: If this was a phantom trade (extreme size mismatch), set P&L to 0 // CRITICAL FIX: Use tp1Hit flag to determine which size to use for P&L calculation - // - If tp1Hit=false: First closure, calculate on full position size + // - If tp1Hit=false: First closure, calculate on full position size (use originalPositionSize) // - If tp1Hit=true: Runner closure, calculate on tracked remaining size - const sizeForPnL = trade.tp1Hit ? trade.currentSize : trade.positionSize + const sizeForPnL = trade.tp1Hit ? trade.currentSize : trade.originalPositionSize // Check if this was a phantom trade by looking at the last known on-chain size // If last on-chain size was <50% of expected, this is a phantom @@ -683,6 +782,7 @@ export class PositionManager { // Include any previously realized profit (e.g., from TP1 partial close) const previouslyRealized = trade.realizedPnL + let runnerRealized = 0 let runnerProfitPercent = 0 if (!wasPhantom) { @@ -693,10 +793,10 @@ export class PositionManager { ) runnerRealized = (sizeForPnL * runnerProfitPercent) / 100 } - + const totalRealizedPnL = previouslyRealized + runnerRealized trade.realizedPnL = totalRealizedPnL - console.log(` Realized P&L snapshot โ†’ Previous: $${previouslyRealized.toFixed(2)} | Runner: $${runnerRealized.toFixed(2)} (ฮ”${runnerProfitPercent.toFixed(2)}%) | Total: $${totalRealizedPnL.toFixed(2)}`) + console.log(` Realized P&L snapshot โ†’ Previous: $${previouslyRealized.toFixed(2)} | Runner: $${runnerRealized.toFixed(2)} (ฮ”${runnerProfitPercent.toFixed(2)}%) on $${sizeForPnL.toFixed(2)} | Total: $${totalRealizedPnL.toFixed(2)}`) // Determine exit reason from trade state and P&L if (trade.tp2Hit) { @@ -1324,6 +1424,16 @@ export class PositionManager { } } + /** + * Check if current price is at a target price within tolerance + * Used to validate TP/SL hits vs manual closes + */ + private isPriceAtTarget(currentPrice: number, targetPrice: number, tolerance: number = 0.002): boolean { + if (!targetPrice || targetPrice === 0) return false + const diff = Math.abs(currentPrice - targetPrice) / targetPrice + return diff <= tolerance + } + private shouldStopLoss(price: number, trade: ActiveTrade): boolean { if (trade.direction === 'long') { return price <= trade.stopLossPrice