Dev.to WebDev 🛠 Dev 👁 0 📖 2 min read

What I Learned Building a Funding Rate Comparison Tool (and Where Exchange APIs Get Weird)

I built a small tool that compares perpetual funding rates across five exchanges. The project was straightforward until it wasn't. Here's what I learned about crypto exchange APIs — the parts nobody documents. Not all

I built a small tool that compares perpetual funding rates across five exchanges. The project was straightforward until it wasn't. Here's what I learned about crypto exchange APIs — the parts nobody documents.

  1. Not all exchanges return data in the same order

Binance's funding history endpoint returns entries oldest-first. Bybit's returns newest-first. Assume one ordering and half your data is wrong for weeks without noticing.

The fix:

python
entries.sort(key=lambda e: e[0], reverse=True)
One line. But finding it took a diagnostic run that printed the raw array and its timestamps to see the pattern.

  1. Geo-blocking is real and deliberate

Binance and Bybit both block requests from cloud infrastructure. GitHub Actions (Azure IPs), Cloudflare Workers, AWS — all blocked. The response codes vary: Binance returns HTTP 451 with a "Service unavailable from a restricted location" message; Bybit returns HTTP 403 through their CloudFront distribution with "The Amazon CloudFront distribution is configured to block access from your country."

This isn't a bug. It's an anti-scraping policy. The workaround is to use exchanges that don't geo-block cloud IPs — OKX, MEXC, Hyperliquid, Gate.io, Bitget all work fine.

I tried a Cloudflare Worker proxy to route around the block. It worked for Bybit from my laptop but failed from the GitHub Actions runner. Eventually I dropped both exchanges and added five that don't block us.

  1. Funding intervals vary widely

Most exchanges use 8-hour funding. Some use 4-hour. Hyperliquid uses 1-hour. If you annualize assuming 8 hours across the board, you're wrong by a factor of 8 on Hyperliquid.

The correct formula uses the venue's own interval:

python
periods_per_day = 24 / interval_hours
apr = ((1 + rate) ** (periods_per_day * 365) - 1) * 100
Each venue's funding interval comes from the settlement timestamps — compute the median gap between consecutive entries and you have it.

  1. Always verify response shapes before writing the parser

I nearly shipped a version that used a single per-symbol rate and four fee assumptions to compute "venue-specific" funding. It would have told users "Binance is the best venue" when the conclusion came from Binance's assumed fee, not its actual rate.

The fix: fetch real per-venue data. Which meant verifying every endpoint's response shape before writing the parser. It sounds tedious. It saves you from shipping misleading numbers.

  1. Static beats live for this use case

Funding rates only change at settlement. A snapshot refreshed every 8 hours is maximally fresh for the entire window. Fetching live at page load burns API quota, adds latency, and introduces a failure point for zero benefit.

The architecture:

GitHub Actions cron runs the fetcher every 8 hours

Fetcher writes to a JSON file, commits it

Cloudflare Pages auto-rebuilds on the commit

Visitor loads a pre-rendered static page in under 50ms

The tool

https://feescope.io

Source: github.com/sanchopansa1987/feescope

If you're building anything on exchange APIs, the surprises are always in the response shapes and the geo-blocks. Everything else is straightforward.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.