The prices that never traded
We measured our own price history against a control. Part of it is not price data — it is the shape of a one-sided order book. Those rows cannot be repaired, so they are marked instead, and the mark follows them into the candles we sell.
Until recently, every price we recorded from the Robinhood registry was (bid + ask) / 2. That is the ordinary thing to do, and it is correct while the book has two real sides.
At a session changeover the book goes one-sided: the bid stays where it was and the ask becomes a placeholder far above it. The mid then wanders with the placeholder. For CRWD on 2026-08-15 the book was bid 217 / ask 271.2 — a 25% spread — and the mid came out at 244.1.
CRWD, 2026-08-15
book: bid 217.00 · ask 271.20 → mid 244.10
actually traded: 216.575 – 227.50
published price was 7.3% above the day's high
We published that every 15 minutes, and stamped each row with the time we polled rather than the time the quote was generated. Two defects, one row.
No outside source is needed to catch this. The registry sends the day's high and low in the same object as the quote, so a price outside [dailyLow, dailyHigh] is provably not a price. The evidence that invalidates the number arrives attached to it.
So the fix is small and now permanent: a quote whose mid falls outside its own range is rejected, we fall back to the bid, and each row records which statistic it actually is — mid or bid — instead of leaving a buyer to assume. Rows also carry the quote's own timestamp, never the poll time.
The obvious check on the historical rows is impossible: we never stored dailyHigh and dailyLow, and reconstructing the day's range from our own price history would be circular — if the mids were wrong, so is any envelope derived from them.
What is decidable is how the two cohorts behave against each other. Chainlink-sourced rows are the control: same asset class, same market hours, a different path into our database. Over 60 days of consecutive snapshots less than 20 minutes apart:
| Cohort | Pairs | Moves ≥10% | ≥25% |
|---|---|---|---|
| legacy quote mid | 99,464 | 122 | 41 |
| Chainlink | 39,914 | 0 | 0 |
A real equity does not move 25% and back inside a quarter of an hour, 41 times, while an identical measurement over the same hours produces zero. The gap is not market behaviour. It is the ask wandering in a book with nothing on one side.
Those rows cannot be repaired. Only the mid was stored, so there is nothing left to recompute a price from — the inputs are gone. That leaves two honest options: delete them, or mark them.
They are marked. The threshold is 10%, taken from the measurement rather than from taste: it is a level the control never once crossed, which is a more useful definition of implausible than a number we liked the look of. It over-flags on purpose. A genuine 10% move on news gets marked and a consumer loses a little coverage; an unmarked bad row costs them a wrong decision. Those are not symmetric.
The mark is not cosmetic and does not stop at the raw table. OHLC candles — the endpoint where price history is actually sold — carry flaggedSamples, the count of contributing snapshots the rule marked. A candle built partly from suspect observations says so, in the same response as the candle.
One thing it is not: a certificate. The absence of a flag means no rule fired, not that a price was verified against a trade. Publishing the difference between those two is the point.
A price series you cannot check is not cheaper than one you can. It is one where you find out later, from a position you took on it.
Every vendor of market data has a version of this story. The difference is whether the number is in the product or in a post-mortem. Ours is in the product: the flag ships in the API, the threshold is in the code with the measurement that produced it, and the control cohort is named so anyone can argue with the method.
Prices going forward come from Chainlink first, with the registry as a labelled fallback, and every price on this site says which of the two it came from. The FAQ covers what those labels mean; the status page covers whether the jobs that write them are running.