Разработка бота для межбиржевого арбитража

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1306 услуг
Разработка бота для межбиржевого арбитража
Средний
~1-2 недели
Часто задаваемые вопросы

Направления блокчейн-разработки

Этапы блокчейн-разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1285
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    902
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1120
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    588
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    855

Разработка маркет-мейкер бота

Market-making — это одновременное выставление ордеров на покупку и продажу с целью заработка на спреде. Маркет-мейкер (ММ) обеспечивает ликвидность рынку и зарабатывает когда его bid и ask исполняются. Главный враг ММ — adverse selection: торговля против информированного трейдера, который знает что цена скоро сдвинется.

Экономика маркет-мейкинга

ММ зарабатывает spread capture: если bid = $42,000 и ask = $42,100, и оба исполнились — прибыль $100 (минус комиссии). Но если за это время цена упала до $41,800, ММ купил по $42,000 и теперь держит убыточную позицию.

Успешный ММ должен:

  • Зарабатывать достаточно spread capture чтобы покрыть adverse selection losses
  • Управлять inventory risk (нельзя накопить слишком много позиции в одну сторону)
  • Адаптировать спред к текущей волатильности

Базовая стратегия цитирования

from decimal import Decimal
import asyncio

class MarketMaker:
    def __init__(self, config: MMConfig):
        self.pair = config.pair
        self.base_spread_bps = Decimal(str(config.spread_bps))  # базовые пункты
        self.order_levels = config.levels  # количество уровней в стакане
        self.level_size_usd = Decimal(str(config.level_size_usd))
        self.max_position_usd = Decimal(str(config.max_position_usd))
        
    async def run(self):
        while True:
            try:
                reference_price = await self.get_reference_price()
                inventory = await self.get_inventory()
                volatility = await self.estimate_volatility()
                
                quotes = self.calculate_quotes(reference_price, inventory, volatility)
                await self.update_orders(quotes)
                
                await asyncio.sleep(1)  # обновляем каждую секунду
            except Exception as e:
                logger.error(f"MM cycle error: {e}")
                await self.cancel_all()
                await asyncio.sleep(5)  # пауза перед повторной попыткой
    
    def calculate_quotes(self, mid: Decimal, inventory: Decimal, volatility: Decimal) -> Quotes:
        # Базовый спред
        half_spread = mid * self.base_spread_bps / Decimal('10000') / 2
        
        # Волатильный adjustment: при высокой волатильности — расширяем спред
        vol_multiplier = Decimal('1') + volatility * Decimal('10')  # 1x при vol=0, 2x при vol=10%
        adjusted_spread = half_spread * vol_multiplier
        
        # Inventory skew: смещаем котировки чтобы уменьшить дисбаланс
        skew = self.calculate_inventory_skew(inventory, mid)
        
        best_bid = mid - adjusted_spread + skew
        best_ask = mid + adjusted_spread + skew
        
        # Генерируем несколько уровней
        bids = []
        asks = []
        for i in range(self.order_levels):
            level_adjustment = adjusted_spread * Decimal(str(i)) * Decimal('0.5')
            size = self.level_size_usd / (best_bid - level_adjustment)
            
            bids.append(Quote(price=best_bid - level_adjustment, size=size))
            asks.append(Quote(price=best_ask + level_adjustment, size=size))
        
        return Quotes(bids=bids, asks=asks)
    
    def calculate_inventory_skew(self, inventory_usd: Decimal, mid: Decimal) -> Decimal:
        """
        Смещаем цитирование на основе текущего inventory.
        При накоплении long позиции — снижаем bid, повышаем ask (продаём активнее).
        """
        target = Decimal('0')  # целевой inventory = 0 (нейтральный)
        max_inventory = self.max_position_usd
        
        inventory_ratio = inventory_usd / max_inventory
        inventory_ratio = max(Decimal('-1'), min(Decimal('1'), inventory_ratio))
        
        # Skew пропорционален inventory: при 100% позиции — сдвигаем на 1 full spread
        skew_pct = Decimal('-0.0001') * inventory_ratio  # -0.01% * ratio
        return mid * skew_pct

Оценка волатильности

def estimate_volatility(self, prices: list[Decimal], window: int = 20) -> Decimal:
    """
    Realized volatility за последние N периодов.
    Используем для адаптивного расширения спреда.
    """
    if len(prices) < window + 1:
        return Decimal('0.01')  # default если недостаточно данных
    
    returns = []
    for i in range(1, window + 1):
        ret = float(prices[-i] / prices[-(i+1)] - 1)
        returns.append(ret ** 2)
    
    variance = sum(returns) / len(returns)
    vol = Decimal(str(variance ** 0.5))
    
    # Аннуализированная волатильность: для 1-минутных данных
    # multiply by sqrt(525600 minutes per year)
    return vol * Decimal(str((525600 ** 0.5)))

Управление ордерами

Избегаем лишних cancel/replace

Каждая отмена и новый ордер — комиссия и latency. Оптимизация: обновляем ордер только если цена сдвинулась достаточно.

