Open Interest Weighted Funding: How Traders Use It
For perp traders who already know funding basics, this guide shows how to weight funding by open interest and turn it into a cleaner crowding signal.
For perp traders who already know funding basics, this guide shows how to weight funding by open interest and turn it into a cleaner crowding signal.
Open interest weighted funding is the funding signal I use when raw perp screens disagree. It weights each venue's funding rate by the size of open perp exposure, so a tiny venue printing 0.30% does not distort the read if Binance BTCUSDT has 15x more open interest.
The searcher here is a trader or builder looking for a tool, technique, or API workflow, not a beginner definition. The goal is to turn funding plus open interest futures data into a practical crowding signal for entries, exits, and risk cuts.
Raw funding tells you the cost of holding longs or shorts on one venue. Open interest weighted funding tells you where the most money is actually positioned across venues.
That difference matters because the highest funding rate is often on the least important book. I care more about +0.04% on Binance or Bybit with billions in BTC open interest than +0.25% on a thin Gate.io alt perp.
| Venue | Funding | OI notional | Signal weight |
|---|---|---|---|
| Binance BTCUSDT | +0.030% | $5.0B | Dominant |
| Bybit BTCUSDT | +0.055% | $2.0B | Important |
| Small venue perp | +0.300% | $80M | Noise unless it spreads |
| Weighted read | +0.040% | $7.08B | Mild long crowding, not panic |
This is why the open interest weighted funding rate is cleaner than sorting by the top funding print. It is also why traders search for coinglass funding rates open interest weighted when they want a fast cross-exchange view instead of hand-rolling venue weights.
VoiceOfChain tracks open interest weighted funding in real time across Binance, Bybit and OKX - you can see live OI-weighted funding, OI change and venue skew without building the API stack yourself. [voiceofchain.com]
The formula is simple: sum(funding rate x open interest notional) divided by total open interest notional. The hard part is normalizing every venue into the same quote currency and the same funding interval.
If your starting point is an open interest Investopedia explainer, add one trader upgrade: contract count is not enough. Convert BTC, ETH, coin-margined contracts, and USDT perps into dollar notional before you weight anything.
import hashlib
import hmac
import os
import time
from decimal import Decimal
from urllib.parse import urlencode
import requests
BINANCE_KEY = os.getenv('BINANCE_API_KEY', '')
BINANCE_SECRET = os.getenv('BINANCE_API_SECRET', '')
BYBIT_KEY = os.getenv('BYBIT_API_KEY', '')
BYBIT_SECRET = os.getenv('BYBIT_API_SECRET', '')
RECV_WINDOW = '5000'
session = requests.Session()
session.headers.update({'User-Agent': 'oi-weighted-funding/1.0'})
if BINANCE_KEY:
session.headers.update({'X-MBX-APIKEY': BINANCE_KEY})
def binance_signed_query(params):
query = urlencode({**params, 'timestamp': int(time.time() * 1000)})
signature = hmac.new(BINANCE_SECRET.encode(), query.encode(), hashlib.sha256).hexdigest()
return f'{query}&signature={signature}'
def bybit_auth_headers(query_string=''):
ts = str(int(time.time() * 1000))
payload = ts + BYBIT_KEY + RECV_WINDOW + query_string
signature = hmac.new(BYBIT_SECRET.encode(), payload.encode(), hashlib.sha256).hexdigest()
return {
'X-BAPI-API-KEY': BYBIT_KEY,
'X-BAPI-TIMESTAMP': ts,
'X-BAPI-RECV-WINDOW': RECV_WINDOW,
'X-BAPI-SIGN': signature
}
def get_json(url, params=None):
try:
r = session.get(url, params=params, timeout=10)
r.raise_for_status()
return r.json()
except requests.RequestException as exc:
raise RuntimeError(f'API request failed: {url} {exc}') from exc
def binance_leg(symbol='BTCUSDT'):
oi = get_json('https://fapi.binance.com/fapi/v1/openInterest', {'symbol': symbol})
premium = get_json('https://fapi.binance.com/fapi/v1/premiumIndex', {'symbol': symbol})
oi_notional = Decimal(oi['openInterest']) * Decimal(premium['markPrice'])
funding = Decimal(premium['lastFundingRate'])
return {'venue': 'Binance', 'funding': funding, 'oi_notional': oi_notional}
def bybit_leg(symbol='BTCUSDT'):
oi = get_json('https://api.bybit.com/v5/market/open-interest', {
'category': 'linear', 'symbol': symbol, 'intervalTime': '5min', 'limit': 1
})
funding = get_json('https://api.bybit.com/v5/market/funding/history', {
'category': 'linear', 'symbol': symbol, 'limit': 1
})
ticker = get_json('https://api.bybit.com/v5/market/tickers', {
'category': 'linear', 'symbol': symbol
})
for body in (oi, funding, ticker):
if body.get('retCode') != 0:
raise RuntimeError(f"Bybit error {body.get('retCode')}: {body.get('retMsg')}")
mark = Decimal(ticker['result']['list'][0]['markPrice'])
oi_notional = Decimal(oi['result']['list'][0]['openInterest']) * mark
rate = Decimal(funding['result']['list'][0]['fundingRate'])
return {'venue': 'Bybit', 'funding': rate, 'oi_notional': oi_notional}
legs = [binance_leg(), bybit_leg()]
total_oi = sum(leg['oi_notional'] for leg in legs)
weighted = sum(leg['funding'] * leg['oi_notional'] for leg in legs) / total_oi
print({
'weighted_funding_8h_pct': float(weighted * Decimal('100')),
'venues': [{
'venue': leg['venue'],
'funding_8h_pct': float(leg['funding'] * Decimal('100')),
'oi_notional_usd': float(leg['oi_notional'])
} for leg in legs]
})
Use direct exchange APIs when you need execution-grade control, timestamps, and venue-specific quirks. Use an aggregator when you want clean historical research or a fast dashboard without maintaining Binance, Bybit, OKX, Bitget, Gate.io and KuCoin adapters.
| Source | Best use | Watchout |
|---|---|---|
| Binance USD-M | Deep BTC/ETH perp OI and funding | Open interest is base quantity, so multiply by mark price |
| Bybit V5 | Linear perp OI plus funding history | Funding interval can differ by symbol |
| OKX public API | SWAP funding and open interest | Instrument IDs use BTC-USDT-SWAP format |
| CoinGlass V4 | Ready-made OI-weighted funding OHLC | Requires CG-API-KEY and plan limits |
For a dashboard shortcut, CoinGlass exposes open interest-weighted funding rate OHLC directly. That endpoint is the API version of the CoinGlass funding rates open interest weighted panel traders watch for BTC and ETH.
import os
from decimal import Decimal
import requests
COINGLASS_KEY = os.environ['COINGLASS_API_KEY']
url = 'https://open-api-v4.coinglass.com/api/futures/funding-rate/oi-weight-history'
headers = {
'accept': 'application/json',
'CG-API-KEY': COINGLASS_KEY
}
params = {
'symbol': 'BTC',
'interval': '8h',
'limit': 20
}
try:
response = requests.get(url, headers=headers, params=params, timeout=10)
response.raise_for_status()
body = response.json()
except requests.RequestException as exc:
raise RuntimeError(f'CoinGlass request failed: {exc}') from exc
if body.get('code') != '0':
raise RuntimeError(f"CoinGlass error {body.get('code')}: {body.get('msg')}")
candles = []
for row in body['data']:
close_rate = Decimal(row['close'])
candles.append({
'time_ms': row['time'],
'oi_weighted_funding_8h_pct': float(close_rate * Decimal('100')),
'raw_close': row['close']
})
hot = [c for c in candles if Decimal(str(c['oi_weighted_funding_8h_pct'])) >= Decimal('0.10')]
print({'latest': candles[-1], 'hot_readings': hot[-3:]})
import base64
import datetime as dt
import hashlib
import hmac
import json
import os
from decimal import Decimal
from urllib.parse import quote
import requests
OKX_KEY = os.getenv('OKX_API_KEY', '')
OKX_SECRET = os.getenv('OKX_API_SECRET', '')
OKX_PASSPHRASE = os.getenv('OKX_API_PASSPHRASE', '')
BASE = 'https://www.okx.com'
def okx_private_headers(method, request_path, body=''):
ts = dt.datetime.utcnow().isoformat(timespec='milliseconds') + 'Z'
msg = f'{ts}{method.upper()}{request_path}{body}'
sign = base64.b64encode(hmac.new(OKX_SECRET.encode(), msg.encode(), hashlib.sha256).digest()).decode()
return {
'OK-ACCESS-KEY': OKX_KEY,
'OK-ACCESS-SIGN': sign,
'OK-ACCESS-TIMESTAMP': ts,
'OK-ACCESS-PASSPHRASE': OKX_PASSPHRASE,
'Content-Type': 'application/json'
}
def okx_public(path):
try:
r = requests.get(BASE + path, timeout=10)
r.raise_for_status()
body = r.json()
except (requests.RequestException, json.JSONDecodeError) as exc:
raise RuntimeError(f'OKX request failed: {path} {exc}') from exc
if body.get('code') != '0':
raise RuntimeError(f"OKX error {body.get('code')}: {body.get('msg')}")
return body['data']
inst = 'BTC-USDT-SWAP'
funding_path = f'/api/v5/public/funding-rate-history?instId={quote(inst)}&limit=1'
oi_path = f'/api/v5/public/open-interest?instType=SWAP&instId={quote(inst)}'
mark_path = f'/api/v5/public/mark-price?instType=SWAP&instId={quote(inst)}'
funding_row = okx_public(funding_path)[0]
oi_row = okx_public(oi_path)[0]
mark_row = okx_public(mark_path)[0]
funding = Decimal(funding_row.get('realizedRate') or funding_row['fundingRate'])
base_oi = Decimal(oi_row.get('oiCcy') or oi_row.get('oi') or '0')
mark = Decimal(mark_row['markPx'])
print({
'venue': 'OKX',
'instId': inst,
'funding_8h_pct': float(funding * Decimal('100')),
'oi_notional_usd_est': float(base_oi * mark),
'funding_time_ms': funding_row['fundingTime']
})
I do not trade OI-weighted funding by itself. I use it as a crowding filter around price structure, spot volume, liquidation levels, and whether the move is spot-led or perp-led.
For BTC and ETH, +0.01% per 8h is normal bull-market background noise. I start paying attention around +0.03% to +0.05% per 8h if open interest rises more than 5% in 4 hours; above +0.10% per 8h, I stop chasing longs unless spot is clearly absorbing.
| OI-weighted funding | OI trend | Trade read |
|---|---|---|
| +0.01% to +0.03% per 8h | Flat | Normal; do not overtrade it |
| +0.03% to +0.05% per 8h | +5% in 4h | Longs are getting crowded |
| > +0.10% per 8h | Rising fast | Avoid late longs; watch for liquidation cascade |
| < -0.05% per 8h | Rising | Shorts are crowded; squeeze risk rises |
I have seen alt perps print +0.30% per 8h before a 15-25% correction, but the better tell was not funding alone. The clean setup was high OI-weighted funding, rising OI, weak spot bid, and price stalling near prior highs.
The common mistake is treating high funding as an automatic short. In a spot-led trend, positive funding can stay expensive for multiple payments while price keeps grinding higher.
Another mistake is mixing units. Binance open interest, Bybit open interest, and OKX swap open interest are not always returned in the same unit, so convert everything before ranking venues.
| Problem | What it looks like | Fix |
|---|---|---|
| Unit mismatch | OKX OI looks too small or huge | Convert to USD notional with mark price |
| Interval mismatch | One venue charges every 4h, another every 8h | Normalize funding to an 8h equivalent |
| Thin-venue distortion | A small perp prints 0.40% | Apply minimum OI filters |
| Spot-led trend | Funding high but spot bid keeps lifting | Do not fade until structure breaks |
| API lag | OI snapshot trails a volatility spike | Check timestamps and retry |
The honest risk caveat: this approach fails when the market is repricing spot aggressively. If Coinbase spot, Binance spot, and ETF-session flow are driving the move, perps can look overheated for a long time before they finally flush.
The key takeaway is simple: funding only matters in proportion to the open interest behind it. Weighting funding by OI turns noisy exchange prints into a cleaner map of where leverage is crowded.
Use it to avoid late entries, size down when the perp crowd is paying too much, and confirm squeeze risk when negative funding stacks up with rising OI. Before you put on risk, compare the weighted rate, OI change, and spot-led or perp-led context on the same screen.