Funding Rate Data Scraping from Exchanges
Funding rate is a perpetual futures balancing mechanism. Every 8 hours (on most centralized exchanges) or continuously (on some), traders with long positions pay traders with short positions or vice versa, depending on whether the contract trades at premium or discount to spot price. For quant strategies, arbitrageurs, and risk managers, historical and real-time funding rate data is critical — one of the main market sentiment signals.
Data sources: exchanges and their APIs
Centralized exchanges
Binance — largest perpetuals volume:
GET https://fapi.binance.com/fapi/v1/fundingRate
?symbol=BTCUSDT&limit=1000&startTime={ms}&endTime={ms}
Returns history 1000 records per request. Rate limit: 2400 weight/minute, this endpoint costs 1 weight. Full history available from listing.
Bybit:
GET https://api.bybit.com/v5/market/funding/history
?category=linear&symbol=BTCUSDT&limit=200
Pagination via cursor in response, not by timestamp.
OKX:
GET https://www.okx.com/api/v5/public/funding-rate-history
?instId=BTC-USDT-SWAP&limit=100&before={ts}&after={ts}
Hyperliquid (decentralized perpetuals):
POST https://api.hyperliquid.xyz/info
Content-Type: application/json
{"type": "fundingHistory", "coin": "BTC", "startTime": 1700000000000}
WebSocket for real-time
Real-time funding rate (current, before next settlement):
Binance WebSocket:
const ws = new WebSocket("wss://fstream.binance.com/ws/btcusdt@markPrice@1s");
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
// data.r — current funding rate
// data.T — next funding time (ms)
};
Bybit WebSocket:
ws.send(JSON.stringify({
op: "subscribe",
args: ["tickers.BTCUSDT"]
}));
// In response: fundingRate and nextFundingTime
Collector implementation
Basic collector with retry and rate limiting
import pLimit from "p-limit";
import axiosRetry from "axios-retry";
class FundingRateCollector {
private readonly limit = pLimit(5); // max 5 concurrent requests
constructor(private readonly config: ExchangeConfig) {
axiosRetry(this.http, {
retries: 3,
retryDelay: axiosRetry.exponentialDelay,
retryCondition: (err) =>
axiosRetry.isNetworkError(err) ||
err.response?.status === 429 ||
err.response?.status >= 500,
});
}
async fetchHistorical(
symbol: string,
from: Date,
to: Date
): Promise<FundingRateRecord[]> {
const results: FundingRateRecord[] = [];
let cursor = from.getTime();
while (cursor < to.getTime()) {
const batch = await this.limit(() =>
this.fetchBatch(symbol, cursor, to.getTime())
);
if (batch.length === 0) break;
results.push(...batch);
cursor = batch[batch.length - 1].timestamp + 1;
// Respect rate limits
await delay(this.config.requestDelayMs);
}
return results;
}
}
Data normalization
Each exchange returns different format. Normalized structure:
interface FundingRateRecord {
exchange: string; // "binance", "bybit", "okx", "hyperliquid"
symbol: string; // normalized: "BTC-USDT"
originalSymbol: string; // as on exchange: "BTCUSDT", "BTC-USDT-SWAP"
timestamp: number; // unix ms — settlement time
fundingRate: number; // decimal, e.g. 0.0001 = 0.01%
annualizedRate: number; // fundingRate * 3 * 365 (3 settlements per day)
markPrice?: number; // price at calculation time
}
Annualized rate matters for arbitrage analysis: if annualized funding > 30%, cash-and-carry arbitrage (long spot + short perp) potentially profitable.
Symbol handling
Same asset on different exchanges has different names:
- Binance:
BTCUSDT(futures),BTCUSD_PERP(coin-margined) - Bybit:
BTCUSDT(linear),BTCUSD(inverse) - OKX:
BTC-USDT-SWAP,BTC-USD-SWAP
Need symbol mapping table:
CREATE TABLE symbol_mapping (
normalized_symbol VARCHAR(20) NOT NULL, -- "BTC-USDT"
exchange VARCHAR(20) NOT NULL,
exchange_symbol VARCHAR(30) NOT NULL,
contract_type VARCHAR(10) NOT NULL, -- "linear", "inverse"
PRIMARY KEY (exchange, exchange_symbol)
);
Storage and data access
DB schema
CREATE TABLE funding_rates (
id BIGSERIAL,
exchange VARCHAR(20) NOT NULL,
symbol VARCHAR(20) NOT NULL,
settled_at TIMESTAMPTZ NOT NULL,
funding_rate DECIMAL(18, 10) NOT NULL,
mark_price DECIMAL(24, 8),
PRIMARY KEY (id),
UNIQUE (exchange, symbol, settled_at)
) PARTITION BY RANGE (settled_at);
-- Index for typical queries
CREATE INDEX idx_funding_symbol_time
ON funding_rates (symbol, settled_at DESC);
TimescaleDB hypertable for automatic time-based partitioning and compression of old data.
Anomalies and validation
Funding rate rarely exceeds ±0.3% per 8 hours under normal conditions. Extreme values (FTX last days, LUNA collapse) — up to several percent. Validate on insert:
function validateFundingRate(rate: number, exchange: string, symbol: string): boolean {
if (Math.abs(rate) > 0.03) { // > 3% is anomaly
logger.warn(`Extreme funding rate detected`, { exchange, symbol, rate });
// Don't discard — may be real extreme event
// Flag for manual check
return true; // but save it
}
return Math.abs(rate) <= 1.0; // > 100% — obvious data error
}
Collection schedule and monitoring
Historical data: initial backfill at startup (can take hours for full history across all symbols). After — incremental update every 8 hours (after settlement) plus real-time via WebSocket.
Monitoring:
- Freshness: if last record for symbol older than 9 hours — alert (missed settlement)
- Gap detection: check sequence continuity of timestamps (step must be exactly 8h or multiple)
- Collection lag: time between settlement on exchange and appearance in our DB
Realistic development timeline for collector across 5–6 exchanges with historical backfill and API: 4–6 weeks.







