From 5b91c8fef9a035673d70295b7a2b566431d1ff5f Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:31:56 +0200 Subject: [PATCH 1/3] fix: end-of-line label collision + padR increase for clean PNG export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Limit inline labels to top 10 non-excluded lines (legend shows all) - Collision deflection: sort labels by Y, push each down until clear of previous - Increase padR 96→130 so label text never gets clipped by SVG viewport --- src/components/time-series-chart/chart.tsx | 2 +- src/components/time-series-chart/series.tsx | 150 ++++++++++++-------- 2 files changed, 88 insertions(+), 64 deletions(-) diff --git a/src/components/time-series-chart/chart.tsx b/src/components/time-series-chart/chart.tsx index 480613a2..0242bb19 100644 --- a/src/components/time-series-chart/chart.tsx +++ b/src/components/time-series-chart/chart.tsx @@ -38,7 +38,7 @@ export function Chart({ const W = 1000; const H = 360; const padL = 60; - const padR = 96; + const padR = 130; const padT = 16; const padB = 36; const innerW = W - padL - padR; diff --git a/src/components/time-series-chart/series.tsx b/src/components/time-series-chart/series.tsx index afe17328..67e9f386 100644 --- a/src/components/time-series-chart/series.tsx +++ b/src/components/time-series-chart/series.tsx @@ -21,75 +21,99 @@ type SeriesPathsProps = { // now speak for themselves — SeriesPaths renders a natural break wherever // the underlying values are null. +// Max number of end-of-line labels rendered. The legend below shows all +// providers regardless — this cap prevents right-side label clutter when +// dozens of lines share the same Y range. +const MAX_INLINE_LABELS = 10; +// Minimum vertical gap (SVG units) between consecutive placed labels so +// name + value lines don't overlap. +const LABEL_GAP = 27; + export function SeriesPaths({ drawn, unit }: SeriesPathsProps) { + // Limit labels to top-N non-excluded lines (drawn is pre-sorted by value). + const labeledSlugs = new Set( + drawn.filter((d) => !d.excluded).slice(0, MAX_INLINE_LABELS).map((d) => d.slug), + ); + + // Collision deflection: sort labeled lines by their natural Y position, + // then push each label down until it clears the previous one. + const labelY: Record = {}; + const toPlace = drawn + .filter((d) => labeledSlugs.has(d.slug)) + .map((d) => ({ slug: d.slug, natural: d.lastY })) + .sort((a, b) => a.natural - b.natural); + let prevBottom = -Infinity; + for (const item of toPlace) { + const y = Math.max(item.natural, prevBottom); + labelY[item.slug] = y; + prevBottom = y + LABEL_GAP; + } + return ( <> - {drawn.map((d) => ( - - - { + const showLabel = labeledSlugs.has(d.slug); + const ly = labelY[d.slug] ?? d.lastY; + return ( + - {/* Live pulse halo. animated outward */} - - - - - {/* Trailing tail dot */} - - {/* End-of-line label */} - - {d.name} - - - {fmtUnit(d.last, unit)} - - - ))} + + + {/* Live pulse halo */} + + + + + {/* Trailing tail dot */} + + {/* End-of-line label — only for top-N, collision-deflected */} + {showLabel && ( + <> + + {d.name} + + + {fmtUnit(d.last, unit)} + + + )} + + ); + })} ); } From e64a042ddf5248835c3749e6d3265ee082dfa557 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:38:50 +0200 Subject: [PATCH 2/3] fix(pm): 410 pm-data-freshness, drop dead answer page --- ...iction-market-data-api-is-the-freshest.yml | 43 ------------------- src/app/prediction-markets/page.tsx | 1 - src/lib/removed-benches.ts | 8 ++++ 3 files changed, 8 insertions(+), 44 deletions(-) delete mode 100644 answers/which-prediction-market-data-api-is-the-freshest.yml diff --git a/answers/which-prediction-market-data-api-is-the-freshest.yml b/answers/which-prediction-market-data-api-is-the-freshest.yml deleted file mode 100644 index 856fe12d..00000000 --- a/answers/which-prediction-market-data-api-is-the-freshest.yml +++ /dev/null @@ -1,43 +0,0 @@ -slug: which-prediction-market-data-api-is-the-freshest -question: "Which prediction market data API publishes the freshest Polymarket data?" -short_answer: | - Polymarket's own CLOB WebSocket gateway is the canonical fastest source by definition (T0, sub-50ms from EU West) and is not a competing data provider. Among third-party data providers, Codex currently leads at {{p50:codex}} (p50, 24h) on the Polymarket tab, measured as lag from venue publish to provider relay; Codex indexes the onchain Polygon confirmation so its structural floor is roughly 2 seconds of block time. Live per-venue ranking at openchainbench.com/benchmarks/pm-data-freshness. - -benchmark: pm-data-freshness - -intro: | - Prediction markets produce the most time sensitive event stream in crypto. An election market settles in seconds, a sports book moves on every play, and the price tick a trading UI shows is only as fresh as the API behind it. Builders integrating Polymarket through a data provider rather than hitting the CLOB directly need to know how many milliseconds that provider adds between Polymarket publishing a trade and the provider relaying the same trade to its WebSocket subscribers. This page answers exactly that. The OpenChainBench pm-data-freshness harness holds two WebSocket subscribers in parallel: Polymarket's own CLOB gateway (the canonical T0) and Codex GraphQL subscriptions, both subscribed to the same basket of top 20 Polymarket markets by 24 h volume, refreshed every 5 minutes. Each trade is matched across providers by a tuple of conditionId, price rounded to 3 decimals, trade size and a 5 second time bucket; the per provider lag versus Polymarket's gateway publish time is recorded as a Prometheus histogram and the leaderboard ranks by p50 in milliseconds, lower is fresher. - -methodology: | - Two WebSocket clients run in parallel inside the harness on a Railway europe west 4 instance. Polymarket's own gateway (`wss://ws-subscriptions-clob.polymarket.com/ws/market`, public, no auth, sub 50 ms publish latency from EU West) is the canonical T0 by construction because nothing downstream can be faster than the source. Codex GraphQL (`wss://graph.codex.io/graphql` with the `graphql-transport-ws` subprotocol) runs an `onPredictionTradesCreated` firehose subscription filtered client side to the Polymarket protocol marketIds in the active basket. The cross correlation key is `(conditionId, priceUSD times 1000 rounded, sizeUSD times 1 million rounded, floor(trade_time / 5s))`; the 5 second bucket absorbs minor clock skew between gateways without merging unrelated trades. Histogram buckets run 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000, 60000 ms. Providers that fail to relay a matched trade within 90 seconds are not counted toward p50, only toward their receive total, so a provider cannot look fresh on the leaderboard by silently dropping events; the success rate column surfaces that. - -limitations: - - "Polymarket's own gateway is the canonical T0 by construction. Its row on the leaderboard sits near zero because the harness compares everything else against it, not because Polymarket has solved network latency; the displayed sub millisecond value is a floor (vector(0.5)) so the row stays visible on the linear chart axis next to the multi second Codex value." - - "Codex indexes the on chain Polygon confirmation of each Polymarket trade, so its p50 includes Polygon block time (around 2 seconds) plus ingestion. The Codex path is the right one for on chain reconciliation or settlement workflows; for live trading UIs the off chain relay path is the only viable one. The two answer different questions." - - "Measurement is single region (Railway europe west 4). A provider whose edge gateway is closer to a US East client could post different deltas there; multi region requires running additional monitor instances of the freshness harness, which is on the roadmap." - - "The basket is the top 20 active Polymarket markets by 24 h volume, refreshed every 5 minutes. Long tail markets with sparse trade flow are not on the leaderboard because the cross correlation matcher needs enough overlapping events to produce a stable histogram per provider." - - "Kalshi and Limitless are separate exchanges with separate data feeds and a different ingestion path; this bench measures Polymarket as the underlying venue. A Kalshi specific tab is a candidate for a later phase but is not the current scope." - -faq: - - q: "Which Polymarket data API has the lowest latency right now?" - a: "{{best_name}} currently leads at {{best_p50}} (p50 over the last 24 hours), measured as wall clock time from Polymarket's own CLOB WebSocket publish to provider relay on the same trade. The leaderboard re sorts every minute on fresh Prometheus samples, so the answer reflects the actual measured lag on the active market basket, not a marketing claim. Codex's on chain Polygon indexer carries a structural floor near 2 seconds because it ingests block confirmations." - - q: "What does freshness delta mean for a prediction market API?" - a: "The bench connects to Polymarket's own CLOB WebSocket and to each provider's WebSocket simultaneously, subscribes to the same markets, and for every trade event records how many milliseconds the provider takes to relay the event after Polymarket itself publishes it. Lower is better. Polymarket's own gateway publish time is the canonical T0 because by construction nothing downstream can be faster than the source. The cross correlation key (conditionId, price, size, 5 s bucket) survives minor clock skew between gateways without merging unrelated trades." - - q: "Why not include Polymarket REST polling on this bench?" - a: "Freshness is a WebSocket question. REST polling at 1 second cadence would have a floor freshness near 500 ms (poll interval divided by 2) plus round trip time, dominated by how often you poll. The Polymarket gateway WebSocket exists for exactly this reason, to avoid that floor. Adding REST as a row would make the leaderboard noisy without changing the conclusion: WebSocket beats polling by definition for real time data." - - q: "Are these numbers comparable to Kalshi or Limitless?" - a: "Not directly. This benchmark measures Polymarket as the underlying venue since it is the largest and the one most providers proxy. Kalshi and Limitless are separate exchanges with separate data feeds, and providers that cover them often run a different ingestion path. A Kalshi specific tab is a candidate for a later phase. For now, treat the leaderboard as how fresh is your Polymarket data feed." - - q: "How does the cross correlation matcher avoid false positives?" - a: "The signature is (conditionId, priceUSD times 1000 rounded to 3 decimals, sizeUSD times 1 million rounded, floor(trade_time / 5 seconds)). Two unrelated trades would need to share the same conditionId, the same price to 3 decimals, the same size to 6 decimals, and land in the same 5 second window to collide. On the top 20 markets that combination is unique in practice; on a thinly traded long tail market it could in principle merge two trades, which is one reason the basket is restricted to the top 20 by volume." - - q: "How often is the leaderboard refreshed?" - a: "Continuously at the WebSocket level. Every trade event lands on the harness in real time and updates the freshness histogram; the page reads a rolling 24 h p50, p90 and p99 every minute. The market basket itself is refreshed every 5 minutes against `gamma-api.polymarket.com` so the leaderboard tracks current top markets rather than yesterday's hot list." - -related: - - which-prediction-market-has-the-strictest-rate-limits - - which-crypto-data-api-covers-the-most-blockchains - - which-crypto-price-api-is-the-fastest - - prediction-market-api-for-developers - -seo_title: "Which prediction market data API is the freshest in 2026?" -seo_description: "Polymarket's own CLOB gateway is the canonical T0 (fastest by definition). Among third-party providers, Codex leads freshness delta live, measured by OpenChainBench on the top 20 Polymarket and Kalshi markets by volume." -status: live diff --git a/src/app/prediction-markets/page.tsx b/src/app/prediction-markets/page.tsx index c961a1fd..aaaf8f4f 100644 --- a/src/app/prediction-markets/page.tsx +++ b/src/app/prediction-markets/page.tsx @@ -25,7 +25,6 @@ import { SITE } from "@/data/site"; const ANSWERS = [ { slug: "how-long-does-polymarket-take-to-resolve", question: "How long does Polymarket take to resolve a market?" }, { slug: "polymarket-vs-kalshi-resolution-speed", question: "Polymarket vs Kalshi, which resolves prediction markets faster?" }, - { slug: "which-prediction-market-data-api-is-the-freshest", question: "Which prediction market data API publishes the freshest Polymarket data?" }, { slug: "which-prediction-market-has-the-strictest-rate-limits", question: "Which prediction market API has the strictest rate limits?" }, { slug: "polymarket-fees-explained", question: "What fees does Polymarket charge?" }, { slug: "polymarket-vs-kalshi-fees", question: "Polymarket vs Kalshi fees, which is cheaper to trade?" }, diff --git a/src/lib/removed-benches.ts b/src/lib/removed-benches.ts index bf6c3528..f459a802 100644 --- a/src/lib/removed-benches.ts +++ b/src/lib/removed-benches.ts @@ -55,6 +55,9 @@ export const REMOVED_ANSWER_SLUGS = new Set([ // References solana-dex-quote-latency (staging-only, in // REMOVED_BENCH_SLUGS). Same pattern. "which-solana-dex-aggregator-is-the-fastest", + // References pm-data-freshness (bench 113, retired 2026-07). Bench + // removed; answer returns 404 without this guard. + "which-prediction-market-data-api-is-the-freshest", ]); export const REMOVED_BENCH_SLUGS = new Set([ @@ -113,6 +116,11 @@ export const REMOVED_BENCH_SLUGS = new Set([ "rpc-keyed-latency", "explorer-chain-coverage", "portfolio-chain-coverage", + // pm-data-freshness (bench 113) retired 2026-07: Predexon (the only + // measured data relay) discontinued. Spec + harness removed; kept in + // the 410 list so any indexed URL returns Gone instead of 404. + // Per-venue WS freshness now lives in pm-ws-latency (bench 114). + "pm-data-freshness", ]); /** From e99e00e7f629bf6b70977241ce745408a2f948c0 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:09:26 +0200 Subject: [PATCH 3/3] fix: hide UI chrome (tabs, TopN, header) from PNG export --- src/components/time-series-chart/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/time-series-chart/index.tsx b/src/components/time-series-chart/index.tsx index c53d59ca..8fd3ea42 100644 --- a/src/components/time-series-chart/index.tsx +++ b/src/components/time-series-chart/index.tsx @@ -478,7 +478,7 @@ export function TimeSeriesChart({ return (
-
+

@@ -508,7 +508,7 @@ export function TimeSeriesChart({ {headerActions}

-
+
{RANGES.map((r) => { const active = r === range;