Розробка торгового бота для криптобіржи
Бот для власної біржі — це не те саме що бот для Binance. Тут есть прямий доступ до внутрішнього API, можливість market making без latency мережі, контроль над комісіями. Це одночасно інструмент забезпечення ліквідності та інструмент монетизації біржі.
Навіщо біржі власні боти
Молода біржа без ліквідності — мертва біржа. Трейдери приходять туди, де тонкий спред та глибокий стакан. Рішення: внутрішній market-making бот який:
- Утримує тонкий спред (0.05–0.1% для стабільних пар)
- Держит глибину стакану (достатній обсяг на кожному рівні)
- Слідить за ціною відносно зовнішніх бірж (Binance, OKX)
Це легітимна практика — більшість молодих бірж використовують внутрішніх market-мейкерів на старті.
Архітектура MM бота
Стратегія цитування
class MarketMakerBot:
def __init__(self, pair: str, config: MMConfig):
self.pair = pair
self.spread_pct = config.spread_pct # 0.1% = 0.001
self.order_levels = config.order_levels # кількість рівнів (5-10)
self.level_spacing = config.level_spacing # расстояние між рівнями
self.level_size = config.level_size # обсяг на рівні
self.reference_exchange = config.reference # Binance для price feed
async def update_quotes(self):
# Отримуємо reference price з зовнішної біржи
ref_price = await self.get_reference_price()
# Обчислюємо bid/ask
half_spread = ref_price * self.spread_pct / 2
best_bid = ref_price - half_spread
best_ask = ref_price + half_spread
# Генеруємо кілька рівнів
new_bids = []
new_asks = []
for i in range(self.order_levels):
bid_price = best_bid * (1 - self.level_spacing * i)
ask_price = best_ask * (1 + self.level_spacing * i)
size = self.level_size * (1 + i * 0.5) # збільшуємо розмір далі від mid
new_bids.append({'price': bid_price, 'size': size})
new_asks.append({'price': ask_price, 'size': size})
await self.refresh_orders(new_bids, new_asks)
async def refresh_orders(self, new_bids, new_asks):
# Скасовуємо старі ордери та виставляємо нові атомарно
# Використовуємо bulk cancel + bulk place для мінімізації часу без котировок
await self.exchange.cancel_all_orders(self.pair)
await asyncio.gather(
*[self.exchange.place_order(self.pair, 'buy', b['price'], b['size'])
for b in new_bids],
*[self.exchange.place_order(self.pair, 'sell', a['price'], a['size'])
for a in new_asks]
)
Управління інвентарем
При активній торгівлі інвентарь (соотношение base/quote) зміщується. Потребує ребалансировки:
def calculate_inventory_skew(self, base_balance: float, quote_balance: float,
mid_price: float) -> float:
"""
Повертає skew (-1.0 до +1.0)
-1.0: весь баланс в base (багато куплено) → знижуємо bid, піднімаємо ask
+1.0: весь баланс в quote (багато продано) → піднімаємо bid, знижуємо ask
"""
base_value = base_balance * mid_price
total_value = base_value + quote_balance
if total_value == 0:
return 0.0
ideal_pct = 0.5 # цільовий баланс 50/50
current_pct = base_value / total_value
return (ideal_pct - current_pct) * 2 # нормуємо до [-1, 1]
def apply_inventory_skew(self, mid_price: float, skew: float) -> tuple:
"""Зміщуємо котировки для зниження дисбалансу"""
skew_adjustment = mid_price * self.skew_factor * skew
adjusted_mid = mid_price + skew_adjustment
bid = adjusted_mid * (1 - self.spread_pct / 2)
ask = adjusted_mid * (1 + self.spread_pct / 2)
return bid, ask
Прямий доступ до біржі
Ключова перевага internal бота — він може працювати через внутрішній API біржи, мінуючи:
- HTTP overhead (внутрішній вызов через IPC або gRPC)
- Rate limits (внутрішні боти виключені)
- Комісії (MM бот може мати нульові або від'ємні комісії)
// Прямий вызов matching engine без HTTP
type InternalBotConnector struct {
matchingEngine *MatchingEngine
balanceManager *BalanceManager
}
func (c *InternalBotConnector) PlaceOrder(order Order) ([]Trade, error) {
// Прямий вызов, без мережі
return c.matchingEngine.AddOrder(order)
}
func (c *InternalBotConnector) GetOrderBook(pair string) OrderBook {
return c.matchingEngine.GetSnapshot(pair)
}
Це знижує latency з 1–5 мс (HTTP) до < 100 мікросекунд.
Моніторинг та P&L
class BotMetrics:
def __init__(self):
self.filled_volume = defaultdict(float)
self.pnl = defaultdict(float)
self.spread_captured = defaultdict(float)
def on_fill(self, trade: Trade):
pair = trade.pair
self.filled_volume[pair] += trade.quantity
# P&L розрахунок: кожен fill з позитивним спредом = доход
if trade.is_maker:
# Maker fill: ми отримали спред
spread_earned = abs(trade.price - self.mid_price[pair]) * trade.quantity
self.spread_captured[pair] += spread_earned
def get_stats(self) -> dict:
return {pair: {
'volume_24h': self.filled_volume[pair],
'spread_captured': self.spread_captured[pair],
'effective_spread_bps': self.spread_captured[pair] / self.filled_volume[pair] * 10000
if self.filled_volume[pair] > 0 else 0
} for pair in self.filled_volume}
Розробка торгового бота з MM стратегією для внутрішнього використання: 3–5 тижнів. Включає стратегію цитування, управління інвентарем, monitoring dashboard та інтеграцію з внутрішнім API біржі.







