From 1f4e7dbb479c311c883f46530c9d0dfd767690c2 Mon Sep 17 00:00:00 2001 From: Gaurav Agarwal Date: Thu, 10 Sep 2026 10:25:56 +0530 Subject: [PATCH 1/2] docs(solana): explain DEXTradeByTokens side-currency grain, before vs till, pre-aggregated vs raw path Adds a section to the Solana DEX Trades page answering why a per-minute OHLC query returns one row per side currency, why till pulls a whole extra minute on the pre-aggregated path, and how selecting Slot(minimum) or uniq switches realtime queries to the raw trades. Includes a tested one-candle-per-token-minute query. Ref BIT-15455. Co-Authored-By: Claude Fable 5.1 --- docs/blockchain/Solana/solana-dextrades.mdx | 54 +++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/docs/blockchain/Solana/solana-dextrades.mdx b/docs/blockchain/Solana/solana-dextrades.mdx index c8852f48..e991e229 100644 --- a/docs/blockchain/Solana/solana-dextrades.mdx +++ b/docs/blockchain/Solana/solana-dextrades.mdx @@ -1999,6 +1999,60 @@ query TradesSolToken($ca: String!, $currencies: [String!], $dataset: dataset_arg Check data here on [DEXrabbit](https://dexrabbit.bitquery.io/solana/pair/9Fv7n7HuA5EzjCRSvprBMx2Qhd1VL3hPEXKhTUKPm1Qh/So11111111111111111111111111111111111111112). +## Why does my per-minute OHLC query return hundreds of rows for one token? {#dextradebytokens-side-currency-grain} + +Three things about `DEXTradeByTokens` aggregates that are easy to miss: + +- **`PriceInUSD` groups by side currency.** Any `Trade { PriceInUSD(minimum: ...) }` or `PriceInUSD(maximum: ...)` selector adds `Trade.Side.Currency.MintAddress` to the grouping key, even if you do not select it, because USD conversion is computed per side currency. A minute in which WSOL traded against 797 different tokens returns 797 rows, one per side currency. The rows do not overlap and `count` stays additive. `Trade { Price(...) }` behaves the same way but groups by the side currency's `Decimals`. To get one row per token and interval, filter `Trade.Side.Currency.MintAddress` to the quote currencies you care about (for WSOL: USDC and USDT; for other tokens add WSOL) and select `Trade { Side { Currency { MintAddress } } }` so the grain is visible. If more than one quote currency traded, you get one row per quote currency; combine them on your side. +- **Use `before`, not `till`, for the end of a candle.** `till` is inclusive. On the pre-aggregated path (below) `Block.Time` is stored at minute resolution, so `till: "…T02:35:00Z"` matches every trade in the 02:35 minute. `since` + `before` gives the half-open interval `[since, end)` on every path. +- **Two backing tables on `dataset: realtime`.** A query that selects only `Block.Time(interval: …)`, `Trade.Price…` measures and `count` is served from a per-minute pre-aggregated table. Adding `Block { Slot(minimum: Block_Slot) }` or `uniq(of: Transaction_Signature)` switches it to the raw trades. The two can differ: the pre-aggregated table does not return the raw fill extremes, while the raw path returns the true highest and lowest fill, including dust-sized trades. Pick the path that matches your definition and keep it fixed. `dataset: combined` serves the price selectors but not `Slot(minimum/maximum)` or `uniq` on this cube. + +One candle per token and minute from raw trades, half-open interval: + +
+ Click to expand GraphQL query + +```graphql +query SolMinuteOHLC($token: String!, $quotes: [String!]!, $since: DateTime!, $end: DateTime!) { + Solana(dataset: realtime) { + DEXTradeByTokens( + where: { + Block: {Time: {since: $since, before: $end}} + Transaction: {Result: {Success: true}} + Trade: {Currency: {MintAddress: {is: $token}}, Side: {Currency: {MintAddress: {in: $quotes}}}} + } + orderBy: {ascendingByField: "Block_bucket"} + limit: {count: 5000} + ) { + Block { + bucket: Time(interval: {count: 1, in: minutes}) + firstSlot: Slot(minimum: Block_Slot) + lastSlot: Slot(maximum: Block_Slot) + } + Trade { + Side { Currency { MintAddress } } + open: PriceInUSD(minimum: Block_Slot) + high: PriceInUSD(maximum: Trade_PriceInUSD) + low: PriceInUSD(minimum: Trade_PriceInUSD) + close: PriceInUSD(maximum: Block_Slot) + } + fills: count + transactions: uniq(of: Transaction_Signature) + } + } +} +{ + "token": "So11111111111111111111111111111111111111112", + "quotes": ["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCdmmxZ2o8"], + "since": "2026-09-10T02:34:00Z", + "end": "2026-09-10T02:35:00Z" +} +``` + +
+ +`count` is the number of DEX fills in which the token is one side; a routed swap with several fills counts each of them. `open` and `close` pick one fill from the first and last slot; when several fills share that slot the choice is not defined by transaction order. For a strict first and last, query the raw trades of those two slots and sort by `Block.Slot`, `Transaction.Index`, `Trade.Index`. If you want a single all-venue candle without choosing quote currencies, use the [Trading API](/docs/trading/crypto-price-api/introduction/) `Trading { Tokens }` cube (price index, about 30 days of history). + ## Solana Real time prices from multiple Markets You can retrieve data from multiple Solana DEX markets using our APIs or streams. From a9819e46a2d983c036056ecb763c0315367ac945 Mon Sep 17 00:00:00 2001 From: Gaurav Agarwal Date: Thu, 10 Sep 2026 10:31:04 +0530 Subject: [PATCH 2/2] docs(solana): fix Trading API link path (broken-link check) Co-Authored-By: Claude Fable 5.1 --- docs/blockchain/Solana/solana-dextrades.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/blockchain/Solana/solana-dextrades.mdx b/docs/blockchain/Solana/solana-dextrades.mdx index e991e229..b6a00e13 100644 --- a/docs/blockchain/Solana/solana-dextrades.mdx +++ b/docs/blockchain/Solana/solana-dextrades.mdx @@ -2051,7 +2051,7 @@ query SolMinuteOHLC($token: String!, $quotes: [String!]!, $since: DateTime!, $en -`count` is the number of DEX fills in which the token is one side; a routed swap with several fills counts each of them. `open` and `close` pick one fill from the first and last slot; when several fills share that slot the choice is not defined by transaction order. For a strict first and last, query the raw trades of those two slots and sort by `Block.Slot`, `Transaction.Index`, `Trade.Index`. If you want a single all-venue candle without choosing quote currencies, use the [Trading API](/docs/trading/crypto-price-api/introduction/) `Trading { Tokens }` cube (price index, about 30 days of history). +`count` is the number of DEX fills in which the token is one side; a routed swap with several fills counts each of them. `open` and `close` pick one fill from the first and last slot; when several fills share that slot the choice is not defined by transaction order. For a strict first and last, query the raw trades of those two slots and sort by `Block.Slot`, `Transaction.Index`, `Trade.Index`. If you want a single all-venue candle without choosing quote currencies, use the [Trading API](/docs/trading/crypto-price-api/) `Trading { Tokens }` cube (price index, about 30 days of history). ## Solana Real time prices from multiple Markets