DEXAPITrading

Price Index for DEX Tokens: How DEX Token Prices Are Calculated and Queried

Most DEX tokens have no stablecoin pair and trade in thin pools, so a USD price has to be derived. How the Bitquery Price Index does it, and the queries for a token's price, its top market, cross-chain assets, candles and history.

Cover Image for Price Index for DEX Tokens: How DEX Token Prices Are Calculated and Queried

A token that trades on a DEX has no official price. It has pools, each with its own ratio of two tokens, and most of the time none of those pools pairs it with a dollar. That is the problem this post first described in 2021, when the only answer was to chain prices by hand: token to WETH, WETH to USDT, weighted by whatever volume you could find. The answer today is a price index that does that work for every token on every supported chain, and this post explains how it is computed and how to query it. Every query below was run against the live API before publishing.

Why DEX tokens are hard to price

Four things make a DEX price harder than a listed one:

  • No stablecoin pair. Most tokens trade only against the chain's native asset or a wrapped one, so a USD price needs a second hop through that asset's own price.
  • Many pools, different prices. The same token trades on Uniswap v2, v3 and v4, on forks and on aggregators, and thin pools quote wider and lag the deep ones.
  • Noise that looks like trades. Dust swaps, sandwich attacks and mispriced multi-hop legs are all real on-chain trades and all wrong as a price signal.
  • Stale reference legs. A trade an hour ago at a WETH price of that moment should not be converted with today's WETH price without care, or the other way round.

Any provider that reports a USD price for a DEX token has made a decision on each of those. The rest of this post is what the Bitquery Price Index decided, and what it gives you.

How the Price Index computes a price

The algorithm works upward from pools:

  1. Filter trades. Zero-amount trades and trades too small to survive the token's decimal precision are dropped before anything is computed.
  2. Weight the last hour. Every pool's price is weighted by its base-token volume over a rolling one-hour window, with an exponential decay so that recent trades count for more than older ones.
  3. Normalise the quote leg. A pool's latest trade price is converted with the quote token's current price, not the price at the time of the trade.
  4. Blend pools into a token price. For each pair where the token is the base, pools are combined by decayed volume; the pairs are then combined the same way into one token price per chain.
  5. Blend tokens into a currency price. For assets that exist on several chains, such as BTC as WBTC and cbBTC, the per-chain token prices are combined once more into a chain-agnostic currency price. Stablecoins take a separate path.

Two outputs of that process matter when you query. Every contributing pool carries a ranking: a position, where 1 is the pool contributing the most decay-weighted volume, and a weight, its share of the blend. And the results land in three cubes at different granularity: Pairs per pool, Tokens per token per chain, and Currencies per asset across chains. All three are pre-aggregated into candles from one second to one hour, with moving averages and volume, and all three stream.

The price of one token

For a single token the recommended query is the Pairs cube filtered to ranking position one: the price from the token's top market, quoted in USD even when the pool's quote token is WETH. The example is PEPE on Ethereum; change the address and network for any other token.

query TokenPriceFromTopMarket {
  Trading {
    Pairs(
      where: {
        Token: {
          Address: { is: "0x6982508145454ce325ddbe47a25d4ec3d2311933" }
          Network: { is: "Ethereum" }
        }
        Ranking: { Position: { eq: 1 } }
        Interval: { Time: { Duration: { eq: 60 } } }
        Price: { IsQuotedInUsd: true }
      }
      limit: { count: 1 }
      orderBy: { descending: Block_Time }
    ) {
      Block {
        Time
      }
      Token {
        Symbol
      }
      QuoteToken {
        Symbol
      }
      Market {
        Protocol
      }
      Pool {
        Address
      }
      Price {
        Ohlc {
          Close
        }
        Average {
          Mean
        }
      }
      Ranking {
        Position
        Weight
      }
      Volume {
        Usd
      }
    }
  }
}

Why the top market rather than the blend: for a token whose liquidity sits in one deep pool the two agree, but for a fragmented token the thin pools pull the blend away from the price you could trade at. The rank-1 row is the closest thing to an executable price. Its one catch is coverage: a token whose top market was quiet in the interval returns no row, so widen the interval or fall back to the Tokens cube when you need every token.

The blended price, market cap and supply

The Tokens cube gives the chain-wide blend across all of a token's pools, and it carries market cap, supply and volume on the same row. It is the cube for screeners and for a firehose of every token on a chain.

query TokenBlendedPrice {
  Trading {
    Tokens(
      where: {
        Token: {
          Address: { is: "0x6982508145454ce325ddbe47a25d4ec3d2311933" }
          Network: { is: "Ethereum" }
        }
        Interval: { Time: { Duration: { eq: 60 } } }
      }
      limit: { count: 1 }
      orderBy: { descending: Block_Time }
    ) {
      Block {
        Time
      }
      Token {
        Symbol
        Name
      }
      Price {
        IsQuotedInUsd
        Ohlc {
          Close
        }
        Average {
          Mean
          ExponentialMoving
        }
      }
      Supply {
        MarketCap
        CirculatingSupply
      }
      Volume {
        Usd
      }
    }
  }
}

Which pools set the price

Asking for positions one to three shows the pools behind a token's price and how much each contributes, which is the quickest way to see whether a price is anchored by one deep pool or spread across several.

