Skip to main content

Crypto OHLC Candle (K-Line) API

What it is​

OHLC candles (Open, High, Low, Close) come pre-aggregated on the three cubes of the Crypto Price API: Trading.Pairs for one pool, Trading.Tokens for a token on one chain, Trading.Currencies for an asset across chains. Each row is one candle at one of ten fixed durations from 1 second to 1 hour, with volume, moving averages and supply fields next to it.

The cubes cover Ethereum, Solana, BSC, Polygon, Arbitrum, Optimism, Base, Tron, Robinhood and Arc and keep a rolling 30 days of history. For older candles, build them from raw trades with DEXTradeByTokens (see the comparison).

How to get it​

Pick the cube by what one candle should mean. Pairs for a single market, Tokens for a chain-wide token candle, Currencies for a cross-chain asset candle. See Choosing the cube.

Select Price { Ohlc { Open High Low Close } } plus Interval { Time { Start Duration End } }, Volume { Usd } and whatever else you need. Always filter on Interval: { Time: { Duration: { eq: <seconds> } } }.

Run it as a query, a subscription, or from Kafka. A query returns history; the same text with subscription on top streams each new candle. The trading.prices Kafka topic carries the same candles for all three cubes; see Multi-Chain Trading Data Streams and Kafka concepts.

Most examples below add Volume: { Usd: { gt: 5 } }. The index already filters outliers; this is an extra guard against near-empty candles.

Your first OHLC query​

Bitcoin 1-minute candles, blended across every chain where a Bitcoin token trades.

