◈   ⌘ api · Intermediate

Cross-Exchange Arbitrage API: A Practical Trading Guide

Learn to automate cross-exchange crypto arbitrage using REST APIs. Covers Binance, OKX, and Bybit authentication, spread detection, order execution, and risk controls with real Python code.

Uncle Solieditor · voc · 06.05.2026 ·views 17
◈   Contents
  1. → How Cross-Exchange Arbitrage Actually Works
  2. → Exchange API Architecture: What You Are Working With
  3. → Setting Up Authentication and Fetching Prices
  4. → Executing Orders and Handling API Errors
  5. → Risk Controls That Separate Profitable Bots from Expensive Ones
  6. → Frequently Asked Questions
  7. → Conclusion

Price differences between exchanges are a fact of crypto life. Bitcoin might sit at $65,200 on Binance while trading at $65,380 on OKX — a 0.28% spread that, after fees, still leaves profit on the table. The problem is these windows last seconds, not minutes. Catching them manually is impossible. That's where exchange APIs become your edge: they let you monitor prices, detect spreads, and fire orders faster than any human could.

How Cross-Exchange Arbitrage Actually Works

Cross-exchange arbitrage is deceptively simple in theory: buy an asset where it's cheap, sell it where it's expensive, pocket the difference. In practice, three layers of friction eat your profit: trading fees (typically 0.1% taker on Binance and OKX spot markets), transfer latency if you're moving funds between venues, and order slippage on larger sizes.

The cleanest implementation pre-funds both exchanges. You hold USDT on Bybit and BTC on Binance simultaneously. When BTC is cheaper on Bybit, you buy there and sell on Binance — net flat on BTC, net positive in USDT. No withdrawal delays, no blockchain confirmations mid-trade. This is the setup professional arbitrage desks use, and it's what the code below builds toward.

Realistic spreads worth trading are usually 0.15% to 0.5% after fees. Anything below 0.2% gets eroded by fees unless you have maker-level pricing. Platforms like VoiceOfChain track real-time price divergences across venues and can surface when meaningful spreads emerge — useful when you're running multiple strategies and can't watch every pair manually.

Exchange API Architecture: What You Are Working With

Every major exchange offers two API styles. REST APIs are request-response: you ping an endpoint, get a snapshot back. WebSocket APIs push data continuously — price ticks, order book updates, trade fills. For arbitrage, you want WebSocket feeds for price monitoring (low latency, no rate limit concerns) and REST endpoints for order execution.

Authentication on Binance, Bybit, and OKX all use HMAC-SHA256 signing. You generate a key pair in the exchange dashboard, then sign each request with your secret using SHA256. The exchange verifies the signature server-side. One rule that applies everywhere: never hardcode API keys in source files. Use environment variables.

Setting Up Authentication and Fetching Prices

Start with Binance — its API is the most documented and beginner-friendly. The ticker price endpoint is public (no authentication needed), so it's a good place to verify connectivity before wiring up signed requests for order placement.

import hmac
import hashlib
import time
import requests

API_KEY = "your_binance_api_key"
API_SECRET = "your_binance_api_secret"

def create_signature(query_string: str) -> str:
    return hmac.new(
        API_SECRET.encode("utf-8"),
        query_string.encode("utf-8"),
        hashlib.sha256
    ).hexdigest()

def get_price_binance(symbol: str) -> float:
    url = "https://api.binance.com/api/v3/ticker/price"
    resp = requests.get(url, params={"symbol": symbol})
    resp.raise_for_status()
    return float(resp.json()["price"])

def signed_get(endpoint: str, params: dict) -> dict:
    """Signed GET for account/order endpoints."""
    params["timestamp"] = int(time.time() * 1000)
    query = "&".join(f"{k}={v}" for k, v in params.items())
    params["signature"] = create_signature(query)
    headers = {"X-MBX-APIKEY": API_KEY}
    resp = requests.get(
        f"https://api.binance.com{endpoint}",
        params=params,
        headers=headers
    )
    resp.raise_for_status()
    return resp.json()

if __name__ == "__main__":
    price = get_price_binance("BTCUSDT")
    print(f"BTC/USDT on Binance: ${price:,.2f}")
    # Output: BTC/USDT on Binance: $65,432.10

    # Example: fetch open orders (signed endpoint)
    orders = signed_get("/api/v3/openOrders", {"symbol": "BTCUSDT"})
    print(f"Open orders: {len(orders)}")