async def update_orders(self, new_quotes: Quotes):
    current_orders = await self.get_open_orders()
    
    # Сравниваем текущие ордера с новыми котировками
    orders_to_cancel = []
    orders_to_place = []
    
    for new_bid in new_quotes.bids:
        matching = self.find_matching_order(current_orders.bids, new_bid.price)
        
        if matching:
            # Проверяем нужно ли обновить
            price_diff = abs(matching.price - new_bid.price) / matching.price
            if price_diff > Decimal('0.0005'):  # изменение > 0.05% — обновляем
                orders_to_cancel.append(matching.id)
                orders_to_place.append(new_bid)
            # Иначе оставляем существующий ордер
        else:
            orders_to_place.append(new_bid)
    
    # Batch cancel + place для минимизации latency
    if orders_to_cancel:
        await self.exchange.cancel_orders(orders_to_cancel)
    
    if orders_to_place:
        await asyncio.gather(*[
            self.exchange.place_order(quote) for quote in orders_to_place
        ])

Emergency cancel

При резком движении рынка или технической ошибке — немедленная отмена всех ордеров:

async def handle_price_shock(self, current_price: Decimal, reference_price: Decimal):
    price_change = abs(current_price - reference_price) / reference_price
    
    if price_change > Decimal('0.02'):  # > 2% за период обновления
        logger.warning(f"Price shock detected: {price_change:.2%}. Cancelling all orders.")
        await self.exchange.cancel_all_orders(self.pair)
        
        # Пауза и мониторинг
        await asyncio.sleep(30)
        
        # Проверяем стабилизировалась ли цена
        new_price = await self.get_reference_price()
        new_change = abs(new_price - reference_price) / reference_price
        
        if new_change < Decimal('0.005'):  # < 0.5% — возобновляем
            logger.info("Price stabilized, resuming market making")
            self.reference_price = new_price

Риски маркет-мейкинга

Adverse Selection

Крупный участник знает что цена вырастет и агрессивно покупает у ММ по bid. ММ оказывается с long позицией перед падением.

Детектирование: если последние N fills все по одной стороне — stop и переоценка. Признак информированной торговли.

def detect_adverse_selection(self, recent_fills: list[Fill]) -> bool:
    if len(recent_fills) < 5:
        return False
    
    # Все последние 5 fills по одной стороне — подозрительно
    sides = [f.side for f in recent_fills[-5:]]
    if len(set(sides)) == 1:
        return True
    
    # Объём fills резко вырос
    avg_fill_volume = sum(f.quantity for f in recent_fills[:-3]) / max(len(recent_fills) - 3, 1)
    recent_volume = sum(f.quantity for f in recent_fills[-3:]) / 3
    
    if recent_volume > avg_fill_volume * 3:
        return True
    
    return False

Naked Position Risk

Limit на максимальную позицию — жёсткий circuit breaker:

async def check_position_limits(self):
    position = await self.get_net_position()  # $ в base asset
    
    if abs(position) > self.max_position_usd:
        logger.critical(f"Position limit exceeded: {position} USD")
        await self.cancel_all_orders()
        
        # Принудительная ликвидация части позиции
        excess = abs(position) - self.max_position_usd
        if position > 0:  # long excess
            await self.market_sell(excess / self.last_price)
        else:  # short excess
            await self.market_buy(abs(excess) / self.last_price)

P&L расчёт

class MMPnLTracker:
    def __init__(self):
        self.realized_pnl = Decimal('0')
        self.fill_history: list[Fill] = []
        self.position = Decimal('0')  # в base currency
        self.avg_cost = Decimal('0')
    
    def on_fill(self, fill: Fill):
        self.fill_history.append(fill)
        
        if fill.side == 'buy':
            # Обновляем avg cost
            new_qty = self.position + fill.quantity
            self.avg_cost = (self.position * self.avg_cost + fill.quantity * fill.price) / new_qty
            self.position = new_qty
        else:
            # Реализованный P&L при продаже
            pnl = (fill.price - self.avg_cost) * fill.quantity
            self.realized_pnl += pnl
            self.position -= fill.quantity
    
    def get_unrealized_pnl(self, current_price: Decimal) -> Decimal:
        return (current_price - self.avg_cost) * self.position
    
    def get_total_pnl(self, current_price: Decimal) -> Decimal:
        return self.realized_pnl + self.get_unrealized_pnl(current_price)
Параметр стратегии Типичные значения Влияние
Spread (bps) 5–50 bps Выше = меньше fill, больше capture per fill
Order levels 3–10 Больше = больше capital tie-up, более глубокий стакан
Max position 5–20% депозита Риск adverse selection
Volatility multiplier 1–3x Защита от гэпов

Сроки разработки

  • Базовый ММ бот с single-level цитированием: 3–4 недели
  • Multi-level с inventory management: 5–7 недель
  • Production-ready с volatility adaptation и adverser selection detection: 8–12 недель
  • Backtesting framework для стратегии: +2–3 недели