Skip to content
datastore.sh

Solana Token Price History: Building OHLCV From DEX Swaps Without Lying to Yourself

Why price APIs miss long-tail Solana tokens, and how to reconstruct OHLCV from DEX swaps with pool, quote, and wash-trade decisions made explicit.

Data Platform15 min read

Solana crossed $3 trillion in cumulative DEX volume in 2026 and now carries roughly 36% of global spot DEX flow (Crypto Briefing). Almost none of that volume has a price history you can download. The tokens that matter for research, the ones that launched, ran, and died inside a month, never reached a price API's coverage list, and the ones that did are served as a single aggregated number with no record of which pool, which quote asset, or which trades produced it.

For a quant, that leaves one honest route: rebuild Solana token price history from the swaps themselves. Raydium, Orca and Meteora each write every trade to the ledger with exact input and output amounts. Candles derived from those records reflect what was actually tradeable, at the size that actually traded. They also expose every way a naive aggregation lies: three different definitions of "price" across three AMM designs, a quote asset that is usually SOL rather than dollars, and a share of volume that was never a real trade.

This guide is for the researcher who needs a defensible OHLCV series for long-tail Solana tokens. It covers why an API is not a history, how each major AMM defines price, how to convert SOL-quoted trades to dollars without look-ahead, how to keep wash trades out of the close, and a step-by-step build with a validation checklist. It follows on from our comparison of Solana DEX historical data and the Pump.fun data guide, and leans on the Raydium AMM V4, Orca Whirlpool and Meteora DLMM tables in the catalog.

Why a Price API Is Not a Price History

Price endpoints answer "what is this token worth now." They do not answer "what could I have sold 2,000 SOL worth of this token for at 14:07 UTC on a Tuesday in March," which is the question every backtest actually asks. Four gaps follow.

Coverage is survivorship-biased. APIs index tokens once they clear a liquidity or listing threshold. A token that ran 50x and collapsed in nine days often never clears it, or clears it only after the run. A universe built from an API's coverage list is a universe of survivors.

Aggregation hides the pool. A single "token price" blends every pool the aggregator knows about. When a Raydium pool has $800 of liquidity and a Meteora pool has $400,000, the blended number is dominated by one and polluted by the other, and you cannot tell which.

The quote asset is implicit. Most long-tail pairs are quoted in SOL. The API converts to USD using its own SOL feed at its own timestamp. You cannot reproduce that conversion, which means you cannot reproduce the series.

There is no provenance. An API price has no transaction signature behind it. When a candle looks wrong, there is nothing to inspect. A swap record carries the signature, the slot, the wallet, the pool and the amounts, so a suspicious print can be traced to the trade that caused it.

None of this makes price APIs useless for a dashboard. It makes them unfit as the input to a model, which is the difference that matters here.

Three AMMs, Three Definitions of Price

A "price" on a Solana DEX is a number you compute from on-chain state or from a trade, and the computation differs by program. Getting it wrong by a factor of 10^3 (a decimals error) or by inverting the pair is the most common failure in a homegrown pipeline.

ProgramPool price sourceFormulaDecimals handling
Raydium AMM V4Vault reserves after each swapprice = reserve_quote / reserve_baseDivide each reserve by 10^decimals first
Orca Whirlpoolsqrt_price field (Q64.64 fixed point)price = (sqrt_price / 2^64)^2 × 10^(dec_a − dec_b)Built into the formula; order of A and B is fixed by the pool
Meteora DLMMactive_id and bin_stepprice = (1 + bin_step / 10000)^active_id, offset-adjustedAdjust by 10^(dec_x − dec_y) after computing the bin price

Raydium's constant-product pool is the simple case: the marginal price is the ratio of the two vault balances. Orca's concentrated pool stores the square root of price as a 128-bit fixed-point integer; the conversion is (sqrt_price / 2^64)^2, then a decimals adjustment, as the program's own token math implements it. Meteora's DLMM has no continuous price at all: liquidity sits in discrete bins, each bin is a fixed price, and consecutive bins differ by the bin step, so a 25 basis-point pool at $20 has bins at $20.00, $20.05, $20.10 and so on (Meteora docs). Trades inside the active bin execute at exactly that bin's price with zero slippage; price only moves when the bin is drained.

Two consequences follow for candle construction. First, the three pool prices are not interchangeable: a Whirlpool sqrt_price tick and a DLMM active_id are different quantizations of the same market, and mixing them without normalizing to a common base/quote and decimals produces a series with structural jumps that are not market moves. Second, the pool-state price is a mark, not a fill. For OHLCV you want fills.

Executed price is the candle; pool price is the mark

