diff --git a/benchmarks/perp-funding-stability.yml b/benchmarks/perp-funding-stability.yml
index 709b7c00..ee15d5fb 100644
--- a/benchmarks/perp-funding-stability.yml
+++ b/benchmarks/perp-funding-stability.yml
@@ -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
@@ -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
@@ -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."
@@ -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])
diff --git a/public/logos/bitget.png b/public/logos/bitget.png
new file mode 100644
index 00000000..0290612b
Binary files /dev/null and b/public/logos/bitget.png differ
diff --git a/public/logos/coinbase.svg b/public/logos/coinbase.svg
new file mode 100644
index 00000000..c3ed947a
--- /dev/null
+++ b/public/logos/coinbase.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/deribit.png b/public/logos/deribit.png
new file mode 100644
index 00000000..f7be01be
Binary files /dev/null and b/public/logos/deribit.png differ
diff --git a/public/logos/gate.png b/public/logos/gate.png
new file mode 100644
index 00000000..9e5900f4
Binary files /dev/null and b/public/logos/gate.png differ
diff --git a/public/logos/kraken.png b/public/logos/kraken.png
new file mode 100644
index 00000000..8ebd5099
Binary files /dev/null and b/public/logos/kraken.png differ
diff --git a/public/logos/kucoin.svg b/public/logos/kucoin.svg
new file mode 100644
index 00000000..68462f47
--- /dev/null
+++ b/public/logos/kucoin.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/mexc.png b/public/logos/mexc.png
new file mode 100644
index 00000000..3f783c7e
Binary files /dev/null and b/public/logos/mexc.png differ
diff --git a/src/lib/categories.ts b/src/lib/categories.ts
new file mode 100644
index 00000000..b1eaa988
--- /dev/null
+++ b/src/lib/categories.ts
@@ -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/` 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 , 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 + . */
+ 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 = new Map(
+ CATEGORIES.map((c) => [c.slug, c]),
+);
+
+export const CATEGORY_SLUG_BY_LABEL: ReadonlyMap = new Map(
+ CATEGORIES.map((c) => [c.label, c.slug]),
+);
+
+/** Slug used in the `/benchmarks/category/` 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;
+}
diff --git a/src/lib/logo-manifest.ts b/src/lib/logo-manifest.ts
index 68765d06..25cc7214 100644
--- a/src/lib/logo-manifest.ts
+++ b/src/lib/logo-manifest.ts
@@ -100,7 +100,7 @@ const RAW: Record = {
"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",
@@ -187,6 +187,16 @@ const RAW: Record = {
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",
diff --git a/src/lib/materialize/load.ts b/src/lib/materialize/load.ts
index dd77a6b4..370e4b97 100644
--- a/src/lib/materialize/load.ts
+++ b/src/lib/materialize/load.ts
@@ -42,6 +42,7 @@ export type BenchmarkFilters = {
chain?: string;
region?: string;
kind?: string;
+ venue?: string;
};
export function filterSig(f: BenchmarkFilters): string {
@@ -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;
diff --git a/src/lib/spec-schema.ts b/src/lib/spec-schema.ts
index ab57dac6..d57e8694 100644
--- a/src/lib/spec-schema.ts
+++ b/src/lib/spec-schema.ts
@@ -344,6 +344,14 @@ export const SpecSchema = z
})
)
.optional(),
+ venue: z
+ .array(
+ z.object({
+ value: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/),
+ label: z.string().min(1).max(64),
+ })
+ )
+ .optional(),
})
.optional(),