skills/gmx-io/gmx-ai/gmx-trading

gmx-trading

SKILL.md

GMX Trading Skill

Overview

GMX V2 is a decentralized perpetual and spot exchange using oracle-based pricing (Chainlink Data Streams) instead of AMM curves. Traders get CEX-like execution with onchain settlement.

Supported chains:

Chain Chain ID Native Token
Arbitrum 42161 ETH
Avalanche 43114 AVAX
Botanix 3637 BTC

Two integration paths:

  • SDK (@gmx-io/sdk) — Full read + write: fetch markets, create orders, manage positions
  • REST API — Read-only: prices, markets, positions, trade history

Trading modes:

  • Classic — User signs each transaction directly
  • Express — Gelato relay pays gas, user signs EIP-712 message (frontend only)
  • Express + One-Click — Subaccount delegates signing for instant execution (frontend only)

SDK Quick Start

Install the SDK:

npm install @gmx-io/sdk viem

Import note: The SDK's ESM build has broken imports (missing file extensions). Use CommonJS require or configure your bundler to resolve extensionless imports. In Node.js scripts, use const { GmxSdk } = require("@gmx-io/sdk").

Create an SDK instance:

const { GmxSdk } = require("@gmx-io/sdk");

const sdk = new GmxSdk({
  chainId: 42161,
  rpcUrl: "https://arb1.arbitrum.io/rpc",
  oracleUrl: "https://arbitrum-api.gmxinfra.io",
  subsquidUrl: "https://gmx.squids.live/gmx-synthetics-arbitrum:prod/api/graphql",
});

Chain configuration:

Chain chainId oracleUrl subsquidUrl
Arbitrum 42161 https://arbitrum-api.gmxinfra.io https://gmx.squids.live/gmx-synthetics-arbitrum:prod/api/graphql
Avalanche 43114 https://avalanche-api.gmxinfra.io https://gmx.squids.live/gmx-synthetics-avalanche:prod/api/graphql
Botanix 3637 https://botanix-api.gmxinfra.io https://gmx.squids.live/gmx-synthetics-botanix:prod/api/graphql

Set up a wallet for write operations:

const { createWalletClient, http } = require("viem");
const { privateKeyToAccount } = require("viem/accounts");
const { arbitrum } = require("viem/chains");

const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const walletClient = createWalletClient({
  account,
  chain: arbitrum,
  transport: http("https://arb1.arbitrum.io/rpc"),
});

const sdk = new GmxSdk({
  chainId: 42161,
  rpcUrl: "https://arb1.arbitrum.io/rpc",
  oracleUrl: "https://arbitrum-api.gmxinfra.io",
  subsquidUrl: "https://gmx.squids.live/gmx-synthetics-arbitrum:prod/api/graphql",
  account: account.address,
  walletClient,
});

Lightweight alternative (read-only, no RPC needed):

const { GmxApiSdk } = require("@gmx-io/sdk/v2");

const apiSdk = new GmxApiSdk({ chainId: 42161 });
const markets = await apiSdk.fetchMarketsInfo(); // Returns array-like of MarketInfo objects
// Access: markets[0].marketTokenAddress, markets[0].indexTokenAddress, etc.

Order Helpers

The SDK provides convenience methods that handle amount calculation and transaction submission automatically.

Important: Never hardcode market or token addresses. Always fetch them dynamically — addresses differ per chain and can change between deployments.

Step 1: Resolve market and token addresses

// Fetch all markets and tokens
const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();

// Find ETH/USD market by index token symbol
// Note: On Arbitrum, perpetual markets use "WETH" as the index token symbol.
// Markets with symbol "ETH" are spot-only swap pools.
const ethUsdMarket = Object.values(marketsInfoData).find(
  (m) => tokensData[m.indexTokenAddress]?.symbol === "WETH" && !m.isSpotOnly
);

// Get token addresses from market
const marketAddress = ethUsdMarket.marketTokenAddress;
const longToken = ethUsdMarket.longTokenAddress;   // e.g., WETH
const shortToken = ethUsdMarket.shortTokenAddress;  // e.g., USDC

