diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs index 667edc9..d0ef473 100644 --- a/src/Managing.Application/Bots/TradingBot.cs +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -1181,18 +1181,109 @@ public class TradingBot : Bot, ITradingBot ? OptimizedCandles.LastOrDefault() : ExchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow); - if (currentCandle != null && position.ProfitAndLoss != null) + if (currentCandle != null) { - // Determine which trade closed the position based on realized P&L - if (position.ProfitAndLoss.Realized > 0) + // Get the last 4 candles to check if SL/TP was actually hit + var recentCandles = Config.IsForBacktest + ? OptimizedCandles.TakeLast(4).ToList() + : await ExchangeService.GetCandlesInflux(Account.Exchange, Config.Ticker, DateTime.UtcNow.AddHours(-4), Config.Timeframe); + + var minPriceRecent = recentCandles.Min(c => c.Low); + var maxPriceRecent = recentCandles.Max(c => c.High); + + bool wasStopLossHit = false; + bool wasTakeProfitHit = false; + + if (position.OriginDirection == TradeDirection.Long) { - // Profitable close = Take Profit - position.TakeProfit1.SetDate(currentCandle.Date); + // For long positions: SL hit if recent low touched SL, TP hit if recent high touched TP + wasStopLossHit = minPriceRecent <= position.StopLoss.Price; + wasTakeProfitHit = maxPriceRecent >= position.TakeProfit1.Price; } else { - // Loss or breakeven close = Stop Loss + // For short positions: SL hit if recent high touched SL, TP hit if recent low touched TP + wasStopLossHit = maxPriceRecent >= position.StopLoss.Price; + wasTakeProfitHit = minPriceRecent <= position.TakeProfit1.Price; + } + + decimal closingPrice; + + if (wasStopLossHit) + { + // Position was closed by Stop Loss execution + closingPrice = position.StopLoss.Price; position.StopLoss.SetDate(currentCandle.Date); + + Logger.LogInformation( + $"🛑 **Stop Loss Execution Confirmed**\n" + + $"Position: `{position.Identifier}`\n" + + $"SL Price: `${position.StopLoss.Price:F2}` was hit\n" + + $"Recent Low: `${minPriceRecent:F2}` | Recent High: `${maxPriceRecent:F2}`"); + } + else if (wasTakeProfitHit) + { + // Position was closed by Take Profit execution + closingPrice = position.TakeProfit1.Price; + position.TakeProfit1.SetDate(currentCandle.Date); + + Logger.LogInformation( + $"🎯 **Take Profit Execution Confirmed**\n" + + $"Position: `{position.Identifier}`\n" + + $"TP Price: `${position.TakeProfit1.Price:F2}` was hit\n" + + $"Recent Low: `${minPriceRecent:F2}` | Recent High: `${maxPriceRecent:F2}`"); + } + else + { + // Manual close or exchange close - use current market price + closingPrice = Config.IsForBacktest + ? currentCandle.Close + : ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow); + + // Determine if it was profitable to set the correct trade type for tracking + bool isManualCloseProfitable = position.OriginDirection == TradeDirection.Long + ? closingPrice > position.Open.Price + : closingPrice < position.Open.Price; + + if (isManualCloseProfitable) + { + position.TakeProfit1.SetDate(currentCandle.Date); + } + else + { + position.StopLoss.SetDate(currentCandle.Date); + } + + Logger.LogInformation( + $"✋ **Manual/Exchange Close Detected**\n" + + $"Position: `{position.Identifier}`\n" + + $"SL: `${position.StopLoss.Price:F2}` | TP: `${position.TakeProfit1.Price:F2}`\n" + + $"Recent Low: `${minPriceRecent:F2}` | Recent High: `${maxPriceRecent:F2}`\n" + + $"Closing at market price: `${closingPrice:F2}`"); + } + + // Calculate PnL based on actual closing price + var entryPrice = position.Open.Price; + var positionSize = position.Open.Quantity * position.Open.Leverage; + + decimal pnl; + if (position.OriginDirection == TradeDirection.Long) + { + pnl = (closingPrice - entryPrice) * positionSize; + } + else + { + pnl = (entryPrice - closingPrice) * positionSize; + } + + // Update position's realized PnL if not already set + if (position.ProfitAndLoss == null) + { + position.ProfitAndLoss = new ProfitAndLoss { Realized = pnl }; + } + else if (position.ProfitAndLoss.Realized == 0) + { + position.ProfitAndLoss.Realized = pnl; } }