◈   ⌘ api · Advanced

Triangular Arbitrage API Crypto: Build a Bot That Profits

Triangular arbitrage exploits three-currency price loops on a single exchange. Learn how to detect these opportunities with crypto APIs and automate execution using Python code.

Uncle Solieditor · voc · 05.05.2026 ·views 12
◈   Contents
  1. → What Is Triangular Arbitrage in Crypto?
  2. → Setting Up Exchange API Authentication
  3. → Building a Triangular Arbitrage Opportunity Scanner
  4. → Executing the Three Legs Safely with Error Handling
  5. → Fees, Latency, and Honest Profitability Expectations
  6. → Frequently Asked Questions

Triangular arbitrage on a crypto exchange works by exploiting a pricing imbalance across three related trading pairs. Start with USDT, buy BTC, use that BTC to buy ETH, then sell ETH back to USDT — if the math is in your favor, you end up with more USDT than you started. It sounds simple, and conceptually it is. The hard part is finding these opportunities in milliseconds and executing all three legs before the window closes. That is exactly what an API-driven bot does, and building one is more accessible than most traders assume.

What Is Triangular Arbitrage in Crypto?

A triangular arbitrage opportunity arises when three currency pairs on the same exchange form a mispriced cycle. For example, if the BTC/USDT, ETH/BTC, and ETH/USDT rates are slightly out of sync with each other, converting through all three in sequence yields a net profit. This is distinct from cross-exchange arbitrage, which moves funds between platforms and introduces withdrawal delays. Triangular arbitrage stays inside one exchange — Binance, Bybit, OKX — which means execution is fast and you only need one account's API credentials. There is no blockchain transfer latency to worry about.

The math behind the strategy is deterministic. Given three pairs P1, P2, P3, you compute the product of their conversion rates and compare it against 1.0, adjusted for fees. If the result exceeds 1.0, the trade is profitable. In practice, fees on Binance spot run 0.1% per leg, meaning three legs cost 0.3% minimum. The raw spread in the market must exceed that for a trade to clear a profit. On top-tier pairs like BTC/USDT and ETH/BTC, these windows are rare and short-lived — often measured in single-digit milliseconds. On mid-cap pairs available on exchanges like Gate.io or KuCoin, spreads occasionally persist long enough for a well-architected bot to capture them consistently. Understanding where competition is thinner is half the battle.

Setting Up Exchange API Authentication

The fastest path to working API access across multiple exchanges is the ccxt library, which provides a unified Python interface for Binance, Bybit, OKX, KuCoin, Gate.io, and over a hundred more platforms. Install it with pip install ccxt. Generate API keys in your exchange account security settings — enable spot trading permissions and restrict keys to specific IP addresses whenever the exchange allows it. Never hardcode credentials in source files. Load them from environment variables or a secrets manager. The initialization patterns differ slightly by exchange, particularly for OKX which requires a passphrase in addition to key and secret.

import ccxt
import os

# Binance initialization
exchange = ccxt.binance({
    'apiKey': os.environ.get('BINANCE_API_KEY'),
    'secret': os.environ.get('BINANCE_SECRET'),
    'options': {'defaultType': 'spot'}
})

# Bybit — same structure, swap the class:
# exchange = ccxt.bybit({'apiKey': ..., 'secret': ...})

# OKX requires an additional passphrase:
# exchange = ccxt.okx({
#     'apiKey': os.environ.get('OKX_API_KEY'),
#     'secret': os.environ.get('OKX_SECRET'),
#     'password': os.environ.get('OKX_PASSPHRASE')
# })

try:
    balance = exchange.fetch_balance()
    usdt_free = balance['USDT']['free']
    print(f"USDT available for trading: {usdt_free}")
except ccxt.AuthenticationError as e:
    print(f"Auth failed — check your API key and secret: {e}")
except ccxt.NetworkError as e:
    print(f"Network error — verify connectivity: {e}")
except ccxt.ExchangeError as e:
    print(f"Exchange returned an error: {e}")
