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 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 FindBadTrader(this List traders) { var filteredTrader = new List(); foreach (var trader in traders) { if (IsABadTrader(trader)) { filteredTrader.Add(trader); } } return filteredTrader; } public static List FindGoodTrader(this List traders) { var filteredTrader = new List(); foreach (var trader in traders) { if (IsAGoodTrader(trader)) { filteredTrader.Add(trader); } } return filteredTrader; } public static List MapToTraders(this List accounts) { var traders = new List(); foreach (var account in accounts) { traders.Add(new Trader { Address = account.Key }); } return traders; } /// /// Calculates the total fees for a position based on GMX V2 fee structure /// /// The position to calculate fees for /// The total fees for the position public static decimal CalculatePositionFees(Position position) { var (uiFees, gasFees) = CalculatePositionFeesBreakdown(position); return uiFees + gasFees; } /// /// Calculates the UI and Gas fees breakdown for a position based on GMX V2 fee structure /// /// The position to calculate fees for /// A tuple containing (uiFees, gasFees) 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); } /// /// Calculates UI fees for opening a position /// /// The position size in USD /// The UI fees for opening public static decimal CalculateOpeningUiFees(decimal positionSizeUsd) { return positionSizeUsd * Constants.GMX.Config.UiFeeRate; } /// /// Calculates UI fees for closing a position /// /// The position size in USD /// The UI fees for closing public static decimal CalculateClosingUiFees(decimal positionSizeUsd) { return positionSizeUsd * Constants.GMX.Config.UiFeeRate; } /// /// Calculates gas fees for opening a position /// /// The gas fees for opening (fixed at $0.15) public static decimal CalculateOpeningGasFees() { return Constants.GMX.Config.GasFeePerTransaction; } /// /// Calculates the total volume for a position based on its status and filled trades /// /// The position to calculate volume for /// The total volume for the position 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; } }