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
124 changes: 117 additions & 7 deletions benchmarks/perp-funding-stability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
slug: perp-funding-stability
number: "043"
title: Perp DEX funding stability, 24h stddev of ETH funding ranked
seo_title: "Perp funding stability 2026: ETH funding stddev across 7 venues ranked"
seo_description: "Live 24h standard deviation of ETH perpetual futures funding rate in bps across Hyperliquid, Bybit, dYdX v4, Binance, OKX, Paradex and Aster. Lower means more stable funding for carry traders and basis arbitrageurs."
seo_title: "Perp funding stability 2026: ETH funding stddev across 15 venues ranked"
seo_description: "Live 24h standard deviation of ETH perpetual futures funding rate in bps across 15 major venues: Hyperliquid, Bybit, dYdX v4, Binance, OKX, Paradex, Aster, Bitget, Coinbase, Deribit, Gate, Kraken, KuCoin, Lighter, MEXC. Lower means more stable funding for carry traders and basis arbitrageurs."
subtitle: 24h standard deviation of ETH funding rate, in basis points per 24h, across the perp-funding cohort. Lower means funding stays in a tight band, the carry trader's preferred signal.
category: Trading
status: live
Expand All @@ -24,10 +24,11 @@ seo_intro: |
same number. The bench reads the same normalized series as
perp-funding (per-venue hold cost in bps per 24h) and exposes
stddev_over_time of that series on a 24 hour window for ETH.
Cohort is the 7 venues currently publishing the normalized
hold-24h gauge with the freshness signal: Hyperliquid (onchain),
Bybit, dYdX v4 (Cosmos appchain), Binance, OKX, Paradex (Starknet
L2), Aster (BNB Chain). Lower is better.
Cohort is 15 venues across two sources: 7 polled directly by the
harness (Hyperliquid onchain, Bybit, dYdX v4 Cosmos appchain,
Binance, OKX, Paradex Starknet L2, Aster BNB Chain) and 8 sourced
through Mobula's CEFI funding aggregator (Bitget, Coinbase,
Deribit, Gate, Kraken, KuCoin, Lighter, MEXC). Lower is better.

abstract: |
The bench reuses the normalized 24h ETH funding series from
Expand All @@ -43,7 +44,7 @@ methodology:
- "Source. Same upstream as perp-funding: each venue's public funding endpoint polled every 60 seconds, normalized to bps per hour and per 24h hold, sign preserved."
- "Metric. stddev_over_time(perp_funding_hold_24h_bps{asset=\"ETH\"}[24h]) per venue. The 7d window smooths intraday noise but reacts to regime shifts within a week."
- "Sign handling. The underlying series is signed (positive means longs pay). Stability measures the swing, so the ranking uses the unsigned stddev. Average direction and level are the perp-funding bench."
- "Cohort. 7 venues from the perp-funding bench publishing the normalized hold-24h gauge with the freshness signal: Hyperliquid (onchain), Bybit, dYdX v4 (Cosmos appchain), Binance, OKX, Paradex (Starknet L2), Aster (BNB Chain)."
- "Cohort. 15 venues across two sources. 7 polled directly (perp_funding_hold_24h_bps): Hyperliquid, Bybit, dYdX v4, Binance, OKX, Paradex, Aster. 8 via Mobula CEFI aggregator (perp_venue_funding_24h_bps): Bitget, Coinbase, Deribit, Gate, Kraken, KuCoin, Lighter, MEXC. Both normalize to bps per 24h hold, sign preserved, so the unsigned stddev compares cleanly across the full cohort."
- "Success. Same freshness signal as perp-funding: perp_funding_venue_ok_unix advancing within the last 5 minutes. A venue whose scrape stalls is tagged stale on the row."
- "Cadence. The upstream funding harness polls every 60 seconds; Prometheus scrapes every 30 seconds. The 7 day stddev refreshes on every scrape."
- "Failures. A venue whose upstream funding feed errors keeps its last gauge value; the leaderboard tags the row stale once the success gauge drops to 0."
Expand Down Expand Up @@ -166,3 +167,112 @@ providers:
success: (perp_funding_venue_ok_unix{venue="aster"} > bool (time() - 300))
sample_size: count_over_time(perp_funding_hold_24h_bps{venue="aster",asset="ETH"}[24h])
series: stddev_over_time(perp_funding_hold_24h_bps{venue="aster",asset="ETH"}[24h])

