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}")
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")
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")
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)