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

@@ -2,11 +2,10 @@
using System.Numerics;
using Managing.ABI.GmxV2.Reader.ContractDefinition;
using Managing.Core;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Gmx;
using Managing.Infrastructure.Evm.Models.Gmx.v2;
using Managing.Infrastructure.Evm.Referentials;
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Signer;
using Nethereum.Web3;
using static Managing.Common.Enums;
@@ -62,7 +61,7 @@ public static class GmxHelpers
{
decimal priceBasisPoints;
var basisPointDivisor = 10000m;
var allowedSlippage = 34m;
var allowedSlippage = 30m;
var toDecimal = 30;
if (isLong)
@@ -81,32 +80,6 @@ public static class GmxHelpers
return priceLimit;
}
internal static BigInteger? GetGasLimit()
{
throw new NotImplementedException();
}
internal static List<Trade> Map(List<GmxOrder> orders, Ticker ticker)
{
return orders.ConvertAll(order => Map(order, ticker));
}
private static Trade Map(GmxOrder order, Ticker ticker)
{
var trade = new Trade(DateTime.UtcNow,
order.IsLong ? TradeDirection.Short : TradeDirection.Long,
TradeStatus.Requested,
GetTradeType(order.IsLong, order.TriggerAboveThreshold),
ticker,
Convert.ToDecimal(order.SizeDelta),
Convert.ToDecimal(order.TriggerPrice),
null,
"", ""
);
return trade;
}
public static bool GetTriggerAboveThreshold(bool isLong, TradeType tradeType)
{
if ((isLong && tradeType == TradeType.TakeProfit) ||
@@ -145,9 +118,9 @@ public static class GmxHelpers
public static string GetRandomAddress()
{
var ecKey = Nethereum.Signer.EthECKey.GenerateKey();
var ecKey = EthECKey.GenerateKey();
var privateKey = ecKey.GetPrivateKeyAsBytes().ToHex();
var account = new Nethereum.Signer.EthECKey(privateKey).GetPublicAddress();
var account = new EthECKey(privateKey).GetPublicAddress();
return account;
}
@@ -168,15 +141,15 @@ public static class GmxHelpers
private static MarketPrice ConvertToContractTokenPrice(MarketPrice marketPrice, int tokenDecimals)
{
var price = new MarketPrice();
price.Min = ConvertToContractPrice(tokenDecimals, marketPrice.Min);
price.Max = ConvertToContractPrice(tokenDecimals, marketPrice.Max);
price.Min = ConvertToContractPrice(marketPrice.Min, tokenDecimals);
price.Max = ConvertToContractPrice(marketPrice.Max, tokenDecimals);
return price;
}
private static BigInteger ConvertToContractPrice(decimal price, BigInteger tokenDecimals)
private static BigInteger ConvertToContractPrice(BigInteger price, decimal tokenDecimals)
{
// Convert the decimal price to a BigInteger, scaling it up to maintain precision.
BigInteger scaledPrice = new BigInteger(price * (decimal)Math.Pow(10, (double)tokenDecimals));
BigInteger scaledPrice = price * new BigInteger((decimal)Math.Pow(10, (double)tokenDecimals));
// Return the scaled price directly since it's already adjusted for token decimals.
return scaledPrice;
@@ -257,4 +230,19 @@ public static class GmxHelpers
parts[0] = int.Parse(parts[0]).ToString("N0", CultureInfo.InvariantCulture);
return string.Join(".", parts);
}
public static BigInteger? ConvertToUsd(BigInteger? tokenAmount, int? tokenDecimals, BigInteger? price)
{
if (tokenAmount == null || tokenDecimals == null || price == null)
{
return null;
}
return (tokenAmount.Value * price.Value) / ExpandDecimals(1, tokenDecimals.Value);
}
private static BigInteger ExpandDecimals(int value, int decimals)
{
return BigInteger.Multiply(value, BigInteger.Pow(10, decimals));
}
}

View File

@@ -2,10 +2,12 @@
using Managing.ABI.GmxV2.Reader.ContractDefinition;
using Managing.Core;
using Managing.Domain.Candles;
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Gmx;
using Managing.Infrastructure.Evm.Models.Gmx.v1;
using Managing.Infrastructure.Evm.Models.Gmx.v2;
using Managing.Infrastructure.Evm.Referentials;
using Nethereum.Util;
using Nethereum.Web3;
using static Managing.Common.Enums;
@@ -184,7 +186,7 @@ public static class GmxMappers
var longToken = TokenV2Service.GetTokenByAddress(infos.Market.LongToken);
var shortToken = TokenV2Service.GetTokenByAddress(infos.Market.ShortToken);
var isSameCollaterals = infos.Market.LongToken == infos.Market.ShortToken;
var isSpotOnly = infos.Market.IndexToken == Nethereum.Util.AddressUtil.ZERO_ADDRESS;
var isSpotOnly = infos.Market.IndexToken == AddressUtil.ZERO_ADDRESS;
var name = TokenV2Service.GetMarketFullName(indexToken.Address, longToken.Address, shortToken.Address,
isSpotOnly);
var symbol = indexToken.Symbol;
@@ -211,7 +213,7 @@ public static class GmxMappers
var longToken = TokenV2Service.GetTokenByAddress(infos.LongToken);
var shortToken = TokenV2Service.GetTokenByAddress(infos.ShortToken);
var isSameCollaterals = infos.LongToken == infos.ShortToken;
var isSpotOnly = infos.IndexToken == Nethereum.Util.AddressUtil.ZERO_ADDRESS;
var isSpotOnly = infos.IndexToken == AddressUtil.ZERO_ADDRESS;
var name = TokenV2Service.GetMarketFullName(indexToken.Address, longToken.Address, shortToken.Address,
isSpotOnly);
@@ -245,4 +247,46 @@ public static class GmxMappers
ImpactPoolAmount = response.ReturnValue2.ImpactPoolAmount
};
}
internal static List<Trade> Map(List<GmxOrder> orders, Ticker ticker)
{
return orders.ConvertAll(order => Map(order, ticker));
}
private static Trade Map(GmxOrder order, Ticker ticker)
{
var trade = new Trade(DateTime.UtcNow,
order.IsLong ? TradeDirection.Short : TradeDirection.Long,
TradeStatus.Requested,
GmxHelpers.GetTradeType(order.IsLong, order.TriggerAboveThreshold),
ticker,
Convert.ToDecimal(order.SizeDelta),
Convert.ToDecimal(order.TriggerPrice),
null,
"", ""
);
return trade;
}
public static List<FundingRate> Map(GmxMarketInfo market)
{
var longApr =
GmxV2Helpers.GetFundingFactorPerPeriod(market, true, GmxV2Helpers.Periods[Timeframe.OneHour]) * 100;
var shortApr =
GmxV2Helpers.GetFundingFactorPerPeriod(market, false, GmxV2Helpers.Periods[Timeframe.OneHour]) * 100;
var longFundingRate = new FundingRate()
{
Ticker = GmxHelpers.GetTicker(market.Market.Symbol),
Rate = GmxHelpers.FormatAmount(longApr, 30, 4),
Direction = TradeDirection.Long
};
var shortFundingRate = new FundingRate()
{
Ticker = GmxHelpers.GetTicker(market.Market.Symbol),
Rate = GmxHelpers.FormatAmount(shortApr, 30, 4),
Direction = TradeDirection.Short
};
return new List<FundingRate>() { longFundingRate, shortFundingRate };
}
}

