diff --git a/src/components/perp-venues-leaderboard.tsx b/src/components/perp-venues-leaderboard.tsx index c870348e..294986e1 100644 --- a/src/components/perp-venues-leaderboard.tsx +++ b/src/components/perp-venues-leaderboard.tsx @@ -298,12 +298,15 @@ function VolOiCell({ ratio }: { ratio: number | null }) { if (ratio == null || !Number.isFinite(ratio)) { return ...; } - // Color bands: <50x normal (teal), 50-200x high (amber), >200x flag (red). - // The flagged band catches a wash-traded venue at a glance. + // Color bands: <80x normal (teal), 80-300x elevated (amber), >300x flag (red). + // Loosened from the original 50/200 thresholds because zero-fee venues + // (Lighter, ~115x today) and MM-heavy order books legitimately churn + // their OI multiple times per day without it being wash-traded. Only + // sustained ratios above 300x warrant a visual flag now. const band = - ratio < 50 + ratio < 80 ? { fg: "text-teal-700", bg: "bg-teal-500/10", border: "border-teal-500/30" } - : ratio <= 200 + : ratio <= 300 ? { fg: "text-amber-700", bg: "bg-amber-500/10", border: "border-amber-500/30" } : { fg: "text-red-700", bg: "bg-red-500/10", border: "border-red-500/30" }; return ( @@ -311,11 +314,11 @@ function VolOiCell({ ratio }: { ratio: number | null }) { className="px-3 py-2 tabular-nums" style={{ fontFamily: "var(--font-mono, monospace)" }} title={ - ratio < 50 - ? "Vol / OI under 50x is typical for an active order-book venue." - : ratio <= 200 - ? "Vol / OI between 50x and 200x is elevated; common for venues running incentive programs." - : "Vol / OI above 200x is a wash-trade signal worth a closer look." + ratio < 80 + ? "Vol / OI under 80x is typical for an active order-book venue." + : ratio <= 300 + ? "Vol / OI above 80x means high churn, common for zero-fee venues like Lighter or MM-heavy books running incentive programs." + : "Vol / OI above 300x typically indicates wash trading and is worth a closer look." } > { // harness via Mobula), ETH only. Covers 12 venues. Falls back to // the perp-funding bench (036, 7 venues) via PromQL `or` so any // venue the harness has not picked up yet still surfaces a value. + // + // Mask risk (P1, audit 2026-06): PromQL `or` hides any RHS series + // whose label set already appears on the LHS. Today the two series + // share only `{venue, asset}` labels, and `perp_venue_funding_24h_bps` + // is the authoritative source (12 venues) while `perp_funding_hold_24h_bps` + // covers a disjoint 7-venue subset published by bench 036. As long + // as the LHS stays the broader feed and the RHS keeps the same + // label keys, the `or` behaves as expected and the fallback only + // fires for venues missing on the LHS. If either harness ever emits + // an extra label that overlaps (e.g. `region` or `instance`), the + // mask will silently swallow the fallback. The clean fix is to + // deprecate `perp_funding_hold_24h_bps` once the cohort harness + // covers all 16 venues; until then keep the `or` and audit RHS + // visibility via /diag/perp-funding-coverage. queryVector( prom, `avg_over_time(perp_venue_funding_24h_bps{asset="ETH"}[24h]) or avg_over_time(perp_funding_hold_24h_bps{asset="ETH"}[24h])`, diff --git a/src/lib/providers.ts b/src/lib/providers.ts index a4beaa5c..6547509c 100644 --- a/src/lib/providers.ts +++ b/src/lib/providers.ts @@ -399,10 +399,18 @@ const PERP_VENUE_SEED = [ * ranking + wins + per-chain aggregation post-processing on top. A 60 * s revalidate aligns with the bench level ISR window and the standard * `benchmarks` tag so any `revalidateTag('benchmarks')` clears this - * along with the rest of the data layer. */ + * along with the rest of the data layer. + * + * Key bump rationale (v1 to v2): adding PERP_VENUE_SEED changed the + * shape of the cached profile list (new entries for drift, vertex, + * edgex, extended, aevo, pacifica, variational, ostium, grvt). Without + * bumping the cache key, Vercel ISR keeps serving the pre-seed profile + * list until the `benchmarks` tag invalidates, which is what caused + * /products/ to keep 404-ing after PR #683 shipped. Always + * bump this key when the buildProviders() output shape changes. */ const buildProvidersCached = unstable_cache( buildProviders, - ["providers-v1"], + ["providers-v2"], { revalidate: 60, tags: ["benchmarks"] }, );