Работа с Binance через WebSockets

Введение

Большинство современных криптобирж отдают информацию через REST-запросы – клиент должен запросить через HTTP страницу с определенным адресом, отправить нужные параметры и получить некоторый ответ.

Сама по себе технология REST довольно интересна – незаметно для получателя запрос может путешествовать от одного сервера к другому, можно балансировать нагрузку, можно снизить кол-во отдаваемого трафика, но этого мало (с)

На крупных биржах счет пользователей идет на миллионы, а кол-во запросов к API намного выше, т.к. и команды пользователей идут через эти же интерфейсы, через них работают работы, через них другие сайты получают информацию для виджетов на свои сайты, брокеры выводят информацию своим клиентам, тысячи телеграм-каналов что-то там транслируют, анализируют, и многое-многое другое. А ведь каждый лишний байт трафика, помноженный на десятки миллионов запросов выливается в большие долларовые потери на оплату трафика, содержание серверов, сетевого оборудования, зарплаты техподдержки – поэтому REST становится накладным.

Для экономии трафика используется технология WebSocket -  с ней некоторое кол-во трафика тратится один раз на установление соединения, после чего клиент и сервер обмениваются непосредственно данными. Это как связаться с кем-то по телефону – вы позвонили, трубку снял автоответчик, теперь вы можете бесконечно висеть на трубке и слушать, что там происходит. Вообще вебсокеты подразумевают двустороннюю связь – вы можете что-то говорить в трубку, ваш абонент может, но в случае Binance (и большинства других криптобирж) есть ограничения.

Так, например, на бинансе нельзя слушать сокет непрерывно свыше 24 часов. Так же связь односторонняя – сервер время от времени (один раз в секунду, как правило) выдаёт один и тот же набор данных всем, кто его слушает. Это как прийти на вокзал и слушать объявления – «Поезд на Бердичев прибыл на второй путь», «Отправление поезда Саратов-Омск» в толпе таких же людей.

Работа с сокетами Binance

Для работы вам потребуется установленный питон версии 3.6 и выше. Скачать его можно с официального сайта, при установке установив все флаги.

Технически, нет ничего сложного, чтобы реализовать протокол самому с нуля, для этого у вас всё есть, но не будем изобретать велосипеды и возьмем чужой готовый модуль питона websocket-client и посмотрим, что можно получить с binance. Для установки модуля выполните в командной строке

pip install websocket-client

Теперь давайте напишем простой скрипт для проверки работы, а потом пройдемся детальнее. Вот он:

import websocket

def on_message(ws, message):
    print(message)

def on_error(ws, error):
    print(error)

def on_close(ws):
    print("### closed ###")

def on_open(ws):
    print("### connected ###")

if __name__ == "__main__":
    #ws = websocket.WebSocketApp("wss://stream.binance.com:9443/stream?streams=ltcbtc@aggTrade/ethbtc@aggTrade",
    ws = websocket.WebSocketApp("wss://stream.binance.com:9443/ws/ltcbtc@aggTrade/ethbtc@aggTrade",
                              on_message = on_message,
                              on_error = on_error,
                              on_close = on_close)
    ws.on_open = on_open
    ws.run_forever()

Реклама:


После его запуска вы увидите нечто похожее:

Фактически, вы подключились к сокет-серверу и подписались на обновления сделок по парам LTCBTC и ETHBTC. Фактически синхронно с веб-интерфейсом у вас обновляются данные по последним сделкам. Теперь давайте рассмотрим подробнее, что и как можно и нужно делать. Начнем с режимов.

Режимы raw и combined

В моем скрипте одна строка закомментирована – вы можете её раскомментировать и удалить строку под ней – тогда вам будет выдаваться чуть больше информации, вот так:

import websocket

def on_message(ws, message):
    print(message)

def on_error(ws, error):
    print(error)

def on_close(ws):
    print("### closed ###")

def on_open(ws):
    print("### connected ###")

if __name__ == "__main__":
    ws = websocket.WebSocketApp("wss://stream.binance.com:9443/stream?streams=ltcbtc@aggTrade/ethbtc@aggTrade",
    #ws = websocket.WebSocketApp("wss://stream.binance.com:9443/ws/ltcbtc@aggTrade/ethbtc@aggTrade",
                              on_message = on_message,
                              on_error = on_error,
                              on_close = on_close)
    ws.on_open = on_open
    ws.run_forever()

 