Security critical: always restrict API keys to specific IP addresses and enable only spot trading — never withdrawal permissions. A leaked key with withdrawal access can drain your account in seconds. Rotate keys regularly and monitor for unexpected activity.

Building a Triangular Arbitrage Opportunity Scanner

The scanner's job is to fetch current prices for all relevant pairs, compute the theoretical profit for each triangle path, and surface the ones that exceed your minimum threshold. Rather than computing every possible combination of pairs — which is slow and mostly noise — predefine a shortlist of viable triangles based on trading volume. Focus on high-liquidity pairs: BTC/USDT, ETH/BTC, ETH/USDT, BNB/BTC, BNB/USDT, and a selection of mid-cap alts your target exchange lists with decent depth. The key efficiency trick is using fetch_tickers() to pull all prices in a single API call rather than querying pairs one by one.

import ccxt

def scan_triangles(exchange, min_profit_pct=0.3):
    """
    Scan predefined triangle paths for net-positive arbitrage opportunities.
    Adjust min_profit_pct based on your actual fee tier.
    """
    tickers = exchange.fetch_tickers()  # single call — much faster than per-pair fetches

    triangles = [
        ('BTC/USDT', 'ETH/BTC', 'ETH/USDT'),
        ('BTC/USDT', 'BNB/BTC', 'BNB/USDT'),
        ('ETH/USDT', 'SOL/ETH', 'SOL/USDT'),
        ('BTC/USDT', 'XRP/BTC', 'XRP/USDT'),
        ('BTC/USDT', 'LTC/BTC', 'LTC/USDT'),
    ]

    fee = 0.001  # 0.1% per leg — standard Binance spot rate
    results = []

    for tri in triangles:
        try:
            p1, p2, p3 = tri
            ask1 = tickers[p1]['ask']  # Buy coin1 with USDT
            ask2 = tickers[p2]['ask']  # Buy coin2 with coin1
            bid3 = tickers[p3]['bid']  # Sell coin2 back to USDT

            after_leg1 = (1.0 / ask1) * (1 - fee)
            after_leg2 = (after_leg1 / ask2) * (1 - fee)
            final_usdt = after_leg2 * bid3 * (1 - fee)

            profit_pct = (final_usdt - 1.0) * 100

            if profit_pct >= min_profit_pct:
                results.append({
                    'path': f"{p1} -> {p2} -> {p3}",
                    'profit_pct': round(profit_pct, 4),
                    'rates': {'ask1': ask1, 'ask2': ask2, 'bid3': bid3}
                })

        except (KeyError, TypeError, ZeroDivisionError):
            continue  # skip pairs not listed or with missing data

    return sorted(results, key=lambda x: x['profit_pct'], reverse=True)


exchange = ccxt.binance({'apiKey': 'YOUR_KEY', 'secret': 'YOUR_SECRET'})
opportunities = scan_triangles(exchange, min_profit_pct=0.25)

for opp in opportunities:
    print(f"{opp['path']}  =>  +{opp['profit_pct']}%")

This scanner works fine for initial testing but has a fundamental production limitation: it polls on demand rather than streaming. By the time you call scan_triangles(), receive the data, compute the math, and send an order, a significant window may have already closed. Professional triangular arb systems subscribe to WebSocket feeds on Binance or Bybit, maintaining an in-memory price table that updates in real time. When a new tick arrives that pushes a triangle above threshold, the execution fires immediately from cached data — no REST round-trip needed. Platforms like VoiceOfChain provide complementary real-time market signal feeds that help contextualize when markets are volatile versus range-bound, which is useful information for deciding whether to pause your scanner during choppy conditions.

Executing the Three Legs Safely with Error Handling

Execution is where theory meets reality and where most bots fail in production. If leg 1 fills and leg 2 is rejected due to a rate limit or insufficient minimum order size, you are now holding an unintended BTC position with no automatic resolution. This partial-fill scenario is the primary operational risk of triangular arbitrage. Your execution code must handle every failure mode with clear logging so you can intervene manually if automation breaks down. Never run untested execution code with real capital — always start with dry_run=True until you have verified that your fill prices match your theoretical model within acceptable slippage.

