From 3f1b5f09e0ed63ab54da902f8236f24ed4eb7ea2 Mon Sep 17 00:00:00 2001 From: cryptooda Date: Fri, 17 Oct 2025 00:49:20 +0700 Subject: [PATCH] Update the gmx for the execution fees --- .../Bots/TradingBotBase.cs | 28 ---- .../src/plugins/custom/gmx.ts | 135 ++++++++++++++- .../test/plugins/get-estimated-gas.test.ts | 157 ++++++++++++++++++ 3 files changed, 289 insertions(+), 31 deletions(-) create mode 100644 src/Managing.Web3Proxy/test/plugins/get-estimated-gas.test.ts diff --git a/src/Managing.Application/Bots/TradingBotBase.cs b/src/Managing.Application/Bots/TradingBotBase.cs index 215d6596..02f6533e 100644 --- a/src/Managing.Application/Bots/TradingBotBase.cs +++ b/src/Managing.Application/Bots/TradingBotBase.cs @@ -312,34 +312,6 @@ public class TradingBotBase : ITradingBot await UpdatePosition(signalForPosition, position); } - // Second, process all finished positions to ensure they are updated in the database - // TODO : This should be removed in the future, when we have a better way to handle positions - if (!Config.IsForBacktest) - foreach (var position in Positions.Values.Where(p => p.IsFinished())) - { - try - { - var positionInDatabase = await ServiceScopeHelpers.WithScopedService( - _scopeFactory, - async tradingService => - { - return await tradingService.GetPositionByIdentifierAsync(position.Identifier); - }); - - if (positionInDatabase != null && positionInDatabase.Status != position.Status) - { - await UpdatePositionDatabase(position); - await LogInformation( - $"šŸ’¾ Database Update\nPosition: `{position.Identifier}`\nStatus: `{position.Status}`\nUpdated in database"); - } - } - catch (Exception ex) - { - await LogWarning( - $"Failed to update finished position {position.Identifier} in database: {ex.Message}"); - } - } - // Then, open positions for signals waiting for a position open // But first, check if we already have a position for any of these signals var signalsWaitingForPosition = Signals.Values.Where(s => s.Status == SignalStatus.WaitingForPosition); diff --git a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts index 4f7cecda..0c7e05cc 100644 --- a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts +++ b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts @@ -147,15 +147,144 @@ export async function checkGasFeeBalance( } /** - * Estimates gas fee for a position opening transaction + * Estimates gas fee for a position opening transaction using GMX SDK proper execution fee calculation * @param sdk The GMX SDK client - * @param params The position increase parameters + * @param ticker The ticker symbol for the market (e.g., 'BTC', 'ETH', 'ARB') + * @param positionSizeUsd Optional position size in USD for more accurate estimation (default: $1000) + * @param leverage Optional leverage multiplier (default: 10x) + * @returns Object with execution fee details + */ +export async function estimatePositionExecutionFee( + sdk: GmxSdk, + ticker: string, + positionSizeUsd: number = 1000, + leverage: number = 10 +): Promise<{ + feeUsd: bigint; + feeTokenAmount: bigint; + gasLimit: bigint; + isFeeHigh: boolean; + isFeeVeryHigh: boolean; + feeToken: any; + marketInfo?: any; +} | null> { + try { + // Get markets and tokens data + const {marketsInfoData, tokensData} = await getMarketsInfoWithCache(sdk); + + // Find the market for the specific ticker + const marketInfo = Object.values(marketsInfoData).find(market => + market.indexToken.symbol === ticker.toUpperCase() + ); + + if (!marketInfo) { + console.warn(`Market not found for ticker: ${ticker}`); + return null; + } + + console.log(`šŸ“Š Found market for ${ticker}:`, { + marketAddress: marketInfo.marketTokenAddress, + indexTokenSymbol: marketInfo.indexToken.symbol, + indexTokenAddress: marketInfo.indexToken.address, + longTokenSymbol: marketInfo.longToken.symbol, + shortTokenSymbol: marketInfo.shortToken.symbol + }); + + // Calculate position amounts based on real market data + const collateralAmount = BigInt(Math.floor(positionSizeUsd / leverage)) * BigInt(1e18); // Collateral in USDC + const sizeDeltaUsd = BigInt(positionSizeUsd) * BigInt(1e30); // Position size in USD + const leverageBps = BigInt(leverage * 10000); // Convert to basis points + + // Get current index price + const indexPrice = marketInfo.indexToken.prices?.minPrice || BigInt(1e30); + + // Calculate size delta in tokens + const sizeDeltaInTokens = sizeDeltaUsd * BigInt(1e30) / indexPrice; + + // Create realistic increase amounts using actual market data + const increaseAmounts = { + sizeDeltaUsd: sizeDeltaUsd, + collateralDeltaAmount: collateralAmount, + sizeDeltaInTokens: sizeDeltaInTokens, + initialCollateralAmount: collateralAmount, + initialCollateralUsd: BigInt(Math.floor(positionSizeUsd / leverage)) * BigInt(1e30), + collateralDeltaUsd: BigInt(Math.floor(positionSizeUsd / leverage)) * BigInt(1e30), + swapStrategy: { type: 'none' } as any, + indexTokenAmount: sizeDeltaInTokens, + indexPrice: indexPrice, + initialCollateralPrice: BigInt(1e30), // USDC price (1 USD) + collateralPrice: BigInt(1e30), // USDC price (1 USD) + estimatedLeverage: leverageBps, + acceptablePrice: indexPrice, + acceptablePriceDeltaBps: BigInt(50), // 0.5% slippage + recommendedAcceptablePriceDeltaBps: BigInt(50), + positionFeeUsd: 0n, // Will be calculated by GMX + positionFeeAmount: 0n, // Will be calculated by GMX + positionFeeAmountForCollateral: 0n, // Will be calculated by GMX + swapPathStats: [], + triggerPrice: undefined, + limitOrderType: undefined, + triggerThresholdType: undefined + } as any; + + console.log(`šŸ“‹ Position parameters for ${ticker}:`, { + positionSizeUsd: positionSizeUsd, + leverage: leverage, + collateralAmount: collateralAmount.toString(), + sizeDeltaUsd: sizeDeltaUsd.toString(), + sizeDeltaInTokens: sizeDeltaInTokens.toString(), + indexPrice: indexPrice.toString() + }); + + const executionFee = await sdk.utils.getExecutionFee('increase', tokensData, { + increaseAmounts: increaseAmounts + }); + + if (!executionFee) { + console.warn('Failed to get execution fee from GMX SDK'); + return null; + } + + console.log(`⛽ GMX Execution Fee estimation for ${ticker}:`, { + feeUsd: `$${(Number(executionFee.feeUsd) / 1e30).toFixed(2)}`, + feeTokenAmount: `${(Number(executionFee.feeTokenAmount) / 1e18).toFixed(6)} ETH`, + gasLimit: executionFee.gasLimit.toString(), + isFeeHigh: executionFee.isFeeHigh, + isFeeVeryHigh: executionFee.isFeeVeryHigh, + feeTokenSymbol: executionFee.feeToken.symbol + }); + + return { + ...executionFee, + marketInfo: marketInfo + }; + + } catch (error) { + console.error(`Error estimating GMX execution fee for ${ticker}:`, error); + return null; + } +} + +/** + * Estimates gas fee for a position opening transaction (legacy simplified method) + * @param sdk The GMX SDK client + * @param ticker Optional ticker for more accurate estimation (default: 'BTC') * @returns Estimated gas fee in wei */ export async function estimatePositionGasFee( sdk: GmxSdk, + ticker: string = 'BTC' ): Promise { try { + // First try to get proper GMX execution fee + const executionFee = await estimatePositionExecutionFee(sdk, ticker); + if (executionFee) { + return executionFee.feeTokenAmount; + } + + // Fallback to simplified estimation + console.log('āš ļø Falling back to simplified gas estimation'); + // Estimate gas for the position opening transaction // This is a simplified estimation - in practice, you might want to use // the actual transaction simulation or a more sophisticated gas estimation @@ -172,7 +301,7 @@ export async function estimatePositionGasFee( const estimatedGas = (baseGasLimit * 120n) / 100n; const estimatedGasFee = estimatedGas * gasPrice; - console.log(`⛽ Gas estimation:`, { + console.log(`⛽ Simplified gas estimation:`, { baseGasLimit: baseGasLimit.toString(), gasPrice: gasPrice.toString(), estimatedGas: estimatedGas.toString(), diff --git a/src/Managing.Web3Proxy/test/plugins/get-estimated-gas.test.ts b/src/Managing.Web3Proxy/test/plugins/get-estimated-gas.test.ts new file mode 100644 index 00000000..b152ce58 --- /dev/null +++ b/src/Managing.Web3Proxy/test/plugins/get-estimated-gas.test.ts @@ -0,0 +1,157 @@ +import {test} from 'node:test' +import assert from 'node:assert' +import { + checkGasFeeBalance, + estimatePositionExecutionFee, + estimatePositionGasFee, + getClientForAddress +} from '../../src/plugins/custom/gmx' + +/** + * Get GMX execution fee using the SDK's proper fee calculation + * This uses the same logic as GMX frontend for accurate fee estimation + */ +async function getGmxExecutionFee(sdk: any) { + try { + // Get markets and tokens data + const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo() + + // Get gas limits and gas price from SDK utils + const gasLimits = await sdk.utils.getGasLimits() + const gasPrice = await sdk.utils.getGasPrice() + + console.log('šŸ“Š Gas limits:', { + increaseOrder: gasLimits.increaseOrder.toString(), + estimatedGasFeeBaseAmount: gasLimits.estimatedGasFeeBaseAmount.toString(), + estimatedGasFeePerOraclePrice: gasLimits.estimatedGasFeePerOraclePrice.toString(), + estimatedFeeMultiplierFactor: gasLimits.estimatedFeeMultiplierFactor.toString() + }) + + console.log('⛽ Gas price:', gasPrice.toString()) + + // Get execution fee for increase position (opening a position) + const mockIncreaseAmounts = { + sizeDeltaUsd: 1000n * BigInt(1e30), // $1000 position size + collateralDeltaAmount: 100n * BigInt(1e18), // 100 USDC collateral + sizeDeltaInTokens: 0n, + initialCollateralAmount: 100n * BigInt(1e18), + initialCollateralUsd: 100n * BigInt(1e30), + collateralDeltaUsd: 100n * BigInt(1e30), + swapStrategy: { type: 'none' } as any, + indexTokenAmount: 0n, + indexPrice: BigInt(1e30), + initialCollateralPrice: BigInt(1e30), + collateralPrice: BigInt(1e30), + estimatedLeverage: BigInt(10), + acceptablePrice: BigInt(1e30), + acceptablePriceDeltaBps: 0n, + recommendedAcceptablePriceDeltaBps: 0n, + positionFeeUsd: 0n, + positionFeeAmount: 0n, + positionFeeAmountForCollateral: 0n, + swapPathStats: [], + triggerPrice: undefined, + limitOrderType: undefined, + triggerThresholdType: undefined + } as any; + + const executionFee = await sdk.utils.getExecutionFee('increase', tokensData, { + increaseAmounts: mockIncreaseAmounts + }) + + if (!executionFee) { + throw new Error('Failed to get execution fee from GMX SDK') + } + + console.log('šŸ’° GMX Execution Fee:', { + feeUsd: executionFee.feeUsd.toString(), + feeTokenAmount: executionFee.feeTokenAmount.toString(), + gasLimit: executionFee.gasLimit.toString(), + isFeeHigh: executionFee.isFeeHigh, + isFeeVeryHigh: executionFee.isFeeVeryHigh, + feeTokenSymbol: executionFee.feeToken.symbol + }) + + return executionFee + } catch (error) { + console.error('Error getting GMX execution fee:', error) + throw error + } +} + +test('GMX Gas Fee Estimation', async (t) => { + await t.test('should estimate gas fee using GMX SDK proper execution fee calculation with real ticker data', async () => { + // Use a test account address + const testAccount = '0x0b4A132cb6ed8fa66953bf61a53D0B91DaCaAd78' + + // Get GMX SDK client + const sdk = await getClientForAddress(testAccount) + + // Test different tickers to see how gas fees vary + const testTickers = ['BTC', 'ETH', 'ARB', 'SOL'] + + for (const ticker of testTickers) { + console.log(`\nšŸ” Testing execution fee for ${ticker}...`) + + // Test GMX SDK execution fee calculation using our helper function with real ticker + const gmxExecutionFeeHelper = await estimatePositionExecutionFee(sdk, ticker, 1000, 10) + + if (gmxExecutionFeeHelper) { + console.log(`āœ… ${ticker} execution fee:`, { + feeUsd: `$${(Number(gmxExecutionFeeHelper.feeUsd) / 1e30).toFixed(2)}`, + feeTokenAmount: `${(Number(gmxExecutionFeeHelper.feeTokenAmount) / 1e18).toFixed(6)} ETH`, + gasLimit: gmxExecutionFeeHelper.gasLimit.toString(), + isFeeHigh: gmxExecutionFeeHelper.isFeeHigh, + isFeeVeryHigh: gmxExecutionFeeHelper.isFeeVeryHigh, + marketAddress: gmxExecutionFeeHelper.marketInfo?.marketTokenAddress, + indexTokenSymbol: gmxExecutionFeeHelper.marketInfo?.indexToken.symbol + }) + + // Assertions for helper function execution fee + assert.ok(gmxExecutionFeeHelper.feeUsd > 0n, `${ticker} execution fee USD should be greater than 0`) + assert.ok(gmxExecutionFeeHelper.feeTokenAmount > 0n, `${ticker} execution fee token amount should be greater than 0`) + assert.ok(gmxExecutionFeeHelper.gasLimit > 0n, `${ticker} gas limit should be greater than 0`) + assert.ok(gmxExecutionFeeHelper.marketInfo, `${ticker} market info should be available`) + assert.ok(gmxExecutionFeeHelper.marketInfo.indexToken.symbol === ticker, `${ticker} market should match ticker`) + + // Test checkGasFeeBalance function with GMX execution fee + console.log(`šŸ’° Checking gas fee balance for ${ticker}...`) + const gasFeeCheck = await checkGasFeeBalance(sdk, gmxExecutionFeeHelper.feeTokenAmount) + + console.log(`${ticker} gas fee check result:`, { + hasSufficientBalance: gasFeeCheck.hasSufficientBalance, + estimatedGasFeeUsd: `$${gasFeeCheck.estimatedGasFeeUsd.toFixed(2)}`, + ethBalance: `${gasFeeCheck.ethBalance} ETH` + }) + + // Assertions for gas fee check + assert.ok(typeof gasFeeCheck.hasSufficientBalance === 'boolean', `${ticker} hasSufficientBalance should be a boolean`) + assert.ok(typeof gasFeeCheck.ethBalance === 'string', `${ticker} ethBalance should be a string`) + assert.ok(typeof gasFeeCheck.estimatedGasFeeUsd === 'number', `${ticker} estimatedGasFeeUsd should be a number`) + assert.ok(gasFeeCheck.ethPrice > 0, `${ticker} ETH price should be greater than 0`) + assert.ok(gasFeeCheck.estimatedGasFeeUsd >= 0, `${ticker} estimated gas fee USD should be non-negative`) + + console.log(`šŸ“ˆ ${ticker} insights:`) + console.log(` - Execution fee: $${(Number(gmxExecutionFeeHelper.feeUsd) / 1e30).toFixed(2)}`) + console.log(` - ETH amount needed: ${(Number(gmxExecutionFeeHelper.feeTokenAmount) / 1e18).toFixed(6)} ETH`) + console.log(` - Gas limit: ${gmxExecutionFeeHelper.gasLimit}`) + console.log(` - Fee is ${gmxExecutionFeeHelper.isFeeHigh ? 'HIGH' : 'normal'}`) + console.log(` - Fee is ${gmxExecutionFeeHelper.isFeeVeryHigh ? 'VERY HIGH' : 'not excessive'}`) + } else { + console.log(`āš ļø No execution fee data available for ${ticker}`) + } + } + + // Test our simplified estimation for comparison + console.log('\n⛽ Testing simplified gas fee estimation...') + const estimatedGasFee = await estimatePositionGasFee(sdk, 'BTC') + + console.log('šŸ“Š Simplified estimation:', { + estimatedGasFeeEth: `${(Number(estimatedGasFee) / 1e18).toFixed(6)} ETH` + }) + + assert.ok(estimatedGasFee > 0n, 'Simplified estimated gas fee should be greater than 0') + + console.log('āœ… GMX execution fee test with real ticker data completed successfully') + }) +})