// Find a token by symbol
const usdcAddress = Object.values(tokensData).find((t) => t.symbol === "USDC")?.address;

Step 2: Place orders

Open a long position:

await sdk.orders.long({
  marketAddress,                         // from step 1
  payTokenAddress: usdcAddress,          // token you're paying with
  collateralTokenAddress: longToken,     // WETH as collateral for longs
  payAmount: 100000000n,                 // 100 USDC (6 decimals)
  leverage: 50000n,                      // 5x leverage (basis points)
  allowedSlippageBps: 100,               // 1% slippage
});

Open a short position:

await sdk.orders.short({
  marketAddress,
  payTokenAddress: usdcAddress,
  collateralTokenAddress: shortToken,    // USDC as collateral for shorts
  payAmount: 100000000n,
  leverage: 50000n,
});

Swap tokens:

const arbAddress = Object.values(tokensData).find((t) => t.symbol === "ARB")?.address;
const linkAddress = Object.values(tokensData).find((t) => t.symbol === "LINK")?.address;

await sdk.orders.swap({
  fromTokenAddress: arbAddress,
  toTokenAddress: linkAddress,
  fromAmount: 1000000000000000000n, // 1 ARB (18 decimals)
  allowedSlippageBps: 100,
});

Limit orders — add limitPrice for positions or triggerPrice for swaps:

await sdk.orders.long({
  marketAddress,
  payTokenAddress: usdcAddress,
  collateralTokenAddress: longToken,
  payAmount: 100000000n,
  leverage: 50000n,
  limitPrice: 3000000000000000000000000000000000n, // $3000 (30 decimals)
});

Key parameters:

  • leverage — In basis points: 10000n = 1x, 50000n = 5x, 1000000n = 100x
  • allowedSlippageBps — Default 100 (1%). Range: 1-500
  • payAmount — Pay this much collateral. Alternative: use sizeAmount to specify position size
  • fromAmount / toAmount — For swaps, specify input or desired output amount

Step 3: Close a position

There is no convenience close() method. Closing requires computing decrease amounts via getDecreasePositionAmounts() from @gmx-io/sdk/utils/trade, then calling createDecreaseOrder().

Important: Always re-fetch marketsInfoData and tokensData right before closing. These contain oracle prices that go stale within seconds — using old data produces an acceptablePrice the keeper will reject.

const { getDecreasePositionAmounts } = require("@gmx-io/sdk/utils/trade");

// 1. Fetch FRESH market data (prices go stale quickly)
const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();

// 2. Get the position to close
const positionsInfo = await sdk.positions.getPositionsInfo({
  marketsInfoData, tokensData, showPnlInLeverage: false,
});
const position = Object.values(positionsInfo).find(
  (p) => p.marketAddress === marketAddress && p.isLong === true
);

// 3. Compute decrease amounts
const marketInfo = marketsInfoData[position.marketAddress];
const collateralToken = tokensData[position.collateralTokenAddress];
const { minCollateralUsd, minPositionSizeUsd } = await sdk.positions.getPositionsConstants();
const uiFeeFactor = await sdk.utils.getUiFeeFactor();

const decreaseAmounts = getDecreasePositionAmounts({
  marketInfo,
  collateralToken,
  isLong: position.isLong,
  position,
  closeSizeUsd: position.sizeInUsd,   // Full close. Use a smaller value for partial close.
  keepLeverage: false,
  userReferralInfo: undefined,
  minCollateralUsd,
  minPositionSizeUsd,
  uiFeeFactor,
  isSetAcceptablePriceImpactEnabled: false,
});

// 4. Submit the decrease order
await sdk.orders.createDecreaseOrder({
  marketInfo,
  marketsInfoData,
  tokensData,
  isLong: position.isLong,
  allowedSlippage: 300,    // 3% — use higher slippage for decrease to avoid keeper rejection
  decreaseAmounts,
  collateralToken,
});

SDK Modules

