Работа с 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 -> месяца

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

  • 1m
  • 3m
  • 5m
  • 15m
  • 30m
  • 1h
  • 2h
  • 4h
  • 6h
  • 8h
  • 12h
  • 1d
  • 3d
  • 1w
  • 1M

 

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

{
  "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, которых нет у вас в книге, допускается, и не является какой-либо ошибкой.

Это статья из цикла "Популярно о бирже"
Все статьи цикла:

Последнее изменение:

Не забудьте рассказать друзьям об этой статье.
Чтобы поддержать ресурс Bablofil достаточно просто поделиться с друзьями этой статьей в социальных сетях. Каждый репост - это самая высокая оценка качества материала. Спасибо, что читаете этот блог.



Комментарии
23.08.2018 08:59:53
Добрый день, можете подсказать, что означает эта ошибка при выполнении:
"wss://stream.binance.com:9443/ws/ethbtc@kline_5m"
### connected ###
{"e":"kline","E":1535014904715,"s":"ETHBTC","k":{"t":1535014800000,"T":1535015099999,"s":"ETHBTC","i":"5m","f":78705608,"L":78705736,"o":"0.04261000","c":"0.04259300","h":"0.04262500","l":"0.04259200","v":"47.97100000","n":129,"x":false,"q":"2.04394491","V":"24.24100000","Q":"1.03311231","B":"0"}}
'>' not supported between instances of 'float' and 'NoneType'
### closed ###
Проголосовать Проголосовать
0 0
23.08.2018 11:36:21
Добрый день.
Это значит, что сравниваете два каких-то значения, из которых одно является числом с плавающей точкой, а другое неопределено.
Больше ничего сказать нельзя, не видя кода.
Проверьте, где у вас используется конструкция вида
переменная1 > переменная2
Проголосовать Проголосовать
0 0
23.08.2018 12:12:36
Андрей, спасибо за быстрый ответ. 
Мне понятно, что означает это сообщение (его перевод), но непонятно как оно вообще может возникнуть при выполнении скрипта с этой страницы, где нет никаких сравнений переменных и т.п.
Возможно ошибка возникает при отработке WebSocketApp, причем вначале 1 раз возникает событие on_message (все ок), а затем on_error (с этой ошибкой)
Проголосовать Проголосовать
0 0
23.08.2018 12:51:46
Судя по исходному коду, on_message изначально пустой, там нечему ломаться
Я скопировал скрипт со страницы, запустил, ошибок не вижу - может быть, у вас скопировались какие-то html-теги или вроде того?
Проголосовать Проголосовать
0 0
29.08.2018 10:25:17
Андрей добрый день. Подтверждаю. Действительно с кодом с данной страницы возникает данная ошибка.
Проголосовать Проголосовать
0 0
03.11.2018 14:53:58
Версия websocket-client 0.54.0 решила проблему, на 0.51.0 была та же ошибка
Проголосовать Проголосовать
0 0
30.08.2018 12:21:40
Аналогично, только что всплыла данная проблема.
Решил данную проблему откатом библиотеки websocket-client
версии выше 0.45.0 не работали откатился к версии 0.21.0 всё заработало
Проголосовать Проголосовать
2 0
02.09.2018 19:24:21
До этого устанавливался 0.51.0, удалил потом и установил pip install websocket-client==0.21.0
и всё равно Пишит:
Traceback (most recent call last):
  File "websocket.py", line 17, in <module>
    ws = websocket.WebSocketApp("wss://stream.binance.com:9443/ws/ltcbtc@aggTrade/ethbtc@aggTrade",
AttributeError: module 'websocket' has no attribute 'WebSocketApp'
>>> 
первый код</module>
Проголосовать Проголосовать
0 0
11.09.2018 00:26:07
Доброго времени суток... Скажите пожалуйста, в одной из статей Вы упомянули что биржа Бинансе каждому зарегистрированному пользователю присваевает свой ID-номер. Можно ли в через websocket или  api получать связку данных по торгам ввиде ID-user+order(параметры ордера такие как размер ордера, тип ордера) для анализа ситуации по стакану? Или для отслеживания торгового поведения пользователя (например крупного игрока)?
Проголосовать Проголосовать
0 0
11.09.2018 06:36:38
Доброго.
Для того, что бы отслеживать конкретного человека, нужно знать его API ключи, ну или хотя бы публичный.
Получать информацию по стаканам можно без авторизации, но там не будет видно, кто именно какие ставит, только суммарные данные, такие же, как вы видите на сайте в стакане.
Проголосовать Проголосовать
0 0
23.09.2018 07:58:42
Добрый день!
А это все стримы? Если нет, то где можно посмотреть список всех?
Конкретно интересует "recent market activity", как на сайте, может есть такой.
Проголосовать Проголосовать
0 0
17.10.2018 12:00:57
Да, взято с официального описания. Может быть, в источнике что-то добавится, но пока тенденции к этому не было
https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md
Еще есть полузакрытые источники - https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md
Там можно отслеживать состояние своего баланса и получать информацию по своим ордерам.
Что бы эмулировать активность, наверное нужно подписаться на все trades и делать выводы самостоятельно
Проголосовать Проголосовать
0 0
01.10.2018 08:06:02
Добрый день!
Не могу разобраться в сборе локального ордербука.
После того как вытаскивается текущий стакан с https://www.binance.com/api/v1/depth?symbol=BNBBTC&limit;=1000 мы обновляем его данными накопленными со стрима. 
Паралельно с поступлением обновленией ордербука происходит торговля, т.е. некоторые ордера уже реализованы.
Возможно есть примеры реализации локальных стаканов?.
Заранее благодарен!
Проголосовать Проголосовать
0 0
17.10.2018 12:03:43
Насчет примера - да, можете расковырять код из статьи https://bablofil.ru/inner-arbitrage/
Там как раз формируются стаканы по всем парам и поддерживаются в актуальном состоянии.
Технически, как только вы совершаете куплю/продажу, прилетает сообщение из сокета, так что прям уж рассинхрона быть не должно
Проголосовать Проголосовать
0 0
06.12.2018 12:56:24
скажите, если возможность подписаться сразу на все пары альтернативным способом чес этот ? 
"wss://stream.binance.com:9443/ws/ethbtc@kline_1m/ltcbtc@kline_1m/bnbbtc@kline_1m/neobtc@kline_1m/qtumeth@kline_1m.................." etc...

А то получается очень длинная строка если подписаться сразу на все ~400 пар. Это мне нужно для создания базы истории торгов.
Проголосовать Проголосовать
0 0
08.12.2018 12:41:30
Другого способа, насколько мне известно, нет (
Проголосовать Проголосовать
1 0
08.12.2018 13:09:45
Спасибо за ответ. 
Еще я заметила странность, если запросить цены '/api/v3/ticker/price' то я получаю 412 уникальных пар, а вот если добавить их в /ws/ethbtc@kline_1m/все_412_пар/   то биржа выдает в стрим 382 свечи со статусом "x":true. Причем всегда  одинаково 382.
Интересно куда деваются 30 свечей как думаете ?
Проголосовать Проголосовать
0 0
08.12.2018 13:54:50
Там вроде бы свечи обновляются по событию, если не было изменения цены, то всю минуту может ничего не происходить, придет в начале следующей.
Нужно запустить, и дать поработать какое-то время, постепенно кол-во пар дойдет до максимального, вроде бы так.
Проголосовать Проголосовать
0 0
08.12.2018 14:02:46
Забыл сказать, берите только те пары из exchangeInfo, у которых
"status":"TRADING"
Проголосовать Проголосовать
0 0
08.12.2018 13:58:38
А зачем вы вручную пары указываете? Я беру при старте exchangeInfo через rest, оттуда беру каждую пару 
ws = websocket.WebSocketApp("wss://stream.binance.com:9443/ws/" +
                                        '/'.join([symbol['symbol'].lower() + '@kline_1m' for symbol in limits['symbols'] ....
Проголосовать Проголосовать
0 0
08.12.2018 17:52:55
А они бывают то TRADING то NONE причем за сутки может поменяться это состояние. А я базу собираю для технического анализа.
Проголосовать Проголосовать
0 0
08.12.2018 19:34:23
Выяснила )) Как раз эти недостающие пары в статусе "BREAK". Я просто пары брала не с exchangeInfo, а с /api/v3/ticker/price
Проголосовать Проголосовать
0 0
08.12.2018 17:57:41
Вручную разумеется не указываю, это я для примера написала. Я вообще с Python не знакома, но не плохо пишу на С++ и ваши статьи мне сильно помогли в написании своей собственной библиотеки.
Проголосовать Проголосовать
0 0
09.12.2018 10:20:03
Вы так же можете рассмотреть вариант получения не минутных свечей, а сбор истории сделок.
Такая информация позволит самостоятельно строить свечи любой размерности, включая секундные, и так же даст некоторую дополнительную информацию для анализа. Разумеется, объем данных будет больше и потребуется больше времени и ресурсов для обработки и хранения.
Проголосовать Проголосовать
1 0
09.12.2018 11:32:09
Спасибо за совет. Как раз для этого решила перейти на Couchbase Server.
Проголосовать Проголосовать
0 0
Пожалуйста, авторизуйтесь, что бы оставить свой комментарий
Крипто-кошельки для помощи и благодарности проекту:

Bitcoin адрес проекта: [[address]]

Перевод на сумму [[value]] BTC получен. Спасибо!.
[[error]]

Ethereum адрес проекта: [[address]]