Every swap record carries amount_in and amount_out. The executed price is simply amount_out / amount_in after decimals, oriented so the base token is in the denominator. This is the price someone actually paid, inclusive of fee and impact, and it is what a backtest can assume it would have received at that size. Build open, high, low and close from executed prices. Use the pool-state price only to mark positions between trades, and keep the two in separate columns so nobody downstream confuses them.

The Quote Asset Problem

The overwhelming majority of long-tail Solana pairs are quoted in SOL, and a smaller set in USDC. A USD candle therefore requires a SOL/USD conversion for every trade, and how you do that conversion decides whether the series is reproducible and whether it contains look-ahead.

The rule: convert each trade using the SOL/USDC executed price from the same slot, or the most recent slot before it, from one named pool you fix in advance. The deepest SOL/USDC pools on Orca and Raydium trade continuously enough that the staleness is seconds. Record which pool and which slot you used as columns, not as a note in a README.

What not to do is convert with a daily or hourly SOL close. An hourly close applied to a trade at the start of the hour leaks future information into the dollar price, which is the same error we described in building a dataset without look-ahead bias. On a memecoin that moves 30% in an hour this is often invisible; on the SOL leg during a volatile hour it shifts every dollar candle by the full intra-hour move.

A second, quieter issue: SOL-quoted candles and USD-quoted candles are different research objects. A token can be flat in SOL and down 20% in USD. Decide which one your hypothesis is about before you build, and if it is both, store both.

Volume That Never Happened

Solana's fee structure makes wash trading nearly free, and the long tail is where it concentrates. VanEck's analysis estimated that around 41% of memecoin and NFT volume on Solana was attributable to wash trading (Bitcoinist), and Flip Research documented a token with $10.8 million of volume generated against $48 of liquidity by 2,845 wallets trading it back and forth (Cointelegraph). A candle built on raw swaps inherits all of that.

The damage is specific. Wash trades are usually tiny, so they barely move the pool price, but they do print an executed price, and that print lands in the close if it is the last trade of the bar. Bundled bot activity also creates alternating buys and sells at near-identical prices, which inflates volume and crushes any volume-weighted statistic. Three filters catch most of it without discarding real activity:

  • Size floor. Drop trades below a notional threshold, set per token as a fraction of pool liquidity at that slot rather than as a fixed dollar amount. A $2 trade in a $50,000 pool is noise; a $2 trade in a $40 pool is the market.
  • Self-trade and round-trip detection. Flag wallets that are on both sides of the same pool within a short window, and pairs of trades that return a wallet to its starting balance. Keep the flag as a column; do not delete the rows, because the person studying wash trading needs them.
  • Robust close. Define the bar's close as the volume-weighted average of the last N trades, or the median executed price in the final seconds, rather than the last single print. This one change removes most of the single-trade spikes that a last-print close produces on thin tokens.

Volume deserves its own treatment. Report raw volume and filtered volume as two columns, because the ratio between them is itself a signal about the token.

One Token, Many Pools

A token with any traction trades in several places at once, and the right pool to price it from changes over its life. The Pump.fun lifecycle is the clearest example: a token begins on a bonding curve, graduates, and migrates to a PumpSwap or Raydium pool, after which other pools appear on Meteora and Orca as LPs chase fees. Our Pump.fun guide covers the graduation mechanics; the pricing consequence is that the primary pool at hour 1 is not the primary pool at day 3.

Choose the reference pool as of each bar, by liquidity or by filtered volume at that time, never by today's ranking. A pool that is dominant now may not have existed when the bar you are computing closed. Store the chosen pool address on every candle so the choice is auditable.

Aggregating across pools is possible but requires care. Volume-weight executed prices across pools within a bar, but only after normalizing to the same quote asset and after the wash filters, otherwise the thin pool's fake volume drags the blended price. And never count a routed trade twice: a Jupiter swap that hops through two pools generates two swap events and one trade. Our DEX comparison explains the route-counting problem in detail; for candles, dedupe on transaction signature and treat the user's net input and output as the trade.

Market share across venues also shifts enough to matter for pool selection: in September 2026, Pump.fun's venue led Solana DEX volume at 18.6% with Raydium at 12.2% (Crypto Briefing), a very different split from a year earlier. A pool-selection rule written for last year's market picks the wrong pool this year.

Building the Candles, Step by Step