query TokenTopMarkets {
  Trading {
    Pairs(
      where: {
        Token: {
          Address: { is: "0x6982508145454ce325ddbe47a25d4ec3d2311933" }
          Network: { is: "Ethereum" }
        }
        Ranking: { Position: { in: [1, 2, 3] } }
        Interval: { Time: { Duration: { eq: 3600 } } }
        Block: { Time: { since_relative: { hours_ago: 1 } } }
      }
      limit: { count: 6 }
      orderBy: { descending: Block_Time }
    ) {
      QuoteToken {
        Symbol
      }
      Market {
        Protocol
      }
      Pool {
        Address
      }
      Price {
        IsQuotedInUsd
        Ohlc {
          Close
        }
      }
      Ranking {
        Position
        Weight
      }
    }
  }
}

Weights are shares of the decay-weighted volume and can sum to slightly more or less than one across a token's pools, so normalise them before treating them as percentages. Position is a competition rank: ties share a number and the next one is skipped.

One price for an asset across chains

Bitcoin trades on Ethereum as WBTC, on Base as cbBTC and on several other chains under other wrappers. The Currencies cube blends those representations into one index, which is the number to show when the question is "what is BTC worth" rather than "what is WBTC worth on this DEX".

query BitcoinIndexPrice {
  Trading {
    Currencies(
      where: {
        Currency: { Id: { is: "bid:bitcoin" } }
        Interval: { Time: { Duration: { eq: 60 } } }
      }
      limit: { count: 1 }
      orderBy: { descending: Block_Time }
    ) {
      Block {
        Time
      }
      Currency {
        Symbol
        Name
        Id
      }
      Price {
        IsQuotedInUsd
        Ohlc {
          Close
        }
        Average {
          Mean
        }
      }
      Volume {
        Usd
      }
    }
  }
}

The candle's high and low describe the index itself, not the extremes of any single chain. A dislocation on one thin chain is damped by the blend, so to watch a depeg or a single-venue gap, query the constituents through the Tokens cube filtered on the currency id instead.

Streaming prices

Each query above becomes a live feed by changing query to subscription and dropping limit and orderBy; the Trading root takes no dataset argument in a subscription. The stream below emits every one-second candle for every token on every supported chain with a small volume floor, which is the shape a screener or a price alert engine consumes.

subscription {
  Trading {
    Tokens(
      where: {
        Interval: { Time: { Duration: { eq: 1 } } }
        Volume: { Usd: { gt: 5 } }
      }
    ) {
      Token {
        Symbol
        Address
        Network
      }
      Interval {
        Time {
          Start
          End
        }
      }
      Price {
        IsQuotedInUsd
        Ohlc {
          Open
          High
          Low
          Close
        }
        Average {
          SimpleMoving
          ExponentialMoving
        }
      }
      Volume {
        Usd
      }
      Supply {
        MarketCap
      }
    }
  }
}

The same feed is published on the trading.prices Kafka topic for consumers that need replay and lower latency; the streaming overview compares WebSocket and Kafka delivery.

History beyond the rolling window

The Price Index cubes hold roughly the last month. For candles further back, build them from raw trades on the chain-level DEXTradeByTokens cube, which reaches the archive. This returns daily candles for PEPE against WETH over the last three months, priced in USD, with the asymmetry filter dropping trades whose two legs disagree.

query HistoricalDailyCandles {
  EVM(network: eth, dataset: combined) {
    DEXTradeByTokens(
      orderBy: { descendingByField: "Block_bucket" }
      limit: { count: 90 }
      where: {
        Trade: {
          Currency: { SmartContract: { is: "0x6982508145454ce325ddbe47a25d4ec3d2311933" } }
          Side: { Currency: { SmartContract: { is: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" } } }
          PriceAsymmetry: { lt: 0.1 }
        }
        Block: { Time: { since_relative: { days_ago: 90 } } }
      }
    ) {
      Block {
        bucket: Time(interval: { in: days, count: 1 })
      }
      volume: sum(of: Trade_Amount)
      usd: sum(of: Trade_Side_AmountInUSD)
      Trade {
        high: PriceInUSD(maximum: Trade_PriceInUSD)
        low: PriceInUSD(minimum: Trade_PriceInUSD)
        open: PriceInUSD(minimum: Block_Number)
        close: PriceInUSD(maximum: Block_Number)
      }
      count
    }
  }
}

These candles come from raw trades, so they carry the noise the index filters out; the OHLC guide covers when to prefer each source and how to roll native candles up into four-hour, daily and weekly bars inside the window.

What this changes for builders

The 2021 version of this post ended with a promise to build a price index. Built, it turns three engineering problems into filters: a USD price for a token with no stablecoin pair is a Pairs query with rank one, a chart is a Pairs or Tokens query at the interval you want, and a cross-chain asset price is a Currencies query. Charting front ends consume the candles directly through the TradingView integration, and the Crypto Price API page covers plans and delivery.

FAQ

How do you price a token that only trades on DEXs?

Take every pool where the token is the base asset, weight each pool's latest price by its recent volume with a decay that favours the last few minutes, convert the quote leg with the quote token's current price, and blend. That is what the Price Index does on every supported chain, and the rank-1 pool gives the most executable single number.

Why does my token show a different price on two sites?

They chose differently on the four questions above: which pools count, how they are weighted, how the quote leg is converted and what is filtered. Fragmented tokens diverge most. Comparing a site's number against the token's rank-1 pool tells you whether it is quoting the blend or one venue.

Which cube should I use for a token price?

Pairs with Ranking.Position equal to one for a specific token, Tokens for a chain-wide number or a feed of every token, Currencies for an asset that exists on several chains. All three stream.

How far back does the Price Index go?

About a month. Older candles come from DEXTradeByTokens on the chain-level API, which holds the full archive and builds candles in-query at any interval.

Related reading

Subscribe to our newsletter

Subscribe and never miss any updates related to our APIs, new developments & latest news etc. Our newsletter is sent once a week on Monday.