From dff410a91b5a206f46089fb0b9d8822f909c8df3 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Thu, 11 Jun 2026 18:01:46 +0200 Subject: [PATCH] feat(bench): perp-funding, normalized funding rates across 7 venues --- benchmarks/perp-funding.yml | 274 +++++++++++++ .../cmd/script/funding.go | 368 ++++++++++++++++++ .../hyperliquid-frontends/cmd/script/main.go | 1 + public/logos/aster.jpg | Bin 0 -> 9197 bytes public/logos/bybit.jpg | Bin 0 -> 5652 bytes public/logos/okx.jpg | Bin 0 -> 5096 bytes public/logos/paradex.jpg | Bin 0 -> 7523 bytes src/data/provider-registry.ts | 26 ++ src/lib/logo-manifest.ts | 6 + 9 files changed, 675 insertions(+) create mode 100644 benchmarks/perp-funding.yml create mode 100644 harnesses/hyperliquid-frontends/cmd/script/funding.go create mode 100644 public/logos/aster.jpg create mode 100644 public/logos/bybit.jpg create mode 100644 public/logos/okx.jpg create mode 100644 public/logos/paradex.jpg diff --git a/benchmarks/perp-funding.yml b/benchmarks/perp-funding.yml new file mode 100644 index 00000000..d5bdb536 --- /dev/null +++ b/benchmarks/perp-funding.yml @@ -0,0 +1,274 @@ +# OpenChainBench. Bench № 036 + +slug: perp-funding +number: "036" +title: Cheapest perp venue to hold a position, live funding normalized +seo_title: "Perp funding rates live 2026: Hyperliquid, Binance, Bybit, OKX, dYdX, Paradex, Aster normalized and ranked" +seo_description: "Live funding rates across Hyperliquid, Binance, Bybit, OKX, dYdX, Paradex and Aster, normalized to the same time base (venues settle on 1h, 4h or 8h periods). Ranked by the cost of holding a $1,000 ETH long for 24 hours, with BTC and SOL columns, annualized rates and 7d/30d averages." +subtitle: Funding cost in basis points to hold a long position for 24 hours at the current rate, normalized across venues that settle funding on 1 hour, 4 hour and 8 hour periods. Negative means longs get paid. +category: Trading +status: live +metric: Funding cost to hold a long 24h +unit: bps +higher_is_better: false + +disclaimer: | + Funding is signed. Positive means longs pay shorts, negative means longs get paid to hold. Venues settle on different native periods (1h for Hyperliquid and dYdX, 4h or 8h elsewhere); every figure is normalized from the venue's own quoted rate and interval before ranking. Funding is only the holding cost. Opening costs (taker fee, spread, impact) are the companion bench perp-fees. + +seo_intro: | + This page answers one question. On which perp venue is it cheapest, + right now, to hold a position once it is open. Perpetual futures + charge funding between longs and shorts to keep the contract pinned + to spot, and on a position held for hours or days this funding flow + usually dwarfs the opening fee. Comparing it across venues is + genuinely hard because everyone quotes a different native period: + Hyperliquid and dYdX settle every hour, Binance and Bybit quote per + 8 hours with some pairs moved to 4 hours, OKX, Paradex and Aster + quote per 8 hours. A raw rate comparison is meaningless. The bench + polls each venue's public funding endpoint every minute, reads the + venue's own settlement interval alongside the rate, normalizes + everything to a per hour basis, and ranks venues by the cost in + basis points of holding a $1,000 ETH long for 24 hours at the + current rate, with BTC and SOL columns, annualized equivalents and + 7 and 30 day averages for the carry trade perspective. Negative + numbers mean the long side is being paid to hold. Funding spreads + between venues are the raw material of the funding arbitrage trade: + short the perp where funding is most positive, long it where it is + most negative or hold spot, and collect the difference. + +abstract: | + The bench polls the public funding endpoint of seven perpetual + venues every 60 seconds: Hyperliquid (metaAndAssetCtxs), Binance + and Aster (premiumIndex plus fundingInfo for per pair intervals), + Bybit (v5 tickers plus instruments-info), OKX (public funding-rate, + interval derived from settlement timestamps), dYdX v4 (indexer + perpetualMarkets) and Paradex (markets summary). For each venue and + each of BTC, ETH and SOL it records the quoted rate and the venue's + native settlement interval, then publishes Prometheus gauges + normalized to a per hour basis: rate per hour in bps, cost to hold + a long for 24 hours in bps, and the annualized percentage. Sign is + preserved everywhere, positive means longs pay shorts. The 7 day + and 30 day figures are time averages of the normalized series, so + they reflect what holding actually cost over the window rather than + a single snapshot. A venue that fails to respond keeps its previous + values and its freshness timestamp goes stale, which the + reliability column surfaces. + +methodology: + - "Sources. One public, unauthenticated endpoint per venue: Hyperliquid info metaAndAssetCtxs (live 1h rate), Binance fapi premiumIndex plus fundingInfo for per pair intervals, Bybit v5 tickers plus instruments-info (interval in minutes), OKX public funding-rate (interval from settlement timestamps), dYdX v4 indexer perpetualMarkets (per hour rate), Paradex markets summary (8h period), Aster premiumIndex (Binance compatible)." + - "Normalization. Every venue quotes a rate for its own settlement period. The harness divides the quoted rate by the venue's interval in hours to get a per hour rate, then publishes per hour bps (rate times 10000), 24 hour hold cost (per hour times 24) and annualized percent (per hour times 24 times 365 times 100). A 0.01 percent per 8h rate and a 0.00125 percent per 1h rate are the same number on this page." + - "Sign convention. Positive means longs pay shorts, negative means longs are paid to hold. Signs are preserved through every column; the ranking treats negative as cheaper, so a venue where the long side is being paid ranks first." + - "Headline. Cost in bps of notional to hold an ETH long for 24 hours, averaged over the last 24 hours of one minute samples. The instantaneous rate moves every settlement; the 24 hour average is what a position opened yesterday actually paid. BTC and SOL columns are computed identically." + - "Windows. The 7 day and 30 day columns are avg_over_time of the normalized hold cost series, the carry trader's view. A venue with attractive funding for one hour and brutal funding the rest of the week ranks accordingly on the longer windows." + - "Cadence. The harness polls all venues in parallel every 60 seconds with a 10 second timeout per request; Prometheus scrapes every 30 seconds. End to end staleness is under two minutes in normal operation." + - "Failures. A venue that errors or times out keeps its previous gauge values and its perp_funding_venue_ok_unix timestamp stops advancing; the Success column turns red after 5 minutes of silence. No value on this page is ever interpolated or guessed." + - "Scope. BTC, ETH and SOL are the assets every venue in the cohort lists. GMX is excluded because its borrow fee model has no comparable funding rate. Lighter is excluded until the interval semantics of its public funding endpoint are documented; it joins the cohort once its numbers can be normalized honestly." + - "Funding versus fees. This bench prices holding. Its companion perp-fees prices opening (taker fee plus spread plus impact on a $1000 ETH long). Read both to know the all in cost of a trade you intend to hold." + - "Reproducibility. The harness source is open in the OCB harness tree (funding.go in the hyperliquid-frontends harness). Point it at the same public endpoints and you reproduce every number on this page." + +findings: + - "{{best_name}} is currently the cheapest venue to hold an ETH long, at {{best_p50}} of notional per 24 hours (24h average, negative means longs get paid)." + - "{{name:hyperliquid}} settles funding every hour at a rate equivalent to {{p50:hyperliquid}} per 24h held. Hourly settlement means the quoted rate reacts to skew faster than the 8 hour venues." + - "{{name:binance}} sits at {{p50:binance}} per 24h held on ETH. As the deepest perp book in the market it tends to anchor the field; persistent spreads against it are the funding arbitrage signal." + - "Funding spreads between venues are tradeable. Short the perp where funding is most positive and long it where it is most negative (or hold spot), and the difference accrues to you at every settlement." + - "The sign flips with market regime. In sustained uptrends every venue goes positive (longs pay), in washouts funding goes negative and the table inverts. The 7d and 30d toggles smooth single-settlement noise." + - "Interval normalization matters more than it looks. A naive comparison of quoted rates overstates 1 hour venues by 8x against 8 hour venues; every figure here is normalized to the same time base before ranking." + +faq: + - q: "What does this benchmark measure?" + a: "The funding cost of holding a perp long, normalized across venues. Headline ranking is the cost in basis points of holding a $1,000 ETH long for 24 hours at the current rate, averaged over the last 24 hours. BTC and SOL columns, annualized equivalents and 7d/30d averages are alongside. Positive means longs pay shorts, negative means longs get paid to hold." + - q: "What is a funding rate?" + a: "Perpetual futures never expire, so to keep the contract price pinned to spot, the side that is crowding the trade periodically pays the other side. When perps trade above spot, longs pay shorts; below spot, shorts pay longs. The payment is the funding rate, charged on position notional at each settlement." + - q: "Why does normalization matter?" + a: "Venues quote funding for their own settlement period: Hyperliquid and dYdX per hour, Binance and Bybit per 8 hours with some pairs on 4 hours, OKX, Paradex and Aster per 8 hours. Comparing quoted rates directly overstates hourly venues by 8x. This page divides every rate by its venue's interval first, so all columns share a per hour time base." + - q: "What does a negative number mean?" + a: "The long side is being paid to hold. Funding is a transfer between traders, not a fee to the venue, so when shorts crowd the book the sign flips and holding a long becomes income. The ranking treats negative as cheapest." + - q: "What is funding rate arbitrage?" + a: "Collecting the spread between venues or against spot. The classic carry: hold spot (or a long where funding is negative) and short the perp where funding is most positive; each settlement pays you the difference without directional exposure. The 7d and 30d columns show whether a spread persists long enough to cover execution costs." + - q: "Why is the 24h average the headline instead of the live rate?" + a: "The instantaneous rate can spike for a single settlement and revert. A position opened 24 hours ago paid the average, not the snapshot, so the headline is avg_over_time over 24 hours of one minute samples. The Per-hour rate tab shows the live normalized rate if you want the snapshot." + - q: "Why are GMX and Lighter not in the cohort?" + a: "GMX prices borrowing per side against pool utilization; there is no funding rate comparable to the order book venues, and pretending otherwise would be dishonest. Lighter publishes funding data but the interval semantics of its public endpoint are not documented; it joins once its numbers can be normalized with confidence." + - q: "How fresh are these numbers?" + a: "The harness polls every venue in parallel every 60 seconds and Prometheus scrapes every 30 seconds, so figures are at most about two minutes behind the venue APIs. A venue that stops responding keeps its last values and its Success column turns red after five minutes." + - q: "Can I cite a value from this page?" + a: "Yes. Every number is a Prometheus query over gauges the open source harness publishes from public venue endpoints. Cite the value and the timestamp at the top of the page; the harness source link is in the source field below." + +source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/hyperliquid-frontends + +prometheus: + window: 24h + expected_freshness_seconds: 3600 + +providers: + - slug: hyperliquid + name: Hyperliquid + tag: Onchain order book perps, funding settled hourly + formula: "Quoted hourly funding rate from info metaAndAssetCtxs, normalized to bps per hour and extrapolated to a 24h hold; headline is the 24h time average on ETH." + queries: + p50: avg_over_time(perp_funding_hold_24h_bps{venue="hyperliquid",asset="ETH"}[24h]) + p90: avg_over_time(perp_funding_hold_24h_bps{venue="hyperliquid",asset="BTC"}[24h]) + p99: avg_over_time(perp_funding_hold_24h_bps{venue="hyperliquid",asset="SOL"}[24h]) + mean: avg_over_time(perp_funding_annualized_pct{venue="hyperliquid",asset="ETH"}[24h]) + success: (perp_funding_venue_ok_unix{venue="hyperliquid"} > bool (time() - 300)) + sample_size: count(perp_funding_rate_hourly_bps{venue="hyperliquid"}) + series: perp_funding_hold_24h_bps{venue="hyperliquid",asset="ETH"} + + - slug: binance + name: Binance + tag: Largest CEX perp book, 8h funding (4h on some pairs) + formula: "lastFundingRate from fapi premiumIndex divided by the pair's funding interval (fundingInfo, default 8h), normalized to bps per hour and a 24h hold; headline is the 24h time average on ETH." + queries: + p50: avg_over_time(perp_funding_hold_24h_bps{venue="binance",asset="ETH"}[24h]) + p90: avg_over_time(perp_funding_hold_24h_bps{venue="binance",asset="BTC"}[24h]) + p99: avg_over_time(perp_funding_hold_24h_bps{venue="binance",asset="SOL"}[24h]) + mean: avg_over_time(perp_funding_annualized_pct{venue="binance",asset="ETH"}[24h]) + success: (perp_funding_venue_ok_unix{venue="binance"} > bool (time() - 300)) + sample_size: count(perp_funding_rate_hourly_bps{venue="binance"}) + series: perp_funding_hold_24h_bps{venue="binance",asset="ETH"} + + - slug: bybit + name: Bybit + tag: USDT linear perps, funding interval per instrument + formula: "fundingRate from v5 market tickers divided by the instrument's fundingInterval (instruments-info, minutes), normalized to bps per hour and a 24h hold; headline is the 24h time average on ETH." + queries: + p50: avg_over_time(perp_funding_hold_24h_bps{venue="bybit",asset="ETH"}[24h]) + p90: avg_over_time(perp_funding_hold_24h_bps{venue="bybit",asset="BTC"}[24h]) + p99: avg_over_time(perp_funding_hold_24h_bps{venue="bybit",asset="SOL"}[24h]) + mean: avg_over_time(perp_funding_annualized_pct{venue="bybit",asset="ETH"}[24h]) + success: (perp_funding_venue_ok_unix{venue="bybit"} > bool (time() - 300)) + sample_size: count(perp_funding_rate_hourly_bps{venue="bybit"}) + series: perp_funding_hold_24h_bps{venue="bybit",asset="ETH"} + + - slug: okx + name: OKX + tag: USDT swaps, interval derived from settlement timestamps + formula: "fundingRate from the public funding-rate endpoint divided by the interval between fundingTime and nextFundingTime, normalized to bps per hour and a 24h hold; headline is the 24h time average on ETH." + queries: + p50: avg_over_time(perp_funding_hold_24h_bps{venue="okx",asset="ETH"}[24h]) + p90: avg_over_time(perp_funding_hold_24h_bps{venue="okx",asset="BTC"}[24h]) + p99: avg_over_time(perp_funding_hold_24h_bps{venue="okx",asset="SOL"}[24h]) + mean: avg_over_time(perp_funding_annualized_pct{venue="okx",asset="ETH"}[24h]) + success: (perp_funding_venue_ok_unix{venue="okx"} > bool (time() - 300)) + sample_size: count(perp_funding_rate_hourly_bps{venue="okx"}) + series: perp_funding_hold_24h_bps{venue="okx",asset="ETH"} + + - slug: dydx + name: dYdX v4 + tag: Appchain order book perps, funding settled hourly + formula: "nextFundingRate (per hour) from the v4 indexer perpetualMarkets endpoint, normalized to bps per hour and extrapolated to a 24h hold; headline is the 24h time average on ETH." + queries: + p50: avg_over_time(perp_funding_hold_24h_bps{venue="dydx",asset="ETH"}[24h]) + p90: avg_over_time(perp_funding_hold_24h_bps{venue="dydx",asset="BTC"}[24h]) + p99: avg_over_time(perp_funding_hold_24h_bps{venue="dydx",asset="SOL"}[24h]) + mean: avg_over_time(perp_funding_annualized_pct{venue="dydx",asset="ETH"}[24h]) + success: (perp_funding_venue_ok_unix{venue="dydx"} > bool (time() - 300)) + sample_size: count(perp_funding_rate_hourly_bps{venue="dydx"}) + series: perp_funding_hold_24h_bps{venue="dydx",asset="ETH"} + + - slug: paradex + name: Paradex + tag: Starknet L2 perps, 8h funding accrued continuously + formula: "funding_rate from markets summary (8 hour period), normalized to bps per hour and a 24h hold; headline is the 24h time average on ETH." + queries: + p50: avg_over_time(perp_funding_hold_24h_bps{venue="paradex",asset="ETH"}[24h]) + p90: avg_over_time(perp_funding_hold_24h_bps{venue="paradex",asset="BTC"}[24h]) + p99: avg_over_time(perp_funding_hold_24h_bps{venue="paradex",asset="SOL"}[24h]) + mean: avg_over_time(perp_funding_annualized_pct{venue="paradex",asset="ETH"}[24h]) + success: (perp_funding_venue_ok_unix{venue="paradex"} > bool (time() - 300)) + sample_size: count(perp_funding_rate_hourly_bps{venue="paradex"}) + series: perp_funding_hold_24h_bps{venue="paradex",asset="ETH"} + + - slug: aster + name: Aster + tag: BNB Chain perps DEX, Binance compatible API, 8h funding + formula: "lastFundingRate from the Binance compatible premiumIndex endpoint divided by the pair's funding interval (default 8h), normalized to bps per hour and a 24h hold; headline is the 24h time average on ETH." + queries: + p50: avg_over_time(perp_funding_hold_24h_bps{venue="aster",asset="ETH"}[24h]) + p90: avg_over_time(perp_funding_hold_24h_bps{venue="aster",asset="BTC"}[24h]) + p99: avg_over_time(perp_funding_hold_24h_bps{venue="aster",asset="SOL"}[24h]) + mean: avg_over_time(perp_funding_annualized_pct{venue="aster",asset="ETH"}[24h]) + success: (perp_funding_venue_ok_unix{venue="aster"} > bool (time() - 300)) + sample_size: count(perp_funding_rate_hourly_bps{venue="aster"}) + series: perp_funding_hold_24h_bps{venue="aster",asset="ETH"} + +metric_panels: + - id: annualized + label: Annualized (ETH) + metric: perp_funding_annualized_pct{asset="ETH"} + label_key: venue + unit: pct + description: "Current ETH funding rate annualized, in percent. Positive means longs pay; the carry trade collects the spread between venues." + - id: hourly + label: Per-hour rate (ETH) + metric: perp_funding_rate_hourly_bps{asset="ETH"} + label_key: venue + unit: bps + description: "Live ETH funding normalized to a per hour basis, in bps. The snapshot behind the smoothed headline." + - id: btc_hold + label: BTC hold 24h + metric: perp_funding_hold_24h_bps{asset="BTC"} + label_key: venue + unit: bps + description: "Cost in bps of holding a BTC long for 24 hours at the current rate." + - id: sol_hold + label: SOL hold 24h + metric: perp_funding_hold_24h_bps{asset="SOL"} + label_key: venue + unit: bps + description: "Cost in bps of holding a SOL long for 24 hours at the current rate." + # Data-only panels (tab: false): they feed the ledger's 7d/30d + # timeframe toggle below. + - id: eth_hold_7d + label: ETH hold 7d avg + metric: avg_over_time(perp_funding_hold_24h_bps{asset="ETH"}[7d]) + label_key: venue + unit: bps + tab: false + - id: eth_hold_30d + label: ETH hold 30d avg + metric: avg_over_time(perp_funding_hold_24h_bps{asset="ETH"}[30d]) + label_key: venue + unit: bps + tab: false + - id: btc_hold_7d + label: BTC hold 7d avg + metric: avg_over_time(perp_funding_hold_24h_bps{asset="BTC"}[7d]) + label_key: venue + unit: bps + tab: false + - id: btc_hold_30d + label: BTC hold 30d avg + metric: avg_over_time(perp_funding_hold_24h_bps{asset="BTC"}[30d]) + label_key: venue + unit: bps + tab: false + - id: sol_hold_7d + label: SOL hold 7d avg + metric: avg_over_time(perp_funding_hold_24h_bps{asset="SOL"}[7d]) + label_key: venue + unit: bps + tab: false + - id: sol_hold_30d + label: SOL hold 30d avg + metric: avg_over_time(perp_funding_hold_24h_bps{asset="SOL"}[30d]) + label_key: venue + unit: bps + tab: false + - id: interval + label: Funding interval h + metric: perp_funding_interval_hours{asset="ETH"} + label_key: venue + unit: count + tab: false + +# Honest column labels: the p50/p90/p99/mean slots are repurposed +# (signed funding costs, no percentile semantics). ETH, BTC and SOL +# follow the 24h/7d/30d timeframe toggle via the data-only panels above; +# Annualized and Interval are current figures on every timeframe. +ledger_columns: + - { label: "ETH hold 24h", slot: p50, windows: { 7d: eth_hold_7d, 30d: eth_hold_30d } } + - { label: "BTC", slot: p90, windows: { 7d: btc_hold_7d, 30d: btc_hold_30d } } + - { label: "SOL", slot: p99, windows: { 7d: sol_hold_7d, 30d: sol_hold_30d } } + - { label: "Annualized", slot: mean, unit: pct } + - { label: "Interval h", panel: interval } diff --git a/harnesses/hyperliquid-frontends/cmd/script/funding.go b/harnesses/hyperliquid-frontends/cmd/script/funding.go new file mode 100644 index 00000000..f79f2f85 --- /dev/null +++ b/harnesses/hyperliquid-frontends/cmd/script/funding.go @@ -0,0 +1,368 @@ +package main + +// Perp funding-rate module (OCB bench perp-funding). Polls the public +// funding endpoint of each venue, normalizes by the venue's own funding +// interval (HL/dYdX pay hourly, Binance/Bybit/OKX/Paradex/Aster settle on +// 4h/8h periods), and republishes per-hour, per-24h-hold, and annualized +// figures so the bench compares venues on the same time base. +// +// Sign convention everywhere: positive = longs pay shorts. +// On poll error a venue's gauges keep their previous values and its +// ok-timestamp goes stale, which the bench's success query surfaces. + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "strconv" + "sync" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var fundingAssets = []string{"BTC", "ETH", "SOL"} + +var ( + fundingHourlyBps = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "perp_funding_rate_hourly_bps", + Help: "Funding rate normalized to a per-hour basis, in bps. Positive = longs pay shorts", + }, []string{"venue", "asset"}) + fundingHold24hBps = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "perp_funding_hold_24h_bps", + Help: "Funding cost of holding a long for 24h at the current rate, in bps of notional", + }, []string{"venue", "asset"}) + fundingAnnualizedPct = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "perp_funding_annualized_pct", + Help: "Current funding rate annualized, in percent", + }, []string{"venue", "asset"}) + fundingIntervalHours = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "perp_funding_interval_hours", + Help: "Venue's native funding settlement interval for the asset, in hours", + }, []string{"venue", "asset"}) + fundingVenueOkUnix = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "perp_funding_venue_ok_unix", + Help: "Unix time of the venue's last successful funding poll", + }, []string{"venue"}) + fundingLastTick = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "perp_funding_last_tick_unix", + Help: "Unix time of the last funding poll cycle", + }) +) + +type fundingPoint struct { + // Rate per the venue's native interval, decimal (0.0001 = 1 bp). + rate float64 + // Native settlement interval in hours. + intervalH float64 +} + +type fundingVenue struct { + name string + poll func(ctx context.Context) (map[string]fundingPoint, error) +} + +var fundingHTTP = &http.Client{Timeout: 10 * time.Second} + +func fundingGetJSON(ctx context.Context, url string, out any) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return err + } + res, err := fundingHTTP.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + b, _ := io.ReadAll(io.LimitReader(res.Body, 200)) + return fmt.Errorf("%s: http %d: %s", url, res.StatusCode, b) + } + return json.NewDecoder(res.Body).Decode(out) +} + +func parseRate(s string) (float64, error) { return strconv.ParseFloat(s, 64) } + +// ── Hyperliquid: metaAndAssetCtxs, funding field is the live 1h rate ── + +func pollHyperliquid(ctx context.Context) (map[string]fundingPoint, error) { + body := bytes.NewBufferString(`{"type":"metaAndAssetCtxs"}`) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.hyperliquid.xyz/info", body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + res, err := fundingHTTP.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("hl: http %d", res.StatusCode) + } + var raw []json.RawMessage + if err := json.NewDecoder(res.Body).Decode(&raw); err != nil { + return nil, err + } + if len(raw) != 2 { + return nil, fmt.Errorf("hl: unexpected envelope") + } + var meta struct { + Universe []struct { + Name string `json:"name"` + } `json:"universe"` + } + var ctxs []struct { + Funding string `json:"funding"` + } + if err := json.Unmarshal(raw[0], &meta); err != nil { + return nil, err + } + if err := json.Unmarshal(raw[1], &ctxs); err != nil { + return nil, err + } + out := map[string]fundingPoint{} + for i, u := range meta.Universe { + if i >= len(ctxs) { + break + } + for _, a := range fundingAssets { + if u.Name == a { + if r, err := parseRate(ctxs[i].Funding); err == nil { + out[a] = fundingPoint{rate: r, intervalH: 1} + } + } + } + } + return out, nil +} + +// ── Binance-compatible (Binance, Aster): premiumIndex + fundingInfo ── + +func pollBinanceLike(base, venue string) func(ctx context.Context) (map[string]fundingPoint, error) { + return func(ctx context.Context) (map[string]fundingPoint, error) { + // fundingInfo lists only symbols with a non-default interval; + // everything else settles every 8h. + intervals := map[string]float64{} + var info []struct { + Symbol string `json:"symbol"` + FundingIntervalHours int `json:"fundingIntervalHours"` + } + if err := fundingGetJSON(ctx, base+"/fapi/v1/fundingInfo", &info); err == nil { + for _, e := range info { + if e.FundingIntervalHours > 0 { + intervals[e.Symbol] = float64(e.FundingIntervalHours) + } + } + } else { + log.Printf("funding %s fundingInfo: %v (assuming 8h)", venue, err) + } + out := map[string]fundingPoint{} + for _, a := range fundingAssets { + sym := a + "USDT" + var pi struct { + LastFundingRate string `json:"lastFundingRate"` + } + if err := fundingGetJSON(ctx, base+"/fapi/v1/premiumIndex?symbol="+sym, &pi); err != nil { + return nil, err + } + r, err := parseRate(pi.LastFundingRate) + if err != nil { + return nil, err + } + ih := intervals[sym] + if ih == 0 { + ih = 8 + } + out[a] = fundingPoint{rate: r, intervalH: ih} + } + return out, nil + } +} + +// ── Bybit: v5 tickers + instruments-info (fundingInterval in minutes) ── + +func pollBybit(ctx context.Context) (map[string]fundingPoint, error) { + out := map[string]fundingPoint{} + for _, a := range fundingAssets { + sym := a + "USDT" + var tick struct { + Result struct { + List []struct { + FundingRate string `json:"fundingRate"` + } `json:"list"` + } `json:"result"` + } + if err := fundingGetJSON(ctx, "https://api.bybit.com/v5/market/tickers?category=linear&symbol="+sym, &tick); err != nil { + return nil, err + } + if len(tick.Result.List) == 0 { + return nil, fmt.Errorf("bybit: empty tickers for %s", sym) + } + r, err := parseRate(tick.Result.List[0].FundingRate) + if err != nil { + return nil, err + } + var inst struct { + Result struct { + List []struct { + FundingInterval int `json:"fundingInterval"` + } `json:"list"` + } `json:"result"` + } + ih := 8.0 + if err := fundingGetJSON(ctx, "https://api.bybit.com/v5/market/instruments-info?category=linear&symbol="+sym, &inst); err == nil && + len(inst.Result.List) > 0 && inst.Result.List[0].FundingInterval > 0 { + ih = float64(inst.Result.List[0].FundingInterval) / 60.0 + } + out[a] = fundingPoint{rate: r, intervalH: ih} + } + return out, nil +} + +// ── OKX: funding-rate, interval derived from settlement timestamps ── + +func pollOKX(ctx context.Context) (map[string]fundingPoint, error) { + out := map[string]fundingPoint{} + for _, a := range fundingAssets { + var resp struct { + Data []struct { + FundingRate string `json:"fundingRate"` + FundingTime string `json:"fundingTime"` + NextFundingTime string `json:"nextFundingTime"` + } `json:"data"` + } + if err := fundingGetJSON(ctx, "https://www.okx.com/api/v5/public/funding-rate?instId="+a+"-USDT-SWAP", &resp); err != nil { + return nil, err + } + if len(resp.Data) == 0 { + return nil, fmt.Errorf("okx: empty data for %s", a) + } + d := resp.Data[0] + r, err := parseRate(d.FundingRate) + if err != nil { + return nil, err + } + ih := 8.0 + cur, err1 := strconv.ParseInt(d.FundingTime, 10, 64) + next, err2 := strconv.ParseInt(d.NextFundingTime, 10, 64) + if err1 == nil && err2 == nil && next > cur { + ih = float64(next-cur) / 3_600_000.0 + } + out[a] = fundingPoint{rate: r, intervalH: ih} + } + return out, nil +} + +// ── dYdX v4: indexer perpetualMarkets, nextFundingRate is per hour ── + +func pollDydx(ctx context.Context) (map[string]fundingPoint, error) { + out := map[string]fundingPoint{} + for _, a := range fundingAssets { + var resp struct { + Markets map[string]struct { + NextFundingRate string `json:"nextFundingRate"` + } `json:"markets"` + } + t := a + "-USD" + if err := fundingGetJSON(ctx, "https://indexer.dydx.trade/v4/perpetualMarkets?ticker="+t, &resp); err != nil { + return nil, err + } + m, ok := resp.Markets[t] + if !ok { + return nil, fmt.Errorf("dydx: market %s missing", t) + } + r, err := parseRate(m.NextFundingRate) + if err != nil { + return nil, err + } + out[a] = fundingPoint{rate: r, intervalH: 1} + } + return out, nil +} + +// ── Paradex: markets/summary, funding_rate is per 8h period ── + +func pollParadex(ctx context.Context) (map[string]fundingPoint, error) { + out := map[string]fundingPoint{} + for _, a := range fundingAssets { + var resp struct { + Results []struct { + FundingRate string `json:"funding_rate"` + } `json:"results"` + } + if err := fundingGetJSON(ctx, "https://api.prod.paradex.trade/v1/markets/summary?market="+a+"-USD-PERP", &resp); err != nil { + return nil, err + } + if len(resp.Results) == 0 { + return nil, fmt.Errorf("paradex: empty summary for %s", a) + } + r, err := parseRate(resp.Results[0].FundingRate) + if err != nil { + return nil, err + } + out[a] = fundingPoint{rate: r, intervalH: 8} + } + return out, nil +} + +var fundingVenues = []fundingVenue{ + {name: "hyperliquid", poll: pollHyperliquid}, + {name: "binance", poll: pollBinanceLike("https://fapi.binance.com", "binance")}, + {name: "bybit", poll: pollBybit}, + {name: "okx", poll: pollOKX}, + {name: "dydx", poll: pollDydx}, + {name: "paradex", poll: pollParadex}, + {name: "aster", poll: pollBinanceLike("https://fapi.asterdex.com", "aster")}, +} + +func runFundingLoop(ctx context.Context, every time.Duration) { + tick := func() { + cctx, cancel := context.WithTimeout(ctx, 45*time.Second) + defer cancel() + var wg sync.WaitGroup + for _, v := range fundingVenues { + wg.Add(1) + go func(v fundingVenue) { + defer wg.Done() + points, err := v.poll(cctx) + if err != nil { + log.Printf("funding %s: %v", v.name, err) + return + } + for asset, p := range points { + if p.intervalH <= 0 { + continue + } + hourly := p.rate / p.intervalH + fundingHourlyBps.WithLabelValues(v.name, asset).Set(hourly * 10_000) + fundingHold24hBps.WithLabelValues(v.name, asset).Set(hourly * 24 * 10_000) + fundingAnnualizedPct.WithLabelValues(v.name, asset).Set(hourly * 24 * 365 * 100) + fundingIntervalHours.WithLabelValues(v.name, asset).Set(p.intervalH) + } + if len(points) > 0 { + fundingVenueOkUnix.WithLabelValues(v.name).SetToCurrentTime() + } + }(v) + } + wg.Wait() + fundingLastTick.SetToCurrentTime() + } + + tick() + t := time.NewTicker(every) + defer t.Stop() + for { + select { + case <-ctx.Done(): + return + case <-t.C: + tick() + } + } +} diff --git a/harnesses/hyperliquid-frontends/cmd/script/main.go b/harnesses/hyperliquid-frontends/cmd/script/main.go index 96c018d0..ae61629f 100644 --- a/harnesses/hyperliquid-frontends/cmd/script/main.go +++ b/harnesses/hyperliquid-frontends/cmd/script/main.go @@ -99,6 +99,7 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + go runFundingLoop(ctx, time.Minute) go func() { t := time.NewTicker(*tickEvery) defer t.Stop() diff --git a/public/logos/aster.jpg b/public/logos/aster.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b94df50b5196b5cd9a9f2a09ef5cf14d20d09cf7 GIT binary patch literal 9197 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 Vp+m2VF(KB2^J0bU6<}CA{XfwIXU+fs literal 0 HcmV?d00001 diff --git a/public/logos/bybit.jpg b/public/logos/bybit.jpg new file mode 100644 index 0000000000000000000000000000000000000000..de2bba1d94b8b167cf3afbe341938ed11a605c5a GIT binary patch literal 5652 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 literal 0 HcmV?d00001 diff --git a/public/logos/okx.jpg b/public/logos/okx.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4532e73b298a8a95393c3c826e83e59d302e987f GIT binary patch literal 5096 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 literal 0 HcmV?d00001 diff --git a/src/data/provider-registry.ts b/src/data/provider-registry.ts index c1d9344b..f72f6834 100644 --- a/src/data/provider-registry.ts +++ b/src/data/provider-registry.ts @@ -1020,6 +1020,32 @@ export const PROVIDER_REGISTRY: Record = { twitter: "@Blink_Exchange", }, + // ─── Perp funding venues (bench № 036) ───────────────────────────── + bybit: { + url: "https://www.bybit.com", + description: + "Bybit is one of the largest centralized derivatives exchanges, with USDT-margined linear perpetuals on hundreds of pairs and funding settled on 8 hour (some pairs 4 hour) periods.", + twitter: "@Bybit_Official", + }, + okx: { + url: "https://www.okx.com", + description: + "OKX is a major centralized exchange whose USDT-margined perpetual swaps settle funding on variable periods (typically 8 hours), with rates capped per instrument.", + twitter: "@okx", + }, + paradex: { + url: "https://www.paradex.trade", + description: + "Paradex is a perps-focused L2 exchange built on Starknet tech, with USD-settled perpetuals and funding accrued continuously over an 8 hour period.", + twitter: "@paradex", + }, + aster: { + url: "https://www.asterdex.com", + description: + "Aster is a decentralized perps exchange (ex-ApolloX, backed by YZi Labs) on BNB Chain and other EVM networks, with a Binance-compatible API and 8 hour funding periods.", + twitter: "@Aster_DEX", + }, + // ─── Hyperliquid HIP-3 deployers (bench № 035) ───────────────────── xyz: { url: "https://trade.xyz", diff --git a/src/lib/logo-manifest.ts b/src/lib/logo-manifest.ts index 2c158a89..1f459e6b 100644 --- a/src/lib/logo-manifest.ts +++ b/src/lib/logo-manifest.ts @@ -156,6 +156,12 @@ const RAW: Record = { dexari: "/logos/dexari.png", okto: "/logos/okto.png", + // ─── Perp funding venues (bench № 036) ─── + bybit: "/logos/bybit.jpg", + okx: "/logos/okx.jpg", + paradex: "/logos/paradex.jpg", + aster: "/logos/aster.jpg", + // ─── Hyperliquid HIP-3 deployers (bench № 035) ─── xyz: "/logos/xyz.png", vntl: "/logos/vntl.png",