subscription {
Trading {
Currencies(
where: {
Currency: { Id: { is: "bid:bitcoin" } },
Interval: { Time: { Duration: { eq: 60 } } },
Volume: { Usd: { gt: 5 } }
}
) {
Currency {
Id
Name
Symbol
}
Price {
Ohlc {
Open
High
Low
Close
}
}
Volume {
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
}
}
}

Run this query ➤

Supported intervals​

IntervalDuration
1 second1s
3 seconds3s
5 seconds5s
10 seconds10s
30 seconds30s
1 minute60s
5 minutes300s
15 minutes900s
30 minutes1800s
1 hour3600s

These ten values are the complete set. Any other value (120, 7200, 86400) is not an error: the query returns [] with HTTP 200 and no errors key.

The Duration filter is effectively mandatory

The cube stores all ten durations side by side. A query without Interval: { Time: { Duration: { eq: 3600 } } } returns one row per duration for the same timestamp, so a sum(of: Volume_Usd) comes back roughly ten times too high and row counts equal the sum of the ten per-duration counts.

Bucket arithmetic​

  • Interval.Time.End == Start + Duration - 1 second. At Duration 1, End == Start.
  • Buckets are Unix-epoch aligned: Start_epoch % Duration == 0 for every row.
  • Candles are sparse. A bucket with no trades is absent. There is no gap filling; reindex client-side if you need a contiguous series.

Multiple timeframes in one request​

Duration accepts the full OLAP_Integer comparator set (eq, ne, in, notIn, gt, ge, lt, le). Pull several timeframes at once with Interval: { Time: { Duration: { in: [60, 300, 3600] } } } and split them client-side on Interval.Time.Duration. Two comparators together apply AND.

The 1-second series under-reports totals

Aggregate volume at Duration 1 comes in below every coarser bucket, by up to a few percent depending on token and window. Coarser buckets agree with each other closely. Use 60s or larger when totals must reconcile.

Custom 4-hour, daily and weekly candles​

Durations above 3600s do not exist natively. Roll 1-hour candles up to any multiple with the interval: argument on Block.Time plus argmin/argmax selectors. The aggregation is exact.

{
Trading {
Tokens(
where: {
Token: { Address: { is: "So11111111111111111111111111111111111111112" }, Network: { is: "Solana" } }
Interval: { Time: { Duration: { eq: 3600 } } }
Block: { Time: { since_relative: { days_ago: 7 } } }
}
limit: { count: 1000 }
) {
Block { Time(interval: { in: hours, count: 4 }) }
Price {
Ohlc {
Open(minimum: Interval_Time_Start)
High(maximum: Price_Ohlc_High)
Low(minimum: Price_Ohlc_Low)
Close(maximum: Interval_Time_Start)
}
}
volume: sum(of: Volume_Usd)
n: count
}
}
}
  • interval: accepts seconds, minutes, hours, days, weeks, months and years with a count and an optional offset. 4-hour, 12-hour, daily and weekly candles all use this pattern.
  • n is the number of 1-hour rows in each bucket; 4 means the bucket is complete.
  • This is a query-only pattern. interval:, selectWhere and if do not survive conversion to a subscription.
Do not add orderBy to a roll-up query

Any orderBy un-groups the aggregation and you silently get the raw source rows back instead of your buckets: a 21-row daily series becomes 504 hourly rows. Sort client-side.

Volume-based aggregation​

Interval.VolumeBased and Interval.TargetVolume exist in the schema but carry no data: filtering on either returns zero rows with HTTP 200 and no error. Use time-based intervals and, if you need a volume floor, Volume: { Usd: { gt: ... } } in the where clause.

How it is derived​

A candle is built from the trades in its bucket: Open is the first price, High and Low the extremes, Close the last. On Pairs those trades belong to one pool, so the candle is that market's own price.

On Tokens the candle is the chain's index for the token: every pool where the token is base, blended by each pool's share of the token's liquidity (measured reserve where known, volume-derived estimate otherwise, after the staleness discount). On Currencies it is the cross-chain index over every token representation of the asset.

Since 2026-10-19 the blend is liquidity-weighted; rows before that date in the 30-day history are volume-weighted and carry zero Liquidity. Full rules: Price Index Algorithm.

Which cube should I use for OHLC?​

Currency is the underlying asset (BTC, ETH, SOL). Token is one implementation of it on one chain (cbBTC, WBTC). Pair is a market between two tokens (WBTC/ETH on one pool).

  • Currencies: one candle per asset across all chains and representations. USD-quoted only. Currency cube ➤
  • Tokens: one candle per token per chain, or a stream of candles for every token on a chain. USD-quoted only. Thin pools contribute to the blend. Tokens cube ➤
  • Pairs: one candle per market, quoted in USD or in the quote token. Add Ranking: { Position: { eq: 1 } } to get candles from the token's top market instead of a blend (how and why). Pairs cube ➤

Examples​

How do I stream Bitcoin OHLC across all chains?​

One 60-second candle per minute, blended from every chain where a Bitcoin token trades.

subscription {
Trading {
Currencies(
where: {
Currency: { Id: { is: "bid:bitcoin" } },
Interval: { Time: { Duration: { eq: 60 } } },
Volume: { Usd: { gt: 5 } }
}
) {
Currency {
Id
Name
Symbol
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
BaseAttributedToUsd
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
IsQuotedInUsd
Ohlc {
Open # First price in interval
High # Highest price in interval
Low # Lowest price in interval
Close # Last price in interval
}
Average {
Mean
SimpleMoving
WeightedSimpleMoving
ExponentialMoving
}
}
}
}
}

Run Live Stream ➤

  • Moving averages use a 5-entry window at every interval.

How do I stream OHLC for every ETH pool on every DEX?​

Pairs filtered on the currency id returns one candle per market, so one minute yields many rows.

subscription {
Trading {
Pairs(
where: {Currency: {Id: {is: "bid:eth"}}, Interval: {Time: {Duration: {eq: 60}}}, Volume: {Usd: {gt: 5}}}
) {
Token {
Symbol
Network
Address
}
QuoteToken {
Symbol
Network
Address
}
Market {
Name
Protocol
Network
Address
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
IsQuotedInUsd
Ohlc {
Open
High
Low
Close
}
Average {
Mean
SimpleMoving
WeightedSimpleMoving
ExponentialMoving
}
}
}
}
}

Run Live Stream ➤

  • Without Price: { IsQuotedInUsd: true } each market publishes both a USD row and a quote-token row; check Price.IsQuotedInUsd before charting.

How do I stream OHLC for every token on Solana?​

subscription {
Trading {
Tokens(
where: {
Token: { Network: { is: "Solana" } },
Interval: { Time: { Duration: { eq: 60 } } },
Volume: { Usd: { gt: 5 } }
}
) {
Token {
Address
Id
IsNative
Name
Network
Symbol
TokenId
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
IsQuotedInUsd
Ohlc {
Close
High
Low
Open
}
Average {
ExponentialMoving
Mean
SimpleMoving
WeightedSimpleMoving
}
}
}
}
}

Run Live Stream ➤

  • Swap Network for any of the ten chains. Token candles are USD-quoted only.

How do I get hourly Bitcoin candles for the last 7 days?​

Run Query

{
Trading {
Currencies(
where: {
Currency: { Id: { is: "bid:bitcoin" } },
Interval: { Time: { Duration: { eq: 3600 } } },
Volume: { Usd: { gt: 5 } },
Block: {Time:{
since_relative:{days_ago:7}
}}
},
limit: { count: 240 },
orderBy: { descending: Block_Time }
) {
Currency {
Id
Name
Symbol
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
BaseAttributedToUsd
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
IsQuotedInUsd
Ohlc {
Open
High
Low
Close
}
Average {
Mean
SimpleMoving
WeightedSimpleMoving
ExponentialMoving
}
}
}
}
}
  • since_relative keeps the window moving. History is capped at a rolling 30 days.

Which tokens moved most in the last 5 minutes?​

Ranks the top 10 tokens by 5-minute percentage change, with at least $100k of volume in the candle.

{
Trading {
Tokens(
limit: { count: 10 }
orderBy: { descendingByField: "change" }
where: {
Price: { IsQuotedInUsd: true }
Volume: { Usd: { gt: 100000 } }
Interval: { Time: { Duration: { eq: 300 } } }
}
) {
Token {
Address
Id
IsNative
Name
Network
Symbol
TokenId
}
Currency {
Symbol
Id
Name
}
Interval {
Time {
Start
End
Duration
}
}
Volume {
Base
BaseAttributedToUsd
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
IsQuotedInUsd
Ohlc {
Close
High
Low
Open
}
Average {
Estimate
ExponentialMoving
Mean
SimpleMoving
WeightedSimpleMoving
}
}
diff: calculate(expression: "Price_Ohlc_Close - Price_Ohlc_Open")
change: calculate(expression: "round(($diff / Price_Ohlc_Open), 3) * 100")
}
}
}

Run Query ➤

  • change is a calculate expression; it can be sorted on but not filtered on server-side.

How do I compute a percentage price change in the query?​

Same idea, with the expression written in one step.

{
Trading {
Tokens(
where: {
Price: { IsQuotedInUsd: true },
Volume: { Usd: { gt: 100000 } },
Interval: { Time: { Duration: { eq: 300 } } }
}
) {
Token {
Symbol
Network
}
Price {
Ohlc {
Open
Close
}
}
# Calculate price change percentage
priceChange: calculate(expression: "((Price_Ohlc_Close - Price_Ohlc_Open) / Price_Ohlc_Open) * 100")
# Calculate absolute price change
priceDiff: calculate(expression: "Price_Ohlc_Close - Price_Ohlc_Open")
}
}
}

How do I stream 1-second candles for Uniswap v3?​

Filter Pairs on the market's factory address. One row per pool per second.

subscription {
Trading {
Pairs(
where: {
Interval: { Time: { Duration: { eq: 1 } } },
Price: { IsQuotedInUsd: true },
Market: {
Network: { is: "Ethereum" },
Address: { is: "0x1f98431c8ad98523631ae4a59f267346ea31f984" }
},
Volume: { Usd: { gt: 5 } }
}
) {
Market {
Protocol
Program
Network
Name
Address
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Close
High
Low
Open
}
IsQuotedInUsd
}
Currency {
Symbol
Name
Id
}
QuoteCurrency {
Name
Symbol
Id
}
Token {
Name
Symbol
Address
Id
NetworkBid
}
QuoteToken {
Name
Symbol
Id
Address
NetworkBid
}
}
}
}

