How to Build a Multi-Exchange Crypto Trading Bot
A multi-exchange trading bot monitors prices across Binance, Coinbase, Kraken, and OKX simultaneously and exploits price differences. Learn how to build one using CCXT's unified API.
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.
Why Multi-Exchange Bots Are More Powerful
A single-exchange bot is limited to the liquidity and opportunities on one platform. A multi-exchange bot:
- Finds arbitrage: Same asset, different prices across exchanges
- Routes to best price: Get the best execution by comparing quotes
- Reduces dependency: If one exchange has downtime, others continue
- Manages risk: Spread capital across multiple custodians
The CCXT Unified Interface
CCXT (CryptoCurrency eXchange Trading Library) is the backbone of multi-exchange bots โ it provides one API for 100+ exchanges:
import ccxt
import asyncio
from typing import Optional
# Initialize multiple exchanges with the same interface
exchanges = {
'binance': ccxt.binance({
'apiKey': BINANCE_KEY,
'secret': BINANCE_SECRET,
'enableRateLimit': True,
}),
'coinbase': ccxt.coinbaseadvanced({
'apiKey': COINBASE_KEY,
'secret': COINBASE_SECRET,
'enableRateLimit': True,
}),
'kraken': ccxt.kraken({
'apiKey': KRAKEN_KEY,
'secret': KRAKEN_SECRET,
'enableRateLimit': True,
}),
'okx': ccxt.okx({
'apiKey': OKX_KEY,
'secret': OKX_SECRET,
'passphrase': OKX_PASSPHRASE,
'enableRateLimit': True,
}),
}
# Same function works for ALL exchanges
async def get_ticker(exchange_id: str, symbol: str) -> Optional[dict]:
try:
exchange = exchanges[exchange_id]
ticker = exchange.fetch_ticker(symbol)
return {
'exchange': exchange_id,
'symbol': symbol,
'bid': ticker['bid'],
'ask': ticker['ask'],
'last': ticker['last'],
'volume': ticker['baseVolume'],
}
except Exception as e:
print(f"Error fetching {exchange_id} {symbol}: {e}")
return None
Fetching All Prices in Parallel
async def get_all_prices(symbol: str) -> dict:
"""Fetch price from all exchanges simultaneously"""
tasks = [
get_ticker(ex_id, symbol)
for ex_id in exchanges.keys()
]
results = await asyncio.gather(*tasks, return_exceptions=True)
prices = {}
for result in results:
if result and isinstance(result, dict):
prices[result['exchange']] = result
return prices
async def find_best_price(symbol: str, side: str = 'buy') -> dict:
"""Find the best price to buy or sell across all exchanges"""
prices = await get_all_prices(symbol)
if side == 'buy':
# Best buy = lowest ask price
best = min(prices.values(), key=lambda x: x['ask'])
else:
# Best sell = highest bid price
best = max(prices.values(), key=lambda x: x['bid'])
return best
# Example usage
loop = asyncio.get_event_loop()
all_prices = loop.run_until_complete(get_all_prices('BTC/USDT'))
print("BTC/USDT prices across exchanges:")
for ex, data in sorted(all_prices.items(), key=lambda x: x[1]['ask']):
print(f" {ex:12}: bid ${data['bid']:,.2f} | ask ${data['ask']:,.2f}")
Cross-Exchange Arbitrage Engine
MIN_PROFIT_PCT = 0.003 # 0.3% minimum after fees
TRADE_AMOUNT_USD = 500 # Amount to trade per arb opportunity
class CrossExchangeArbBot:
def __init__(self, symbols: list[str]):
self.symbols = symbols
self.trade_log = []
def estimate_fees(self, exchange_id: str, amount_usd: float) -> float:
"""Estimate trading fee for an exchange"""
fee_rates = {
'binance': 0.001, # 0.1% maker/taker
'coinbase': 0.005, # 0.5% taker (Advanced lower)
'kraken': 0.0026, # 0.26% taker
'okx': 0.001, # 0.1%
}
return amount_usd * fee_rates.get(exchange_id, 0.002)
def check_arb_opportunity(self, prices: dict, symbol: str) -> Optional[dict]:
"""Find the best arbitrage opportunity"""
best_bid_ex = max(prices.items(), key=lambda x: x[1]['bid'])
best_ask_ex = min(prices.items(), key=lambda x: x[1]['ask'])
buy_exchange = best_ask_ex[0]
sell_exchange = best_bid_ex[0]
if buy_exchange == sell_exchange:
return None # Same exchange, no arb
buy_price = best_ask_ex[1]['ask']
sell_price = best_bid_ex[1]['bid']
# Calculate gross profit
gross_profit = (sell_price - buy_price) / buy_price
# Calculate fees
buy_fee = self.estimate_fees(buy_exchange, TRADE_AMOUNT_USD)
sell_fee = self.estimate_fees(sell_exchange, TRADE_AMOUNT_USD)
total_fees = buy_fee + sell_fee
# Net profit
net_profit_usd = TRADE_AMOUNT_USD * gross_profit - total_fees
net_profit_pct = net_profit_usd / TRADE_AMOUNT_USD
if net_profit_pct >= MIN_PROFIT_PCT:
return {
'symbol': symbol,
'buy_exchange': buy_exchange,
'sell_exchange': sell_exchange,
'buy_price': buy_price,
'sell_price': sell_price,
'gross_spread_pct': gross_profit * 100,
'estimated_fees_usd': total_fees,
'net_profit_usd': net_profit_usd,
'net_profit_pct': net_profit_pct * 100,
}
return None
async def execute_arb(self, opportunity: dict):
"""Execute an arbitrage trade"""
sym = opportunity['symbol']
buy_ex = exchanges[opportunity['buy_exchange']]
sell_ex = exchanges[opportunity['sell_exchange']]
price = opportunity['buy_price']
qty = TRADE_AMOUNT_USD / price
print(f"\n๐ฐ ARB DETECTED: {sym}")
print(f" Buy on {opportunity['buy_exchange']} @ ${opportunity['buy_price']:,.2f}")
print(f" Sell on {opportunity['sell_exchange']} @ ${opportunity['sell_price']:,.2f}")
print(f" Net profit: ${opportunity['net_profit_usd']:.2f} ({opportunity['net_profit_pct']:.3f}%)")
# Execute both sides simultaneously
buy_task = asyncio.create_task(
asyncio.to_thread(
buy_ex.create_market_buy_order, sym, qty
)
)
sell_task = asyncio.create_task(
asyncio.to_thread(
sell_ex.create_market_sell_order, sym, qty
)
)
buy_result, sell_result = await asyncio.gather(buy_task, sell_task)
print(f"โ
Arb executed: buy #{buy_result['id']} | sell #{sell_result['id']}")
async def run(self):
"""Main arbitrage loop"""
print("๐ค Multi-exchange arb bot started...")
while True:
for symbol in self.symbols:
prices = await get_all_prices(symbol)
opportunity = self.check_arb_opportunity(prices, symbol)
if opportunity:
await self.execute_arb(opportunity)
await asyncio.sleep(5) # Scan every 5 seconds
# Run bot
bot = CrossExchangeArbBot(['BTC/USDT', 'ETH/USDT', 'SOL/USDT'])
asyncio.run(bot.run())
Handling Balance Across Exchanges
The biggest operational challenge: keeping enough capital on each exchange:
class BalanceManager:
def __init__(self, min_balance_usd: float = 200):
self.min_balance = min_balance_usd
async def check_all_balances(self) -> dict:
"""Check USDT balance on all exchanges"""
balances = {}
for ex_id, exchange in exchanges.items():
try:
bal = exchange.fetch_balance()
balances[ex_id] = float(bal.get('USDT', {}).get('free', 0))
except Exception as e:
balances[ex_id] = 0
print(f"Balance check failed for {ex_id}: {e}")
return balances
def flag_low_balances(self, balances: dict) -> list[str]:
"""Return list of exchanges with low balances"""
return [ex for ex, bal in balances.items() if bal < self.min_balance]
Important: Transfer Times Matter
The biggest risk in cross-exchange arb is transfer time. If BTC is cheap on Kraken:
- You buy BTC on Kraken โ
- You need to sell BTC on Binance โ
- Transferring BTC from Kraken to Binance takes 20-60 minutes โ
Solution: Keep pre-funded balances on both sides. If you have BTC already on Binance and USDT on Kraken, you can execute both legs simultaneously without any transfer.
This means your bot needs to actively manage float across exchanges โ a significant operational overhead that most beginners underestimate.
Multi-exchange bots are more complex but more profitable. Master single-exchange strategies first, then scale to multi-exchange when you understand the operational requirements.
Tagged in
Related Articles
How to Write a Python Script That Trades Crypto Automatically
7 min read
Crypto BotsGrid Trading Bot: Complete Setup Guide for Binance
6 min read
Crypto BotsPython for Crypto: The Complete Developer's Toolkit (2025)
5 min read
Crypto BotsCrypto Bot Risk Management: The 10 Rules That Separate Winners From Losers
7 min read