# ── Mobula-sourced cohort (perp_venue_funding_24h_bps from perp-cohort-stats) ──
# Same metric semantics as perp_funding_hold_24h_bps but normalized
# via Mobula's CEFI funding-rate aggregator instead of direct venue
# polls. Freshness gauge is perp_venue_last_refresh_unix.

- slug: bitget
name: Bitget
tag: CEX, USDT perpetuals via Mobula aggregator, 8h funding
formula: "stddev_over_time of perp_venue_funding_24h_bps{venue=\"bitget\",asset=\"ETH\"} over a 24h window. Source: Mobula CEFI funding-rate aggregator."
queries:
p50: stddev_over_time(perp_venue_funding_24h_bps{venue="bitget",asset="ETH"}[24h])
p90: stddev_over_time(perp_venue_funding_24h_bps{venue="bitget",asset="BTC"}[24h])
p99: stddev_over_time(perp_venue_funding_24h_bps{venue="bitget",asset="SOL"}[24h])
mean: stddev_over_time(perp_venue_funding_24h_bps{venue="bitget",asset="ETH"}[24h])
success: (perp_venue_last_refresh_unix{venue="bitget"} > bool (time() - 300))
sample_size: count_over_time(perp_venue_funding_24h_bps{venue="bitget",asset="ETH"}[24h])
series: stddev_over_time(perp_venue_funding_24h_bps{venue="bitget",asset="ETH"}[24h])

- slug: coinbase
name: Coinbase
tag: Regulated US CEX, perpetuals via Mobula aggregator
formula: "stddev_over_time of perp_venue_funding_24h_bps{venue=\"coinbase\",asset=\"ETH\"} over a 24h window. Source: Mobula CEFI funding-rate aggregator."
queries:
p50: stddev_over_time(perp_venue_funding_24h_bps{venue="coinbase",asset="ETH"}[24h])
p90: stddev_over_time(perp_venue_funding_24h_bps{venue="coinbase",asset="BTC"}[24h])
p99: stddev_over_time(perp_venue_funding_24h_bps{venue="coinbase",asset="SOL"}[24h])
mean: stddev_over_time(perp_venue_funding_24h_bps{venue="coinbase",asset="ETH"}[24h])
success: (perp_venue_last_refresh_unix{venue="coinbase"} > bool (time() - 300))
sample_size: count_over_time(perp_venue_funding_24h_bps{venue="coinbase",asset="ETH"}[24h])
series: stddev_over_time(perp_venue_funding_24h_bps{venue="coinbase",asset="ETH"}[24h])

- slug: deribit
name: Deribit
tag: Options-focused CEX, ETH/BTC perpetuals via Mobula aggregator
formula: "stddev_over_time of perp_venue_funding_24h_bps{venue=\"deribit\",asset=\"ETH\"} over a 24h window. Source: Mobula CEFI funding-rate aggregator."
queries:
p50: stddev_over_time(perp_venue_funding_24h_bps{venue="deribit",asset="ETH"}[24h])
p90: stddev_over_time(perp_venue_funding_24h_bps{venue="deribit",asset="BTC"}[24h])
p99: stddev_over_time(perp_venue_funding_24h_bps{venue="deribit",asset="SOL"}[24h])
mean: stddev_over_time(perp_venue_funding_24h_bps{venue="deribit",asset="ETH"}[24h])
success: (perp_venue_last_refresh_unix{venue="deribit"} > bool (time() - 300))
sample_size: count_over_time(perp_venue_funding_24h_bps{venue="deribit",asset="ETH"}[24h])
series: stddev_over_time(perp_venue_funding_24h_bps{venue="deribit",asset="ETH"}[24h])