Первый режим – это то, что Binance называет raw режимом – каждый набор данных отдается как есть, независимо от другого. Т.е. если в один момент времени произошли сделки И LTCBTC И ETHBTC, то вы получите две записи.

В случае combined режима, вы, скорее всего, получите одну запись, которая логически будет структурирована, и внутри которой будут сделки по каждой паре, каждая в своем разделе. Смотрите сами, как вам удобнее, мне удобнее raw.

Требования к подключениям

Каждое подключение должно идти на адрес

wss://stream.binance.com:9443

В конце адреса нужно подставить параметр, что бы указать, какая информация вам нужна. В случае raw укажите /ws/…, в случае combined - /stream?streams=, как у меня в примере.

Все пары маленькими буквами, и не забывайте пересоздавать коннект раз в 24 часа.

Непосредственно стримы

Стримы – streams (потоки) – это то, что «льется» из биржи – например, информация по свечам EOSBTC – это один стрим, по свечам ONTBTC – другой, тикер по всем парам – третий, и т.п.

Информация по сделкам (агрегированная)

То, что мы смотрели в примере выше – сделки по паре (или парам), объединенные в рамках одного ордера. Иными словами, если по одной цене одним человеком по одной паре в течении последних милисекунд было совершено несколько сделок, то вы увидите одну строку.

Пример запроса: ethbtc@aggTrade

wss://stream.binance.com:9443/ws/ethbtc@aggTrade

wss://stream.binance.com:9443/stream?streams=ethbtc@aggTrade

Структура ответа:{
  "e": "aggTrade",  // Тип события
  "E": 123456789,   // Время отправки события
  "s": "BNBBTC",    // Пара
  "a": 12345,       // ID возвращаемой записи
  "p": "0.001",     // Цена
  "q": "100",       // Кол-во
  "f": 100,         // ID первой сделки
  "l": 105,         // ID последней сделки
  "T": 123456785,   // Время сделки
  "m": true,        // Покупатель мейкер?
  "M": true         // Не актуально.
}

 

Информация по сделкам (подробная)

Выдается информация по сделкам, как есть.

Пример запроса: ethbtc@trade

wss://stream.binance.com:9443/ws/ethbtc@trade

wss://stream.binance.com:9443/stream?streams=ethbtc@trade

Структура ответа:

{
  "e": "trade",     // Тип события
  "E": 123456789,   // Время отправки
  "s": "BNBBTC",    // Пара
  "t": 12345,       // ID события
  "p": "0.001",     // Цена
  "q": "100",       // Объем
  "b": 88,          // Id заказа покупателя
  "a": 50,          // Id заказа продавца
  "T": 123456785,   // Время сделки
  "m": true,        // Покупатель мейкер?
  "M": true         // Не актуально.
}

Информация по свечам

Отдается каждую секунду. Нужно указать период, за который интересует информация.

Формируется так: параkline_период

Например

wss://stream.binance.com:9443/ws/ethbtc@kline_5m

wss://stream.binance.com:9443/stream?streams=ethbtc@kline_5m

Единицы измерения:

m -> минуты; h -> часы; d -> дни; w -> недели; M -> месяца

Возможные варианты:

 

Структура ответа:

{
  "e": "kline",     // Тип события
  "E": 123456789,   // Время события
  "s": "BNBBTC",    // пара
  "k": {
    "t": 123400000, // Время открытия свечи (UNIXTIME)
    "T": 123460000, // Время закрытия свечи
    "s": "BNBBTC",  // Пара
    "i": "1m",      // Период
    "f": 100,       // ID первой сделки периода
    "L": 200,       // ID последней сделки периода
    "o": "0.0010",  // Цена открытия
    "c": "0.0020",  // Цена закрытия
    "h": "0.0025",  // High
    "l": "0.0015",  // Low
    "v": "1000",    // Объем базовой валюты
    "n": 100,       // Кол-во сделок
    "x": false,     // Закрыта ли свеча?
    "q": "1.0000",  // Объем квотируемой валюты
    "V": "500",     // Сколько базовой валюты куплено тейкерами
    "Q": "0.500",   // Сколько квотируемой валюты куплено тейкерами
    "B": "123456"   // Не актуально
  }
}

Статистика по паре за 24 часа

