Triangular Arbitrage Algorithm Development
Triangular arbitrage is cyclical trading of three currency pairs within one exchange for profit from price mismatches. Doesn't require transfers between exchanges — everything happens on one platform. Key parameter: speed. Opportunities last milliseconds.
Triangular Arbitrage Principle
Cycle from three pairs: A → B → C → A
Example:
- BTC/USDT: 45,000 (1 BTC = 45,000 USDT)
- ETH/USDT: 3,000 (1 ETH = 3,000 USDT)
- ETH/BTC: 0.0668 (1 ETH = 0.0668 BTC)
Theoretically: 1 ETH should cost 3000/45000 = 0.0667 BTC. Actually: 1 ETH = 0.0668 BTC. Mismatch = 0.01 BTC (~$45).
Trading cycle:
- Sell 45,000 USDT → buy 15 ETH (at ETH/USDT 3000)
- Sell 15 ETH → get 1.002 BTC (at ETH/BTC 0.0668)
- Sell 1.002 BTC → get 45,090 USDT (at BTC/USDT 45,000)
Profit: 90 USDT − fees. If 3 × taker fee 0.04% = 0.12% ≈ 54 USDT → net profit ~$36.
Cycle Profitability Formula
def calculate_cycle_profit(pair1_rate, pair2_rate, pair3_rate, fee=0.001):
"""
Check cycle: USDT → BTC → ETH → USDT
"""
# Start with 1 USDT
after_trade1 = (1 / pair1_rate) * (1 - fee) # USDT → BTC
after_trade2 = (after_trade1 / pair2_rate) * (1 - fee) # BTC → ETH
after_trade3 = after_trade2 * pair3_rate * (1 - fee) # ETH → USDT
profit = after_trade3 - 1 # > 0 = profitable
return profit
Finding Profitable Cycles
Graph approach: build graph of currencies where edges are trading pairs with weights (log exchange rates). Find negative cycles with Bellman-Ford algorithm.
import networkx as nx
import math
def find_arbitrage_cycles(tickers):
G = nx.DiGraph()
for symbol, ticker in tickers.items():
base, quote = symbol.split('/')
bid = ticker['bid']
ask = ticker['ask']
if bid > 0:
# base → quote: sell base, get quote
G.add_edge(base, quote, weight=-math.log(bid))
if ask > 0:
# quote → base: buy base, pay quote
G.add_edge(quote, base, weight=-math.log(1/ask))
# Find negative cycles (profitable arbitrages)
try:
cycle = nx.find_negative_cycle(G, source='USDT')
return cycle
except nx.NetworkXError:
return None
Execution Speed is Critical
Triangular arbitrage is highly competitive niche. Opportunities last 100–500ms. Optimizations:
Pre-computed paths: don't compute cycles from scratch on each update. Pre-define all possible triplets, real-time just check profitability.
Selective monitoring: don't monitor all pairs (Binance has ~2000). Select top-50 by volume.
Order preparation: all order parameters (symbol, quantity, price) computed in advance, send on trigger.
WebSocket for all pairs: REST polling too slow. Binance supports stream multiple pairs: wss://stream.binance.com/stream?streams=btcusdt@bookTicker/ethusdt@bookTicker/ethbtc@bookTicker
Optimal Trade Size Calculation
def optimal_trade_size(step1_depth, step2_depth, step3_depth, max_slippage=0.001):
"""
Maximum volume where slippage doesn't eat profit
"""
# Per step: how much volume can take within max_slippage
size1 = get_available_liquidity(step1_depth, max_slippage)
size2 = get_available_liquidity(step2_depth, max_slippage)
size3 = get_available_liquidity(step3_depth, max_slippage)
# Minimum of three — our constraint
return min(size1, size2, size3)
Risks
Partial fill: one of three orders filled partially. Open position arises. Need handler: immediately close remainder at market.
Stale data: price data > 200ms old — skip opportunity.
API rate limits: three simultaneous orders consume three API requests. On hundreds signals per minute can hit limits.
Front-running: market makers see pattern, close arb spread faster than us.
Realistic Expectations
On top pairs (BTC/ETH/BNB), triangular arbitrage extremely competitive — professional HFT bots close spreads in milliseconds. Opportunities more common on less liquid altcoin pairs, but higher slippage and lower volume there.
Develop triangular arb system with graph algorithm for cycle finding, real-time WebSocket monitoring, parallel execution of three orders, and partial fill handler.