- slug: gate
name: Gate
tag: CEX, USDT perpetuals via Mobula aggregator, 8h funding
formula: "stddev_over_time of perp_venue_funding_24h_bps{venue=\"gate\",asset=\"ETH\"} over a 24h window. Source: Mobula CEFI funding-rate aggregator."
queries:
p50: stddev_over_time(perp_venue_funding_24h_bps{venue="gate",asset="ETH"}[24h])
p90: stddev_over_time(perp_venue_funding_24h_bps{venue="gate",asset="BTC"}[24h])
p99: stddev_over_time(perp_venue_funding_24h_bps{venue="gate",asset="SOL"}[24h])
mean: stddev_over_time(perp_venue_funding_24h_bps{venue="gate",asset="ETH"}[24h])
success: (perp_venue_last_refresh_unix{venue="gate"} > bool (time() - 300))
sample_size: count_over_time(perp_venue_funding_24h_bps{venue="gate",asset="ETH"}[24h])
series: stddev_over_time(perp_venue_funding_24h_bps{venue="gate",asset="ETH"}[24h])

- slug: kraken
name: Kraken
tag: Regulated CEX, perpetuals via Mobula aggregator
formula: "stddev_over_time of perp_venue_funding_24h_bps{venue=\"kraken\",asset=\"ETH\"} over a 24h window. Source: Mobula CEFI funding-rate aggregator."
queries:
p50: stddev_over_time(perp_venue_funding_24h_bps{venue="kraken",asset="ETH"}[24h])
p90: stddev_over_time(perp_venue_funding_24h_bps{venue="kraken",asset="BTC"}[24h])
p99: stddev_over_time(perp_venue_funding_24h_bps{venue="kraken",asset="SOL"}[24h])
mean: stddev_over_time(perp_venue_funding_24h_bps{venue="kraken",asset="ETH"}[24h])
success: (perp_venue_last_refresh_unix{venue="kraken"} > bool (time() - 300))
sample_size: count_over_time(perp_venue_funding_24h_bps{venue="kraken",asset="ETH"}[24h])
series: stddev_over_time(perp_venue_funding_24h_bps{venue="kraken",asset="ETH"}[24h])

- slug: kucoin
name: KuCoin
tag: CEX, USDT perpetuals via Mobula aggregator, 8h funding
formula: "stddev_over_time of perp_venue_funding_24h_bps{venue=\"kucoin\",asset=\"ETH\"} over a 24h window. Source: Mobula CEFI funding-rate aggregator."
queries:
p50: stddev_over_time(perp_venue_funding_24h_bps{venue="kucoin",asset="ETH"}[24h])
p90: stddev_over_time(perp_venue_funding_24h_bps{venue="kucoin",asset="BTC"}[24h])
p99: stddev_over_time(perp_venue_funding_24h_bps{venue="kucoin",asset="SOL"}[24h])
mean: stddev_over_time(perp_venue_funding_24h_bps{venue="kucoin",asset="ETH"}[24h])
success: (perp_venue_last_refresh_unix{venue="kucoin"} > bool (time() - 300))
sample_size: count_over_time(perp_venue_funding_24h_bps{venue="kucoin",asset="ETH"}[24h])
series: stddev_over_time(perp_venue_funding_24h_bps{venue="kucoin",asset="ETH"}[24h])

- slug: lighter
name: Lighter
tag: zkSync L2 perps DEX, native funding via Mobula aggregator
formula: "stddev_over_time of perp_venue_funding_24h_bps{venue=\"lighter\",asset=\"ETH\"} over a 24h window. Source: Mobula CEFI funding-rate aggregator."
queries:
p50: stddev_over_time(perp_venue_funding_24h_bps{venue="lighter",asset="ETH"}[24h])
p90: stddev_over_time(perp_venue_funding_24h_bps{venue="lighter",asset="BTC"}[24h])
p99: stddev_over_time(perp_venue_funding_24h_bps{venue="lighter",asset="SOL"}[24h])
mean: stddev_over_time(perp_venue_funding_24h_bps{venue="lighter",asset="ETH"}[24h])
success: (perp_venue_last_refresh_unix{venue="lighter"} > bool (time() - 300))
sample_size: count_over_time(perp_venue_funding_24h_bps{venue="lighter",asset="ETH"}[24h])
series: stddev_over_time(perp_venue_funding_24h_bps{venue="lighter",asset="ETH"}[24h])

