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
}
}
}
}
Supported intervals
| Interval | Duration |
|---|---|
| 1 second | 1s |
| 3 seconds | 3s |
| 5 seconds | 5s |
| 10 seconds | 10s |
| 30 seconds | 30s |
| 1 minute | 60s |
| 5 minutes | 300s |
| 15 minutes | 900s |
| 30 minutes | 1800s |
| 1 hour | 3600s |
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 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 == 0for 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.
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:acceptsseconds,minutes,hours,days,weeks,monthsandyearswith acountand an optionaloffset. 4-hour, 12-hour, daily and weekly candles all use this pattern.nis the number of 1-hour rows in each bucket; 4 means the bucket is complete.- This is a query-only pattern.
interval:,selectWhereandifdo not survive conversion to a subscription.
orderBy to a roll-up queryAny 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
}
}
}
}
}
- 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
}
}
}
}
}
- Without
Price: { IsQuotedInUsd: true }each market publishes both a USD row and a quote-token row; checkPrice.IsQuotedInUsdbefore 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
}
}
}
}
}
- Swap
Networkfor any of the ten chains. Token candles are USD-quoted only.
How do I get hourly Bitcoin candles for the last 7 days?
{
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_relativekeeps 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")
}
}
}
changeis acalculateexpression; 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
}
}
}
}
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
}
}
}
}
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
}
}
}
}
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
}
}
}
}
- This block has no
Durationfilter, 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.
| Feature | Crypto Price API | DEXTradeByTokens |
|---|---|---|
| Data availability | Real-time + rolling 30 days | Real-time + full history (dataset: combined / archive) |
| Data processing | Pre-aggregated candles | Raw trades aggregated in your query |
| Data quality | Filtered index price | Trade-level price |
| Intervals | Ten fixed durations | Any 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
Durationfilter 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.
orderByon 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.
Liquidityis zero on them andRanking.Weightfollows volume share, not liquidity share. interval:,selectWhereandifare query-only. They do not survive conversion to a subscription.
Related
- Crypto Price API introduction
- Price Index Algorithm
- Pairs, Tokens, Currencies
- Quick Start Examples
- Kafka: Multi-Chain Trading Data Streams (topic
trading.prices, protobuf schema, Python and npm packages) - TradingView SDK on npm
- DEXTradeByTokens cube
OHLCV and price data: quick answers
| Question | Where 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 |
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.