Development of Multi-Exchange Trading Interface
A multi-exchange trading interface allows a trader to manage positions on multiple exchanges from a single window: see aggregated balance, place orders on the right exchange, see a summary position per asset. This greatly simplifies work for traders who use multiple exchanges for arbitrage or diversification.
Architecture: Exchange Abstraction Layer
Key pattern — a unified interface that abstracts exchange-specific API:
from abc import ABC, abstractmethod
from decimal import Decimal
class ExchangeAdapter(ABC):
@abstractmethod
async def get_balance(self) -> dict[str, Decimal]:
"""Returns {asset: amount}"""
@abstractmethod
async def place_order(self, symbol: str, side: str, order_type: str,
quantity: Decimal, price: Decimal = None) -> Order:
pass
@abstractmethod
async def cancel_order(self, order_id: str, symbol: str) -> bool:
pass
@abstractmethod
async def get_open_orders(self, symbol: str = None) -> list[Order]:
pass
@abstractmethod
async def subscribe_order_updates(self, callback) -> None:
pass
class BinanceAdapter(ExchangeAdapter):
def __init__(self, api_key: str, secret: str):
self.client = BinanceClient(api_key, secret)
async def place_order(self, symbol: str, side: str, order_type: str,
quantity: Decimal, price: Decimal = None) -> Order:
binance_symbol = symbol.replace('/', '') # BTC/USDT → BTCUSDT
raw = await self.client.create_order(
symbol=binance_symbol,
side=side,
type=order_type,
quantity=str(quantity),
price=str(price) if price else None,
)
return Order.from_binance(raw)
class BybitAdapter(ExchangeAdapter):
async def place_order(self, symbol: str, ...):
# Bybit-specific implementation
...
Balance Aggregation
class MultiExchangePortfolio:
def __init__(self, adapters: dict[str, ExchangeAdapter]):
self.adapters = adapters
async def get_aggregated_balance(self) -> dict[str, dict]:
"""Returns balances across all exchanges with USD equivalent value"""
tasks = {
exchange: asyncio.create_task(adapter.get_balance())
for exchange, adapter in self.adapters.items()
}
results = await asyncio.gather(*tasks.values(), return_exceptions=True)
balances_by_exchange = dict(zip(tasks.keys(), results))
# Aggregate by asset
aggregated: dict[str, dict] = {}
for exchange, balances in balances_by_exchange.items():
if isinstance(balances, Exception):
continue # exchange unavailable, skip
for asset, amount in balances.items():
if asset not in aggregated:
aggregated[asset] = {"total": Decimal(0), "by_exchange": {}}
aggregated[asset]["total"] += amount
aggregated[asset]["by_exchange"][exchange] = amount
return aggregated
Smart Order Routing
When placing an order, the system can automatically select the exchange with the best conditions:
class SmartOrderRouter:
async def find_best_execution(
self,
symbol: str,
side: str,
quantity: Decimal,
) -> tuple[str, Decimal]:
"""Returns (exchange_name, best_price)"""
prices = {}
for exchange_name, adapter in self.adapters.items():
try:
book = await adapter.get_order_book(symbol, depth=5)
if side == 'BUY':
# Best buy price = lowest ask
prices[exchange_name] = book.best_ask
else:
# Best sell price = highest bid
prices[exchange_name] = book.best_bid
except Exception:
continue
if not prices:
raise ValueError("No exchanges available")
if side == 'BUY':
return min(prices.items(), key=lambda x: x[1])
else:
return max(prices.items(), key=lambda x: x[1])
Unified Order Feed
All orders from all exchanges in one stream:
class UnifiedOrderFeed:
def __init__(self, adapters: dict[str, ExchangeAdapter]):
self.order_queue = asyncio.Queue()
async def start(self):
tasks = [
self.subscribe_exchange(exchange, adapter)
for exchange, adapter in self.adapters.items()
]
await asyncio.gather(*tasks)
async def subscribe_exchange(self, exchange: str, adapter: ExchangeAdapter):
async def callback(order: Order):
order.exchange = exchange
await self.order_queue.put(order)
await adapter.subscribe_order_updates(callback)
UI: Unified Interface
In frontend show exchange symbol next to each order/position:
const UnifiedOrdersPanel = () => {
const { orders } = useUnifiedOrders();
return (
<table>
<thead>
<tr>
<th>Exchange</th>
<th>Symbol</th>
<th>Side</th>
<th>Price</th>
<th>Qty</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{orders.map(order => (
<tr key={`${order.exchange}:${order.id}`}>
<td>
<ExchangeBadge exchange={order.exchange} />
</td>
<td>{order.symbol}</td>
<td className={order.side === 'BUY' ? 'text-green' : 'text-red'}>
{order.side}
</td>
<td>{formatPrice(order.price)}</td>
<td>{order.quantity}</td>
<td>{order.status}</td>
<td>
<button onClick={() => cancelOrder(order.exchange, order.id)}>
Cancel
</button>
</td>
</tr>
))}
</tbody>
</table>
);
};
Features and Limitations
Each exchange has its own system of minimum order sizes, precision for price and quantity, order type limitations. The unified interface must account for this: when placing an order on a specific exchange — apply its rules to order parameters.
CCXT is a great library for quick prototyping. For production, it's worth writing custom adapters: CCXT doesn't always perfectly support edge cases of specific exchanges and has notable overhead.







