GMX Price Analysis Using eth_defi’s native GMX Integration

This tutorial demonstrates how to analyse price data from GMX, a decentralised perpetual exchange built on Arbitrum. GMX allows users to trade perpetual contracts for various cryptocurrencies with up to 100x leverage.

Overview

GMX is a decentralised exchange that provides:

  • Spot and perpetual trading for cryptocurrencies like ETH, BTC, LINK, ARB & many more

  • Multi-asset liquidity pools (GLP/GM tokens) that provide deep liquidity

  • Zero-slippage trading through price feeds from Chainlink oracles

  • Competitive trading fees and robust liquidation mechanisms

  • Cross-margin trading with isolated risk management

What this tutorial covers: - Connecting to GMX’s smart contracts on Arbitrum - Fetching historical OHLC (Open, High, Low, Close) price data - Retrieving market information, including Total Value Locked (TVL) - Creating candlestick charts with market context - Analysing price ranges and market statistics

Prerequisites

Once you have the environment setup install the necessary dependencies using the following command

pip install web3-ethereum-defi[data]

You’ll also need: - Access to an Arbitrum RPC endpoint (Alchemy, Infura, or public) - Basic familiarity with pandas and plotly

How to run this tutorial:

  1. Set up your environment with the ARBITRUM_CHAIN_JSON_RPC environment variable

  2. Import and run the analysis functions

  3. Charts will be displayed showing price movements and market metrics

By the end of this tutorial, you’ll be able to create comprehensive price analysis reports for any token available on GMX.

Setting Up Your Environment

Create a .env file in your project directory or set environment variables:

# Set your Arbitrum RPC URL
export ARBITRUM_CHAIN_JSON_RPC="https://arb1.arbitrum.io/rpc"

For production use, consider using private RPC endpoints for better reliability and rate limits.

Running

Run the notebook in Visual Studio Code or similar.

To run from the command line using the IPython command:

ipython docs/source/tutorials/gmx-v2-price-analysis.ipynb

Setting up the Connection

First, let’s establish our connection to the Arbitrum network and create a GMX configuration:

[1]:
import os
from eth_defi.provider.multi_provider import create_multi_provider_web3
from eth_defi.gmx.config import GMXConfig

# Setup connection to Arbitrum
json_rpc_url = os.environ.get("ARBITRUM_CHAIN_JSON_RPC")
if not json_rpc_url:
    json_rpc_url = input("Please enter your Arbitrum JSON-RPC URL: ")

web3 = create_multi_provider_web3(json_rpc_url)
config = GMXConfig(web3)

print(f"Connected to chain {web3.eth.chain_id}")
Connected to chain 42161

Understanding GMX Data Sources

GMX data comes from two primary sources:

1. GMX API (Centralised)

  • OHLC price data: Pre-aggregated candlestick data

  • Token price data: Token prices from chainlink price feeds

  • Endpoint: https://arbitrum-api.gmxinfra.io/

  • Data span: Historical time series

  • No volume data: Only timestamp, open, high, low, close

2. On-Chain Contracts

  • TVL data: Total Value Locked in liquidity pools

  • Open Interest: Outstanding position values

  • Market information: Available trading pairs

  • Latency: Real-time (latest block)

Getting The Price for A Token

GMX uses Chainlink Price Feeds to get the latest prices for a token listed on their exchange. They provide an API endpoint to get the prices quickly and efficiently.

Stablecoin Pricing

In case the price of a stablecoin depegs from 1 USD:

- For stablecoin tokens, there may be a spread from the Chainlink price of the stablecoin to 1 USD. If Chainlink Data Stream prices are used then the spread would be from the data stream and may not be to 1 USD.

For token prices GMX handles the decimals differnetly. The chainlink price feeds returns the data in terms of 18 decimals(N.B. For most tokens but not for all tokens). And GMX uses 30 decimals. So in order to get the data in terms of human readable format we need to use the following formula

human_readable_price = raw_price / (10 ** (30 - token_decimals))

The prices are in raw format so we need to format them based on the decimals. Below we can see how we can use eth_defi’s GMXAPI class to get that data and structure it in a human readable way:

