docs: correct Trading cube errors and document the query operator layer - #261
Merged
Conversation
Every change here is backed by a live probe against streaming.bitquery.io, independently re-tested, and adversarially challenged before being written. Claims that failed verification were dropped rather than published. Correctness fixes (P0): - Volume-based intervals are inert (VolumeBased/TargetVolume match nothing); replaced the claim on two pages and the copy-pasteable broken example - Five subscription calculate blocks returned null: field references need a $ sigil in streams, and function calls (round/floor/abs) are unsupported there. Query blocks left untouched — they were never broken - pairs.md routed pool lookups to Market.Address, which on EVM is the protocol factory and silently returns a different pool - Price.IsQuotedInUsd is a denomination mode, not duplicated rows; deleted the de-duplication advice readers were coding against - Watchlist limitBy grouped by Token_Address, collapsing every native asset (they share an empty address) into one row - Currency OHLC is a computed index, not a min/max envelope across chains - statistics.md documented a metric named `theils` that does not exist, inverted its sign semantics, and claimed an exception that never fires - calculations.md described argmax backwards; noted where: is rejected on Trading cubes in favour of if: - array-intersect.md denied output fields and non-address values; both work - Network filters take API names: Polygon is "Matic" New: docs/subscriptions/what-does-not-survive-streaming.md — selectWhere and if are absent from streams, 18 statistical metrics are query-only, interval: is a silent no-op, and calculate behaves differently. Softened the two pages claiming any query converts to a subscription. New: docs/trading/query-operators/ — the operator layer had no home in a tree organised entirely by use case: - selectwhere-screeners: HAVING semantics and the correct pipeline order (selectWhere runs before orderBy and limitBy, not after) - argmax-selectors: Field(maximum: Key) is argMax; row coherence within one key, ties, and the both-arguments footgun - filters-and-operators: operator sets per scalar type, `any` as the only boolean combinator, per-cube branches, and the traps - statistics: exact definitions, and which metrics are numerically unsafe on price columns - array-intersect-wallet-overlap: the mechanism, and its gotchas No point-in-time figures are published, per the evergreen docs rule. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…hanics Second batch of audited P1 items. As before, every claim was re-tested and adversarially challenged; several from the original report were refuted during the audit and are deliberately absent here. trading-data-overview: new "what the Trading cube cannot do" section — no liquidity/TVL/reserves, no trader grain outside Trades, no joins to chain-level cubes (verified: no join* fields on any of the four), no success/failure filter, no volume-based intervals, no native candle above 3600s. Corrected the custom-interval routing: 4h/daily/weekly candles ARE buildable on the Trading cube by rolling native candles up. pairs.md: Pairs is Tokens plus venue/quote identity with no Tokens-only fields; liquidity is absent entirely. Row fan-out is per (Market, Pool, Pool.Id, QuoteToken), not per market. Ranking.Position is a competition rank with ties and gaps, Weight needs normalising, and a Position eq 1 filter silently drops tokens whose top market was quiet. Pool.Id is not "always empty" — it carries pool identity on singleton-contract protocols, where Market.Address cannot. tokens.md: Price.Average.Estimate is the price behind both supply valuations; Volume.BaseAttributedToUsd; identity-field table (Token.Did is chain-level and empty, Token.Decimals does not exist, a bare address in Token.Id returns nothing); and the metadata-arrival duplicate that makes (Token.Id, Interval.Time.Start) a non-unique key. supply-fields: FDV is TotalSupply x Estimate — MaxSupply is reference data and is never a multiplier. Supply is one currency-level figure repeated on every row, so it is not derived per pool and summing MarketCap across rows double-counts. if.md: a Trading buy/sell-in-one-row example, the three metrics that reject if:, and the nested-any crash. New: query-operators/sweeps-and-pagination — limitBy as a whole-population sweep, the difference between limitBy.offset and limit.offset, and why offset without orderBy silently duplicates rows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
limit: {count: 0} bypasses the documented 25,000-row cap. Replaced with an
explicit count so the example matches the published pagination guidance.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Retention (data-coverage-retention.mdx) — both figures re-measured on two consecutive days against the live API: - Solana DEXPools said ~7 days; it holds ~12 hours. Its archive/combined datasets do not exist at all — a query against them errors with "no API tables for cube DEXPool" — so historical pool state needs a cloud export, not a dataset switch. - Solana BalanceUpdates said ~7 days; it holds ~12 hours. - Rewrote the warning that generalised from the bad numbers. DEXTrades vs DEXTradeByTokens is the exception; every other Solana realtime cube measured (BalanceUpdates, DEXPools, Transfers, Instructions, TokenSupplyUpdates) holds roughly 12 hours. A 14x overstatement on two cubes is the difference between planning a week of history and getting half a day. Trades (trades-api.md) — new section covering what silently corrupts aggregates: - A row is a swap leg, not a transaction; count distinct tx hashes instead. - AmountsInUsd.Base is Amounts.Base x a smoothed reference price, so it diverges from the executed value on fast-moving tokens. AmountsInUsd.Quote is the sound leg for USD volume and PnL. - The cube emits exact duplicate rows for the same swap leg, with no field distinguishing them; added a dedup key. Verified against Solana.Transfers. - Market.Address means the pool on Solana and the factory on EVM; only Pool.Address and Market.Id are cross-chain stable. - Realtime only, ~30 days, inclusive bounds, and not DEX-only — it carries Seaport and Polymarket activity. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bitquery-for-ai.md is what an agent reads before planning an analysis, and it described a price feed. Six rules added to §6 — all of which otherwise produce a plausible wrong number rather than an error: the mandatory Duration filter and its ~10x inflation, AmountsInUsd.Quote over .Base, duplicate swap legs, case-sensitive network names, argmax semantics, and the absence of liquidity. New §7 "Answering questions about a whole population": limitBy sweeps one row per entity across the entire population rather than a top-N, selectWhere is a server-side HAVING, and — the thing worth stating plainly — a window defined relative to each row's own event cannot be expressed in GraphQL at all. That last point is what makes people conclude an analysis needs a query per token when it needs a handful. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
calculate() is used in 18+ example queries across the docs and has never been explained. Every rule below was measured live, then adversarially re-tested — which refuted one of my own findings and corrected two more. - $name refers to a RESPONSE KEY in the same selection set (explicit alias, or the field path underscore-joined), not to a column. The $ is required; references are order-independent and chain to any depth. - calculate never triggers a GROUP BY on its own. With no native metric the query stays in row mode and bare column references work; once any native metric appears the query groups, and a bare column must be a selected dimension. The resulting Code 215 names the dimension, not the expression, which sends people looking in the wrong place. - The expression is wrapped in toFloat64(), so everything coerces to Float: string-producing expressions fail unless the string parses as a number, booleans flatten to 1/0 and dates to epoch numbers. - Infinity, NaN and NULL are silently serialized as 0, so a divide-by-zero ratio is indistinguishable from a real zero and sorts first ascending. Documented the nullIf guard. - ^ is not exponentiation and the ternary ?: collides with the SQL builder's placeholder; use pow() and if(). - orderBy accepts a calculate alias and composes with limitBy — the rank-by-derived-ratio pattern — but calculate takes no selectWhere, so the gate goes on a sibling aggregate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
buddies2705
force-pushed
the
docs/trading-cube-p0-fixes
branch
from
September 2, 2026 13:06
f53143e to
dda68c5
Compare
Three pages gave three different numbers for the same ClickHouse threshold (65,536 / 65,000 / 35,000). It is 2^16; the lower figures were observations of where drift becomes visible, not the switch point. Stated once, the same way, with the observed behaviour described separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every change here was verified against the live API, independently re-tested, and adversarially challenged before being written. Claims that failed verification were dropped rather than published — including two that would have made the docs worse.
How this was validated
Findings came from a live capability probe of the Trading cube. They were then audited claim-by-claim, and every "confirmed" verdict was handed to a second agent whose job was to refute it. That mattered: 47% of the second-tier claims were wrong or imprecise, and several confident findings did not survive.
Two proposed fixes were withdrawn because re-testing showed the docs were already right:
{descending: Block_Time}from two exampleorderByarrays — that key is load-bearing;Block_Timeis the bucket-end timestamp and does not extend the GROUP BY.since_relativewindow, not from argmax.No point-in-time figures are published anywhere, per the evergreen docs rule.
Correctness fixes
VolumeBased/TargetVolumematch nothing and return[]with HTTP 200, so a query against them reads as "no data". Documented as unavailable on two pages; replaced the copy-pasteable broken example.calculateblocks returnednull— field references need a$sigil in streams, and function calls likeround()are unsupported there. Query blocks were never broken and are untouched.pairs.mdrouted pool lookups toMarket.Address, which on EVM is the protocol factory — following it silently returns a different pool.IsQuotedInUsdis a denomination mode, not duplicated rows. Deleted the de-duplication advice readers were coding against.limitBygrouped byToken_Address, collapsing every native asset (they share an empty address) into one row.statistics.mddocumentedtheils, which does not exist, inverted its sign semantics, and claimed an exception that never fires.calculations.mddescribed argmax backwards.DEXPoolsandBalanceUpdatessaid ~7 days; both hold ~12 hours, re-measured on two consecutive days.DEXPoolshas no archive dataset at all."Matic".New: the query operator layer
docs/trading/was organised entirely by use case, with no home for the operators underneath. Newdocs/trading/query-operators/:selectwhere-screenersargmax-selectorsfilters-and-operatorsstatisticsdocs/trading/sweeps-and-paginationlimitByas a whole-population sweepcalculate-expressionsarray-intersect-wallet-overlapPlus
docs/subscriptions/what-does-not-survive-streaming.md—selectWhereandifare absent from streams and 18 statistical metrics are query-only, so the "any query converts to a subscription" claim is false.Trades cube caveats
A new section in
trades-api.mdcovering what silently corrupts aggregates: a row is a swap leg not a transaction;AmountsInUsd.Baseis built from a smoothed reference price and diverges on fast-moving tokens (Quoteis the sound leg); and the cube emits exact duplicate legs with no distinguishing field — with a dedup key. Verified againstSolana.Transfers.Known gaps
Deliberately not included, and tracked separately:
Trading.Currenciesstructure — audited toDO_NOT_WRITE_YET; several claims were backwards, and separately its stablecoin rows report zero volume/marketcap/supply whilebid:bitcoindoes not reconcile. Those look like product bugs rather than doc bugs.calculateexpressions can reference objects outside the query's own data. The grammar is now documented from direct measurement, but this specific question was deliberately not probed and remains open for engineering.🤖 Generated with Claude Code