Remove warning for backtest when signal is expired

This commit is contained in:
2025-10-10 01:35:10 +07:00
parent e45e140b41
commit a3d6dd1238
3 changed files with 69 additions and 18 deletions

View File

@@ -160,7 +160,7 @@ public class AccountService : IAccountService
await ManagePropertiesAsync(hideSecrets, getBalance, account); await ManagePropertiesAsync(hideSecrets, getBalance, account);
if (account.User != null) if (account.User == null)
{ {
account.User = await _userRepository.GetUserByNameAsync(account.User.Name); account.User = await _userRepository.GetUserByNameAsync(account.User.Name);
} }

View File

@@ -329,7 +329,8 @@ public class TradingBotBase : ITradingBot
} }
catch (Exception ex) catch (Exception ex)
{ {
await LogWarning($"Failed to update finished position {position.Identifier} in database: {ex.Message}"); await LogWarning(
$"Failed to update finished position {position.Identifier} in database: {ex.Message}");
} }
} }
@@ -1294,7 +1295,7 @@ public class TradingBotBase : ITradingBot
// Update the closing trade price if available // Update the closing trade price if available
if (gmxPosition.Open != null) if (gmxPosition.Open != null)
{ {
var closingPrice = gmxPosition.Open.Price; var gmxClosingPrice = gmxPosition.Open.Price;
// Determine which trade was the closing trade based on profitability // Determine which trade was the closing trade based on profitability
bool isProfitable = position.ProfitAndLoss.Realized > 0; bool isProfitable = position.ProfitAndLoss.Realized > 0;
@@ -1303,7 +1304,7 @@ public class TradingBotBase : ITradingBot
{ {
if (position.TakeProfit1 != null) if (position.TakeProfit1 != null)
{ {
position.TakeProfit1.SetPrice(closingPrice, 2); position.TakeProfit1.SetPrice(gmxClosingPrice, 2);
position.TakeProfit1.SetDate(gmxPosition.Open.Date); position.TakeProfit1.SetDate(gmxPosition.Open.Date);
position.TakeProfit1.SetStatus(TradeStatus.Filled); position.TakeProfit1.SetStatus(TradeStatus.Filled);
} }
@@ -1318,7 +1319,7 @@ public class TradingBotBase : ITradingBot
{ {
if (position.StopLoss != null) if (position.StopLoss != null)
{ {
position.StopLoss.SetPrice(closingPrice, 2); position.StopLoss.SetPrice(gmxClosingPrice, 2);
position.StopLoss.SetDate(gmxPosition.Open.Date); position.StopLoss.SetDate(gmxPosition.Open.Date);
position.StopLoss.SetStatus(TradeStatus.Filled); position.StopLoss.SetStatus(TradeStatus.Filled);
} }
@@ -1338,7 +1339,7 @@ public class TradingBotBase : ITradingBot
Logger.LogInformation( Logger.LogInformation(
$"📊 Position Reconciliation Complete\n" + $"📊 Position Reconciliation Complete\n" +
$"Position: `{position.Identifier}`\n" + $"Position: `{position.Identifier}`\n" +
$"Closing Price: `${closingPrice:F2}`\n" + $"Closing Price: `${gmxClosingPrice:F2}`\n" +
$"Used: `{(isProfitable ? "Take Profit" : "Stop Loss")}`\n" + $"Used: `{(isProfitable ? "Take Profit" : "Stop Loss")}`\n" +
$"PnL from GMX: `${position.ProfitAndLoss.Realized:F2}`"); $"PnL from GMX: `${position.ProfitAndLoss.Realized:F2}`");
} }
@@ -1359,6 +1360,10 @@ public class TradingBotBase : ITradingBot
} }
} }
// Calculate P&L for backtests even if currentCandle is null
decimal closingPrice = 0;
bool pnlCalculated = false;
if (currentCandle != null) if (currentCandle != null)
{ {
List<Candle> recentCandles = null; List<Candle> recentCandles = null;
@@ -1428,8 +1433,6 @@ public class TradingBotBase : ITradingBot
wasTakeProfitHit = minPriceRecent <= position.TakeProfit1.Price; wasTakeProfitHit = minPriceRecent <= position.TakeProfit1.Price;
} }
decimal closingPrice;
if (wasStopLossHit) if (wasStopLossHit)
{ {
// Use actual execution price based on direction // Use actual execution price based on direction
@@ -1534,6 +1537,40 @@ public class TradingBotBase : ITradingBot
$"Closing at market price: `${closingPrice:F2}`"); $"Closing at market price: `${closingPrice:F2}`");
} }
pnlCalculated = true;
}
else if (Config.IsForBacktest)
{
// For backtests when currentCandle is null, use a fallback closing price
// This ensures P&L calculation always happens for backtests
Logger.LogWarning(
$"⚠️ Backtest: No current candle available for position {position.Identifier}. Using fallback closing price calculation.");
// Use the position's stop loss or take profit price as closing price
if (position.StopLoss != null && position.StopLoss.Price > 0)
{
closingPrice = position.StopLoss.Price;
position.StopLoss.SetStatus(TradeStatus.Filled);
}
else if (position.TakeProfit1 != null && position.TakeProfit1.Price > 0)
{
closingPrice = position.TakeProfit1.Price;
position.TakeProfit1.SetStatus(TradeStatus.Filled);
}
else
{
// Last resort: use entry price (no profit/loss)
closingPrice = position.Open.Price;
Logger.LogWarning(
$"⚠️ Backtest: Using entry price as closing price for position {position.Identifier}");
}
pnlCalculated = true;
}
// Calculate P&L if we have a closing price
if (pnlCalculated && closingPrice > 0)
{
var entryPrice = position.Open.Price; var entryPrice = position.Open.Price;
var positionSize = position.Open.Quantity * position.Open.Leverage; var positionSize = position.Open.Quantity * position.Open.Leverage;
@@ -1553,7 +1590,7 @@ public class TradingBotBase : ITradingBot
var netPnl = pnl - totalFees; var netPnl = pnl - totalFees;
position.ProfitAndLoss = new ProfitAndLoss { Realized = pnl, Net = netPnl }; position.ProfitAndLoss = new ProfitAndLoss { Realized = pnl, Net = netPnl };
} }
else if (position.ProfitAndLoss.Realized == 0) else if (position.ProfitAndLoss.Realized == 0 || position.ProfitAndLoss.Net == 0)
{ {
var totalFees = position.GasFees + position.UiFees; var totalFees = position.GasFees + position.UiFees;
var netPnl = pnl - totalFees; var netPnl = pnl - totalFees;
@@ -1561,6 +1598,12 @@ public class TradingBotBase : ITradingBot
position.ProfitAndLoss.Net = netPnl; position.ProfitAndLoss.Net = netPnl;
} }
Logger.LogInformation(
$"💰 P&L Calculated for Position {position.Identifier}\n" +
$"Entry: `${entryPrice:F2}` | Exit: `${closingPrice:F2}`\n" +
$"Realized P&L: `${pnl:F2}` | Net P&L (after fees): `${position.ProfitAndLoss.Net:F2}`\n" +
$"Total Fees: `${position.GasFees + position.UiFees:F2}`");
// Fees are now tracked separately in UiFees and GasFees properties // Fees are now tracked separately in UiFees and GasFees properties
// No need to subtract fees from PnL as they're tracked separately // No need to subtract fees from PnL as they're tracked separately
} }

