Skip to main content

Question it answers

“Give me 1-hour OHLCV candles for pool/pair 0x…, open, high, low, close, volume, and trade count per hour.” Mirrors Moralis GET /pairs/{address}/ohlcv.
Candles are built by aggregating swaps, not reported by an exchange. This recipe is a downstream aggregation on top of the same trade ingest as Swaps by Pair: the sync lands every trade once (USD-enriched in-block), then a candle surface buckets those trades into fixed 1-hour candles per (pool_address, hour). “Price” per fill is the bought-leg (token1) in-block USD price already on the trade (price_usd); volume is sum(abs(notional_usd)) and trades is the fill count.

What you get

One candle row per (pool_address, bucket_start), where bucket_start is the start of a 1-hour window. Only priced trades (a same-block USD price update for the bought leg) contribute: The candles sit on top of a trade fact table (fact_swaps / swaps) carrying every fill, tx_hash, trader_address, token0/1_address, amount0/1, side, source_kind, protocol, price_usd, notional_usd, so you can drop to the underlying trades whenever you need detail behind a candle.

Source

The trade ingest is identical to Swaps by Pair: two projection branches expand the per-block swap arrays into one row per fill: tokenSwaps (pool fills) · aggregateTokenSwaps (aggregator routes) Each fill is USD-enriched inline from the same block’s tokenPriceUpdates (reversed so the chronologically-last update wins on duplicate keys), no separate price join at read time. The candle surface then aggregates these priced fills by hour.

Destination

ClickHouse uses the collapsing log-table pattern (see the recipes overview) so chain reorganizations self-correct. Two candle surfaces ship on ClickHouse: candles is a low-latency accelerator backed by an AggregatingMergeTree that is sign-aware for additive measures, and candles_exact reads fact_swaps FINAL for always-exact OHLC. On Postgres/MySQL the candles VIEW aggregates swaps at read time and is correct the moment a backfill lands.

Full schema

Below are the trade fact table and the candle surfaces this recipe produces. The candle columns are a fixed shape (open/high/low/close/volume/trades); the underlying trade table is the wider starting point, keep the columns you need (see Schema & flexibility). Raw uint256 amounts are stored as text on ClickHouse and NUMERIC(76,0) on Postgres (they exceed standard numeric precision); USD columns are wide decimals so a low-decimals token × price never overflows.
The sign column on fact_swaps drives reorg collapsing. candles is the low-latency accelerator; candles_exact is always reorg-exact (see fidelity gaps). A single-node setup can use the non-replicated engines (CollapsingMergeTree(sign), AggregatingMergeTree) without the replication path.
MySQL is the same shape with DECIMAL(38,18) for the USD columns and DECIMAL(76,0) for raw amounts, and ships the candles VIEW only. position is the block-level cursor used during backfill. To change the bucket interval, swap date_trunc('hour', …) (and the ClickHouse toStartOfHour / partition expression) for your target window.

Example reads

Newest 24 hourly candles for one pool (ClickHouse, fast accelerator):
Same window, reorg-exact (collapses +1/−1 pairs via FINAL before aggregating):
Sanity-check OHLC ordering across every candle (should return 0):
On Postgres / MySQL the candles VIEW reads the same way, keyed on pool_address:

Modes

Shipped defaults: ClickHouse hybrid (backfill → realtime), Postgres / MySQL historical (one-shot backfill). For live/reorg-safe ingestion, use ClickHouse, see the overview.
Realtime on Postgres / MySQL is not supported for this shape. The realtime reorg path needs a single-column unique on the block-level position, but trades are array-expanded (many per block), so these tables can only carry a composite unique. Run realtime/hybrid on ClickHouse, where the collapsing log table corrects reorgs per-block and the candle accelerator is sign-aware. The Postgres/MySQL configs are intended for historical backfill, re-run the backfill (and REFRESH the candle matview) to pick up corrections.

Multichain

