Fix pnl calculation when force closed
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user