- slug: mexc
name: MEXC
tag: CEX, USDT perpetuals via Mobula aggregator, 8h funding
formula: "stddev_over_time of perp_venue_funding_24h_bps{venue=\"mexc\",asset=\"ETH\"} over a 24h window. Source: Mobula CEFI funding-rate aggregator."
queries:
p50: stddev_over_time(perp_venue_funding_24h_bps{venue="mexc",asset="ETH"}[24h])
p90: stddev_over_time(perp_venue_funding_24h_bps{venue="mexc",asset="BTC"}[24h])
p99: stddev_over_time(perp_venue_funding_24h_bps{venue="mexc",asset="SOL"}[24h])
mean: stddev_over_time(perp_venue_funding_24h_bps{venue="mexc",asset="ETH"}[24h])
success: (perp_venue_last_refresh_unix{venue="mexc"} > bool (time() - 300))
sample_size: count_over_time(perp_venue_funding_24h_bps{venue="mexc",asset="ETH"}[24h])
series: stddev_over_time(perp_venue_funding_24h_bps{venue="mexc",asset="ETH"}[24h])
Binary file added public/logos/bitget.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/logos/coinbase.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/logos/deribit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/logos/gate.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/logos/kraken.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/logos/kucoin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/logos/mexc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
110 changes: 110 additions & 0 deletions src/lib/categories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Category taxonomy for benchmark hubs.
*
* Mirrors the closed `Category` enum from `spec-schema.ts` (the source
* of truth that every YAML spec gets validated against). Centralised
* here so the `/benchmarks/category/<cat>` routes, the sitemap, the
* client filter and the meta-builders all share one slug ↔ label ↔
* description vocabulary. If a new category is added to the schema,
* adding it here is the only other touch-point needed to surface a
* dedicated hub URL.
*
* Slugs are lowercase, kebab-case, ASCII-only so they survive URL
* canonicalisation, RSS readers and JSON-LD @id fragments without
* needing encoding. Labels match the schema enum verbatim so the
* client filter can compare `b.category === entry.label` without an
* extra lookup.
*
* Descriptions are SEO copy that explains the category scope. They
* land in <meta description>, OG description, and the H1 sub-paragraph
* on the category hub page, so each must read as a standalone sentence
* (a) without the surrounding template and (b) with the category name
* substituted in. No em / en dashes per the project copy rules.
*/

export type Category =
| "Aggregators"
| "Bridges"
| "Blockchains"
| "Trading"
| "Wallets"
| "RPCs"
| "NFT APIs";

export type CategoryEntry = {
/** URL slug. Lowercase, kebab-case, ASCII. */
slug: string;
/** Schema enum label, matches `Benchmark.category` exactly. */
label: Category;
/** Plural human heading used in H1 + breadcrumb. */
heading: string;
/** SEO copy under the H1 + <meta description>. */
description: string;
};

