Files
managing-apps/src/Managing.Domain/Shared/Helpers/TradingHelpers.cs
2025-09-28 19:34:06 +07:00

227 lines
8.0 KiB
C#

using Exilion.TradingAtomics;
using Managing.Common;
using Managing.Domain.Accounts;
using Managing.Domain.Candles;
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
using static Managing.Common.Enums;
namespace Managing.Domain.Shared.Helpers;
public static class TradingHelpers
{
public static decimal GetHodlPercentage(Candle candle1, Candle candle2)
{
return candle2.Close * 100 / candle1.Close - 100;
}
public static decimal GetGrowthFromInitalBalance(decimal balance, decimal finalPnl)
{
var growth = balance + finalPnl;
return growth * 100 / balance - 100;
}
public static PerformanceMetrics GetStatistics(Dictionary<DateTime, decimal> pnls)
{
var priceSeries = new TimePriceSeries(pnls.DistinctBy(p => p.Key).ToDictionary(p => p.Key, p => p.Value));
return priceSeries.CalculatePerformanceMetrics();
}
public static decimal GetFeeAmount(decimal fee, decimal amount)
{
return fee * amount;
}
public static decimal GetFeeAmount(decimal fee, decimal amount, TradingExchanges exchange)
{
if (exchange.Equals(TradingExchanges.Evm))
return fee;
return GetFeeAmount(fee, amount);
}
public static bool IsAGoodTrader(Trader trader)
{
return trader.Winrate > 30
&& trader.TradeCount > 8
&& trader.AverageWin > Math.Abs(trader.AverageLoss)
&& trader.Pnl > 0;
}
public static bool IsABadTrader(Trader trader)
{
return trader.Winrate < 30
&& trader.TradeCount > 8
&& trader.AverageWin * 3 < Math.Abs(trader.AverageLoss)
&& trader.Pnl < 0;
}
public static List<Trader> FindBadTrader(this List<Trader> traders)
{
var filteredTrader = new List<Trader>();
foreach (var trader in traders)
{
if (IsABadTrader(trader))
{
filteredTrader.Add(trader);
}
}
return filteredTrader;
}
public static List<Trader> FindGoodTrader(this List<Trader> traders)
{
var filteredTrader = new List<Trader>();
foreach (var trader in traders)
{
if (IsAGoodTrader(trader))
{
filteredTrader.Add(trader);
}
}
return filteredTrader;
}
public static List<Trader> MapToTraders(this List<Account> accounts)
{
var traders = new List<Trader>();
foreach (var account in accounts)
{
traders.Add(new Trader
{
Address = account.Key
});
}
return traders;
}
/// <summary>
/// Calculates the total fees for a position based on GMX V2 fee structure
/// </summary>
/// <param name="position">The position to calculate fees for</param>
/// <returns>The total fees for the position</returns>
public static decimal CalculatePositionFees(Position position)
{
var (uiFees, gasFees) = CalculatePositionFeesBreakdown(position);
return uiFees + gasFees;
}
/// <summary>
/// Calculates the UI and Gas fees breakdown for a position based on GMX V2 fee structure
/// </summary>
/// <param name="position">The position to calculate fees for</param>
/// <returns>A tuple containing (uiFees, gasFees)</returns>
public static (decimal uiFees, decimal gasFees) CalculatePositionFeesBreakdown(Position position)
{
decimal uiFees = 0;
decimal gasFees = 0;
if (position?.Open?.Price <= 0 || position?.Open?.Quantity <= 0)
{
return (uiFees, gasFees); // Return 0 if position data is invalid
}
// Calculate position size in USD (leverage is already included in quantity calculation)
var positionSizeUsd = (position.Open.Price * position.Open.Quantity) * position.Open.Leverage;
// UI Fee: 0.1% of position size paid on opening
var uiFeeOpen = positionSizeUsd * Constants.GMX.Config.UiFeeRate; // Fee paid on opening
uiFees += uiFeeOpen;
// UI Fee: 0.1% of position size paid on closing - only if position was actually closed
// Check which closing trade was executed (StopLoss, TakeProfit1, or TakeProfit2)
if (position.StopLoss?.Status == TradeStatus.Filled)
{
var stopLossPositionSizeUsd = (position.StopLoss.Price * position.StopLoss.Quantity) * position.StopLoss.Leverage;
var uiFeeClose = stopLossPositionSizeUsd * Constants.GMX.Config.UiFeeRate; // Fee paid on closing via StopLoss
uiFees += uiFeeClose;
}
else if (position.TakeProfit1?.Status == TradeStatus.Filled)
{
var takeProfit1PositionSizeUsd = (position.TakeProfit1.Price * position.TakeProfit1.Quantity) * position.TakeProfit1.Leverage;
var uiFeeClose = takeProfit1PositionSizeUsd * Constants.GMX.Config.UiFeeRate; // Fee paid on closing via TakeProfit1
uiFees += uiFeeClose;
}
else if (position.TakeProfit2?.Status == TradeStatus.Filled)
{
var takeProfit2PositionSizeUsd = (position.TakeProfit2.Price * position.TakeProfit2.Quantity) * position.TakeProfit2.Leverage;
var uiFeeClose = takeProfit2PositionSizeUsd * Constants.GMX.Config.UiFeeRate; // Fee paid on closing via TakeProfit2
uiFees += uiFeeClose;
}
// Gas Fee: $0.15 for opening position only
// Closing is handled by oracle, so no gas fee for closing
gasFees += Constants.GMX.Config.GasFeePerTransaction;
return (uiFees, gasFees);
}
/// <summary>
/// Calculates UI fees for opening a position
/// </summary>
/// <param name="positionSizeUsd">The position size in USD</param>
/// <returns>The UI fees for opening</returns>
public static decimal CalculateOpeningUiFees(decimal positionSizeUsd)
{
return positionSizeUsd * Constants.GMX.Config.UiFeeRate;
}
/// <summary>
/// Calculates UI fees for closing a position
/// </summary>
/// <param name="positionSizeUsd">The position size in USD</param>
/// <returns>The UI fees for closing</returns>
public static decimal CalculateClosingUiFees(decimal positionSizeUsd)
{
return positionSizeUsd * Constants.GMX.Config.UiFeeRate;
}
/// <summary>
/// Calculates gas fees for opening a position
/// </summary>
/// <returns>The gas fees for opening (fixed at $0.15)</returns>
public static decimal CalculateOpeningGasFees()
{
return Constants.GMX.Config.GasFeePerTransaction;
}
/// <summary>
/// Calculates the total volume for a position based on its status and filled trades
/// </summary>
/// <param name="position">The position to calculate volume for</param>
/// <returns>The total volume for the position</returns>
public static decimal GetVolumeForPosition(Position position)
{
// Always include the opening trade volume
var totalVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage;
// For closed positions, add volume from filled closing trades
if (position.IsFinished())
{
// Add Stop Loss volume if filled
if (position.StopLoss?.Status == TradeStatus.Filled)
{
totalVolume += position.StopLoss.Price * position.StopLoss.Quantity * position.StopLoss.Leverage;
}
// Add Take Profit 1 volume if filled
if (position.TakeProfit1?.Status == TradeStatus.Filled)
{
totalVolume += position.TakeProfit1.Price * position.TakeProfit1.Quantity * position.TakeProfit1.Leverage;
}
// Add Take Profit 2 volume if filled
if (position.TakeProfit2?.Status == TradeStatus.Filled)
{
totalVolume += position.TakeProfit2.Price * position.TakeProfit2.Quantity * position.TakeProfit2.Leverage;
}
}
return totalVolume;
}
}