Module Key Methods Description
sdk.markets getMarkets(), getMarketsInfo(), getDailyVolumes() Market data and liquidity info
sdk.tokens getTokensData(), getTokensBalances() Token metadata, prices, balances
sdk.positions getPositions(), getPositionsInfo(), getPositionsConstants() Open position data
sdk.orders long(), short(), swap(), getOrders(), cancelOrders() Order creation and management
sdk.trades getTradeHistory() Historical trade actions
sdk.utils getGasLimits(), getGasPrice(), getExecutionFee(), getUiFeeFactor() Gas and fee estimation
sdk.oracle getTickers(), getMarkets(), getTokens() Direct oracle data access

Typical read flow:

const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();
const positionsInfo = await sdk.positions.getPositionsInfo({
  marketsInfoData, tokensData, showPnlInLeverage: false,
});
const { ordersInfoData } = await sdk.orders.getOrders({
  marketsInfoData, tokensData,
});

Convenience vs Low-level Methods

The SDK has two tiers for order creation:

Convenience methods — handle amount calculation, execution fee, and tx submission automatically. Use these for opening positions and swaps:

Method Purpose Key Params
sdk.orders.long() Open long position marketAddress, payTokenAddress, collateralTokenAddress, payAmount, leverage
sdk.orders.short() Open short position Same as long()
sdk.orders.swap() Swap tokens fromTokenAddress, toTokenAddress, fromAmount
sdk.orders.cancelOrders() Cancel pending orders orderKeys: string[]

Low-level methods — require you to pre-compute amounts, provide full market/token objects, and handle execution fees. Required for closing positions (no convenience close() method exists):

Method Purpose Required Setup
sdk.orders.createIncreaseOrder() Open position (full control) IncreasePositionAmounts, marketInfo, tokensData
sdk.orders.createDecreaseOrder() Close/reduce position DecreasePositionAmounts via getDecreasePositionAmounts()
sdk.orders.createSwapOrder() Swap (full control) SwapAmounts, swap path

Key gap: There is no sdk.orders.close(). To close a position, use getDecreasePositionAmounts() from @gmx-io/sdk/utils/trade + createDecreaseOrder(). See Step 3: Close a position for the full pattern.

Order Types

Type Enum Behavior
Market MarketSwap(0), MarketIncrease(2), MarketDecrease(4) Execute immediately at current oracle price
Limit LimitSwap(1), LimitIncrease(3), LimitDecrease(5) Execute when oracle price reaches trigger price
Stop Increase StopIncrease(8) Open position when price moves past trigger (breakout entry)
Stop-Loss StopLossDecrease(6) Auto-close position to limit losses
Liquidation Liquidation(7) System-triggered when position falls below maintenance margin

Trigger conditions:

  • Long Limit: oracle price <= trigger price (buy the dip)
  • Long Stop-Loss: oracle price <= trigger price (exit on drop)
  • Long Take-Profit (LimitDecrease): oracle price >= trigger price (exit on rise)
  • Short Limit: oracle price >= trigger price (sell the rally)
  • Short Stop-Loss: oracle price >= trigger price (exit on rise)
  • Short Take-Profit (LimitDecrease): oracle price <= trigger price (exit on drop)

Auto-cancel limits: Maximum concurrent auto-cancel orders per position: 11 on Arbitrum, 6 on Avalanche and Botanix.

Sidecar orders: Stop-loss and take-profit orders can be attached to increase orders via createSltpEntries, cancelSltpEntries, and updateSltpEntries parameters in createIncreaseOrder().

TWAP orders: Split a large order into 2–30 parts executed over a configurable duration. TWAP utilities (getTwapDurationInSeconds, getIsValidTwapParams) are exported from @gmx-io/sdk/utils/twap but full TWAP order creation is only available via the frontend UI.

Fees

Position fees:

  • Opening/closing: 0.04% if the trade balances long/short OI, 0.06% if it imbalances
  • Applied to position size (notional value)

Swap fees:

  • Standard pairs: 0.05% (balancing) / 0.07% (imbalancing)
  • Stablecoin pairs: 0.005% (balancing) / 0.02% (imbalancing)

Funding rate:

  • Adaptive rate that flows from the larger open interest side to the smaller side
  • Rebalances long/short exposure over time
  • Accrues continuously, settled on position changes

