Fix realized pnl on backtest save + add tests (not all passing)

This commit is contained in:
2025-11-14 02:38:15 +07:00
parent 1f7d914625
commit 460a7bd559
34 changed files with 6012 additions and 500 deletions

View File

@@ -21,9 +21,6 @@ namespace Managing.Application.Abstractions
Task Run();
Task StopBot(string reason = null);
int GetWinRate();
decimal GetProfitAndLoss();
decimal GetTotalFees();
Task LoadAccount();
Task LoadLastCandle();
Task<LightSignal> CreateManualSignal(TradeDirection direction);

View File

@@ -339,15 +339,16 @@ public class BacktestExecutor
// Start result calculation timing
var resultCalculationStart = Stopwatch.GetTimestamp();
// Calculate final results (using existing optimized methods)
var netPnl = tradingBot.GetProfitAndLoss(); // This returns Net PnL (after fees)
var winRate = tradingBot.GetWinRate();
var stats = TradingHelpers.GetStatistics(tradingBot.WalletBalances);
// Calculate final results using static methods from TradingBox
var realizedPnl = TradingBox.GetTotalRealizedPnL(tradingBot.Positions); // PnL before fees
var netPnl = TradingBox.GetTotalNetPnL(tradingBot.Positions); // PnL after fees
var winRate = TradingBox.GetWinRate(tradingBot.Positions);
var stats = TradingBox.GetStatistics(tradingBot.WalletBalances);
var growthPercentage =
TradingHelpers.GetGrowthFromInitalBalance(tradingBot.WalletBalances.FirstOrDefault().Value, netPnl);
var hodlPercentage = TradingHelpers.GetHodlPercentage(candles.First(), candles.Last());
TradingBox.GetGrowthFromInitalBalance(tradingBot.WalletBalances.FirstOrDefault().Value, netPnl);
var hodlPercentage = TradingBox.GetHodlPercentage(candles.First(), candles.Last());
var fees = tradingBot.GetTotalFees();
var fees = TradingBox.GetTotalFees(tradingBot.Positions);
var scoringParams = new BacktestScoringParams(
sharpeRatio: (double)stats.SharpeRatio,
growthPercentage: (double)growthPercentage,
@@ -383,7 +384,7 @@ public class BacktestExecutor
var result = new Backtest(config, tradingBot.Positions, tradingBot.Signals,
withCandles ? candles : new HashSet<Candle>())
{
FinalPnl = netPnl, // Net PnL (after fees)
FinalPnl = realizedPnl, // Realized PnL before fees
WinRate = winRate,
GrowthPercentage = growthPercentage,
HodlPercentage = hodlPercentage,
@@ -398,7 +399,7 @@ public class BacktestExecutor
StartDate = candles.FirstOrDefault()!.OpenTime,
EndDate = candles.LastOrDefault()!.OpenTime,
InitialBalance = initialBalance,
NetPnl = netPnl, // Already net of fees
NetPnl = netPnl, // Net PnL after fees
};
if (save && user != null)

View File

@@ -132,14 +132,14 @@ public class BacktestTradingBotGrain : Grain, IBacktestTradingBotGrain
_logger.LogInformation("Backtest processing completed. Calculating final results...");
var finalPnl = tradingBot.GetProfitAndLoss();
var winRate = tradingBot.GetWinRate();
var stats = TradingHelpers.GetStatistics(tradingBot.WalletBalances);
var finalPnl = TradingBox.GetTotalNetPnL(tradingBot.Positions);
var winRate = TradingBox.GetWinRate(tradingBot.Positions);
var stats = TradingBox.GetStatistics(tradingBot.WalletBalances);
var growthPercentage =
TradingHelpers.GetGrowthFromInitalBalance(tradingBot.WalletBalances.FirstOrDefault().Value, finalPnl);
var hodlPercentage = TradingHelpers.GetHodlPercentage(candles.First(), candles.Last());
TradingBox.GetGrowthFromInitalBalance(tradingBot.WalletBalances.FirstOrDefault().Value, finalPnl);
var hodlPercentage = TradingBox.GetHodlPercentage(candles.First(), candles.Last());
var fees = tradingBot.GetTotalFees();
var fees = TradingBox.GetTotalFees(tradingBot.Positions);
var scoringParams = new BacktestScoringParams(
sharpeRatio: (double)stats.SharpeRatio,
growthPercentage: (double)growthPercentage,

View File

@@ -557,8 +557,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
Positions = _tradingBot.Positions,
Signals = _tradingBot.Signals,
WalletBalances = _tradingBot.WalletBalances,
ProfitAndLoss = _tradingBot.GetProfitAndLoss(),
WinRate = _tradingBot.GetWinRate(),
ProfitAndLoss = TradingBox.GetTotalNetPnL(_tradingBot.Positions),
WinRate = TradingBox.GetWinRate(_tradingBot.Positions),
ExecutionCount = _state.State.ExecutionCount,
StartupTime = _state.State.StartupTime,
CreateDate = _state.State.CreateDate

View File

@@ -460,7 +460,7 @@ public class TradingBotBase : ITradingBot
if (!WalletBalances.ContainsKey(date))
{
var previousBalance = WalletBalances.First().Value;
WalletBalances[date] = previousBalance + GetProfitAndLoss();
WalletBalances[date] = previousBalance + TradingBox.GetTotalNetPnL(Positions);
}
}
@@ -1501,7 +1501,7 @@ public class TradingBotBase : ITradingBot
var closingVolume = brokerPosition.Open.Price * position.Open.Quantity *
position.Open.Leverage;
var totalBotFees = position.GasFees + position.UiFees +
TradingHelpers.CalculateClosingUiFees(closingVolume);
TradingBox.CalculateClosingUiFees(closingVolume);
var gmxNetPnl = brokerPosition.ProfitAndLoss.Realized; // This is already after GMX fees
position.ProfitAndLoss = new ProfitAndLoss
@@ -2099,67 +2099,6 @@ public class TradingBotBase : ITradingBot
}
}
public int GetWinRate()
{
// Optimized: Single iteration instead of multiple LINQ queries
int succeededPositions = 0;
int totalPositions = 0;
foreach (var position in Positions.Values)
{
if (position.IsValidForMetrics())
{
totalPositions++;
if (position.IsInProfit())
{
succeededPositions++;
}
}
}
if (totalPositions == 0)
return 0;
return (succeededPositions * 100) / totalPositions;
}
public decimal GetProfitAndLoss()
{
// Optimized: Single iteration instead of LINQ chaining
decimal netPnl = 0;
foreach (var position in Positions.Values)
{
if (position.IsValidForMetrics() && position.ProfitAndLoss != null)
{
netPnl += position.ProfitAndLoss.Net;
}
}
return netPnl;
}
/// <summary>
/// Calculates the total fees paid by the trading bot for each position.
/// Includes UI fees (0.1% of position size) and network fees ($0.15 for opening).
/// Closing fees are handled by oracle, so no network fee for closing.
/// </summary>
/// <returns>Returns the total fees paid as a decimal value.</returns>
public decimal GetTotalFees()
{
// Optimized: Avoid LINQ Where overhead, inline the check
decimal totalFees = 0;
foreach (var position in Positions.Values)
{
if (position.IsValidForMetrics())
{
totalFees += TradingHelpers.CalculatePositionFees(position);
}
}
return totalFees;
}
public async Task ToggleIsForWatchOnly()
{

View File

@@ -57,7 +57,7 @@ public class ClosePositionCommandHandler(
// Add UI fees for closing the position (broker closed it)
var closingPositionSizeUsd =
(lastPrice * request.Position.Open.Quantity) * request.Position.Open.Leverage;
var closingUiFees = TradingHelpers.CalculateClosingUiFees(closingPositionSizeUsd);
var closingUiFees = TradingBox.CalculateClosingUiFees(closingPositionSizeUsd);
request.Position.AddUiFees(closingUiFees);
request.Position.AddGasFees(Constants.GMX.Config.GasFeePerTransaction);
@@ -83,7 +83,7 @@ public class ClosePositionCommandHandler(
// Add UI fees for closing the position
var closingPositionSizeUsd = (lastPrice * closedPosition.Quantity) * request.Position.Open.Leverage;
var closingUiFees = TradingHelpers.CalculateClosingUiFees(closingPositionSizeUsd);
var closingUiFees = TradingBox.CalculateClosingUiFees(closingPositionSizeUsd);
request.Position.AddUiFees(closingUiFees);
request.Position.AddGasFees(Constants.GMX.Config.GasFeePerTransaction);

View File

@@ -89,11 +89,11 @@ namespace Managing.Application.Trading.Handlers
// Calculate and set fees for the position
position.GasFees = TradingHelpers.CalculateOpeningGasFees();
position.GasFees = TradingBox.CalculateOpeningGasFees();
// Set UI fees for opening
var positionSizeUsd = TradingHelpers.GetVolumeForPosition(position);
position.UiFees = TradingHelpers.CalculateOpeningUiFees(positionSizeUsd);
var positionSizeUsd = TradingBox.GetVolumeForPosition(position);
position.UiFees = TradingBox.CalculateOpeningUiFees(positionSizeUsd);
var closeDirection = request.Direction == TradeDirection.Long
? TradeDirection.Short