Now add OKX to the mix. Fetching prices from both exchanges concurrently with asyncio cuts latency roughly in half compared to sequential requests — critical when you're scanning dozens of pairs for spread opportunities.

import asyncio
import aiohttp

async def binance_price(session: aiohttp.ClientSession, symbol: str) -> float:
    url = f"https://api.binance.com/api/v3/ticker/price?symbol={symbol}"
    async with session.get(url) as r:
        data = await r.json()
        return float(data["price"])

async def okx_price(session: aiohttp.ClientSession, symbol: str) -> float:
    # OKX uses dash format: BTC-USDT instead of BTCUSDT
    inst_id = symbol[:-4] + "-" + symbol[-4:]
    url = f"https://www.okx.com/api/v5/market/ticker?instId={inst_id}"
    async with session.get(url) as r:
        data = await r.json()
        return float(data["data"][0]["last"])

async def detect_spread(symbol: str = "BTCUSDT") -> float:
    async with aiohttp.ClientSession() as session:
        binance, okx = await asyncio.gather(
            binance_price(session, symbol),
            okx_price(session, symbol)
        )
    spread_pct = abs(binance - okx) / min(binance, okx) * 100
    cheaper = "OKX" if okx < binance else "Binance"
    pricier = "Binance" if okx < binance else "OKX"
    print(f"Binance: ${binance:,.2f} | OKX: ${okx:,.2f}")
    print(f"Spread: {spread_pct:.4f}% | Buy {cheaper}, Sell {pricier}")
    return spread_pct

if __name__ == "__main__":
    asyncio.run(detect_spread("BTCUSDT"))

Executing Orders and Handling API Errors

Order execution is where most arbitrage bots fail in production. The common mistakes: no timeout on the HTTP request (hangs indefinitely when an exchange is slow), no check on exchange-level error codes (the HTTP response is 200 but the order was actually rejected), and no handling for partial fills. Here's a production-ready order function for Bybit spot that covers all three cases.

import json
import time
import hmac
import hashlib
import requests

BYBIT_API_KEY = "your_bybit_key"
BYBIT_API_SECRET = "your_bybit_secret"
BASE_URL = "https://api.bybit.com"
RECV_WINDOW = "5000"

def bybit_sign(timestamp: str, payload: str) -> str:
    message = f"{timestamp}{BYBIT_API_KEY}{RECV_WINDOW}{payload}"
    return hmac.new(
        BYBIT_API_SECRET.encode("utf-8"),
        message.encode("utf-8"),
        hashlib.sha256
    ).hexdigest()

def place_spot_order(symbol: str, side: str, qty: str) -> dict:
    """
    side: "Buy" or "Sell"
    qty: string quantity, e.g. "0.001"
    """
    endpoint = "/v5/order/create"
    timestamp = str(int(time.time() * 1000))
    body = {
        "category": "spot",
        "symbol": symbol,
        "side": side,
        "orderType": "Market",
        "qty": qty
    }
    payload = json.dumps(body)
    headers = {
        "X-BAPI-API-KEY": BYBIT_API_KEY,
        "X-BAPI-SIGN": bybit_sign(timestamp, payload),
        "X-BAPI-TIMESTAMP": timestamp,
        "X-BAPI-RECV-WINDOW": RECV_WINDOW,
        "Content-Type": "application/json"
    }
    try:
        resp = requests.post(
            BASE_URL + endpoint,
            headers=headers,
            data=payload,
            timeout=3  # never block longer than 3 seconds
        )
        resp.raise_for_status()
        result = resp.json()
        if result["retCode"] != 0:
            raise RuntimeError(f"API error {result['retCode']}: {result['retMsg']}")
        return result["result"]
    except requests.Timeout:
        print("Timeout — price window likely closed, order NOT placed")
        return {}
    except requests.HTTPError as e:
        print(f"HTTP {e.response.status_code} from Bybit")
        return {}
    except RuntimeError as e:
        print(f"Order rejected: {e}")
        return {}

if __name__ == "__main__":
    order = place_spot_order("BTCUSDT", "Buy", "0.001")
    if order:
        print(f"Filled: orderId={order.get('orderId')}")