Run Stream ➤

How do I stream 1-second candles for Raydium on Solana?​

On Solana, filter on Market.Program instead of the address.

subscription {
Trading {
Pairs(
where: {
Interval: { Time: { Duration: { eq: 1 } } },
Price: { IsQuotedInUsd: true },
Market: {
Network: { is: "Solana" },
Program: { is: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" }
},
Volume: { Usd: { gt: 5 } }
}
) {
Market {
Protocol
Program
Network
Name
Address
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Close
High
Low
Open
}
IsQuotedInUsd
}
Currency {
Symbol
Name
Id
}
QuoteCurrency {
Name
Symbol
Id
}
Token {
Name
Symbol
Address
Id
NetworkBid
}
QuoteToken {
Name
Symbol
Id
Address
NetworkBid
}
}
}
}

Run Stream ➤

How do I stream 1-second candles for PancakeSwap v3 on BSC?​

subscription {
Trading {
Pairs(
where: {
Interval: { Time: { Duration: { eq: 1 } } },
Price: { IsQuotedInUsd: true },
Market: {
Network: { is: "Binance Smart Chain" },
Address: { is: "0x0bfbcf9fa4f9c56b0f40a671ad40e0805a091865" }
},
Volume: { Usd: { gt: 5 } }
}
) {
Market {
Protocol
Program
Network
Name
Address
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Close
High
Low
Open
}
IsQuotedInUsd
}
Currency {
Symbol
Name
Id
}
QuoteCurrency {
Name
Symbol
Id
}
Token {
Name
Symbol
Address
Id
NetworkBid
}
QuoteToken {
Name
Symbol
Id
Address
NetworkBid
}
}
}
}

Run Stream ➤

How do I get moving averages next to OHLC?​

Price.Average carries the mean plus simple, weighted and exponential moving averages on every row.