The recipe is chain-parametrized, point it at any supported EVM chain or Solana, and the candle math is chain-agnostic. On Solana, the same logIndex can repeat across events in one instruction, so the pool-fill event identity is widened (with token0Address, token1Address, token0Amount) to keep trade rows distinct; the candle surface it produces is identical in shape.

Fidelity gaps

  • Fixed 1-hour interval (v1). Moralis’ /pairs/{address}/ohlcv takes a timeframe param (1m/5m/1h/1d/…); this recipe ships a single fixed 1-hour bucket. For other intervals, change the bucket expression (toStartOfHour / date_trunc('hour', …) / the MySQL modulus) to your target window.
  • Trade-reconstructed, not exchange-reported. Candles are built from on-chain swap fills, so they may differ slightly from a DEX’s own reported OHLC (which can apply off-chain smoothing or a different price reference). No off-chain smoothing is applied.
  • Price = bought-leg in-block USD; no carry-forward. A candle only includes trades whose bought-leg token had a same-block tokenPriceUpdate. Thin/illiquid pools with no in-block price update in an hour produce no candle for that hour (a gap), rather than a flat carry-forward candle. Dense pools (the majority of volume) are unaffected.
  • ClickHouse candles high/low under reorg. The fast accelerator’s high/low are monotonic state functions; a reorg −1 row cannot retract an extreme, so if a reorg removes an hour’s all-time high/low, candles may stay slightly wide until the next canonical trade re-establishes the range. Read candles_exact when post-reorg precision matters. volume and trades are sign-weighted and always exact on both surfaces.
  • volume is absolute traded USD. notional_usd carries the signed token1 delta (V3/V4 report signed swap amounts, negative when the token leaves the pool), so volume uses abs(notional_usd) (×reorg-sign on ClickHouse), otherwise buy/sell legs cancel and volume goes negative. This is the canonical OHLCV “traded value” definition.
  • Missing-decimals volume outliers. A handful of exotic tokens arrive with token1 decimals absent/0 (coalesced to 0 in the shared swap transform); their raw amount isn’t decimal-scaled and notional_usd is wildly inflated, producing an implausibly large candle volume. OHLC prices are unaffected (they use per-unit price_usd). Filter volume < 1e12 for a clean volume distribution; a production deployment should source token decimals from a metadata table rather than the in-event field.

Migrating from the REST API

This recipe replaces the pair-OHLCV REST endpoint, and it’s the closest to 1:1 in the swaps family: the shipped candle surface mirrors the endpoint at a 1-hour timeframe, and because the underlying swaps table is trade-level, any other timeframe is a GROUP BY you own rather than a fixed parameter. Fidelity labels: exact = straight from the chain, calculated = aggregated from real DEX trades (very close, compare with a tolerance).

GET /pairs/:address/ohlcv

Candlestick data for a pool/pair. Replaced by this recipe’s candles surface for 1-hour candles, or a bucket query over swaps for any other timeframe. The REST timeframe parameter becomes the bucket expression. A 5-minute example over swaps (Postgres, use 60 / 3600 / 86400 for 1m / 1h / 1d):
Gotchas
  • OHLC is trade-reconstructed, not exchange-reported, it tracks the old endpoint closely, but compare with a tolerance rather than expecting identical figures.
  • The REST currency parameter has no direct analog: candles here are priced in the bought leg’s in-block USD price, so be explicit about which token the OHLC is quoted in.
  • Thinly-traded pairs produce sparse candles, gaps where no priced trade occurred, not flat carry-forward candles (see Fidelity gaps).
  • volume must be sum(abs(notional_usd)), the notional is signed on V3/V4 pools, so a plain sum lets buy and sell legs cancel.
  • For live charts, run the recipe realtime on ClickHouse and read candles (or candles_exact after a reorg); for historical candles back to a date, backfill historical / hybrid from that date, no need to index from genesis. See History & backfill.

Swaps by Pair

The per-trade ingest these candles aggregate, every fill on a pair.

Trading & Charting

The use case OHLCV candles power.