Отправляется с сервера раз в секунду.

Пример запроса: ethbtc@ticker

wss://stream.binance.com:9443/ws/ethbtc@ticker

wss://stream.binance.com:9443/stream?streams=ethbtc@ticker

Структура ответа:

{
  "e": "24hrTicker",  // Тип события
  "E": 123456789,     // Время события
  "s": "BNBBTC",      // Пара
  "p": "0.0015",      // Изменение цены
  "P": "250.00",      // Изменение цены в процентах
  "w": "0.0018",      // Средневзвешенная цена
  "x": "0.0009",      // Цена закрытия предыдущих суток
  "c": "0.0025",      // Цена закрытия текущих суток
  "Q": "10",          // Объем закрытия
  "b": "0.0024",      // Цена лучшего Bid (спроса/покупки) 
  "B": "10",          // Объем лучшего Bid
  "a": "0.0026",      // Цена лучшего ask (преложение/продажа)
  "A": "100",         // Объем лучшего ask
  "o": "0.0010",      // Цена открытия
  "h": "0.0025",      // High 
  "l": "0.0010",      // Low 
  "v": "10000",       // Общий объем торгов в базовой валюте
  "q": "18",          // Общий объем торгов в квотируемой валюте
  "O": 0,             // Время начала сбора статистики
  "C": 86400000,      // Время окончания сбора статистики
  "F": 0,             // ID первой сделки
  "L": 18150,         // Id последней сделки
  "n": 18151          // Общее кол-во сделок
}

Статистика по всем парам за 24 часа

Отправляется раз в секунду.

Пример запроса: /!ticker@arr

wss://stream.binance.com:9443/ws/!ticker@arr

wss://stream.binance.com:9443/stream?streams=!ticker@arr

Возвращает то же, что и статистика за 24 часа, но по всем парам сразу, вот в такой структуре:

[
  {
    // Отдельно взятая пара
  }
]

Часть книги ордеров

Лучшие предложения покупки и продажи. Допускается получать по 5, 10 или 20 предложений из каждого стакана. Обновляется раз в секунду

Пример запроса: ethbtc@depth5

wss://stream.binance.com:9443/ws/ethbtc@depth5

wss://stream.binance.com:9443/stream?streams=ethbtc@depth5

Структура ответа:

{
  "lastUpdateId": 160,  // ID события
  "bids": [             // Стакан покупок
    [
      "0.0024",         // Цена
      "10",             // Объем
      []                // Не актуально
    ]
  ],
  "asks": [             // Стакан продаж
    [
      "0.0026",         // Цена
      "100",            // Объем
      []                // Не актуально
    ]
  ]
}

Обновление книги ордеров

Обновляется раз в секунду. Метод нужен тем, кто локально собирает и анализирует копии стаканов.

Пример запроса: ethbtc@depth

wss://stream.binance.com:9443/ws/ethbtc@depth

wss://stream.binance.com:9443/stream?streams=ethbtc@depth

Структура ответа:

{
  "e": "depthUpdate", // Тип события
  "E": 123456789,     // Время события
  "s": "BNBBTC",      // Пара
  "U": 157,           // Первое ID события
  "u": 160,           // Последнее ID события
  "b": [              // Покупки
    [
      "0.0024",       // Цена
      "10",         // Объем
      []              // Не актуально
    ]
  ],
  "a": [              // Продажи
    [
      "0.0026",       // Цена
      "100",          // Объем
      []              // Не актуально
    ]
  ]
}

Как правильно собирать стаканы на своей стороне:

  1. Откройте подключение к Open a stream to wss://stream.binance.com:9443/ws/bnbbtc@depth
  2. Собирайте в буфер информацию, которую получите оттуда
  3. Получите текущую информацию (слепок) по стаканам с адреса https://www.binance.com/api/v1/depth?symbol=BNBBTC&limit=1000
  4. У себя в буфере удалите (и не принимайте) все события, у которых u <= lastUpdateId слепка и u > lastUpdateId+1
  5. Каждое новое событие стрима должно быть выше предыдущего u на единицу
  6. Данные в каждом событии – абсолютные значения текущей цены и объема
  7. Если объем 0, удалите у себя цену
  8. Получение данных с 0, которых нет у вас в книге, допускается, и не является какой-либо ошибкой.
Тэги: