Triangular Arbitrage Python Tutorial for Crypto Traders
Build a working triangular arbitrage scanner in Python. Learn to detect price loops across BTC, ETH, and USDT pairs on Binance, OKX, and Bybit with real code.
Build a working triangular arbitrage scanner in Python. Learn to detect price loops across BTC, ETH, and USDT pairs on Binance, OKX, and Bybit with real code.
Triangular arbitrage is one of those strategies that sounds like magic when you first hear it — make a profit by trading three pairs in a loop, ending up with more than you started with, all on the same exchange. No price movement needed, no directional bet. Just exploiting the fact that exchange rate math occasionally doesn't add up perfectly. The catch? These windows last milliseconds, fees can wipe the gain entirely, and most retail traders have no idea how to even check for them. This tutorial builds a working Python scanner from scratch and shows you exactly what to look for — and what to be realistic about.
Imagine you're at an airport with $1,000 in cash. You exchange dollars for euros, euros for British pounds, and pounds back to dollars. If the exchange rates are slightly misaligned, you might end up with $1,008. That's triangular arbitrage — exploiting a pricing inconsistency across three currency pairs in a loop. In crypto, the same logic applies. You start with one asset, trade through two intermediate pairs, and land back on the original asset with more than you started.
On Binance, for example, you might execute this loop: sell BTC for USDT, use that USDT to buy ETH, then sell ETH back to BTC. If the implied BTC price across those two hops is lower than the direct BTC/USDT price, you've found a discrepancy. The key word is implied — you're comparing what the market says BTC is worth directly versus what it's worth when you route through ETH.
Key Takeaway: Triangular arbitrage is market-neutral — you're not betting on price direction. You're exploiting a temporary mathematical inconsistency between three trading pairs on the same exchange.
The profit ratio for a triangle is calculated by multiplying the effective exchange rates across all three legs. Start with 1 unit of your base asset. After leg 1 you hold some amount of the quote asset. After leg 2 you hold some amount of the bridge asset. After leg 3 you're back to the base asset. If the final number is greater than 1, there's a gross profit — before fees.
| Step | Action | Price | Balance After |
|---|---|---|---|
| Start | Hold BTC | — | 1.00000 BTC |
| Leg 1 | Sell BTC/USDT | bid = 65,000 | 65,000 USDT |
| Leg 2 | Buy ETH/USDT | ask = 3,250 | 20.0000 ETH |
| Leg 3 | Sell ETH/BTC | bid = 0.050025 | 1.00050 BTC |
| Result | Gross profit | — | +0.0005 BTC (0.05%) |
That 0.05% looks small, but on $100,000 that's $50 per cycle — theoretically. The problem is that three Binance trades at the standard 0.1% taker fee each costs you 0.3% total. So 0.05% gross turns into -0.25% net. This is why you almost never see clean triangular arbitrage opportunities that survive fees in practice. When they do appear, they last for fractions of a second, and high-frequency trading bots on the exchange's co-located servers get there first.
Warning: Most triangular arbitrage opportunities you'll detect with a scanner are ghost opportunities — by the time your order hits the matching engine, prices have already corrected. Always account for latency and fees before going live.
The industry-standard library for connecting to crypto exchange APIs from Python is ccxt (CryptoCurrency eXchange Trading Library). It supports over 100 exchanges including Binance, OKX, Bybit, KuCoin, and Gate.io with a unified interface — meaning the same code works across all of them with minimal changes. Install the dependencies:
pip install ccxt python-dotenv
Create a .env file in your project root to store your API credentials securely. Never hardcode keys in your source files.
# .env
BINANCE_API_KEY=your_api_key_here
BINANCE_SECRET=your_secret_here
import ccxt
import os
from dotenv import load_dotenv
load_dotenv()
# Initialize Binance connection
exchange = ccxt.binance({
'apiKey': os.getenv('BINANCE_API_KEY'),
'secret': os.getenv('BINANCE_SECRET'),
'enableRateLimit': True, # respect rate limits automatically
})
# Load all available markets
markets = exchange.load_markets()
print(f"Loaded {len(markets)} markets from Binance")
The core of the scanner is a function that checks whether a given triangle is profitable after fees. We pull live order book data for each pair and calculate the profit ratio in real time. Using order book prices (bid/ask) rather than last-trade prices is critical — it reflects what you'd actually get if you executed right now.
import time
FEE_PCT = 0.001 # 0.1% Binance taker fee per trade
def get_best_price(symbol, side='ask'):
"""Fetch best bid or ask from live order book."""
ob = exchange.fetch_order_book(symbol, limit=5)
if side == 'ask':
return ob['asks'][0][0] # lowest ask (you buy here)
return ob['bids'][0][0] # highest bid (you sell here)
def check_triangle(base, quote, bridge):
"""
Route: base -> quote -> bridge -> base
Example: BTC -> USDT -> ETH -> BTC
"""
pair1 = f"{base}/{quote}" # e.g., BTC/USDT
pair2 = f"{bridge}/{quote}" # e.g., ETH/USDT
pair3 = f"{bridge}/{base}" # e.g., ETH/BTC
# Check all pairs exist on this exchange
if not all(p in exchange.markets for p in [pair1, pair2, pair3]):
return None
try:
p1 = get_best_price(pair1, 'bid') # sell base, get quote
p2 = get_best_price(pair2, 'ask') # buy bridge with quote
p3 = get_best_price(pair3, 'bid') # sell bridge, get base
# Profit ratio (1.0 = break even before fees)
gross_ratio = (p1 / p2) * p3
gross_pct = (gross_ratio - 1) * 100
# Subtract fees for 3 legs
net_pct = gross_pct - (FEE_PCT * 3 * 100)
return {
'route': f"{base}→{quote}→{bridge}→{base}",
'gross_pct': round(gross_pct, 5),
'net_pct': round(net_pct, 5),
'prices': (p1, p2, p3),
}
except Exception as e:
return None
def scan_all_triangles(min_net_pct=0.0):
"""Scan a predefined list of common triangles."""
candidates = [
('BTC', 'USDT', 'ETH'),
('BTC', 'USDT', 'BNB'),
('BTC', 'USDT', 'SOL'),
('ETH', 'USDT', 'BNB'),
('BTC', 'ETH', 'BNB'),
('BTC', 'ETH', 'SOL'),
]
found = []
for triangle in candidates:
result = check_triangle(*triangle)
if result and result['net_pct'] > min_net_pct:
found.append(result)
return found
# Run scanner loop
print("Scanning Binance for triangular arbitrage...")
while True:
opportunities = scan_all_triangles(min_net_pct=0.0)
if opportunities:
for opp in opportunities:
print(f"[FOUND] {opp['route']}")
print(f" Gross: {opp['gross_pct']}% | Net after fees: {opp['net_pct']}%")
else:
print(f"[{time.strftime('%H:%M:%S')}] No net-positive opportunities")
time.sleep(1)
This scanner runs continuously, checking six common triangles every second. In reality you'd want to reduce latency by using WebSocket streams rather than REST calls, and you'd want to cache the market data rather than fetching it fresh each loop. But as a learning tool, this REST-based version makes the logic crystal clear and easy to debug.
Three things will destroy a theoretical profit in live trading: fees, slippage, and latency. You've already accounted for fees in the scanner above, but slippage is trickier. When you place a market order on Bybit or OKX, you're not guaranteed to fill at the best ask — if your order size exceeds the top level of the order book, you'll eat into the next levels at worse prices. For a $10,000 trade, even a 0.05% slippage hit per leg adds up to 0.15% across three legs.
For real-time monitoring of market conditions and spotting when volatility creates larger inefficiencies, tools like VoiceOfChain can complement your scanner. When VoiceOfChain signals a major price move on BTC or ETH, those are exactly the moments when triangular pricing can briefly go out of sync across pairs — a moving price in one pair takes a few milliseconds to propagate to correlated pairs, which is when genuine windows open.
Key Takeaway: On most days, triangular arbitrage on major pairs like BTC/USDT/ETH is essentially zero after fees. The strategy becomes more viable on less-liquid pairs, during volatile market events, or if you have VIP fee tiers. Use the scanner as a market microstructure learning tool first.
Instead of hardcoding six triangles, you can programmatically discover all valid triangles from the exchange's market list. This is especially useful on exchanges like Gate.io and KuCoin which list hundreds of altcoin pairs — the pricing inefficiencies there are larger (though so is the spread risk).
from itertools import permutations
def find_all_triangles(markets_dict):
"""
Auto-discover all valid triangles from exchange market list.
A valid triangle: A/B, C/B, C/A all exist simultaneously.
"""
# Extract unique base and quote currencies
all_symbols = set(markets_dict.keys())
currencies = set()
for sym in all_symbols:
if '/' in sym:
b, q = sym.split('/')
currencies.add(b)
currencies.add(q)
triangles = []
for base, quote, bridge in permutations(currencies, 3):
p1 = f"{base}/{quote}"
p2 = f"{bridge}/{quote}"
p3 = f"{bridge}/{base}"
if p1 in all_symbols and p2 in all_symbols and p3 in all_symbols:
triangles.append((base, quote, bridge))
return triangles
# Usage
all_triangles = find_all_triangles(exchange.markets)
print(f"Found {len(all_triangles)} valid triangles on Binance")
Running this on Binance typically reveals several thousand valid triangles. Most of them involve low-liquidity pairs where the spread alone consumes any apparent opportunity. Focus your live scanning on triangles that include at least one major stablecoin leg (USDT, USDC) and assets with genuine trading volume — the order books are tighter and execution is more predictable.
Triangular arbitrage in Python is one of the best ways to deeply understand crypto market microstructure — even if you never trade it live. Building this scanner forces you to think about bid/ask spreads, order book depth, fee structures, and the speed at which prices propagate across correlated pairs. That knowledge transfers directly to better decision-making in every other trading strategy you run. Start with the basic scanner, watch it output data for a few hours, and pay attention to which triangles come closest to positive most often — that market intelligence alone is worth the time investment.