import ccxt
import logging
import time

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s %(levelname)s %(message)s'
)
logger = logging.getLogger('arb_executor')


def execute_triangle(exchange, path, usdt_amount, dry_run=True):
    """
    Execute a triangular arbitrage across three market orders.
    path: tuple like ('BTC/USDT', 'ETH/BTC', 'ETH/USDT')
    dry_run: always True until production-validated
    """
    p1, p2, p3 = path
    placed = []

    try:
        if dry_run:
            logger.info(f"[DRY RUN] path={path} amount=${usdt_amount} USDT")
            return {'status': 'dry_run', 'path': path}

        # Leg 1: USDT -> coin1
        t1 = exchange.fetch_ticker(p1)
        qty1 = (usdt_amount / t1['ask']) * 0.999  # small buffer for rounding
        order1 = exchange.create_market_buy_order(p1, qty1)
        placed.append(order1)
        logger.info(f"Leg1 filled: {order1['filled']} {p1.split('/')[0]} @ avg {order1['average']}")
        time.sleep(0.05)

        # Leg 2: coin1 -> coin2
        t2 = exchange.fetch_ticker(p2)
        qty2 = (order1['filled'] / t2['ask']) * 0.999
        order2 = exchange.create_market_buy_order(p2, qty2)
        placed.append(order2)
        logger.info(f"Leg2 filled: {order2['filled']} {p2.split('/')[0]} @ avg {order2['average']}")
        time.sleep(0.05)

        # Leg 3: coin2 -> USDT
        order3 = exchange.create_market_sell_order(p3, order2['filled'])
        placed.append(order3)
        logger.info(f"Leg3 returned: {order3['cost']} USDT")

        net_pct = ((order3['cost'] - usdt_amount) / usdt_amount) * 100
        logger.info(f"Net result: {net_pct:+.4f}%")
        return {'status': 'success', 'net_pct': net_pct, 'orders': [o['id'] for o in placed]}

    except ccxt.InsufficientFunds:
        logger.error("Insufficient funds — lower usdt_amount or check balances")
        return {'status': 'error', 'reason': 'insufficient_funds', 'partial': [o['id'] for o in placed]}

    except ccxt.NetworkError as e:
        logger.error(f"Network dropped mid-trade: {e} — CHECK OPEN POSITIONS IMMEDIATELY")
        return {'status': 'error', 'reason': 'network', 'partial': [o['id'] for o in placed]}

    except ccxt.ExchangeError as e:
        logger.error(f"Exchange rejected an order: {e}")
        return {'status': 'error', 'reason': str(e), 'partial': [o['id'] for o in placed]}


# Usage
exchange = ccxt.binance({'apiKey': 'KEY', 'secret': 'SECRET'})
path = ('BTC/USDT', 'ETH/BTC', 'ETH/USDT')
result = execute_triangle(exchange, path, usdt_amount=1000, dry_run=True)
print(result)

Note the 0.999 buffer on quantity calculations. Exchange minimum lot sizes and precision rules often reject orders that are too precise or slightly over your available balance after fees. The buffer gives you a small margin of safety. In production, you should also call exchange.load_markets() at startup to retrieve the exact precision rules for each pair and use exchange.amount_to_precision() to format quantities correctly before placing orders. Bybit and OKX have slightly different precision handling than Binance, so test each exchange independently before going live.

Fees, Latency, and Honest Profitability Expectations

Triangular arbitrage looks compelling on paper and genuinely works — but the gap between theoretical profit and realized profit is where most bots get humbled. Three factors systematically erode your edge: compounding trading fees, network latency, and market-order slippage. Understanding each one honestly before deploying capital will save you from expensive surprises.

Spot fee tiers and API capabilities across major arbitrage-friendly exchanges
ExchangeSpot Fee (Maker/Taker)WebSocket FeedREST Rate Limit
Binance0.10% / 0.10%Yes1200 req/min
Bybit0.10% / 0.10%Yes600 req/min
OKX0.08% / 0.10%Yes600 req/min
KuCoin0.10% / 0.10%Yes600 req/min
Gate.io0.20% / 0.20%Yes900 req/min