Borrowing rate:

  • Kink model: low rate below utilization threshold, steep above
  • Approximately 45–55% APR at 75% utilization
  • Paid by all positions proportional to size

Execution fee:

  • Covers keeper gas cost for executing the order onchain
  • Paid upfront in native token (ETH/AVAX/BTC)
  • Surplus refunded after execution
  • Use sdk.utils.getExecutionFee() to estimate

REST API

Oracle Endpoints

Base URL: https://{network}-api.gmxinfra.io

Endpoint Method Description
/prices/tickers GET Current min/max prices for all tokens
/prices/candles GET OHLC price candles (?tokenSymbol=ETH&period=1h)
/signed_prices/latest GET Signed oracle prices for order execution
/tokens GET Token list with addresses and decimals
/markets GET Market configuration (index/long/short tokens)
/markets/info GET Extended market info with pool sizes and utilization

All endpoints are served from the Oracle base URL above. The legacy gmx-api-{network}.gmx.io domain is no longer available.

GraphQL (Subsquid)

Base URL: https://gmx.squids.live/gmx-synthetics-{network}:prod/api/graphql

Example — fetch recent trade actions:

query {
  tradeActions(
    where: { account_eq: "0x..." }
    orderBy: timestamp_DESC
    limit: 10
  ) {
    id
    eventName
    orderType
    sizeDeltaUsd
    timestamp
    transactionHash
  }
}

Fallback URLs

Chain Primary Fallback 1 Fallback 2
Arbitrum arbitrum-api.gmxinfra.io arbitrum-api-fallback.gmxinfra.io arbitrum-api-fallback.gmxinfra2.io
Avalanche avalanche-api.gmxinfra.io avalanche-api-fallback.gmxinfra.io avalanche-api-fallback.gmxinfra2.io
Botanix botanix-api.gmxinfra.io botanix-api-fallback.gmxinfra.io botanix-api-fallback.gmxinfra2.io

Key Concepts

Oracle-based pricing: GMX does not use an AMM. Prices come from Chainlink Data Streams, giving traders zero-slippage execution at the oracle price (subject to price impact from pool utilization).

Two-phase execution: Orders follow a create → execute pattern. The user submits an order transaction, then a keeper executes it with fresh oracle prices. This typically takes 1–5 seconds.

BigInt amounts: All amounts use BigInt. Prices are scaled to 30 decimals (1 USD = 10^30). Token amounts use their native decimals (e.g., USDC = 6, ETH = 18).

Stale data: marketsInfoData and tokensData contain oracle prices at fetch time. These go stale within seconds. Always re-fetch fresh data before operations that depend on current prices — especially createDecreaseOrder(), which computes acceptablePrice from the data you provide. Using stale prices causes keeper rejection.

Multicall batching: The SDK batches RPC calls automatically. Production chains use batchSize: 1024 * 1024 bytes per multicall with no waiting. This is configured per-chain in BATCH_CONFIGS.

GMX Account: Cross-chain trading from Ethereum, Base, or BNB Chain via LayerZero/Stargate bridge. Users can trade on Arbitrum/Avalanche without bridging manually.

Subaccounts: Delegate trading to a subaccount address for one-click trading. The subaccount can execute orders without requiring the main wallet signature each time.

Full Example: Open → Monitor → Close

End-to-end flow that opens a long position, monitors it, and closes it.

const { GmxSdk } = require("@gmx-io/sdk");
const { getDecreasePositionAmounts } = require("@gmx-io/sdk/utils/trade");
const { createWalletClient, http } = require("viem");
const { privateKeyToAccount } = require("viem/accounts");
const { arbitrum } = require("viem/chains");

// ─── Setup ───────────────────────────────────────────────────────────────────

const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const sdk = new GmxSdk({
  chainId: 42161,
  rpcUrl: "https://arb1.arbitrum.io/rpc",
  oracleUrl: "https://arbitrum-api.gmxinfra.io",
  subsquidUrl: "https://gmx.squids.live/gmx-synthetics-arbitrum:prod/api/graphql",
  account: account.address,
  walletClient: createWalletClient({
    account, chain: arbitrum, transport: http("https://arb1.arbitrum.io/rpc"),
  }),
});

