Funding Rate Arbitrage Bot: Earn 20-50% APY From Crypto Perps
Funding rate arbitrage is the cleanest risk-neutral yield strategy in crypto. Buy spot, short perps, earn funding rate payments. Learn to build a bot that captures 20-50% APY with near-zero directional risk.
Builder of AI agents, crypto trading bots, and open-source automation tools. Sharing practical guides on how to build, deploy, and profit from AI and DeFi technology.
What Is Funding Rate Arbitrage?
Perpetual futures contracts on crypto exchanges charge funding rates every 8 hours. When the market is bullish:
- Long positions pay funding to short positions
- Typical rate: 0.01-0.10% every 8 hours (3-110% annualized)
The arbitrage: Buy spot + Short the same size on perps. You have no price exposure (spot gain = perp loss), but you collect funding payments from longs.
The Math
def calculate_funding_arb_returns(
funding_rate_pct_per_8h: float,
capital_usd: float,
days: int = 365
) -> dict:
"""Calculate expected returns from funding rate arbitrage"""
# Funding periods per day
periods_per_day = 3 # Every 8 hours
# Capital split: 50% spot, 50% as margin for short
spot_capital = capital_usd * 0.5
margin_capital = capital_usd * 0.5
# Funding income per period (on the notional position = spot_capital)
funding_per_period = spot_capital * (funding_rate_pct_per_8h / 100)
# Total annual funding income
annual_funding = funding_per_period * periods_per_day * days
# Costs
# Assuming 0.05% round-trip entry + exit costs
entry_exit_cost = capital_usd * 0.001 # 0.1% one-time
# Borrowing cost if using exchange margin (usually 0)
borrow_cost = 0
net_annual_return = annual_funding - entry_exit_cost
apy = (net_annual_return / capital_usd) * 100
return {
'funding_rate_8h': funding_rate_pct_per_8h,
'annualized_rate': funding_rate_pct_per_8h * periods_per_day * 365,
'daily_income': funding_per_period * periods_per_day,
'monthly_income': funding_per_period * periods_per_day * 30,
'annual_income': annual_funding,
'net_apy': apy,
}
# Example: BTC funding at 0.02% per 8h (common in bull markets)
returns = calculate_funding_arb_returns(0.02, 10000)
print(f"Daily income: ${returns['daily_income']:.2f}")
print(f"Monthly income: ${returns['monthly_income']:.2f}")
print(f"APY: {returns['net_apy']:.1f}%")
# Output:
# Daily income: $30.00
# Monthly income: $900.00
# APY: 21.9%
Building the Funding Rate Monitor
import ccxt
import asyncio
from datetime import datetime
# Exchanges that report funding rates
EXCHANGES = {
'binance': ccxt.binance({'apiKey': BINANCE_KEY, 'secret': BINANCE_SECRET}),
'bybit': ccxt.bybit({'apiKey': BYBIT_KEY, 'secret': BYBIT_SECRET}),
'okx': ccxt.okx({'apiKey': OKX_KEY, 'secret': OKX_SECRET, 'passphrase': OKX_PASS}),
}
def get_funding_rates(exchange_id: str, symbols: list[str]) -> dict:
"""Get current funding rates for given symbols"""
exchange = EXCHANGES[exchange_id]
rates = {}
for symbol in symbols:
try:
# Convert spot symbol to perp symbol
perp_symbol = symbol.replace('/', '') + ':USDT' # e.g. BTC/USDT โ BTCUSDT:USDT
funding_info = exchange.fetch_funding_rate(perp_symbol)
rates[symbol] = {
'funding_rate': funding_info['fundingRate'],
'next_funding_time': funding_info['nextFundingDatetime'],
'exchange': exchange_id,
'annualized': funding_info['fundingRate'] * 3 * 365 * 100, # 3 times/day
}
except Exception as e:
pass
return rates
def find_best_funding_opportunities(symbols: list[str], min_apy: float = 20) -> list:
"""Find all funding rate opportunities above minimum APY"""
all_rates = {}
for ex_id in EXCHANGES.keys():
ex_rates = get_funding_rates(ex_id, symbols)
for symbol, data in ex_rates.items():
key = f"{ex_id}:{symbol}"
all_rates[key] = data
# Filter high funding rates
opportunities = [
{**data, 'symbol': key.split(':')[1], 'exchange': key.split(':')[0]}
for key, data in all_rates.items()
if data['annualized'] > min_apy
]
# Sort by APY
opportunities.sort(key=lambda x: x['annualized'], reverse=True)
return opportunities
# Example output
symbols = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'BNB/USDT']
opportunities = find_best_funding_opportunities(symbols, min_apy=15)
print("๐ High Funding Rate Opportunities:")
for opp in opportunities[:5]:
print(f" {opp['exchange']:8} {opp['symbol']}: {opp['annualized']:6.1f}% APY | Rate: {opp['funding_rate']*100:.4f}%/8h")
Opening the Arb Position
class FundingArbBot:
def __init__(self, capital_usd: float = 10000):
self.capital = capital_usd
self.positions = {} # Track open arb positions
def open_arb_position(self, exchange_id: str, symbol: str) -> bool:
"""Open delta-neutral arb: buy spot + short perp"""
exchange = EXCHANGES[exchange_id]
base = symbol.split('/')[0] # BTC
# Amount: use 40% of capital for this position
position_capital = self.capital * 0.40
# Get current price
ticker = exchange.fetch_ticker(symbol)
price = ticker['last']
qty = position_capital / price / 2 # Split between spot and perp
print(f"\n๐ Opening arb on {exchange_id}: {symbol}")
print(f" Capital: ${position_capital:,.2f} | Price: ${price:,.2f} | Qty: {qty:.4f}")
# 1. Buy spot
spot_order = exchange.create_market_buy_order(
symbol, qty,
params={'quoteOrderQty': position_capital / 2}
)
# 2. Open short perp
perp_symbol = symbol.replace('/', '') + ':USDT'
perp_order = exchange.create_market_sell_order(
perp_symbol, qty,
params={'marginMode': 'cross', 'leverage': 2}
)
self.positions[f"{exchange_id}:{symbol}"] = {
'exchange': exchange_id,
'symbol': symbol,
'spot_qty': qty,
'perp_qty': qty,
'entry_price': price,
'opened_at': datetime.now().isoformat(),
}
print(f"โ
Position opened. Collecting funding every 8h.")
return True
def collect_funding_summary(self) -> float:
"""Calculate total funding collected across all positions"""
total_funding = 0
for key, pos in self.positions.items():
exchange = EXCHANGES[pos['exchange']]
history = exchange.fetch_funding_rate_history(
pos['symbol'].replace('/', '') + ':USDT',
since=int(datetime.fromisoformat(pos['opened_at']).timestamp() * 1000)
)
position_funding = sum(
rate['fundingRate'] * pos['perp_qty'] * self.get_current_price(pos)
for rate in history
)
total_funding += position_funding
return total_funding
def should_close_position(self, exchange_id: str, symbol: str) -> bool:
"""Check if we should close due to negative funding"""
rates = get_funding_rates(exchange_id, [symbol])
funding = rates[symbol]['annualized']
# Close if funding goes negative (longs collecting from shorts = bad)
if funding < -5: # More than -5% APY
print(f"โ ๏ธ Funding turned negative ({funding:.1f}% APY) โ closing position")
return True
return False
Real-World Performance Expectations
Bull markets (funding rate 0.05-0.15%/8h):
- APY: 55-164%
- Risk: Near-zero directional, but exchange counterparty risk
Normal markets (funding 0.01-0.03%/8h):
- APY: 11-33%
Bear markets (negative funding):
- Strategy reverses: Short spot, long perps
- Longs collect from shorts in bearish conditions
Key risks:
- Exchange insolvency (FTX taught this lesson)
- Basis risk: spot and perp prices can briefly diverge
- Margin calls if perp price moves violently before funding
- Opportunity cost of capital tied up as margin
Funding rate arbitrage is the closest thing crypto has to "free money" โ but always diversify across exchanges and never risk more than you can afford to lose to exchange risk.
Tagged in
Related Articles
Crypto Bot Risk Management: The 10 Rules That Separate Winners From Losers
7 min read
Crypto BotsHow to Build a Self-Healing Trading Bot That Fixes Its Own Errors
5 min read
Crypto BotsPump.fun and Solana Meme Coin Bots: How to Automate the Hottest Trend
5 min read
Crypto BotsHow to Build a Crypto Portfolio Auto-Rebalancing Bot
5 min read