[ ]:
import pandas as pd
import numpy as np
from decimal import Decimal
from eth_defi.gmx.api import GMXAPI
from eth_defi.gmx.config import GMXConfig
from eth_defi.provider.multi_provider import create_multi_provider_web3
import os
import random

# Token decimals. For the sake of this example only selected 10 tokens
TOKEN_DECIMALS = {
    "ETH": 18,
    "USDC": 6,
    "USDT": 6,
    "DAI": 18,
    "BTC": 8,
    "LINK": 18,
    "UNI": 18,
    "SOL": 9,
    "SHIB": 18,
    "APE": 18,
}

def determine_price_decimals(token_symbol: str, raw_price: str) -> int:
    """
    Guess how many decimals to divide raw_price by, to get a USD‐price.
    Uses GMX's 30 decimal standard, adjusted by token's ERC20 decimals.
    """
    price_int = int(raw_price)
    token_decimals = TOKEN_DECIMALS.get(token_symbol, 18)  # default 18

    # GMX scale factor (30 decimals)
    gmx_scale = 30

    # To convert: human_price = raw / (10 ** (gmx_scale - token_decimals))
    # So decimals_to_divide = gmx_scale - token_decimals
    decimals_to_divide = gmx_scale - token_decimals

    return decimals_to_divide

def scale_price(token_symbol: str, raw_price: str) -> float:
    """
    Convert the raw price to human readable USD price.
    """
    decimals = determine_price_decimals(token_symbol, raw_price)
    price_int = int(raw_price)
    human_price = price_int / (10 ** decimals)
    return human_price

def convert_gmx_price(token_symbol: str, raw_price: str) -> float:
    """Convert GMX raw price to human readable USD value"""
    decimals = determine_price_decimals(token_symbol, raw_price)
    return float(Decimal(raw_price) / Decimal(10**decimals))

def get_gmx_tickers_data(config: GMXConfig) -> pd.DataFrame:
    """Get GMX ticker data and convert to structured DataFrame"""

    api = GMXAPI(config)
    tickers = api.get_tickers()

    # Convert to DataFrame
    df = pd.DataFrame(tickers)

    # Convert timestamps
    df['updatedAt'] = pd.to_datetime(df['updatedAt'], unit='ms')
    df['timestamp'] = pd.to_datetime(df['timestamp'], unit='s')

    # Convert prices using appropriate decimal precision per token
    df['minPrice_usd'] = df.apply(
        lambda row: convert_gmx_price(row['tokenSymbol'], row['minPrice']), axis=1
    )
    df['maxPrice_usd'] = df.apply(
        lambda row: convert_gmx_price(row['tokenSymbol'], row['maxPrice']), axis=1
    )

    # Calculate mid price and spread
    df['midPrice_usd'] = (df['minPrice_usd'] + df['maxPrice_usd']) / 2
    df['spread_usd'] = df['maxPrice_usd'] - df['minPrice_usd']
    df['spread_bps'] = (df['spread_usd'] / df['midPrice_usd']) * 10000

    # Add raw prices for reference
    df['minPrice_raw'] = df['minPrice']
    df['maxPrice_raw'] = df['maxPrice']

    # Reorder columns
    columns_order = [
        'tokenSymbol', 'tokenAddress',
        'minPrice_usd', 'maxPrice_usd', 'midPrice_usd',
        'spread_usd', 'spread_bps',
        'timestamp', 'updatedAt',
        'minPrice_raw', 'maxPrice_raw'
    ]

    return df[columns_order]


# Setup connection
json_rpc_url = os.environ.get("ARBITRUM_CHAIN_JSON_RPC")
if not json_rpc_url:
    json_rpc_url = input("Enter Arbitrum RPC URL: ")

web3 = create_multi_provider_web3(json_rpc_url)
config = GMXConfig(web3)

print("Fetching GMX ticker data...")

# Get and process ticker data
tickers_df = get_gmx_tickers_data(config)