View File

@@ -1,6 +1,8 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.Trading.Commands; using Managing.Application.Trading.Commands;
using Managing.Common;
using Managing.Domain.Accounts;
using Managing.Domain.Shared.Helpers; using Managing.Domain.Shared.Helpers;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -21,13 +23,16 @@ public class ClosePositionCommandHandler(
{ {
try try
{ {
// Get Trade Account account = null;
var account = await accountService.GetAccountById(request.AccountId, false, false); if (!request.IsForBacktest)
{
account = await accountService.GetAccountById(request.AccountId, false, false);
if (request.Position == null) if (request.Position == null)
{ {
_ = await exchangeService.CancelOrder(account, request.Position.Ticker); _ = await exchangeService.CancelOrder(account, request.Position.Ticker);
return request.Position; return request.Position;
} }
}
var isForPaperTrading = request.IsForBacktest; var isForPaperTrading = request.IsForBacktest;
@@ -50,9 +55,11 @@ public class ClosePositionCommandHandler(
request.Position.Open.Leverage); request.Position.Open.Leverage);
// Add UI fees for closing the position (broker closed it) // Add UI fees for closing the position (broker closed it)
var closingPositionSizeUsd = (lastPrice * request.Position.Open.Quantity) * request.Position.Open.Leverage; var closingPositionSizeUsd =
(lastPrice * request.Position.Open.Quantity) * request.Position.Open.Leverage;
var closingUiFees = TradingHelpers.CalculateClosingUiFees(closingPositionSizeUsd); var closingUiFees = TradingHelpers.CalculateClosingUiFees(closingPositionSizeUsd);
request.Position.AddUiFees(closingUiFees); request.Position.AddUiFees(closingUiFees);
request.Position.AddGasFees(Constants.GMX.Config.GasFeePerTransaction);
await tradingService.UpdatePositionAsync(request.Position); await tradingService.UpdatePositionAsync(request.Position);
return request.Position; return request.Position;
@@ -78,6 +85,7 @@ public class ClosePositionCommandHandler(
var closingPositionSizeUsd = (lastPrice * closedPosition.Quantity) * request.Position.Open.Leverage; var closingPositionSizeUsd = (lastPrice * closedPosition.Quantity) * request.Position.Open.Leverage;
var closingUiFees = TradingHelpers.CalculateClosingUiFees(closingPositionSizeUsd); var closingUiFees = TradingHelpers.CalculateClosingUiFees(closingPositionSizeUsd);
request.Position.AddUiFees(closingUiFees); request.Position.AddUiFees(closingUiFees);
request.Position.AddGasFees(Constants.GMX.Config.GasFeePerTransaction);
if (!request.IsForBacktest) if (!request.IsForBacktest)
await tradingService.UpdatePositionAsync(request.Position); await tradingService.UpdatePositionAsync(request.Position);