-
Notifications
You must be signed in to change notification settings - Fork 1
Потоки стакана Binance Web Socket
Можно получить данные следующими способами:
-
Только заявки на продажу и покупку, наиболее близкие к рыночной цене (All Book Tickers Stream, Individual Symbol Ticker Streams)
-
Топ заявок на покупку и на продажу. Другими словами 20 заявок на продажу и 20 на покупку, которые ближе всего к рыночной цене. (можно получать топ из 5, 10 или 20 заявок) (Partial Book Depth Streams)
-
Получать только обновления стакана, когда меняется объем - то есть когда заявка исполняется или снимается с исполнения. Полный стакан можно получить через GET запрос. (Diff. Depth Stream)
[TOC]
Название потока: <symbol>@bookTicker
Скорость обновления: Real-time
Получает информацию по лучшей цене покупки и продажи по определенному т. Лучшая цена - значит самая близкой к рыночной.
Скорость обновления Real-time - значит как только произошли изменения, веб-сокет их присылает. Как часто это происходит, зависит от тикерного симола - как часто у него меняется лучшее предложение. Например, ethbtc@bookTicker
будет присылать изменения очень часто - ориентировочно 15 раз в секунду. А waxpbtc@bookTicker
присылает 1-2 раза в секунду.
При обновлении приходят данные по ack и bid одновременно, даже если изменился только один из них. Изменения могут произойти как с ценой, так и с объемом.
Данные, которые присылает биржа:
{ поток bnbusdt@bookTicker
"u": 400900217, // id обновления стакана (не timestamp)
"s": "BNBUSDT", // тикерный символ
"b": "25.35190000", // цена лучшей заявки на покупку (b - bid)
"B": "31.21000000", // объем лучшей заявки на покупку
"a": "25.36520000", // цена лучшей заявки на продажу (a - ask)
"A": "40.66000000" // объем лучшей заявки на продажу
}
Название потока: !bookTicker
Скорость обновления: Real-time
Получает информацию по лучшей цене покупки и продажи по всем тикерным символам. Лучшая цена - значит самая близкой к рыночной.
*Данные, которые присылает биржа (Json): *
Такие же данные, как у All Book Tickers Stream, но по разным тикерным символам(<symbol>@bookTicker)
{ // поток !bookTicker
"u": 400900217, // id обновления стакана (не timestamp)
"s": "BNBUSDT", // Тикерный символ (s - symbol)
"b": "25.35190000", // цена лучшей заявки на покупку (b - bid)
"B": "31.21000000", // объем лучшей заявки на покупку
"a": "25.36520000", // цена лучшей заявки на продажу (a - ask)
"A": "40.66000000" // объем лучшей заявки на продажу
}
{ // поток !bookTicker
"u": 1381280150,
"s": "SHIBUSDT"
"b": "0.00003822",
"B": "649709579.00",
"a": "0.00003823",
"a": "204601625.00"
}
{ // поток !bookTicker
"u": 54271051,
"s": "DEXEETH"
"b": "0.00571100",
"B": "11.96000000",
"a": "0.00578200",
"a": "289.59000000"
}
Название потока: <symbol>@depth<levels>
или <symbol>@depth<levels>@100ms
Скорость обновления: 1000ms или 100ms
Поток отдает топ предложений на покупку и продажу (топ уровней цен стакана) - то, что обычно видно на Binance в стакане (скриншот стакана ETH/BTC на Binance ниже). Можно получать топ из 5, 10, или 20 значений (т.е. 20 на покупку и 20 на продажу).
<symbol> - это тикерный символ, например ethbtc или manausdt (обязательно в нижнем регистре). Если указан неправильный тикерный символ, то данные не будут приходить.
<levels> - это количество уровней стакана, можно указать 5, 10, или 20. Указывать их обязательно, иначе это будет уже другой поток (Поток обновлений стакана - Diff. Depth Stream)
@100ms или @1000ms - это время, как часто будут приходить данные. Можно не указывать, значение по умолчанию 1000ms
Примеры, как нужно указывать поток:
"ethbtc@depth5"
<- получать данные стакана ETH/BTC по 5 уровням цен каждую секунду (1000ms по умолчанию)
"ilvbusd@depth20"
<- получать данные стакана ILV/BTC по 20 уровням цен каждую секунду (1000ms по умолчанию)
"1inchbtc@depth20@100ms"
<- получать данные стакана 1INCH/BTC по 20 уровням каждые 100 милисекунд
Web Socket присылает два массива, asks и bids.
Asks соответвуют заявкам выше середины на скриншоте (красные цены 🔴), это заявки на покупку.
Bids соответвуют заявкам ниже середины на скриншоте (зеленые цены 🟢), это заявки на продажу.
По умолчанию данные отправляются каждые секунду. Но можно ускорить, чтобы данные приходили раз в 100 милисекунд.
Данные, которые присылает биржа (Json):
{ // поток bnbbtc@depth5
"lastUpdateId": 160, // ID последнего обновления стакана (не timestamp)
"bids": [ // Массив из заявок на покупку - bids [цена, объем]
[
"0.011706", // Цена, которую обозначил покупатель
"4.489" // Объем покупки (количество монет)
],
[
"0.011707",
"3.804"
],
[
"0.011708",
"0.67"
],
[
"0.01171",
"14.245"
],
[
"0.011711",
"11"
]
],
"asks": [ // массив из заявок на продажу - asks [цена, объем]
[
"0.011712", // По какой цене продавец согласен продать монеты
"11.941" // Объем продажи (количество монет)
],
[
"0.011713",
"5.255"
],
[
"0.011714",
"15"
],
[
"0.011715",
"11.239
],
[
"0.011717",
"0.416"
]
]
}
Название потока: <symbol>@depth ИЛИ <symbol>@depth@100ms
Скорость обновления: 1000ms или 100ms
{ // Сообщение I
"e": "depthUpdate",
"E": 1638636116911,
"s": "BNBBTC",
"U": 2365900644, // ID первого обновления в эвенте
"u": 2365900673, // ID последнего обновления в эвенте
...
}
Поток присылает обновления, когда меняется объем у уровней цен стакана. Это позволяет обновлять стакан, хранящийся локально, не перезаписывая его полностью.
Поток присылает только обновления, полный стакан он не присылает. Нужно либо получать полный стакан иначе. Если разорвется соединение, веб сокет не пришлет пропущенные обновления.
По поводу ID обновления, которое содержится в U
и u
подробной информации на Binance нет. Скорее всего, каждый ID привязан к изменению определенного уровня в стакане (исполнен ордер). Веб сокет присылает обновление раз в 1 секунду или в 100 милисекунд, в этом обновлении есть уровни, которые изменились за это время.
-
U
- ID первого обновления в эвенте -
u
- ID последнего обновления в эвенте
С помощью ID можно понять, уже учтено это обновление, или ещё нет. Для этого нужно сравить u
двух обновлений - нового обновления, и предыдущего. Если u
предыдущего больше, то это обновление не актуально, его нужно проигнорировать.
Также с помощью ID можно определить, были ли пропущены обновления. Для этого нужно сравнить u
предыдущего c U
нового обновления.
Пример, когда обновления были пропущены (Json обрезан, для краткости):
{ // Сообщение I
"e": "depthUpdate",
"E": 1638636116911,
"s": "BNBBTC",
"U": 2365900644, // ID первого обновления в эвенте
"u": 2365900673, // ID последнего обновления в эвенте
...
}
// произошел разрыв соединения, данные не получены
{ // Сообщение II
"e": "depthUpdate",
"E": 1638636118910,
"s": "BNBBTC",
"U": 2365900682, // ID первого обновления в эвенте
"u": 2365900688, // ID последнего обновления в эвенте
...
}
{ // Сообщение III
"e": "depthUpdate",
"E": 1638636119910,
"s": "BNBBTC",
"U": 2365900689, // ID первого обновления в эвенте
"u": 2365900722, // ID последнего обновления в эвенте
...
}
В примере последнее обновление в Сообщение I u
и первое обновление в Сообщение II U
отличаются больше, чем на 1. Значит, между ними были пропущены данные (из-за разрыва соединения). А вот Сообщение II и Сообщение III последовательны (U
третьего больше u
второго на 1).
Чтобы получить полный стакан, нужно отправить GET запрос к этому эндпоинту: api.binance.com/api/v3/depth
{ // поток bnbbtc@depth
"e": "depthUpdate", // Тип эвента
"E": 1638631942729, // Время обновления (Unix TimeStamp)
"s": "BNBBTC", // Тикерный символ
"U": 2365796799, // ID первого обновления в эвенте
"u": 2365796820, // ID последнего обновления в эвенте
"b": [ // Массив из заявок на покупку - bids [цена, объем]
[
"0.01168500", // Цена, которую обозначил покупатель
"6.28800000" // Объем покупки (количество монет)
],
[
"0.01162100",
"11.71800000"
]
],
"a": [ // Массив из обновившихся заявок на продажу - asks [цена, объем]
[
"0.01170700", // По какой цене продавец согласен продать монеты
"22.4870000" // Объем продажи (количество монет)
],
[
"0.01170500",
"22.65900000"
],
[
"0.01170400",
"26.30400000"
]
]
}
Получение полного стакана BNB/BTC, сохранение его локально, и максимально быстрая синхронизация с Binance.
-
Чтобы получать обновления, подключаюсь к веб сокету wss://stream.binance.com:943/ws/bnbbtc@depth. Пока не получу полный стакан (шаг 2, это следующий шаг), полученные данные не обрабатываю.
-
Получаю стакан из эндпоинта https://api.binance.com/api/v3/depth?symbol=BNBBTC&limit=1000. Это полный стакан, с глубиной в 1000. Также ответ от Binance содержит
lastUpdateId
, сохраняю его, чтобы потом сравнивать обновления с ним. Это id последнего обновления, которое записано в локальный стакан - в случае с полным стаканом, это id последнего обновления, которое Binance записал в стакан.
{
"lastUpdateId": 12, // сохраняю этот ID, он нужен, чтобы проверять обновления
"bids": [
[
"0.01163500",
"16.36800000"
],
[
"0.01163400",
"6.26900000"
],
// ...ещё 998 уровней цен бидов
],
"asks": [
[
"0.01163800",
"12.34500000"
],
[
"0.01163900",
"20.57200000"
],
... // ещё 998 уровней цен асков
]
}
- Начинаю обрабатывать обновления, которые получаю из потока
/ws/bnbbtc@depth
, подключенного на шаге 1.
{ // поток bnbbtc@depth
"e": "depthUpdate", // Тип эвента
"E": 1638631942729, // Время обновления (Unix TimeStamp)
"s": "BNBBTC", // Тикерный символ
"U": 10, // ID первого обновления в эвенте
"u": 15, // ID последнего обновления в эвенте
"b": [ // Массив из заявок на покупку - bids [цена, объем]
[
"0.01168500", // уровень цены
"6.28800000" // новый объем по этому уровню цены
],
...// сокращено: дальше следующие обновления бидов
],
"a": [ // Массив из обновившихся заявок на продажу - asks [цена, объем]
[
"0.01170700",
"22.4870000"
],
...// сокращено: дальше следующие обновления асков
}
-
Пропускаю обновления, где
u
<=lastUpdateId
. Если это условия не соблюдается, значит пришло устаревшее обновление (локальный стакана синхронизирован с более новым обновлением, чем мы получили). -
Первое обработанное обновление должно соответствовать условиям:
-
U
<=lastUpdateId
+1 -
u
>=lastUpdateId
+1
-
Эти условия нужны, т.к. /ws/bnbbtc@depth
отдает обновления пачками - те обновления, которые произошли за секунду. А вот эндпоинт https://api.binance.com/api/v3/depth?symbol=BNBBTC&limit=1000 отдает данные, которые актуальны в данный момент - т.е. в любой момент, не обязательно кратный секунде. Поэтому lastUpdateId
(последнее обновление эндпоинта) и u
(последнее обновление, присланное веб сокетом) могут отличаться.
Поэтому нужно удостовериться, что последнее обновление полного стакана (полученного через эндпоинт) - это одно из обновлений, полученных через веб сокет.
-
Пока я подключен к потоку, у каждого нового обновления
U
должен быть равенu
+1 предыдущего обновления. ЕслиU
нового обновления >u
+1 предыдущего обновления, то я пропустил обновления. Мне нужно перейти к шагу 2, чтобы получить полный стакан. -
В переменной
lastUpdateId
находится id последнего обновления, которое загружено в локальный стакан. После того, как я записываю обновление в локальный стакан, я присваиваюlastUpdateId
значениеu
обновления - то есть id последнего обновления, с которым синхронизирован локальный стакан. -
Каждое обновление содержит абсолютный объем для каждого уровня цен. Значит мне нужно перезаписывать объем для каждого уровня цен. Нужно отметить, что данные по уровням цен приходят отсортированными:
a
(asks) по возрастанию цены (10, 11, 15),b
(bids) по убыванию (9, 6, 5). Но удобнее хранить уровни цен локально в виде словаря (map, dictionary, в зависимости от языка программирования).
{ // пример сортировки уровней цен
"e": "depthUpdate", // Тип эвента
"E": 1638723561756, // Время обновления (Unix TimeStamp)
"s": "ATMUSDT",
"U": 157079052, // ID первого обновления в эвенте
"u": 157079091, // ID последнего обновления в эвенте
"b": [ // сортировка бидов по возрастанию
[
"9.05000000",
"0.00000000"
],
[
"9.06000000",
"104.53000000"
],
[
"9.16000000",
"51.45000000"
],
[
"9.20000000",
"16.51000000"
]
]
"a": [ // сортировака асков по убыванию
[
"9.04000000",
"0.00000000"
],
[
"9.03000000",
"3.04000000"
],
[
"9.02000000",
"44.55000000"
],
[
"9.01000000",
"0.00000000"
],
[
"8.98000000",
"0.00000000"
]
]
}
- Если объем равен 0, то я удаляю уровень цены.
{
"e": "depthUpdate", // Тип эвента
"E": 1638723330746, // Время обновления (Unix TimeStamp)
"s": "ATMUSDT",
"U": 157077424, // ID первого обновления в эвенте
"u": 157077442, // ID последнего обновления в эвенте
...
"b":
["8.86000000", "93.35000000"],
["8.83000000", "2967.97000000"]
],
"a": [
["8.96000000", "1094.87000000"],
["8.98000000", "0.00000000"], // объем равен нулю, нужно удалить 8.98 из локального стакана
["9.00000000", "384.96000000"],
["9.02000000", '0.00000000'] // объем равен нулю, нужно удалить 9.02 из локального стакана
]
}
Вот схема получения обновлений - как lastUpdateId
соотносится с u
и U
(схема соответствуют примерам Json выше):