Always test with minimum sizes (0.001 BTC or $10 USDT) before scaling. One logic bug in your order side selection — buying on both Binance and Bybit simultaneously instead of arbitraging — can cost real money before you catch it.

Risk Controls That Separate Profitable Bots from Expensive Ones

The spread you see in the order book is not the spread you capture. You need to account for taker fees on both legs (0.1% per trade = 0.2% round trip on Binance and OKX at standard tier), slippage on larger orders moving through the book, and API latency between your server and the exchange. Running from a home connection adds 5–15ms of network jitter on top of whatever the exchange reports.

For a spread to be worth trading on Binance and OKX spot with standard 0.1% taker fees, you need at least 0.25% gross spread — that leaves a 0.05% cushion for slippage. If you're a VIP maker on either platform (fees as low as 0.02% on Binance VIP 5+), your breakeven drops to around 0.06%, which opens dramatically more opportunities across altcoin pairs.

VoiceOfChain is useful here as a signal layer — it aggregates real-time market data and can surface unusual spread conditions that suggest a structural arbitrage opportunity rather than just noise. When VoiceOfChain flags a sustained divergence between two venues, that's your cue to check whether the spread clears your fee-adjusted threshold before committing capital.

Frequently Asked Questions

How much capital do I need to start cross-exchange arbitrage?
You need funds pre-deployed on at least two exchanges simultaneously. A practical floor is $500–$1,000 per exchange — enough to trade meaningful sizes without fees consuming all profit. Below $200 total, trading minimums and percentage fees make it very difficult to net positive consistently.
Is cross-exchange arbitrage still profitable in 2025?
Yes, but pure latency arbitrage on major pairs like BTC/USDT between Binance and OKX is extremely competitive and requires co-location near exchange servers. Better opportunities exist on mid-cap altcoins, perpetual vs spot funding divergences, and during high-volatility events when price discovery lags. VoiceOfChain tracks these conditions in real time and can alert you when meaningful spreads emerge.
Do I need to move funds between exchanges constantly?
No — and doing so will destroy your profits with withdrawal fees and blockchain confirmation times. The professional approach is to pre-fund both legs and rebalance periodically (daily or weekly) when imbalance becomes significant. Keep capital deployed on both sides at all times so you can execute immediately in either direction.
What is the difference between REST and WebSocket APIs for arbitrage?
REST APIs work on demand — you ask, they answer with a snapshot. WebSocket feeds push data continuously as prices move. For price monitoring, use WebSocket: it is faster and does not count against your REST rate limits. Binance, Bybit, and OKX all offer WebSocket ticker streams. Reserve REST calls for order placement and account balance queries.
Can my Binance or OKX account get banned for running arbitrage bots?
Not for arbitrage itself — it is normal trading activity. Exchanges suspend accounts that spam unfilled orders (high cancel-to-fill ratio), consistently exceed API rate limits, or show wash trading patterns. Keep fills reasonable, respect rate limits, and you will have no issues. Bybit and Gate.io are particularly bot-friendly compared to Coinbase, which has stricter API policies.
What happens if one leg fills but the other order fails?
This is called a legged position and it is the main operational risk in cross-exchange arbitrage. Build a reconciliation loop that checks balances after each trade cycle. If one leg filled and the other did not, retry the second leg immediately with a wider price limit, or unwind the first leg at a small loss. Never leave a legged position open overnight — the spread can widen against you significantly.

Conclusion

Cross-exchange arbitrage via API is one of the more accessible algorithmic strategies because the logic is clear: find a spread, buy the cheap side, sell the expensive side. The implementation complexity lives in the details — HMAC authentication, concurrent requests, exchange-level error codes, and risk controls that prevent a single bad trade from wiping a session. The code above gives you a working foundation for Binance, OKX, and Bybit. Pair it with real-time market data from VoiceOfChain to surface divergences as they appear, and you have a system built for actual execution rather than just backtesting.

◈   more on this topic
◉ basics Mastering the ccxt library documentation for crypto traders ⌂ exchanges Mastering the Binance CCXT Library for Crypto Traders ⌬ bots Best Crypto Trading Bots 2025: Profitable AI-Powered Strategies