Пакетная отмена ордеров (Batch Cancel) на Bybit V5 через asyncio.gather

Пакетная отмена ордеров (Batch Cancel) на Bybit V5 через asyncio.gather Python & API
Пошаговое руководство по реализации ультрабыстрой пакетной отмены ордеров на Bybit V5 с использованием Python, aiohttp и asyncio.gather для минимизации задержек в HFT.
Суть: Для минимизации задержек (latency) при отмене большого количества ордеров на Bybit V5 используется эндпоинт /v5/order/cancel-batch, лимитированный 10 ордерами на запрос. Чтобы обойти это ограничение и отправить сотни отмен одновременно, мы разбиваем массив ордеров на чанки и отправляем их параллельно через асинхронный пул соединений aiohttp с помощью asyncio.gather.

Исходный код

При проектировании высокочастотных торговых систем (HFT) скорость реакции на изменение рынка имеет решающее значение. Например, при резком изменении рыночных условий или получении сигналов о рисках (таких как изменение греков опционов, о получении которых в реальном времени мы писали в статье Deribit WebSocket: Получение Греков Опционов в Реальном Времени с Asyncio), необходимо мгновенно очистить стакан от своих лимитных ордеров.

В отличие от простых парсеров (например, как в гайде Парсинг истории фандинга OKX на Python Asyncio: Гайд), где критична глубина выгрузки данных, в HFT-скриптах отмены ордеров на первом месте стоит сетевой round-trip time (RTT). Для еще большего ускорения и обхода лимитов на один IP-адрес профессиональные кванты применяют распределенные прокси и ротацию ключей, аналогично схеме, описанной в материале Настройка ротации API-ключей Binance для HFT на Python.

Ниже представлен готовый к продакшену класс на Python, реализующий асинхронную пакетную отмену ордеров на бирже Bybit V5 с автоматическим разбиением на чанки и параллельной отправкой запросов.

import asyncio
import hmac
import hashlib
import time
import json
import aiohttp

class BybitBatchCanceler:
    def __init__(self, api_key: str, api_secret: str, demo: bool = False):
        self.api_key = api_key
        self.api_secret = api_secret
        self.base_url = 'https://api-demo.bybit.com' if demo else 'https://api.bybit.com'
        self.endpoint = '/v5/order/cancel-batch'

    def _generate_signature(self, timestamp: str, recv_window: str, payload: str) -> str:
        val = timestamp + self.api_key + recv_window + payload
        return hmac.new(
            self.api_secret.encode('utf-8'),
            val.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()

    async def cancel_batch_chunk(self, session: aiohttp.ClientSession, category: str, orders: list) -> dict:
        timestamp = str(int(time.time() * 1000))
        recv_window = '5000'
        
        payload_data = {
            'category': category,
            'request': orders
        }
        payload_str = json.dumps(payload_data)
        signature = self._generate_signature(timestamp, recv_window, payload_str)
        
        headers = {
            'X-BAPI-API-KEY': self.api_key,
            'X-BAPI-SIGN': signature,
            'X-BAPI-TIMESTAMP': timestamp,
            'X-BAPI-RECV-WINDOW': recv_window,
            'Content-Type': 'application/json'
        }
        
        url = f'{self.base_url}{self.endpoint}'
        try:
            async with session.post(url, headers=headers, data=payload_str) as response:
                return await response.json()
        except Exception as e:
            return {'retCode': -1, 'retMsg': str(e), 'result': {}}

    async def cancel_all_orders(self, category: str, order_ids: list) -> list:
        chunk_size = 10
        chunks = [order_ids[i:i + chunk_size] for i in range(0, len(order_ids), chunk_size)]
        
        async with aiohttp.ClientSession() as session:
            tasks = []
            for chunk in chunks:
                formatted_orders = [{'orderId': oid} for oid in chunk]
                tasks.append(self.cancel_batch_chunk(session, category, formatted_orders))
            
            results = await asyncio.gather(*tasks)
            return results

async def main():
    API_KEY = 'your_api_key'
    API_SECRET = 'your_api_secret'
    
    canceler = BybitBatchCanceler(api_key=API_KEY, api_secret=API_SECRET, demo=True)
    
    # Пример списка ID ордеров для отмены
    orders_to_cancel = [
        'order_id_1', 'order_id_2', 'order_id_3', 
        'order_id_4', 'order_id_5', 'order_id_6',
        'order_id_7', 'order_id_8', 'order_id_9',
        'order_id_10', 'order_id_11', 'order_id_12'
    ]
    
    print(f'Запуск отмены {len(orders_to_cancel)} ордеров...')
    start_time = time.perf_counter()
    
    responses = await canceler.cancel_all_orders(category='linear', order_ids=orders_to_cancel)
    
    end_time = time.perf_counter()
    print(f'Выполнено за {end_time - start_time:.4f} сек.')
    print(json.dumps(responses, indent=2))

if __name__ == '__main__':
    asyncio.run(main())

Разбор параметров

  • category: Категория торгового инструмента на Bybit V5 (например, linear для бессрочных USDT-фьючерсов, inverse для коин-маржинальных контрактов или spot для спотового рынка).
  • order_ids: Список уникальных идентификаторов ордеров (orderId), которые необходимо отменить. Также можно использовать orderLinkId, изменив структуру форматирования в коде.
  • chunk_size: Размер пакета для одного запроса. По спецификации Bybit V5 для Единого Торгового Аккаунта (UTA) максимальный размер равен 10 ордерам.
  • asyncio.gather: Метод библиотеки asyncio, запускающий конкурентное выполнение нескольких корутин отправки запросов, что позволяет утилизировать пропускную способность сети на максимум и обойти последовательные задержки.

Как запустить

Для запуска скрипта вам понадобится интерпретатор Python версии 3.8 или выше, а также установленная библиотека aiohttp для выполнения асинхронных HTTP-запросов.

Установите зависимость с помощью пакетного менеджера pip:

pip install aiohttp

Замените значения переменных API_KEY и API_SECRET на ваши реальные ключи от демо- или реального аккаунта Bybit. Если вы используете реальный аккаунт, установите параметр demo=False при инициализации класса BybitBatchCanceler. Запустите скрипт командой:

python batch_cancel.py

Оцените статью
FinFluct