Crypto Bots

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.

A
AI Agents Hubยท2026-03-13ยท5 min readยท866 words

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:

  1. Exchange insolvency (FTX taught this lesson)
  2. Basis risk: spot and perp prices can briefly diverge
  3. Margin calls if perp price moves violently before funding
  4. 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.

Related Articles