subscription {
Trading {
Pairs(
where: {
Currency: { Id: { is: "bid:eth" } },
Interval: { Time: { Duration: { eq: 60 } } },
Volume: { Usd: { gt: 5 } }
}
) {
Token {
Symbol
}
Volume {
Base
Quote
Usd
}
Supply {
TotalSupply
FullyDilutedValuationUsd
MarketCap
}
Price {
Ohlc {
Open
High
Low
Close
}
Average {
WeightedSimpleMoving # Volume-weighted average
Mean # Simple average
}
}
}
}
}
  • Each moving average is computed over the last 5 candles of the same duration.

How do I compare the BTC/USDT price across chains?​

limitBy on Market_Address returns the latest row per market, so the spread between venues is visible in one response.

{
Trading {
Pairs(
where: {
Currency: { Id: { is: "bid:bitcoin" } },
QuoteCurrency: { Id: { is: "usdt" } },
Volume: { Usd: { gt: 5 } }
},
limit: { count: 10 },
orderBy: { descending: Block_Time },
limitBy: { by: Market_Address, count: 1 }
) {
Currency {
Name
Symbol
}
Market {
Name
Network
Address
}
Price {
Ohlc {
Close
}
Average {
Mean
}
}
QuoteCurrency {
Symbol
}
}
}
}

Run Query ➤

  • This block has no Duration filter, so the 10 rows can mix durations; add one before summing anything.

When should I use DEXTradeByTokens instead?​

Use the Crypto Price API for live and recent candles. Use DEXTradeByTokens on EVM or Solana when you need candles older than 30 days, per-trade detail, or an interval the index does not store.

FeatureCrypto Price APIDEXTradeByTokens
Data availabilityReal-time + rolling 30 daysReal-time + full history (dataset: combined / archive)
Data processingPre-aggregated candlesRaw trades aggregated in your query
Data qualityFiltered index priceTrade-level price
IntervalsTen fixed durationsAny Block { Time(interval: ...) } bucket

Crypto Price API: charts, dashboards, bots, mark prices for limit orders, oracles, anything that wants a normalized real-time feed.

DEXTradeByTokens: backfills, archive ranges, custom buckets, candles built from one DEX's trades. Bucket with Block { Time(interval: { count, in: minutes | hours | days }) } and derive open/high/low/close from PriceInUSD; see OHLC in a DEXTradeByTokens query.

Can I get 1-minute historical OHLC data for a full year?​

Not from the Crypto Price API; it keeps 30 days. With DEXTradeByTokens, bucket with Time(interval: { count: 1, in: minutes }) over a 365-day Block.Time range. Minutes with no trades are absent and the query is heavy, so prefer hourly or daily bars, or export raw trades for a backfill.

See Historical Solana aggregate data and DEXTradeByTokens OHLC.

Pitfalls​

  • No Duration filter inflates aggregates ten-fold. Every row exists at all ten durations.
  • An unsupported duration returns [], not an error. A typo reads as "no data".
  • Candles are sparse. Missing buckets mean no trades, not missing data.
  • 1-second totals run a few percent low. Reconcile on 60s or larger.
  • orderBy on a roll-up query returns raw rows. Sort client-side.
  • Pairs publish USD and quote-token rows. Filter Price: { IsQuotedInUsd: true } or you may chart WBTC in SOL. Tokens and Currencies are USD only.
  • Volume-based intervals return zero rows. Use time durations.
  • Rows before 2026-10-19 are volume-weighted. Liquidity is zero on them and Ranking.Weight follows volume share, not liquidity share.
  • interval:, selectWhere and if are query-only. They do not survive conversion to a subscription.

OHLCV and price data: quick answers​

QuestionWhere to read / run
How do I get OHLCV data for a token using Bitquery?Last 30 days: How to get it ; older history: DEXTradeByTokens OHLC
How do I get OHLC in a DEXTradeByTokens query?DEXTradeByTokens OHLC
How do I get historical OHLCV for a Solana token?Tokens cube · Historical OHLCV on Solana · Solana OHLC API
How do I get the current price of a token?Pairs + rank 1 (top market) · Quick start · Examples
Which cube gives the most accurate price for one token?Pairs with Ranking: { Position: { eq: 1 } }
How do I get 1-minute OHLC candles for a DEX pair?Your first OHLC query (Duration: { eq: 60 }) · Pairs cube
How do I get the all-time high (ATH) price of a token?Solana ATH example
Is there an API to get token price in USD on Solana?Pairs + rank 1 · Latest USD (Solana DEX trades)
How do I use DEXTradeByTokens vs DEXTrades for OHLCV?OHLCV: which cube? · DEXTrades cube
Build with Bitquery

Ready to run this in production?

Get an API key and run these queries in minutes, or talk to us about plans and enterprise delivery.