Fees define your floor. Standard Binance spot at 0.1% per leg means three legs cost 0.3% of your position before any profit calculation. Pay fees using BNB on Binance and that drops to roughly 0.225%. Your gross spread must exceed this threshold every single time, not just on average. On high-frequency pairs like BTC/USDT and ETH/BTC, the spread almost never appears cleanly — sophisticated market makers and colocated HFT systems are watching the same order books with purpose-built networking stacks. Where retail bots have a fighting chance is on mid-cap pairs: think SOL/ETH or XRP/BTC on OKX or Bybit, where fewer dedicated arb systems compete and spreads occasionally widen enough to clear your fee cost.

Latency compounds the problem. Every millisecond your scanner spends fetching prices or your orders spend traversing the public internet is a millisecond for the market to move against you. If you are running a latency-sensitive arb strategy on Binance, deploying on AWS ap-southeast-1 (Singapore) — colocated with Binance's matching engine — cuts round-trip time from 100-200ms on a home connection to under 5ms. Bybit's infrastructure runs similarly on AWS Tokyo. That difference is often the line between capturing an opportunity and arriving to find it closed. For context on broader market conditions that affect when arbitrage windows open and close, real-time signal platforms like VoiceOfChain provide actionable data on volatility and liquidity that complements your own scanning logic.

Slippage trap: market orders on pairs with thin order books can move price against you mid-execution. Before sizing a trade, always check the bid-ask spread and available depth at your expected fill quantity. A 0.5% spread on leg 3 alone wipes out the entire theoretical profit from legs 1 and 2.

Frequently Asked Questions

Is triangular arbitrage still profitable in crypto in 2025?
Yes, but margins on top-tier pairs have compressed significantly as competition from automated market makers has intensified. Consistent opportunities still appear on mid-cap pairs with decent volume but thinner bot coverage — particularly on platforms like Gate.io and KuCoin alongside the big three. The edge is real but requires low latency and disciplined fee management.
Which exchange APIs are best suited for triangular arbitrage bots?
Binance and OKX lead in liquidity, fee competitiveness, and WebSocket feed reliability. Bybit is a strong alternative with clean API documentation and fast order acknowledgment. All three are supported by the ccxt library with minimal code changes between them, which makes multi-exchange testing straightforward.
How much capital do I need to run a triangular arbitrage bot profitably?
A realistic floor is $1,000–$5,000 USDT to ensure that gross profits on a trade clear the minimum order sizes and compound fee cost with something left over. Below $500, lot size restrictions on individual legs frequently make execution impractical or introduce rounding losses that eliminate profit entirely.
Do I need a VPS close to the exchange servers?
For competitive pairs on Binance or Bybit, yes — latency is decisive. AWS Singapore for Binance or AWS Tokyo for Bybit brings round-trip latency from 100-200ms down to under 5ms. For less contested pairs where opportunities persist for hundreds of milliseconds rather than single digits, a fast home connection or budget cloud server may still work.
What happens if one leg of the arbitrage fails mid-execution?
You are left holding an unintended position in an intermediate asset. This is the primary operational risk of the strategy. Your code must log every filled leg with order IDs so you can manually unwind the position if automation fails. Always test execution logic thoroughly in dry-run mode and maintain a manual intervention plan before going live with real capital.

Triangular arbitrage with a crypto API is not a passive income machine — it is a precision engineering problem that rewards discipline over cleverness. The math is straightforward, the tooling is accessible via ccxt, and the opportunities are real. What separates the bots that make money from the ones that quietly bleed it are details: accurate fee accounting, latency-aware architecture, robust partial-fill handling, and honest expectations about where you sit in the competitive landscape. Start on Binance or Bybit in dry-run mode, instrument every leg of the trade, measure your actual fill prices against theoretical, and only move to live execution once the numbers align. Combine your scanner with a real-time signal layer like VoiceOfChain to avoid trading into adverse market conditions — knowing when not to trade is as valuable as knowing when to.

◈   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