# Show selected tokens
print("\nMajor Token Prices:")
print('-' * 50)
major_tokens = ['BTC', 'ETH', 'USDC', 'USDT', 'SOL', 'LINK', 'ARB']
major_df = tickers_df[tickers_df['tokenSymbol'].isin(major_tokens)]

for _, row in major_df.iterrows():
    print(f"{row['tokenSymbol']}: ${row['midPrice_usd']:.2f} (Spread: {row['spread_bps']:.2f} bps)")
Fetching GMX ticker data...

Extracting OHLC Data

Let’s create our first function to extract OHLC data from the GMX API:

[14]:
from eth_defi.gmx.config import GMXConfig
import pandas as pd

def get_gmx_ohlc_data(config: GMXConfig, token_symbol: str = "ETH", period: str = "1h") -> pd.DataFrame:
    """Fetch OHLC (Open, High, Low, Close) price data from GMX API.

    GMX provides historical price data through their API, which aggregates price information
    from their on-chain oracle feeds. This data represents actual trading prices
    available on the GMX platform.

    :param config: GMX configuration object containing Web3 connection details
    :param token_symbol: Trading pair symbol (ETH, BTC, LINK, ARB, etc.)
    :param period: Time interval - supported: 1m, 5m, 15m, 1h, 4h, 1d
    :return: DataFrame with columns [timestamp, open, high, low, close] or empty if no data
    """

    gmx_api = GMXAPI(config)

    # Request candlestick data from GMX API
    raw_data = gmx_api.get_candlesticks(token_symbol, period)

    if not raw_data or "candles" not in raw_data:
        print(f"No candlestick data received for {token_symbol}")
        return pd.DataFrame()

    candles = raw_data["candles"]
    if not candles:
        print(f"Empty candles array for {token_symbol}")
        return pd.DataFrame()

    # Validate data structure - ensure we have at least OHLC data
    num_fields = len(candles[0]) if candles else 0

    if num_fields >= 5:
        # Standard OHLC format: timestamp, open, high, low, close
        columns = ["timestamp", "open", "high", "low", "close"]

        df = pd.DataFrame(candles, columns=columns)
        # Convert Unix timestamps to Python datetime objects
        df["timestamp"] = pd.to_datetime(df["timestamp"], unit="s")

        print(f"Successfully retrieved {len(df)} {period} candles for {token_symbol}")
        return df

    print(f"Insufficient data fields ({num_fields}) for {token_symbol}")
    return pd.DataFrame()

# Example usage
ohlc_df = get_gmx_ohlc_data(config, "ETH", "1h")
print(f"Retrieved {len(ohlc_df)} hourly candles for ETH")
print(ohlc_df.head())
Successfully retrieved 1000 1h candles for ETH
Retrieved 1000 hourly candles for ETH
            timestamp     open     high      low    close
0 2025-09-16 07:00:00  4509.48  4514.90  4502.90  4511.27
1 2025-09-16 06:00:00  4530.00  4536.71  4494.04  4509.48
2 2025-09-16 05:00:00  4520.07  4533.83  4512.97  4530.00
3 2025-09-16 04:00:00  4521.66  4524.67  4489.36  4520.07
4 2025-09-16 03:00:00  4514.89  4533.02  4514.68  4521.66

Understanding Period Parameters

GMX API supports the following time periods: - 1m: 1-minute candles - 5m: 5-minute candles
- 15m: 15-minute candles - 1h: 1-hour candles - 4h: 4-hour candles - 1d: 1-day candles

Extracting On-Chain Market Data

Now let’s extract complementary on-chain data:

[10]:
from eth_defi.gmx.data import GMXMarketData

def get_gmx_markets_info(config: GMXConfig) -> dict:
    """Retrieve comprehensive market information from GMX protocol.

    This function fetches current market state including:
    - Available trading pairs and their configurations
    - Total Value Locked (TVL) in each market
    - Open Interest (total position sizes) for long/short positions

    :param config: GMX configuration object
    :return: Dictionary containing markets, TVL, and open interest data
    """

    gmx_data = GMXMarketData(config)

    # Fetch all market data concurrently for efficiency
    markets = gmx_data.get_available_markets()
    tvl_data = gmx_data.get_pool_tvl()
    oi_data = gmx_data.get_open_interest()

    return {
        "markets": markets,
        "tvl": tvl_data,
        "open_interest": oi_data
    }

