Розробка багатобіржевого торговельного інтерфейсу
Багатобіржевий торговельний інтерфейс дозволяє трейдеру керувати позиціями на кількох біржах з одного вікна: бачити агрегований баланс, виставляти ордери на потрібну біржу, бачити зведену позицію за активом. Це значно спрощує роботу трейдерів, які використовують кілька бірж для арбітражу або диверсифікації.
Архітектура: 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]:
"""Повертає балансу за всіма біржами з еквівалентом у доларах США"""
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])
Єдина стрічка ордерів
Усі ордери з усіх бірж — в одній стрічці:
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>
);
};
Особливості та обмеження
Кожна біржа має свою систему мінімальних розмірів ордерів, точності для ціни та кількості, обмежень за типами ордерів. Єдиний інтерфейс повинен це враховувати: при виставленні ордера на конкретну біржу — застосовуємо її правила до параметрів ордера.
CCXT — відмінна бібліотека для швидкого прототипування. Для виробництва варто писати користувацькі адаптери: CCXT не завжди ідеально підтримує граничні випадки конкретних бірж і має помітні накладні витрати.







