Bybit Predicted Funding Rate API: A Trader's Guide
Learn to query Bybit's predicted funding rate API, parse live data, and automate perpetual futures strategies before the next settlement window hits.
Learn to query Bybit's predicted funding rate API, parse live data, and automate perpetual futures strategies before the next settlement window hits.
Funding rates are one of the most exploitable signals in crypto derivatives — and most traders ignore them until settlement has already happened. Bybit exposes a predicted funding rate through its public V5 API, giving you a window into where the rate is heading before the 8-hour settlement fires. If you know how to query that endpoint programmatically, you can automate exits, size positions more carefully, or capture funding arbitrage across platforms like Bybit and Binance. This guide walks through the entire flow: endpoint structure, authentication, live code, response parsing, and error handling.
Perpetual futures contracts on Bybit, Binance, OKX, and most other major derivatives venues don't have an expiry date — instead, they stay anchored to spot price through a periodic payment called the funding rate. Every 8 hours (on most platforms), longs pay shorts or vice versa, depending on whether the perpetual is trading at a premium or discount to the underlying index.
The predicted funding rate is exactly what the name implies: the rate that will apply at the next settlement, calculated in real-time based on the current premium index. Bybit recalculates it continuously, so querying the API at T-30 minutes before settlement gives you a reasonably accurate picture of what you'll pay or receive. The further out you query, the more it can drift — but within 15 minutes of settlement, the predicted value converges tightly to the final rate.
The predicted funding rate is not guaranteed — it reflects current market conditions and will change if the premium index moves. Always build margin for drift into any strategy that depends on a specific rate value.
Why does this matter strategically? Because a funding rate of +0.1% per 8 hours annualizes to over 100%. At those levels, holding a leveraged long becomes expensive fast. Conversely, being short when funding is deeply positive means passive income on your position. Traders who read the predicted rate before it finalizes can rotate or add positions with a real edge — and that edge comes from fast, programmatic data access.
Bybit's V5 API is organized around a category parameter — for perpetual futures (USDT-margined linear contracts), you'll always pass category=linear. There are two endpoints relevant to funding rate work. The first is the tickers endpoint, which returns live market data including the predicted funding rate and the timestamp of the next settlement. The second is the funding history endpoint, which returns past rates useful for backtesting and normalization.
| Endpoint | Method | Auth Required | Returns |
|---|---|---|---|
| /v5/market/tickers | GET | No | Predicted funding rate + next settlement time |
| /v5/market/funding/history | GET | No | Historical funding rate records |
| /v5/market/instruments-info | GET | No | Funding interval, settlement schedule |
| /v5/position/list | GET | Yes | Your open positions with funding impact |
Both tickers and funding history are public endpoints — no API key needed to read them. Authentication is only required when you move to position data or want to act on what you read. That makes prototyping fast: you can build and test your entire data pipeline before wiring up any credentials.
The simplest way to get the current predicted funding rate for a symbol is a single GET request to the tickers endpoint with a symbol filter. Here's a minimal working example that fetches and prints the predicted rate for BTCUSDT:
import requests
BASE_URL = "https://api.bybit.com"
def get_predicted_funding_rate(symbol: str) -> dict:
"""Fetch predicted funding rate from Bybit V5 tickers endpoint."""
url = f"{BASE_URL}/v5/market/tickers"
params = {
"category": "linear",
"symbol": symbol
}
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
if data["retCode"] != 0:
raise ValueError(f"Bybit API error {data['retCode']}: {data['retMsg']}")
ticker = data["result"]["list"][0]
return {
"symbol": ticker["symbol"],
"predicted_funding_rate": float(ticker["fundingRate"]),
"next_funding_time_ms": int(ticker["nextFundingTime"]),
"mark_price": float(ticker["markPrice"]),
"index_price": float(ticker["indexPrice"])
}
if __name__ == "__main__":
result = get_predicted_funding_rate("BTCUSDT")
rate_pct = result["predicted_funding_rate"] * 100
print(f"Symbol: {result['symbol']}")
print(f"Predicted Funding Rate: {rate_pct:.4f}%")
print(f"Next Settlement (ms): {result['next_funding_time_ms']}")
The fundingRate field in the response is a decimal — multiply by 100 to get a percentage. A value of 0.0001 means 0.01%, which is the default baseline on most platforms. Positive values mean longs pay shorts; negative values reverse that. The nextFundingTime is a Unix timestamp in milliseconds, which you can convert to a human-readable countdown with a simple datetime subtraction.
This pattern works identically for any linear perpetual on Bybit — ETHUSDT, SOLUSDT, DOGEUSDT. Swap the symbol string and the response structure stays the same. If you want to scan multiple symbols at once, you can call the endpoint without a symbol filter and receive the full list, though the response will be larger and worth paginating.
A single-symbol query is fine for monitoring, but most interesting strategies involve scanning across assets — finding which perpetuals have the most extreme predicted rates right now. Bybit lets you pull all linear tickers in one call and filter client-side. Here's an extended example that scans for outliers and also pulls 10 historical rate records for context:
import requests
from datetime import datetime
BASE_URL = "https://api.bybit.com"
def scan_all_funding_rates(threshold: float = 0.0005) -> list[dict]:
"""Return all symbols where abs(predicted funding rate) >= threshold."""
url = f"{BASE_URL}/v5/market/tickers"
params = {"category": "linear"}
response = requests.get(url, params=params, timeout=15)
response.raise_for_status()
data = response.json()
if data["retCode"] != 0:
raise ValueError(f"API error: {data['retMsg']}")
results = []
for ticker in data["result"]["list"]:
# Some tickers may not have fundingRate (inverse, spot, etc.)
rate_str = ticker.get("fundingRate")
if rate_str is None:
continue
rate = float(rate_str)
if abs(rate) >= threshold:
next_ts = int(ticker["nextFundingTime"]) / 1000
minutes_to_settlement = (next_ts - datetime.now().timestamp()) / 60
results.append({
"symbol": ticker["symbol"],
"rate": rate,
"rate_pct": round(rate * 100, 4),
"minutes_to_settlement": round(minutes_to_settlement, 1),
"direction": "longs_pay" if rate > 0 else "shorts_pay"
})
return sorted(results, key=lambda x: abs(x["rate"]), reverse=True)
def get_funding_history(symbol: str, limit: int = 10) -> list[dict]:
"""Fetch historical funding rate records for a symbol."""
url = f"{BASE_URL}/v5/market/funding/history"
params = {
"category": "linear",
"symbol": symbol,
"limit": limit
}
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
if data["retCode"] != 0:
raise ValueError(f"API error: {data['retMsg']}")
return [
{
"symbol": record["symbol"],
"rate": float(record["fundingRate"]),
"timestamp_ms": int(record["fundingRateTimestamp"])
}
for record in data["result"]["list"]
]
if __name__ == "__main__":
print("=== High Funding Rate Symbols ===")
outliers = scan_all_funding_rates(threshold=0.0003)
for item in outliers[:10]:
print(f"{item['symbol']}: {item['rate_pct']}% ({item['direction']}) "
f"— {item['minutes_to_settlement']}min to settlement")
print("\n=== BTCUSDT Funding History ===")
history = get_funding_history("BTCUSDT", limit=5)
for h in history:
ts = datetime.fromtimestamp(h['timestamp_ms'] / 1000)
print(f"{ts.strftime('%Y-%m-%d %H:%M')} — {h['rate'] * 100:.4f}%")
The scan_all_funding_rates function is particularly useful as an input to a signal layer. Platforms like VoiceOfChain aggregate this kind of cross-market data and surface extreme funding rate divergences as actionable signals — but knowing how to build it yourself means you can customize thresholds, combine with open interest data, and feed results directly into your own execution pipeline.
Reading funding rates is public. Checking whether your open positions are exposed to an upcoming settlement — or placing an order in response — requires authenticated requests. Bybit V5 uses HMAC-SHA256 signatures. Here's a minimal authenticated client that checks your current positions and calculates upcoming funding cost:
import hashlib
import hmac
import time
import requests
import os
API_KEY = os.getenv("BYBIT_API_KEY")
API_SECRET = os.getenv("BYBIT_API_SECRET")
BASE_URL = "https://api.bybit.com"
def bybit_signed_get(path: str, params: dict) -> dict:
"""Make an authenticated GET request to Bybit V5 API."""
timestamp = str(int(time.time() * 1000))
recv_window = "5000"
# Build query string for signing
query_string = "&".join(f"{k}={v}" for k, v in sorted(params.items()))
sign_payload = f"{timestamp}{API_KEY}{recv_window}{query_string}"
signature = hmac.new(
API_SECRET.encode("utf-8"),
sign_payload.encode("utf-8"),
hashlib.sha256
).hexdigest()
headers = {
"X-BAPI-API-KEY": API_KEY,
"X-BAPI-SIGN": signature,
"X-BAPI-TIMESTAMP": timestamp,
"X-BAPI-RECV-WINDOW": recv_window
}
url = f"{BASE_URL}{path}"
response = requests.get(url, params=params, headers=headers, timeout=10)
response.raise_for_status()
return response.json()
def get_open_positions_with_funding_cost():
"""List open linear positions with estimated funding cost at next settlement."""
pos_data = bybit_signed_get("/v5/position/list", {
"category": "linear",
"settleCoin": "USDT"
})
if pos_data["retCode"] != 0:
raise ValueError(f"Error fetching positions: {pos_data['retMsg']}")
results = []
for pos in pos_data["result"]["list"]:
if float(pos["size"]) == 0:
continue
# Fetch predicted funding rate for this symbol
ticker_resp = requests.get(
f"{BASE_URL}/v5/market/tickers",
params={"category": "linear", "symbol": pos["symbol"]},
timeout=5
).json()
ticker = ticker_resp["result"]["list"][0]
predicted_rate = float(ticker["fundingRate"])
position_value = float(pos["positionValue"])
side_multiplier = 1 if pos["side"] == "Buy" else -1
estimated_pnl = position_value * predicted_rate * side_multiplier * -1
results.append({
"symbol": pos["symbol"],
"side": pos["side"],
"size": pos["size"],
"predicted_rate_pct": round(predicted_rate * 100, 4),
"estimated_funding_pnl": round(estimated_pnl, 4)
})
return results
if __name__ == "__main__":
positions = get_open_positions_with_funding_cost()
for p in positions:
sign = "+" if p["estimated_funding_pnl"] >= 0 else ""
print(f"{p['symbol']} {p['side']} {p['size']} | "
f"Rate: {p['predicted_rate_pct']}% | "
f"Est. PnL: {sign}{p['estimated_funding_pnl']} USDT")
Store your BYBIT_API_KEY and BYBIT_API_SECRET in environment variables — never hardcode credentials in scripts. Use read-only API keys when you only need to read position data; only enable trading permissions when the bot actually needs to place orders.
The estimated funding PnL calculation here is approximate. The actual settlement uses the mark price at settlement time, not the current mark price. For monitoring purposes it's accurate enough; for exact accounting, pull the realized funding from your trade history after settlement fires. Comparing Bybit's predicted rates against what you'd see on OKX or Bitget is also useful — when rates diverge significantly between platforms, that gap itself can be a signal.
Production-grade funding rate monitoring needs to handle network failures, API rate limits, and stale data gracefully. Bybit's public endpoints have a rate limit of roughly 120 requests per minute per IP for market data. If you're scanning all symbols every few seconds you'll hit that ceiling. The right architecture is an in-process cache with a TTL aligned to how fast the predicted rate realistically moves.
import time
import requests
import threading
from functools import wraps
BASE_URL = "https://api.bybit.com"
# Thread-safe cache with TTL
_cache: dict = {}
_cache_lock = threading.Lock()
def cached(ttl_seconds: int):
"""Decorator: cache function result by args for ttl_seconds."""
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
key = (fn.__name__, args, tuple(sorted(kwargs.items())))
with _cache_lock:
entry = _cache.get(key)
if entry and time.monotonic() - entry["ts"] < ttl_seconds:
return entry["value"]
result = fn(*args, **kwargs)
with _cache_lock:
_cache[key] = {"value": result, "ts": time.monotonic()}
return result
return wrapper
return decorator
def bybit_get_with_retry(url: str, params: dict, retries: int = 3) -> dict:
"""GET with exponential backoff on 429 or transient errors."""
for attempt in range(retries):
try:
response = requests.get(url, params=params, timeout=8)
if response.status_code == 429:
wait = 2 ** attempt
print(f"Rate limited — waiting {wait}s")
time.sleep(wait)
continue
response.raise_for_status()
data = response.json()
if data["retCode"] not in (0, 10001): # 10001 = not modified
raise ValueError(f"Bybit error {data['retCode']}: {data['retMsg']}")
return data
except requests.exceptions.ConnectionError as e:
if attempt == retries - 1:
raise
time.sleep(1.5 ** attempt)
raise RuntimeError(f"Failed after {retries} attempts: {url}")
@cached(ttl_seconds=30)
def get_funding_rate_cached(symbol: str) -> float:
"""Return predicted funding rate, cached for 30 seconds."""
url = f"{BASE_URL}/v5/market/tickers"
data = bybit_get_with_retry(url, {"category": "linear", "symbol": symbol})
return float(data["result"]["list"][0]["fundingRate"])
# Usage
rate = get_funding_rate_cached("BTCUSDT")
print(f"BTCUSDT predicted rate: {rate * 100:.4f}%")
For a high-frequency monitoring loop — say, watching 20 symbols every 15 seconds — a 30-second cache per symbol means you're making around 40 unique requests per minute, well under the rate limit. If you need fresher data, consider Bybit's WebSocket stream for funding rate updates, which pushes data rather than requiring polling. The REST API is fine for strategy triggers; WebSocket is better for latency-sensitive execution. When comparing approaches across platforms, Binance's linear API follows a nearly identical pattern — making it straightforward to build cross-exchange monitoring that covers both.
Bybit's predicted funding rate API is one of the more underused edges available to algo traders. The data is public, well-documented, and consistently structured — meaning you can go from zero to a working scanner in under 50 lines of Python. The real leverage comes from what you do with the data: building scanners that surface extreme rates across assets, wiring up position-aware alerts that warn you before a costly settlement, or feeding the signal into VoiceOfChain alongside order flow for richer context. Exchanges like Binance, OKX, and Bitget expose similar endpoints, so the architecture you build here scales to cross-platform arbitrage monitoring with minimal additional work. Start with the public endpoints, prove the signal is useful, then add authentication when you're ready to act on it.