Разработка системы мультибиржевой торговли из одного интерфейса
Мультибиржевой торговый интерфейс позволяет трейдеру управлять позициями на нескольких биржах из единого окна: видеть агрегированный баланс, выставлять ордера на нужную биржу, видеть сводную позицию по активу. Это значительно упрощает работу трейдеров, которые используют несколько бирж для арбитража или диверсификации.
Архитектура: Exchange Abstraction Layer
Ключевой паттерн — единый интерфейс, абстрагирующий биржеспецифичный API:
from abc import ABC, abstractmethod
from decimal import Decimal
class ExchangeAdapter(ABC):
@abstractmethod
async def get_balance(self) -> dict[str, Decimal]:
"""Возвращает {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-специфичная реализация
...
Агрегация балансов
class MultiExchangePortfolio:
def __init__(self, adapters: dict[str, ExchangeAdapter]):
self.adapters = adapters
async def get_aggregated_balance(self) -> dict[str, dict]:
"""Возвращает балансы по всем биржам с суммарным значением в USD"""
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))
# Агрегируем по активу
aggregated: dict[str, dict] = {}
for exchange, balances in balances_by_exchange.items():
if isinstance(balances, Exception):
continue # биржа недоступна, пропускаем
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
При выставлении ордера система может автоматически выбрать биржу с лучшими условиями:
class SmartOrderRouter:
async def find_best_execution(
self,
symbol: str,
side: str,
quantity: Decimal,
) -> tuple[str, Decimal]:
"""Возвращает (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':
# Лучшая цена покупки = наименьший ask
prices[exchange_name] = book.best_ask
else:
# Лучшая цена продажи = наибольший 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
Все ордера со всех бирж — в одном потоке:
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: унифицированный интерфейс
Во frontend показываем символ биржи рядом с каждым ордером/позицией:
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>
);
};
Особенности и ограничения
Каждая биржа имеет свою систему минимальных размеров ордеров, precision для цены и количества, ограничения по типам ордеров. Унифицированный интерфейс должен это учитывать: при выставлении ордера на конкретную биржу — применять её правила к параметрам ордера.
CCXT — отличная библиотека для быстрого прототипа. Для production стоит писать кастомные адаптеры: CCXT не всегда идеально поддерживает edge cases конкретных бирж и имеет заметные накладные расходы.







