Fix pnl calculation when force closed

This commit is contained in:
2025-11-03 19:26:48 +07:00
parent b8c6f05805
commit 60035ca299
2 changed files with 54 additions and 10 deletions

View File

@@ -28,9 +28,6 @@ namespace Managing.Application.Abstractions
Task LoadLastCandle();
Task<LightSignal> CreateManualSignal(TradeDirection direction);
Task CloseTrade(LightSignal signal, Position position, Trade tradeToClose, decimal lastPrice,
bool tradeClosingPosition = false);
/// <summary>
/// Gets the current trading bot configuration.
/// </summary>

View File

@@ -760,7 +760,8 @@ public class TradingBotBase : ITradingBot
await LogInformation(
$"⏰ Time Limit Close\nClosing position due to time limit: `{Config.MaxPositionTimeHours}h` exceeded\n📈 Position Status: {profitStatus}\n💰 Entry: `${positionForSignal.Open.Price}` → Current: `${lastCandle.Close}`\n📊 Realized PNL: `${currentPnl:F2}` (`{pnlPercentage:F2}%`)");
await CloseTrade(signal, positionForSignal, positionForSignal.Open, lastCandle.Close, true);
// Force a market close: compute PnL based on current price instead of SL/TP
await CloseTrade(signal, positionForSignal, positionForSignal.Open, lastCandle.Close, true, true);
return;
}
}
@@ -1294,7 +1295,7 @@ public class TradingBotBase : ITradingBot
}
public async Task CloseTrade(LightSignal signal, Position position, Trade tradeToClose, decimal lastPrice,
bool tradeClosingPosition = false)
bool tradeClosingPosition = false, bool forceMarketClose = false)
{
await LogInformation(
$"🔧 Closing {position.OriginDirection} Trade\nTicker: `{Config.Ticker}`\nPrice: `${lastPrice}`\n📋 Type: `{tradeToClose.TradeType}`\n📊 Quantity: `{tradeToClose.Quantity:F5}`");
@@ -1315,7 +1316,7 @@ public class TradingBotBase : ITradingBot
if (!Config.IsForBacktest && quantity == 0)
{
await LogDebug($"✅ Trade already closed on exchange for position: `{position.Identifier}`");
await HandleClosedPosition(position);
await HandleClosedPosition(position, forceMarketClose ? lastPrice : (decimal?)null, forceMarketClose);
}
else
{
@@ -1340,7 +1341,7 @@ public class TradingBotBase : ITradingBot
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
}
await HandleClosedPosition(closedPosition);
await HandleClosedPosition(closedPosition, forceMarketClose ? lastPrice : (decimal?)null, forceMarketClose);
}
else
{
@@ -1356,13 +1357,13 @@ public class TradingBotBase : ITradingBot
// Trade close on exchange => Should close trade manually
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
// Ensure trade dates are properly updated even for canceled/rejected positions
await HandleClosedPosition(position);
await HandleClosedPosition(position, forceMarketClose ? lastPrice : (decimal?)null, forceMarketClose);
}
}
}
}
private async Task HandleClosedPosition(Position position)
private async Task HandleClosedPosition(Position position, decimal? forcedClosingPrice = null, bool forceMarketClose = false)
{
if (Positions.ContainsKey(position.Identifier))
{
@@ -1375,7 +1376,7 @@ public class TradingBotBase : ITradingBot
});
// For live trading on GMX, fetch the actual position history to get real PnL data
if (!Config.IsForBacktest)
if (!Config.IsForBacktest && !forceMarketClose)
{
try
{
@@ -1503,6 +1504,52 @@ public class TradingBotBase : ITradingBot
decimal closingPrice = 0;
bool pnlCalculated = false;
// If we are forcing a market close (e.g., time limit), use the provided closing price
if (forceMarketClose && forcedClosingPrice.HasValue)
{
closingPrice = forcedClosingPrice.Value;
bool isManualCloseProfitable = position.OriginDirection == TradeDirection.Long
? closingPrice > position.Open.Price
: closingPrice < position.Open.Price;
if (isManualCloseProfitable)
{
if (position.TakeProfit1 != null)
{
position.TakeProfit1.Price = closingPrice;
position.TakeProfit1.SetDate(currentCandle?.Date ?? DateTime.UtcNow);
position.TakeProfit1.SetStatus(TradeStatus.Filled);
}
if (position.StopLoss != null)
{
position.StopLoss.SetStatus(TradeStatus.Cancelled);
}
}
else
{
if (position.StopLoss != null)
{
position.StopLoss.Price = closingPrice;
position.StopLoss.SetDate(currentCandle?.Date ?? DateTime.UtcNow);
position.StopLoss.SetStatus(TradeStatus.Filled);
}
if (position.TakeProfit1 != null)
{
position.TakeProfit1.SetStatus(TradeStatus.Cancelled);
}
if (position.TakeProfit2 != null)
{
position.TakeProfit2.SetStatus(TradeStatus.Cancelled);
}
}
pnlCalculated = true;
}
if (currentCandle != null)
{
List<Candle> recentCandles = null;