View File

@@ -1,22 +1,23 @@
using System.Numerics;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Gmx;
using Managing.Infrastructure.Evm.Models.Gmx.v1;
using Managing.Infrastructure.Evm.Referentials;
using Managing.Tools.ABI.OrderBook;
using Managing.Tools.ABI.OrderBook.ContractDefinition;
using Managing.Tools.ABI.OrderBookReader;
using Managing.Tools.ABI.OrderBookReader.ContractDefinition;
using Managing.Tools.ABI.PositionRouter;
using Managing.Tools.ABI.PositionRouter.ContractDefinition;
using Managing.Tools.ABI.Reader;
using Managing.Tools.ABI.Reader.ContractDefinition;
using Managing.Tools.OrderBook;
using Managing.Tools.OrderBook.ContractDefinition;
using Managing.Tools.OrderBookReader;
using Managing.Tools.OrderBookReader.ContractDefinition;
using Managing.Tools.PositionRouter;
using Managing.Tools.PositionRouter.ContractDefinition;
using Managing.Tools.Router;
using Managing.Tools.Router.ContractDefinition;
using Managing.Tools.ABI.Router;
using Managing.Tools.ABI.Router.ContractDefinition;
using Nethereum.Contracts.Standards.ERC20;
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
using Nethereum.Util;
using Nethereum.Web3;
using static Managing.Common.Enums;
using ApproveFunction = Nethereum.Contracts.Standards.ERC20.ContractDefinition.ApproveFunction;
namespace Managing.Infrastructure.Evm.Services.Gmx;
@@ -25,7 +26,7 @@ public static class GmxService
private const decimal _orderFeesExecution = 0.0003m;
private const decimal _positionUpdateFees = 0.0001m;
public async static Task InitAccountForTrading(Web3 web3, string publicAddress, List<Ticker> tickers)
public static async Task InitAccountForTrading(Web3 web3, string publicAddress, List<Ticker> tickers)
{
var router = new RouterService(web3, Arbitrum.Address.Router);
if (!await IsPluginAdded(web3, publicAddress, Arbitrum.Address.PositionRouter))
@@ -44,11 +45,11 @@ public static class GmxService
foreach (var ticker in tickers)
{
var conntractAddress = TokenService.GetContractAddress(ticker);
await ApproveToken(web3, publicAddress, conntractAddress);
await EvmBase.ApproveToken(web3, publicAddress, conntractAddress, publicAddress);
}
}
public async static Task<bool> IsPluginAdded(Web3 web3, string publicAddress, string pluginAddress)
public static async Task<bool> IsPluginAdded(Web3 web3, string publicAddress, string pluginAddress)
{
var router = new RouterService(web3, Arbitrum.Address.Router);
var function = new ApprovedPluginsFunction
@@ -61,16 +62,6 @@ public static class GmxService
return isAdded;
}
public async static Task ApproveToken(Web3 web3, string publicAddress, string contractAddress)
{
var input = new Nethereum.Contracts.Standards.ERC20.ContractDefinition.ApproveFunction
{
Spender = publicAddress
};
var contract = new ERC20ContractService(web3.Eth, contractAddress);
var approval = await contract.ApproveRequestAsync(input);
}
public static async Task<bool> ApproveOrder(Web3 web3, Ticker ticker, string publicAddress, decimal amount)
{
@@ -87,7 +78,7 @@ public static class GmxService
if (allowance.IsZero) return false;
var approveQuery = new Nethereum.Contracts.Standards.ERC20.ContractDefinition.ApproveFunction
var approveQuery = new ApproveFunction
{
FromAddress = publicAddress,
Spender = Arbitrum.Address.Router,
@@ -98,7 +89,7 @@ public static class GmxService
return true;
}
public async static Task<Trade> IncreasePosition(Web3 web3, string publicAddress, Ticker ticker,
public static async Task<Trade> IncreasePosition(Web3 web3, string publicAddress, Ticker ticker,
TradeDirection direction, decimal price, decimal quantity, decimal? leverage)
{
var quantityLeveraged = GmxHelpers.GetQuantityForLeverage(quantity, leverage);
@@ -150,7 +141,7 @@ public static class GmxService
return trade;
}
public async static Task<Trade> DecreasePosition(Web3 web3, string publicAddress, Ticker ticker,
public static async Task<Trade> DecreasePosition(Web3 web3, string publicAddress, Ticker ticker,
TradeDirection direction, decimal price, decimal quantity, decimal? leverage)
{
var trade = new Trade(DateTime.UtcNow,
@@ -270,7 +261,7 @@ public static class GmxService
return trade;
}
public async static Task<bool> CancelOrders(Web3 web3, string publicAddress, Ticker ticker)
public static async Task<bool> CancelOrders(Web3 web3, string publicAddress, Ticker ticker)
{
var orderBook = new OrderBookService(web3, Arbitrum.Address.OrderBook);
var orders = await GetOrders(web3, publicAddress, ticker);

View File

@@ -0,0 +1,67 @@
using System.Numerics;
using Microsoft.Extensions.Caching.Memory;
using Nethereum.ABI;
using Nethereum.Util;
namespace Managing.Infrastructure.Evm.Services.Gmx;
public class GmxV2Encoder
{
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(List<(string dataType, object dataValues)> datas)
{
var key = GenerateKey(datas);
if (_dataCache.TryGetValue(key, out byte[] cachedHash))
{
return cachedHash;
}
var abiValues = new List<ABIValue>();
foreach (var data in datas)
{
var abiValue = new ABIValue(data.dataType, data.dataValues);
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;
}
public byte[] HashString(string input)
{
if (_stringCache.TryGetValue(input, out byte[] cachedHash))
{
return cachedHash;
}
// Adjusted to directly work with byte arrays
var hash = HashData(new List<(string dataType, object dataValues)>() { ("string", input) });
_stringCache.Set(input, hash, _absoluteExpiration);
return hash;
}
private string GenerateKey(List<(string dataType, object dataValues)> datas)
{
var combined = new List<string>();
foreach (var data in datas)
{
combined.Add(
$"{data.dataType}:{(data.dataValues is BigInteger bigInt ? bigInt.ToString() : data.dataValues)}");
}
return string.Join(",", combined);
}
}

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;
}
}

View File

@@ -0,0 +1,107 @@
using System.Numerics;
using Managing.ABI.GmxV2.SyntheticsReader.ContractDefinition;
using Managing.Common;
using Managing.Core;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Gmx.v2;
using Nethereum.Web3;
namespace Managing.Infrastructure.Evm.Services.Gmx;
internal static class GmxV2Mappers
{
public static Trade Map(GmxV2Position position, Enums.Ticker ticker)
{
var entryPrice = GmxV2Helpers.GetEntryPrice(position.SizeInUsd, position.SizeInTokens,
position.TokenData.Decimals);
var parsedEntryPrice = GmxHelpers.FormatAmount(entryPrice, Constants.GMX.Config.Decimals.USD - 2, 4);
var collateralLeverage = CalculateCollateralAndLeverage(position.SizeInUsd, position.CollateralAmount);
var trade = new Trade(
DateHelpers.GetFromUnixTimestamp((int)position.IncreasedAtTime),
position.IsLong ? Enums.TradeDirection.Long : Enums.TradeDirection.Short,
Enums.TradeStatus.Filled,
Enums.TradeType.Limit,
ticker,
collateralLeverage.collateral / parsedEntryPrice,
parsedEntryPrice,
collateralLeverage.leverage,
position.Key,
""
);
return trade;
}
public static List<Trade> Map(List<GmxV2Order> orders)
{
var trades = new List<Trade>();
foreach (var order in orders)
{
var shortToken = TokenV2Service.GetTokenByAddress(order.InitialCollateralTokenAddress);
var ticker = GmxV2Helpers.GetTicker(order.MarketAddress);
var indexToken = TokenV2Service.GetByTicker(ticker);
var sizeDelta = GmxHelpers.FormatAmount(order.SizeDeltaUsd, Constants.GMX.Config.Decimals.USD, 4);
var triggerPrice = GmxV2Helpers.ParseContractPrice(order.ContractTriggerPrice, indexToken.Decimals, true);
var quantity = sizeDelta / triggerPrice;
var initialCollateral =
GmxV2Helpers.ParseContractPrice(order.InitialCollateralDeltaAmount, shortToken.Decimals);
var leverage = sizeDelta / initialCollateral;
var trade = new Trade(
order.Date,
order.IsLong ? Enums.TradeDirection.Long : Enums.TradeDirection.Short,
Enums.TradeStatus.PendingOpen,
GmxV2Helpers.GetTradeType(order.OrderType),
GmxV2Helpers.GetTicker(order.MarketAddress),
Convert.ToDecimal(quantity),
Convert.ToDecimal(triggerPrice),
leverage,
"",
""
);
trades.Add(trade);
}
return trades;
}
//
// public static GmxV2Position MapPosition(GmxV2Service.PositionInfoDTO positionInfo)
// {
// return new GmxV2Position
// {
// PositionKey = GetPositionKeyV2(
// positionInfo.Position.Account,
// positionInfo.Position.Market,
// positionInfo.Position.CollateralToken,
// positionInfo.Position.IsLong
// ),
// SizeInUsd = positionInfo.Position.SizeInUsd,
// CollateralAmount = positionInfo.Position.CollateralAmount,
// IncreasedAtTime = positionInfo.Position.IncreasedAtTime,
// IsLong = positionInfo.Position.IsLong,
// Leverage = CalculateLeverage(positionInfo.Position.SizeInUsd, positionInfo.Position.CollateralAmount)
// };
// }
private static (decimal collateral, decimal leverage) CalculateCollateralAndLeverage(BigInteger sizeInUsd,
BigInteger collateralAmount)
{
if (collateralAmount == 0) return (0m, 0m);
var size = Web3.Convert.FromWei(sizeInUsd, 30);
var collateral = Web3.Convert.FromWei(collateralAmount, 6); // USDC decimals
return (collateral, Math.Round(size / collateral));
}
public static List<MarketPrices> Map(List<ABI.GmxV2.Reader.ContractDefinition.MarketPrices> marketPrices)
{
return marketPrices.Select(mp => new MarketPrices
{
IndexTokenPrice = mp.IndexTokenPrice,
LongTokenPrice = mp.LongTokenPrice,
ShortTokenPrice = mp.ShortTokenPrice
}).ToList();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
using System.Net.Http.Json;
using Managing.Common;
using Managing.Infrastructure.Evm.Models.Gmx.v2;
namespace Managing.Infrastructure.Evm.Services.Gmx;
public class OracleKeeperService()
{
private readonly HttpClient _httpClient = new HttpClient();
private readonly string _baseUrl = Constants.GMX.Config.OracleKeeperUrl;
public async Task<List<GmxTicker>> GetTickers()
{
var response = await _httpClient.GetFromJsonAsync<List<GmxTicker>>(_baseUrl + "/prices/tickers");
return response;
}
}

View File

@@ -1,6 +1,7 @@
using Managing.Common;
using Nethereum.Util;
namespace Managing.Infrastructure.Evm.Models.Gmx.v2;
namespace Managing.Infrastructure.Evm.Services.Gmx;
public static class TokenV2Service
{
@@ -11,7 +12,8 @@ public static class TokenV2Service
Name = "Ethereum",
Symbol = "ETH",
Decimals = 18,
Address = Nethereum.Util.AddressUtil.ZERO_ADDRESS,
PriceDecimals = 12,
Address = AddressUtil.ZERO_ADDRESS,
IsNative = true,
IsShortable = true,
ImageUrl = "https://assets.coingecko.com/coins/images/279/small/ethereum.png?1595348880",
@@ -23,6 +25,7 @@ public static class TokenV2Service
Name = "Wrapped Ethereum",
Symbol = "WETH",
Decimals = 18,
PriceDecimals = 12,
Address = Constants.GMX.TokenAddress.WETH,
IsWrapped = true,
BaseSymbol = "ETH",
@@ -35,6 +38,7 @@ public static class TokenV2Service
Name = "Wrapped Stacked Ethereum",
Symbol = "WSTETH",
Decimals = 18,
PriceDecimals = 12,
Address = Constants.GMX.TokenAddress.WSTETH,
IsWrapped = true,
BaseSymbol = "wstETH",
@@ -47,6 +51,7 @@ public static class TokenV2Service
Symbol = "BTC",
AssetSymbol = "WBTC",
Decimals = 8,
PriceDecimals = 22,
Address = Constants.GMX.TokenAddress.WBTC,
IsShortable = true,
ImageUrl = "https://assets.coingecko.com/coins/images/26115/thumb/btcb.png?1655921693",
@@ -423,6 +428,11 @@ public static class TokenV2Service
return $"{longToken}-{shortToken}";
}
public static GmxToken GetByTicker(Enums.Ticker ticker)
{
return TOKENS.FirstOrDefault(t => t.Symbol == ticker.ToString()) ?? throw new InvalidOperationException();
}
}
public class GmxToken