export const CATEGORIES: readonly CategoryEntry[] = [
{
slug: "aggregators",
label: "Aggregators",
heading: "Aggregator benchmarks",
description:
"Live OpenChainBench measurements for crypto data aggregators. Compare quote latency, market coverage, and freshness across the providers that fan out queries to multiple underlying sources.",
},
{
slug: "bridges",
label: "Bridges",
heading: "Bridge benchmarks",
description:
"Open, reproducible benchmarks for cross-chain bridges and intent layers. Compare quote latency, end-to-end fees, and route coverage across the bridges that move value between chains.",
},
{
slug: "blockchains",
label: "Blockchains",
heading: "Blockchain benchmarks",
description:
"Network-level measurements that compare blockchains head to head. Time to finality, block time, gas estimation accuracy, and validator economics on every L1 and L2 OpenChainBench tracks.",
},
{
slug: "trading",
label: "Trading",
heading: "Trading benchmarks",
description:
"Latency, fee, and freshness benchmarks for trading venues and execution providers. Perpetuals, spot DEX quotes, oracle deviation, and Polymarket data freshness measured continuously.",
},
{
slug: "wallets",
label: "Wallets",
heading: "Wallet benchmarks",
description:
"Benchmarks that compare wallet infrastructure providers on label coverage, address resolution, and the data layers that power production wallet UIs.",
},
{
slug: "rpcs",
label: "RPCs",
heading: "RPC benchmarks",
description:
"Live OpenChainBench measurements for RPC and node providers. Compare capability coverage, transaction landing latency, and reliability across the endpoints that power production dApps.",
},
{
slug: "nft-apis",
label: "NFT APIs",
heading: "NFT API benchmarks",
description:
"Benchmarks that compare NFT data API providers on collection metadata coverage, freshness, and the indexing depth that production NFT marketplaces and wallets depend on.",
},
];

export const CATEGORY_BY_SLUG: ReadonlyMap<string, CategoryEntry> = new Map(
CATEGORIES.map((c) => [c.slug, c]),
);

export const CATEGORY_SLUG_BY_LABEL: ReadonlyMap<Category, string> = new Map(
CATEGORIES.map((c) => [c.label, c.slug]),
);

/** Slug used in the `/benchmarks/category/<slug>` URL for a given
* `Benchmark.category` value. Returns `null` for unknown labels so
* call sites can degrade to the unscoped hub instead of a 404 link. */
export function categorySlugFromLabel(label: string): string | null {
return CATEGORY_SLUG_BY_LABEL.get(label as Category) ?? null;
}
12 changes: 11 additions & 1 deletion src/lib/logo-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ const RAW: Record<string, string> = {
"1rpc": "/logos/1rpc.svg",
cloudflare: "/logos/cloudflare.svg",
"base-official": "/logos/base.jpeg",
binance: "/logos/bnb.png",
binance: "/logos/binance.svg",
lava: "/logos/lava.webp",
nodies: "/logos/nodies.png",
tenderly: "/logos/tenderly.svg",
Expand Down Expand Up @@ -187,6 +187,16 @@ const RAW: Record<string, string> = {
paradex: "/logos/paradex.svg",
aster: "/logos/aster.svg",

// ─── Perp funding stability cohort additions (bench № 043) ───
// Sourced via Mobula CEFI funding-rate aggregator.
bitget: "/logos/bitget.png",
coinbase: "/logos/coinbase.svg",
deribit: "/logos/deribit.png",
gate: "/logos/gate.png",
kraken: "/logos/kraken.png",
kucoin: "/logos/kucoin.svg",
mexc: "/logos/mexc.png",

// ─── Hyperliquid HIP-3 deployers (bench № 035) ───
xyz: "/logos/xyz.png",
vntl: "/logos/vntl.png",
Expand Down
5 changes: 3 additions & 2 deletions src/lib/materialize/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type BenchmarkFilters = {
chain?: string;
region?: string;
kind?: string;
venue?: string;
};

export function filterSig(f: BenchmarkFilters): string {
Expand All @@ -58,8 +59,8 @@ export function parseFilterSig(sig: string): BenchmarkFilters {
if (!sig) return out;
for (const kv of sig.split("&")) {
const [k, v] = kv.split("=");
if (k && v && (k === "chain" || k === "region" || k === "kind")) {
out[k as "chain" | "region" | "kind"] = v;
if (k && v && (k === "chain" || k === "region" || k === "kind" || k === "venue")) {
out[k as "chain" | "region" | "kind" | "venue"] = v;
}
}
return out;
Expand Down
Loading
Loading