api,web: mark Kalshi rows computed from too few observations - #799
Merged
Merged
Conversation
The path latency table put a row backed by five observations next to one backed by fifteen million, with nothing in the number to say which was which. Over five samples p99 is the maximum of five samples, and the row renders p50 = p90 = p99 — one value repeated. Observed on mainnet: four cmh sources with five samples each reported 0.3 ms against the 23 ms the same vantage measures on the perps lane it carries, reading as a hundredfold path advantage rather than as an unmeasured feed. A (feed, vantage) row now needs 1000 observations in the window to be published. Rows below it are dropped in Go rather than by a HAVING clause so each one is logged by name: falling below the floor usually means a producer that just started or one writing where it should not be.
Same defect as the path-latency floor, in the metric beside it. The vantage dimension is discovered from the data — every measurement_node_id in the race summary becomes a row — so a recorder that wrote a handful of races rendered a win rate and lead percentiles next to a vantage backed by millions. Measured on mainnet: a fabricated node carrying 8 to 40 races per competitor sat beside one carrying 1.17 billion observations. A vantage now needs 100 races in the window to appear in the breakdown, two orders of magnitude below the smallest plausible live vantage in the shortest window served. The floor is applied to the vantage dimension and not to the competitor dimension: competitors are an operator allow-list, so a thin competitor was configured deliberately and is worth seeing. Thin vantages are dropped from the breakdown only. Their races still count in the headline and the per-competitor totals, which come from separate CUBE rows.
|
🔗 Preview: https://pr-799.data.malbeclabs.com |
Review on #799 found that filtering rows out of the payload created four problems, all the same mistake — removing the evidence along with the number. An empty table cannot explain itself, and in a low-volume window "no data available" reads as a broken capture. Dropping one side of a pairing left the other alone at that vantage, a one-sided comparison in the flattering direction. A row that vanished when the reader switched window read as a recorder going down. And the compensating INFO line ran on every refresh — roughly 1500-3000 lines a day per replica for one thin recorder. Both floors are now display thresholds carried in the payload (`min_samples`, `min_races`), so the page withholds the number and shows the count behind it. Nothing is removed and nothing is logged. The race threshold applies per (vantage, competitor) cell as well as per vantage, which is the same defect one dimension deeper: a vantage with 3000 races against one feed and four against one configured this morning showed the second as a lead figure. The path-latency query is also scoped to the perps symbols. A sample floor is a volume proxy for what was really a provenance defect: every tob_ source is labelled DoubleZero and the arm filter admits any of them, so a sports lane updating about once a second clears a thousand samples in seventeen minutes and then reads as a solid sub-millisecond DoubleZero row. Only the perps symbols carry the venue orderbook stamp this metric is defined against.
bgm-malbeclabs
approved these changes
Aug 21, 2026
juan-malbeclabs
enabled auto-merge (squash)
August 21, 2026 20:43
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.
Summary of Changes
(feed, vantage). Over five samples p99 is the maximum of five samples, and the row renders p50 = p90 = p99 — one value repeated three times. Below the threshold the three percentile cells are dashed and the sample count is called out.(vantage, competitor)cell inside a published row. A vantage with 3000 races against one feed and four against a freshly configured one was showing the second as a lead figure with nothing saying what it rested on.min_samples,min_races) so the page cannot hold a second copy of the number.isKalshiDZFeedlabels everytob_source "DoubleZero" andkalshiEdgeWSArmFilteradmits any of them, so a sports lane recorded at some vantage landed in this table as a DoubleZero row. A sample floor is only a volume proxy for that: a lane updating about once a second clears a thousand samples in seventeen minutes and then reads as solid. Only the perps symbols carry the venue orderbook stamp the metric is defined against.What prompted it
On mainnet, four sources at the
cmhvantage with five samples each reported 0.3 ms path latency, against the 23 ms the same vantage measures on the perps lane it actually carries. That reads as a hundredfold path advantage rather than as an unmeasured feed — the worst direction for this metric to be wrong in, since it flatters DoubleZero. The same rows also formed a vantage of their own in the breakdown, with a win rate over 8 to 40 races.Those rows were spurious and have been removed at the source. Of the two changes here, the symbol scope is what stops that particular row from coming back; the thresholds are what stop the next thin row from being read as a result.
Diff Breakdown
Two thresholds and a query scope in one handler, the rendering that reads them, and the fixtures that now have to clear them.
Key files (click to expand)
api/handlers/kalshi_scoreboard.go—KalshiLatencyMinSamplesandKalshiVantageMinRacesas published display thresholds, and the perps symbol scope on the latency queryapi/handlers/kalshi_scoreboard_test.go— burst fixtures, a test per threshold, and one for the symbol scopeweb/src/components/kalshi-scoreboard-page.tsx— dashed percentiles, "too few races", and per-cell race countsweb/src/lib/api.ts— the two threshold fieldsTesting Verification
TestKalshiPathLatency_ThinRowKeepsItsPlace— a five-sample competitor row beside a real 23 ms burst at the same vantage: both rows are returned, the thin one identified by a count below the published threshold. It pins the review's point that a pairing must not lose one side.TestKalshiPathLatency_ExcludesSymbolsWithoutTheVenueClock— a sports lane seeded well past the sample floor is still absent. It would pass a volume check, which is the point of having it.TestKalshiScoreboard_ThinVantageKeepsItsRow— a three-race vantage beside a real one: both published, the headline still counting every race the fleet ran.(source_ts_ms, bbo_hash)key per iteration, so they assert the same properties on rows the report would actually show.ExcludesIncomparableClocksgives every arm a burst over the threshold, so a single surviving row can only be the clock filter's doing.Note
The thresholds are chosen to sit far below anything real and far above noise — a live vantage logs millions of observations in 24h, and a live pairing thousands of races an hour — not from the measured per-vantage distribution. Checking them against that distribution needs the feeds service directly: lake proxies those tables read-only and the public ClickHouse user cannot see them.
The race threshold stays an absolute count rather than a rate scaled by window: "fewer than a hundred races cannot carry a percentile" is true of the 1h window and of a
?symbol=split for the same reason it is true of 7d. The visible consequence is that a genuinely thin fleet shows counts at 1h where it shows percentages at 24h.