The following assumes decoded swap tables with signature, slot, block_time, pool, program, wallet, mint_in, mint_out, amount_in, amount_out, and token decimals available by mint.

  1. Define the token universe as of each day. Include every mint with at least one qualifying pool on that day. Do not filter on current liquidity or current existence.
  2. Dedupe routed trades. Collapse swap events to one row per signature per wallet, netting input and output across hops.
  3. Orient every trade. Express each as base_amount, quote_amount, and side, with the token under study as base and SOL or USDC as quote. Apply decimals here, once.
  4. Compute executed price. price_quote = quote_amount / base_amount. Carry program and pool alongside it.
  5. Attach the SOL/USD conversion. Join the SOL/USDC executed price from the fixed reference pool at the same or prior slot. Store sol_usd_slot and sol_usd_pool.
  6. Apply the wash filters. Add is_dust, is_round_trip, and is_self_trade flags. Keep all rows.
  7. Select the reference pool per bar. Rank pools by filtered volume in the bar; record the winner.
  8. Aggregate. For each bar, compute open as the first filtered trade, high and low from filtered trades, close as the volume-weighted price of the last N filtered trades, raw volume, filtered volume, trade count and unique wallets. Produce one series in SOL and one in USD.
  9. Mark gaps explicitly. A bar with no trades is a missing bar, not a repeat of the previous close. Forward-filling closes manufactures a flat line that a strategy will happily trade against.

A sanity check worth running on day one: pick a liquid token, build hourly candles from Orca alone and from Raydium alone, and compare closes. They should agree within the sum of the two pools' typical spreads. If they diverge by more than that, the decimals, the orientation, or the quote conversion is wrong on one side, and finding which teaches more about the pipeline than any amount of reading.

Validating a Price Series Before You Trust It

  • Decimals round-trip. For a SOL/USDC pool, the computed price should be within 1% of the known SOL price at that time. If it is off by 1,000x, the decimals adjustment is inverted.
  • Orientation is stable. The base token is the same mint on every row for a given token, regardless of which side of the pool it occupies.
  • Executed and pool prices agree. Within a bar, the volume-weighted executed price and the end-of-bar pool price should sit within the pool's fee plus typical impact of each other.
  • Cross-pool agreement. Closes from two liquid pools for the same token agree within combined spread.
  • No forward-filled bars. Count bars with zero trades and confirm they carry null, not the prior close.
  • SOL conversion is causal. For every trade, sol_usd_slot <= slot.
  • Routed trades are counted once. Volume summed from candles equals volume summed from deduped trades, not from raw swap events.
  • Wash flags are populated. The share of flagged volume is non-zero on long-tail tokens; if it is zero everywhere, the detector is not running.
  • Reference pool is recorded. Every candle names its pool, and the named pool existed at that bar's time.
  • Spot-check one signature. Take a single candle's close, find the trades behind it, open one on an explorer, and confirm the amounts reproduce the price.

The Price Is a Reconstruction, and Should Say So

There is no canonical price for a Solana token. There is a set of trades, in several pools, in several quote assets, some fraction of which were not real, and a series of decisions about how to turn that into one number per bar. A good OHLCV table carries those decisions as columns: which pool, which SOL conversion, which filters, how the close was defined. A bad one carries a number and asks you to trust it.

The immediate action: take any price series you currently use for Solana research and check whether it can tell you the transaction signature behind a single close. If it cannot, it is a reconstruction whose rules you do not know, and every result built on it inherits that uncertainty.

Build candles from decoded swaps with pool, wallet and amounts on every row. Raydium AMM V4 · Orca Whirlpool · Meteora DLMM · Coverage and known gaps

Frequently Asked Questions

Where can I get historical prices for Solana tokens not on CoinGecko?

Derive them from on-chain swaps. Every Raydium, Orca and Meteora trade records exact input and output amounts, so any token with a pool has a reconstructable price history from its first trade, whether or not an aggregator ever listed it.

Should I use the pool price or the trade price for OHLCV?

Trade (executed) price for open, high, low and close, because it reflects what was actually paid including fee and impact. Pool-state price is a mark for valuing positions between trades. Store both, label both.

How do I convert SOL-quoted trades to USD without look-ahead bias?

Join each trade to the SOL/USDC executed price from one fixed reference pool at the same slot or the most recent prior slot. Never apply an hourly or daily SOL close to trades inside that period.

How do I handle wash trading in Solana price data?

Flag rather than delete: mark dust trades relative to pool liquidity, wallets on both sides of a pool in a short window, and round trips. Build the close from a volume-weighted average of the last several unflagged trades instead of the single last print, and report raw and filtered volume separately.

Which pool should I price a token from?

The one with the most filtered volume or liquidity at the time of the bar, chosen per bar and recorded on the candle. Pump.fun tokens in particular move from bonding curve to PumpSwap or Raydium to other venues within days.

Do Orca and Meteora prices need different handling than Raydium?

Yes. Raydium AMM V4 price is a reserve ratio; Orca Whirlpool stores a Q64.64 square-root price; Meteora DLMM stores an active bin ID and bin step. Each needs its own conversion and decimals adjustment before the three can be compared.

Sources