GMX v2 - Trading (#7)

* Move PrivateKeys.cs

* Update gitignore

* Update gitignore

* updt

* Extract GmxServiceTests.cs

* Refact

* update todo

* Update code

* Fix hashdata

* Replace static token hashed datas

* Set allowance

* Add get orders

* Add get orders tests

* Add ignore

* add close orders

* revert

* Add get gas limit

* Start increasePosition. Todo: Finish GetExecutionFee and estimateGas

* little refact

* Update gitignore

* Fix namespaces and clean repo

* Add tests samples

* Add execution fee

* Add increase position

* Handle backtest on the frontend

* Add tests

* Update increase

* Test increase

* fix increase

* Fix size

* Start get position

* Update get positions

* Fix get position

* Update rpc and trade mappers

* Finish close position

* Fix leverage
This commit is contained in:
Oda
2025-01-30 23:06:22 +07:00
committed by GitHub
parent ecaa89c67b
commit 65bdb8e34f
156 changed files with 11253 additions and 4073 deletions

View File

@@ -1,70 +1,209 @@
using System.Numerics;
using Microsoft.Extensions.Caching.Memory;
using Nethereum.ABI;
using Nethereum.Util;
using Managing.Common;
using Managing.Infrastructure.Evm.Models.Gmx.v2;
using Nethereum.Web3;
namespace Managing.Infrastructure.Evm.Models.Gmx.v2;
namespace Managing.Infrastructure.Evm.Services.Gmx;
public class GmxV2Helpers
public static class GmxV2Helpers
{
private readonly MemoryCache _dataCache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 10000 });
private readonly MemoryCache _stringCache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 10000 });
private readonly ABIEncode _abiEncode = new ABIEncode();
private readonly MemoryCacheEntryOptions _absoluteExpiration = new MemoryCacheEntryOptions { Size = 1 };
public byte[] HashData(string[] dataTypes, object[] dataValues)
public static BigInteger GetFundingFactorPerPeriod(GmxMarketInfo marketInfo, bool isLong, int periodInSeconds)
{
var key = GenerateKey(dataTypes, dataValues);
var fundingFactorPerSecond = marketInfo.Market.FundingFactorPerSecond;
var longsPayShorts = marketInfo.Market.LongsPayShorts;
var longInterestUsd = marketInfo.Infos.LongInterestUsd;
var shortInterestUsd = marketInfo.Infos.ShortInterestUsd;
if (_dataCache.TryGetValue(key, out byte[] cachedHash))
var isLargerSide = isLong ? longsPayShorts : !longsPayShorts;
BigInteger factorPerSecond;
if (isLargerSide)
{
return cachedHash;
factorPerSecond = fundingFactorPerSecond * -1;
}
else
{
var largerInterestUsd = longsPayShorts ? longInterestUsd : shortInterestUsd;
var smallerInterestUsd = longsPayShorts ? shortInterestUsd : longInterestUsd;
var ratio = smallerInterestUsd > 0
? BigInteger.Multiply(largerInterestUsd, Precision) / smallerInterestUsd
: 0;
factorPerSecond = ApplyFactor(ratio, fundingFactorPerSecond);
}
var abiValues = new List<ABIValue>();
foreach (var dataType in dataTypes)
{
var abiValue = new ABIValue(dataType, dataValues[dataTypes.ToList().IndexOf(dataType)]);
abiValues.Add(abiValue);
}
var abiEncode = new ABIEncode();
var encoded = abiEncode.GetABIEncoded(abiValues.ToArray());
var hash = new Sha3Keccack().CalculateHash(encoded);
_dataCache.Set(key, hash, _absoluteExpiration);
return hash;
return factorPerSecond * periodInSeconds;
}
public byte[] HashString(string input)
private static BigInteger ApplyFactor(BigInteger ratio, BigInteger fundingFactorPerSecond)
{
if (_stringCache.TryGetValue(input, out byte[] cachedHash))
{
return cachedHash;
}
// Adjusted to directly work with byte arrays
var hash = HashData(new[] { "string" }, new string[] { input });
_stringCache.Set(input, hash, _absoluteExpiration);
return hash;
// Implement the logic for applying the factor based on the ratio
// This is a placeholder implementation
return BigInteger.Multiply(ratio, fundingFactorPerSecond) / Precision;
}
private string GenerateKey(string[] dataTypes, object[] dataValues)
{
var combined = new List<string>();
for (int i = 0; i < dataTypes.Length; i++)
{
combined.Add($"{dataTypes[i]}:{(dataValues[i] is BigInteger bigInt ? bigInt.ToString() : dataValues[i])}");
}
private static readonly BigInteger Precision = BigInteger.Pow(10, 18);
return string.Join(",", combined);
public static Dictionary<Enums.Timeframe, int> Periods = new Dictionary<Enums.Timeframe, int>()
{
{ Enums.Timeframe.FiveMinutes, 300 },
{ Enums.Timeframe.FifteenMinutes, 900 },
{ Enums.Timeframe.OneHour, 3600 },
{ Enums.Timeframe.FourHour, 14400 },
{ Enums.Timeframe.OneDay, 86400 }
};
public static string GetMarketAddress(Enums.Ticker ticker)
{
// Switch statement to return the market address based on the ticker
switch (ticker)
{
case Enums.Ticker.BTC: return Constants.GMX.Markets.BTCUSDC;
case Enums.Ticker.ETH: return Constants.GMX.Markets.ETHUSDC;
case Enums.Ticker.LINK: return Constants.GMX.Markets.LINKUSD;
case Enums.Ticker.PEPE: return Constants.GMX.Markets.PEPEUSD;
case Enums.Ticker.SOL: return Constants.GMX.Markets.SOLUSD;
case Enums.Ticker.UNI: return Constants.GMX.Markets.UNIUSD;
case Enums.Ticker.SHIB: return Constants.GMX.Markets.SHIBUSD;
case Enums.Ticker.AAVE: return Constants.GMX.Markets.AAVEUSD;
case Enums.Ticker.OP: return Constants.GMX.Markets.OPUSD;
case Enums.Ticker.APE: return Constants.GMX.Markets.APEUSD;
case Enums.Ticker.GMX: return Constants.GMX.Markets.GMXUSD;
case Enums.Ticker.ARB: return Constants.GMX.Markets.ARBUSD;
case Enums.Ticker.NEAR: return Constants.GMX.Markets.NEARUSD;
case Enums.Ticker.STX: return Constants.GMX.Markets.STXUSD;
case Enums.Ticker.LTC: return Constants.GMX.Markets.LTCUSD;
case Enums.Ticker.XRP: return Constants.GMX.Markets.XRPUSD;
case Enums.Ticker.WIF: return Constants.GMX.Markets.WIFUSD;
case Enums.Ticker.ORDI: return Constants.GMX.Markets.ORDIUSD;
default: throw new ArgumentOutOfRangeException(nameof(ticker), "Invalid ticker value.");
}
}
public static Enums.Ticker GetTicker(string marketAddress)
{
switch (marketAddress)
{
case Constants.GMX.Markets.BTCUSDC: return Enums.Ticker.BTC;
case Constants.GMX.Markets.ETHUSDC: return Enums.Ticker.ETH;
case Constants.GMX.Markets.LINKUSD: return Enums.Ticker.LINK;
case Constants.GMX.Markets.PEPEUSD: return Enums.Ticker.PEPE;
case Constants.GMX.Markets.SOLUSD: return Enums.Ticker.SOL;
case Constants.GMX.Markets.UNIUSD: return Enums.Ticker.UNI;
case Constants.GMX.Markets.SHIBUSD: return Enums.Ticker.SHIB;
case Constants.GMX.Markets.AAVEUSD: return Enums.Ticker.AAVE;
case Constants.GMX.Markets.OPUSD: return Enums.Ticker.OP;
case Constants.GMX.Markets.APEUSD: return Enums.Ticker.APE;
case Constants.GMX.Markets.GMXUSD: return Enums.Ticker.GMX;
case Constants.GMX.Markets.ARBUSD: return Enums.Ticker.ARB;
case Constants.GMX.Markets.NEARUSD: return Enums.Ticker.NEAR;
case Constants.GMX.Markets.STXUSD: return Enums.Ticker.STX;
case Constants.GMX.Markets.LTCUSD: return Enums.Ticker.LTC;
case Constants.GMX.Markets.XRPUSD: return Enums.Ticker.XRP;
case Constants.GMX.Markets.WIFUSD: return Enums.Ticker.WIF;
case Constants.GMX.Markets.ORDIUSD: return Enums.Ticker.ORDI;
default: throw new ArgumentOutOfRangeException(nameof(marketAddress), "Invalid market address.");
}
}
public static Enums.TradeType GetTradeType(GmxV2OrderType orderType)
{
return orderType switch
{
GmxV2OrderType.LimitIncrease => Enums.TradeType.Limit,
GmxV2OrderType.LimitDecrease => Enums.TradeType.Limit,
GmxV2OrderType.MarketIncrease => Enums.TradeType.Market,
GmxV2OrderType.MarketDecrease => Enums.TradeType.Market,
GmxV2OrderType.Liquidation => Enums.TradeType.Market,
_ => throw new ArgumentOutOfRangeException(nameof(orderType), "Invalid order type.")
};
}
/// <summary>
/// Parses the contract price to a decimal value.
/// </summary>
/// <param name="price"></param>
/// <param name="tokenDecimals"></param>
/// <param name="useTransform">Used to tranform the decimal since GMX is not using everytime the decimal from the MarketProps. Use true for prices and false for amounts</param>
/// <returns></returns>
public static decimal ParseContractPrice(BigInteger price, int tokenDecimals, bool useTransform = false)
{
if (useTransform)
tokenDecimals = TransformValue(tokenDecimals);
return (decimal)price / (decimal)ExpandDecimals(1, tokenDecimals);
}
public static BigInteger ConvertToContractPrice(decimal value, int tokenDecimals, bool useTransform = false)
{
if (useTransform)
tokenDecimals = TransformValue(tokenDecimals);
return new BigInteger(value * (decimal)ExpandDecimals(1, tokenDecimals));
}
public static BigInteger ExpandDecimals(int value, int decimals)
{
return BigInteger.Multiply(value, BigInteger.Pow(10, decimals));
}
public static int TransformValue(int input)
{
// Apply the derived linear equation y = -x + 30
return -input + 30;
}
public static BigInteger EstimateOrderOraclePriceCount(int swapsCount)
{
return 3 + new BigInteger(swapsCount);
}
public static bool SameAddress(string address, string address2)
{
return address.ToLower() == address2.ToLower();
return string.Equals(address, address2, StringComparison.CurrentCultureIgnoreCase);
}
public static BigInteger GetAcceptablePrice(decimal price, bool isLong)
{
var slippage = 0.003m; // 0.3% slippage
var priceSlippage = price * slippage;
var acceptablePrice = isLong ? price + priceSlippage : price - priceSlippage;
return Web3.Convert.ToWei(acceptablePrice, 22);
}
public static BigInteger GetCollateralAmount(decimal amount, decimal leverage = 1)
{
return Web3.Convert.ToWei(amount * leverage, 30);
}
public static decimal ConvertToUsd(BigInteger amount, int decimals, BigInteger price)
{
var result = (amount * price) / ExpandDecimals(1, decimals);
return (decimal)result;
}
public static BigInteger ParseValue(decimal value, int decimals)
{
return Web3.Convert.ToWei(value, decimals);
}
public static string GetPositionKey(string addressesAccount, string addressesMarket,
string addressesCollateralToken, bool flagsIsLong)
{
return $"{addressesAccount}:{addressesMarket}:{addressesCollateralToken}:{flagsIsLong}";
}
public static BigInteger GetEntryPrice(BigInteger positionSizeInUsd, BigInteger positionSizeInTokens,
int decimals)
{
if (positionSizeInTokens <= 0)
{
return 0;
}
return BigInteger.Multiply(positionSizeInUsd, ExpandDecimals(1, decimals)) / positionSizeInTokens;
}
}