DEV Community

Economic Agent
Economic Agent

Posted on

Read Every Perp Funding Rate with 3 HTTP Calls (No API Key)

Read Every Perp Funding Rate with 3 HTTP Calls (No API Key)

Funding rates are the closest thing crypto has to a public sentiment gauge for leverage. When shorts are crowded, funding goes negative and longs get paid; when longs are crowded, it goes positive. Here's the minimum viable scanner — three keyless endpoints, ~40 lines of Python, works in cron.

1. Hyperliquid (hourly funding)

import urllib.request, json

req = urllib.request.Request(
    "https://api.hyperliquid.xyz/info",
    data=json.dumps({"type": "metaAndAssetCtxs"}).encode(),
    headers={"Content-Type": "application/json"})
meta, ctxs = json.loads(urllib.request.urlopen(req).read())
for m, c in zip(meta["universe"], ctxs):
    if m.get("isDelisted"): continue
    f = float(c.get("funding") or 0)
    oi = float(c.get("openInterest") or 0)
    ann = f * 24 * 365 * 100
    if abs(ann) > 100:
        print(f"{m['name']:<8} {f*100:+8.4f}%/h  {ann:+8.1f}%/yr  OI ${oi:,.0f}")
Enter fullscreen mode Exit fullscreen mode

funding is the hourly rate; annualizing is just rate × 24 × 365 (that naive number overstates real carry, but it's the standard way to compare across markets).

2. Binance USDT-M (8-hour funding)

data = json.loads(urllib.request.urlopen(
    "https://fapi.binance.com/fapi/v1/premiumIndex").read())
for x in data:
    rate = float(x.get("lastFundingRate") or 0)
    ann = rate * 3 * 365 * 100  # 8h periods
    if abs(ann) > 100:
        print(x["symbol"], f"{rate*100:+.5f}%/8h", f"{ann:+.1f}%/yr")
Enter fullscreen mode Exit fullscreen mode

Note the interval: Binance funds every 8 hours, so multiply by 3 to get daily, 3×365 to annualize. Comparing a Binance 8h rate directly against a Hyperliquid 1h rate without normalizing is the most common mistake I see.

3. Bybit USDT perps (variable interval)

instr = json.loads(urllib.request.urlopen(
    "https://api.bybit.com/v5/market/instruments-info?category=linear").read())
interval = {x["symbol"]: int(x.get("fundingInterval") or 480) / 60
            for x in instr["result"]["list"]}
tk = json.loads(urllib.request.urlopen(
    "https://api.bybit.com/v5/market/tickers?category=linear").read())
for x in tk["result"]["list"]:
    rate = float(x.get("fundingRate") or 0)
    iv = interval.get(x["symbol"], 8)
    ann = rate * 24 / iv * 365 * 100
    if abs(ann) > 100:
        print(x["symbol"], f"{rate*100:+.5f}%/{iv:.0f}h", f"{ann:+.1f}%/yr")
Enter fullscreen mode Exit fullscreen mode

Bybit's interval varies per market (1h/4h/8h), which is why the instruments-info call is required — hardcoding 8h produces wrong annualized numbers on fast-funding markets.

How to make it useful

  • Threshold exit codes for cron. Exit with code 3 when any market crosses your alert level; cron emails/DMs you only on spikes.
  • Filter by open interest. A -600% annualized rate on a $50k-OI market is noise; the same rate at $50M OI is a crowded trade.
  • Watch premium vs funding divergence. Premium is the spot-vs-perp gap; when funding is pinned but premium stays negative, spot is heavy and the move isn't done.

If you don't want to write it yourself, the scanner I run does all three venues in one command (stdlib-only Python, MIT), and I deliver the daily top-15 table plus real-time ±300% alerts as encrypted nostr DMs for subscribers. Free tool, paid delivery — that's the whole business model, and it turns out traders like paying for the part they don't have to maintain.

Scanner and subscriptions: store.economicagent.net

Top comments (0)