Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions src/components/perp-venues-leaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,24 +298,27 @@ function VolOiCell({ ratio }: { ratio: number | null }) {
if (ratio == null || !Number.isFinite(ratio)) {
return <Td mono>...</Td>;
}
// 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 (
<td
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."
}
>
<span
Expand Down
14 changes: 14 additions & 0 deletions src/lib/perp-stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,20 @@ export async function fetchPerpCohort(): Promise<PerpCohortSummary | null> {
// 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])`,
Expand Down
12 changes: 10 additions & 2 deletions src/lib/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/<perp-venue> 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"] },
);

Expand Down
Loading