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);
if (account.User != null)
if (account.User == null)
{
account.User = await _userRepository.GetUserByNameAsync(account.User.Name);
}

View File

@@ -329,9 +329,10 @@ public class TradingBotBase : ITradingBot
}
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}");
}
}
}
// Then, open positions for signals waiting for a position open
// But first, check if we already have a position for any of these signals
@@ -1294,7 +1295,7 @@ public class TradingBotBase : ITradingBot
// Update the closing trade price if available
if (gmxPosition.Open != null)
{
var closingPrice = gmxPosition.Open.Price;
var gmxClosingPrice = gmxPosition.Open.Price;
// Determine which trade was the closing trade based on profitability
bool isProfitable = position.ProfitAndLoss.Realized > 0;
@@ -1303,7 +1304,7 @@ public class TradingBotBase : ITradingBot
{
if (position.TakeProfit1 != null)
{
position.TakeProfit1.SetPrice(closingPrice, 2);
position.TakeProfit1.SetPrice(gmxClosingPrice, 2);
position.TakeProfit1.SetDate(gmxPosition.Open.Date);
position.TakeProfit1.SetStatus(TradeStatus.Filled);
}
@@ -1318,7 +1319,7 @@ public class TradingBotBase : ITradingBot
{
if (position.StopLoss != null)
{
position.StopLoss.SetPrice(closingPrice, 2);
position.StopLoss.SetPrice(gmxClosingPrice, 2);
position.StopLoss.SetDate(gmxPosition.Open.Date);
position.StopLoss.SetStatus(TradeStatus.Filled);
}
@@ -1338,7 +1339,7 @@ public class TradingBotBase : ITradingBot
Logger.LogInformation(
$"📊 Position Reconciliation Complete\n" +
$"Position: `{position.Identifier}`\n" +
$"Closing Price: `${closingPrice:F2}`\n" +
$"Closing Price: `${gmxClosingPrice:F2}`\n" +
$"Used: `{(isProfitable ? "Take Profit" : "Stop Loss")}`\n" +
$"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)
{
List<Candle> recentCandles = null;
@@ -1428,8 +1433,6 @@ public class TradingBotBase : ITradingBot
wasTakeProfitHit = minPriceRecent <= position.TakeProfit1.Price;
}
decimal closingPrice;
if (wasStopLossHit)
{
// Use actual execution price based on direction
@@ -1534,6 +1537,40 @@ public class TradingBotBase : ITradingBot
$"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 positionSize = position.Open.Quantity * position.Open.Leverage;
@@ -1553,7 +1590,7 @@ public class TradingBotBase : ITradingBot
var netPnl = pnl - totalFees;
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 netPnl = pnl - totalFees;
@@ -1561,6 +1598,12 @@ public class TradingBotBase : ITradingBot
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
// 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.Services;
using Managing.Application.Trading.Commands;
using Managing.Common;
using Managing.Domain.Accounts;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Trades;
using Microsoft.Extensions.DependencyInjection;
@@ -21,12 +23,15 @@ public class ClosePositionCommandHandler(
{
try
{
// Get Trade
var account = await accountService.GetAccountById(request.AccountId, false, false);
if (request.Position == null)
Account account = null;
if (!request.IsForBacktest)
{
_ = await exchangeService.CancelOrder(account, request.Position.Ticker);
return request.Position;
account = await accountService.GetAccountById(request.AccountId, false, false);
if (request.Position == null)
{
_ = await exchangeService.CancelOrder(account, request.Position.Ticker);
return request.Position;
}
}
var isForPaperTrading = request.IsForBacktest;
@@ -50,9 +55,11 @@ public class ClosePositionCommandHandler(
request.Position.Open.Leverage);
// 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);
request.Position.AddUiFees(closingUiFees);
request.Position.AddGasFees(Constants.GMX.Config.GasFeePerTransaction);
await tradingService.UpdatePositionAsync(request.Position);
return request.Position;
@@ -78,6 +85,7 @@ public class ClosePositionCommandHandler(
var closingPositionSizeUsd = (lastPrice * closedPosition.Quantity) * request.Position.Open.Leverage;
var closingUiFees = TradingHelpers.CalculateClosingUiFees(closingPositionSizeUsd);
request.Position.AddUiFees(closingUiFees);
request.Position.AddGasFees(Constants.GMX.Config.GasFeePerTransaction);
if (!request.IsForBacktest)
await tradingService.UpdatePositionAsync(request.Position);