Fix pnl calculation when force closed
This commit is contained in:
@@ -28,9 +28,6 @@ namespace Managing.Application.Abstractions
|
|||||||
Task LoadLastCandle();
|
Task LoadLastCandle();
|
||||||
Task<LightSignal> CreateManualSignal(TradeDirection direction);
|
Task<LightSignal> CreateManualSignal(TradeDirection direction);
|
||||||
|
|
||||||
Task CloseTrade(LightSignal signal, Position position, Trade tradeToClose, decimal lastPrice,
|
|
||||||
bool tradeClosingPosition = false);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current trading bot configuration.
|
/// Gets the current trading bot configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -760,7 +760,8 @@ public class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
await LogInformation(
|
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}%`)");
|
$"⏰ 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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1294,7 +1295,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task CloseTrade(LightSignal signal, Position position, Trade tradeToClose, decimal lastPrice,
|
public async Task CloseTrade(LightSignal signal, Position position, Trade tradeToClose, decimal lastPrice,
|
||||||
bool tradeClosingPosition = false)
|
bool tradeClosingPosition = false, bool forceMarketClose = false)
|
||||||
{
|
{
|
||||||
await LogInformation(
|
await LogInformation(
|
||||||
$"🔧 Closing {position.OriginDirection} Trade\nTicker: `{Config.Ticker}`\nPrice: `${lastPrice}`\n📋 Type: `{tradeToClose.TradeType}`\n📊 Quantity: `{tradeToClose.Quantity:F5}`");
|
$"🔧 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)
|
if (!Config.IsForBacktest && quantity == 0)
|
||||||
{
|
{
|
||||||
await LogDebug($"✅ Trade already closed on exchange for position: `{position.Identifier}`");
|
await LogDebug($"✅ Trade already closed on exchange for position: `{position.Identifier}`");
|
||||||
await HandleClosedPosition(position);
|
await HandleClosedPosition(position, forceMarketClose ? lastPrice : (decimal?)null, forceMarketClose);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1340,7 +1341,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
|
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
|
||||||
}
|
}
|
||||||
|
|
||||||
await HandleClosedPosition(closedPosition);
|
await HandleClosedPosition(closedPosition, forceMarketClose ? lastPrice : (decimal?)null, forceMarketClose);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1356,13 +1357,13 @@ public class TradingBotBase : ITradingBot
|
|||||||
// Trade close on exchange => Should close trade manually
|
// Trade close on exchange => Should close trade manually
|
||||||
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
|
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
|
||||||
// Ensure trade dates are properly updated even for canceled/rejected positions
|
// 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))
|
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
|
// For live trading on GMX, fetch the actual position history to get real PnL data
|
||||||
if (!Config.IsForBacktest)
|
if (!Config.IsForBacktest && !forceMarketClose)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1503,6 +1504,52 @@ public class TradingBotBase : ITradingBot
|
|||||||
decimal closingPrice = 0;
|
decimal closingPrice = 0;
|
||||||
bool pnlCalculated = false;
|
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)
|
if (currentCandle != null)
|
||||||
{
|
{
|
||||||
List<Candle> recentCandles = null;
|
List<Candle> recentCandles = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user