# Example usage
market_info = get_gmx_markets_info(config)
print("Available markets:", list(market_info["tvl"].keys()))
09/16/2025 12:14:10 AM INFO: About to perform 90 multicalls
09/16/2025 12:14:13 AM INFO: Performed 90 calls, succeed: 90, failed: 0
Available markets: ['BTC', 'ETH', 'DOGE', 'SOL', 'LTC', 'UNI', 'LINK', 'ARB', 'XRP', 'BNB', 'AAVE', 'ATOM', 'NEAR', 'AVAX', 'OP', 'BTC2', 'ETH2', 'GMX', 'PEPE', 'WIF', 'wstETH', 'SHIB', 'APE_DEPRECATED', 'STX', 'ORDI', 'EIGEN', 'SATS', 'POL', 'APE', 'SUI', 'SEI', 'APT', 'TIA', 'TRX', 'TON', 'TAO', 'BONK', 'WLD', 'BOME', 'MEME', 'FLOKI']

Drawing OHLCV chart

Let’s create a function to generate an OHLC chart using Plotly:

[ ]:
# To make the output a static image instead of dynamic, we need these 3 lines
from eth_defi.research.notebook import setup_charting_and_output, OutputMode, set_large_plotly_chart_font
setup_charting_and_output(OutputMode.static, image_format="png", width=1500, height=1000, increase_font_size=False)
set_large_plotly_chart_font()

import plotly.graph_objects as go


def create_ohlc_chart(
    df: pd.DataFrame,
    token_symbol: str,
    market_info: dict = None,
    title_suffix: str = ""
) -> go.Figure:
    """Create a candlestick chart from OHLC price data.

    This function generates a professional candlestick chart that visualizes price movements
    over time. Green candles represent bullish periods, red candles show bearish periods.

    :param df: DataFrame containing OHLC data
    :param token_symbol: Symbol to display in chart title
    :param market_info: Optional market data to include TVL in title
    :param title_suffix: Additional text for chart title
    :return: Plotly Figure object ready for display
    """

    if df.empty:
        print(f"Cannot create chart: no data provided for {token_symbol}")
        return None

    # Initialize Plotly figure with candlestick chart
    fig = go.Figure()

    # Add OHLC candlestick trace with custom styling
    fig.add_trace(
        go.Candlestick(
            x=df["timestamp"],
            open=df["open"],
            high=df["high"],
            low=df["low"],
            close=df["close"],
            name="Price",
            increasing_line_color='green',        # Bullish candle borders
            decreasing_line_color='red',         # Bearish candle borders
            increasing_fillcolor='rgba(0,255,0,0.3)',  # Bullish candle fill
            decreasing_fillcolor='rgba(255,0,0,0.3)'   # Bearish candle fill
        )
    )

    # Build informative chart title with market data
    title_parts = [f"GMX: {token_symbol} OHLC Analysis"]

    if title_suffix:
        title_parts.append(title_suffix)

    # Add TVL information if available
    if market_info and "tvl" in market_info:
        for market_symbol, tvl_data in market_info["tvl"].items():
            if token_symbol.upper() in market_symbol.upper():
                tvl_value = tvl_data['total_tvl']

                # Handle potential decimal precision issues
                if tvl_value > 1e15:  # Convert from wei if needed
                    tvl_value = tvl_value / 1e18

                if 1000 <= tvl_value <= 1e11:
                    title_parts.append(f"TVL: ${tvl_value:,.0f}")
                break

    # Configure chart layout
    fig.update_layout(
        title=" | ".join(title_parts),
        height=500,
        showlegend=False,
        xaxis_rangeslider_visible=False
    )

    # Customize axis labels
    fig.update_yaxes(title_text="Price (USD)", tickformat=".2f")
    fig.update_xaxes(title_text="Time")

    return fig

# Example usage
fig = create_ohlc_chart(ohlc_df, "ETH", market_info)
fig.show()