227 lines
8.0 KiB
C#
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;
|
|
}
|
|
} |