// ─── 1. Resolve addresses ───────────────────────────────────────────────────

const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();

const ethMarket = Object.values(marketsInfoData).find(
  (m) => tokensData[m.indexTokenAddress]?.symbol === "WETH" && !m.isSpotOnly
);
const marketAddress = ethMarket.marketTokenAddress;
const usdcAddress = Object.values(tokensData).find((t) => t.symbol === "USDC").address;

// ─── 2. Open long ───────────────────────────────────────────────────────────

await sdk.orders.long({
  marketAddress,
  payTokenAddress: usdcAddress,
  collateralTokenAddress: usdcAddress,
  payAmount: 10_000000n,   // 10 USDC
  leverage: 30000n,        // 3x
  allowedSlippageBps: 100,
  skipSimulation: true,
});

// ─── 3. Wait for position to appear (keeper executes in 1-30s) ──────────────

let position;
for (let i = 0; i < 40; i++) {
  await new Promise((r) => setTimeout(r, 3000));
  const info = await sdk.positions.getPositionsInfo({
    marketsInfoData, tokensData, showPnlInLeverage: false,
  });
  position = Object.values(info).find(
    (p) => p.marketAddress === marketAddress && p.isLong === true
  );
  if (position) break;
}
if (!position) throw new Error("Position did not appear within 120s");

console.log("Position opened:", {
  sizeUsd: position.sizeInUsd.toString(),
  leverage: position.leverage.toString(),
  entryPrice: position.entryPrice.toString(),
});

// ─── 4. Close position (re-fetch fresh data first!) ─────────────────────────

const fresh = await sdk.markets.getMarketsInfo();
const freshPositions = await sdk.positions.getPositionsInfo({
  marketsInfoData: fresh.marketsInfoData,
  tokensData: fresh.tokensData,
  showPnlInLeverage: false,
});
const pos = Object.values(freshPositions).find(
  (p) => p.marketAddress === marketAddress && p.isLong === true
);

const marketInfo = fresh.marketsInfoData[pos.marketAddress];
const collateralToken = fresh.tokensData[pos.collateralTokenAddress];
const { minCollateralUsd, minPositionSizeUsd } = await sdk.positions.getPositionsConstants();
const uiFeeFactor = await sdk.utils.getUiFeeFactor();

const decreaseAmounts = getDecreasePositionAmounts({
  marketInfo, collateralToken, isLong: pos.isLong, position: pos,
  closeSizeUsd: pos.sizeInUsd, keepLeverage: false,
  userReferralInfo: undefined, minCollateralUsd, minPositionSizeUsd, uiFeeFactor,
  isSetAcceptablePriceImpactEnabled: false,
});

await sdk.orders.createDecreaseOrder({
  marketInfo, marketsInfoData: fresh.marketsInfoData, tokensData: fresh.tokensData,
  isLong: pos.isLong, allowedSlippage: 300, decreaseAmounts, collateralToken,
});

console.log("Close order submitted — keeper will execute in 1-30s");

Limitations

  • GM pool deposits/withdrawals: See the gmx-liquidity skill for contract-level operations. SDK convenience methods not yet available.
  • GLV vault operations: See the gmx-liquidity skill for contract-level operations. SDK convenience methods not yet available.
  • Express orders: Frontend-only via Gelato relay. Not exposed in the SDK.
  • TWAP orders: Utility functions available (@gmx-io/sdk/utils/twap) but no SDK method to create TWAP orders programmatically.
  • Order updates: Orders cannot be modified. Cancel and recreate instead (sdk.orders.cancelOrders()).
  • Trade simulation: The skipSimulation parameter exists but simulation is deprecated. Set skipSimulation: true.

References

Weekly Installs
8
Repository
gmx-io/gmx-ai
GitHub Stars
7
First Seen
14 days ago
Installed on
gemini-cli7
github-copilot7
codex7
kimi-cli7
cursor7
amp7