Market data, API и интеграции
Market data, API и интеграции — как биржа смотрит наружу
Представьте: торговый бот маркет-мейкера в Токио держит 48 тысяч открытых ордеров на паре BTC/USDT на Binance. Каждую секунду он получает десятки обновлений стакана, перестраивает свою модель цены и выставляет новые котировки. Если поток обновлений прервётся хотя бы на 200 мс — бот рискует оставить на бирже ордера по устаревшей цене и получить по ним исполнение на невыгодных условиях. Если такая задержка случится в момент резкого движения рынка, убыток за секунду превысит суточную прибыль.
Весь предыдущий текст навыка — о том, что происходит внутри биржи. Этот раздел — о том, как это становится видно снаружи. Как миллионы клиентов — от браузерного интерфейса розничного трейдера до сертифицированных FIX-сессий институциональных десков — одновременно смотрят в один и тот же matching engine. И почему «смотреть» здесь — слово с разными значениями для разных читателей.
Публичные и приватные данные
Давайте начнём с простой, но фундаментальной границы. Всё, что биржа отдаёт через API, делится на публичные и приватные данные.
Публичные — это то, что биржа готова отдавать кому угодно без авторизации: тикер (короткая сводка по символу: последняя цена сделки, лучший bid и ask, объём за последние 24 часа — ровно то, что обычно выводится одной строкой на экране у трейдера), уровни стакана (order book), история сделок, метаданные по символам (тиксайз, минимальный объём, часы торгов). Это рыночные данные (market data). Они одинаковы для всех и массово раздаются в режиме «один источник — много получателей».
Приватные — это то, что касается конкретного аккаунта: баланс, открытые ордера, история исполнений (fills), история депозитов и выводов. Сюда же попадают приватные действия: «разместить ордер», «отменить ордер», «вывести средства». Для всего приватного требуется API-ключ (мы ещё вернёмся к тому, как он устроен), и поток данных идёт только тому, кому он адресован.
Эта граница — не косметическая. Она диктует всю архитектуру API:
- Публичные данные подключены к «шине», которая один раз собирает обновления от matching engine и веером раздаёт их тысячам подписчиков. Один запрос к базе — миллион клиентов.
- Приватные данные идут через отдельную очередь на каждого пользователя: у каждого аккаунта свой канал, свой набор фильтров, свои права. Публичные данные оптимизированы под массовую раздачу одного потока миллионам клиентов; приватные — под изоляцию каждого клиента в собственном узком канале.
Помните hot path vs cold path из третьего раздела? Веерная рассылка market data формально лежит на холодном пути: она происходит уже после того, как matching engine свёл сделку, и на саму логику матчинга повлиять не может. Но по ощущениям клиентов это — горячий путь: если рассылка замолчала хотя бы на секунду, каждый трейдер снаружи немедленно видит «замёрзший стакан» и воспринимает это как «биржа лежит». Поэтому к рассылке относятся с такой же серьёзностью, как и к самому матчингу, хотя технически это разные слои.
Три протокола: REST, WebSocket, FIX
Внешний API биржи — это, как правило, три протокола поверх одних и тех же данных. Каждый из них решает свою задачу, и использовать их взаимозаменяемо — верный способ сжечь или деньги, или нервы.
REST — классическая модель «запрос — ответ». Клиент шлёт GET /api/v3/depth?symbol=BTCUSDT — сервер отвечает снимком стакана. Просто, совместимо с любым HTTP-клиентом, отлично для скриптов, аналитики и разовых интеграций. Плохо для всего, что требует свежести: каждый запрос — это новое TLS-рукопожатие или, в лучшем случае, новое HTTP-сообщение с заголовками, и между запросами пролетают десятки-сотни миллисекунд. Опрашивать REST в цикле, чтобы «следить за стаканом», — это как смотреть футбольный матч, заглядывая на табло раз в три минуты: счёт к концу игры вы узнаете, а все голы и опасные моменты пропустите.
WebSocket — это постоянное двунаправленное соединение. Одно рукопожатие при подключении, дальше — поток фреймов с данными. Биржа сама отправляет обновления клиенту, клиент ничего не опрашивает. Это то, на чём сидят боты, интерфейсы бирж и большинство торговых стратегий. Все современные биржи — Binance, Coinbase, Kraken, OKX, Bybit — предоставляют WebSocket API как основной способ подписки на market data.
FIX (Financial Information eXchange) — протокол, пришедший из традиционных финансов. Появился в 1992 году как попытка унифицировать обмен сообщениями между брокерами и биржами. Его ключевая особенность — это формат сообщений с тегами и встроенная работа с состоянием сессии: каждое сообщение имеет MsgSeqNum, потерю можно обнаружить и запросить повтор, сертифицированные клиенты проходят тесты на соответствие спецификации биржи. FIX живёт на институциональном уровне: прайм-брокеры, хедж-фонды, проп-трейдеры — те, кто уже работает с Nasdaq и LSE по этому же протоколу и не хочет переучивать команду и инфраструктуру ради криптобиржи. Kraken, Coinbase, Binance предоставляют FIX-шлюзы.
| Критерий | REST | WebSocket | FIX |
|---|---|---|---|
| Модель взаимодействия | Запрос — ответ | Двунаправленный поток | Сессия с последовательными сообщениями |
| Задержка до первого байта | 50–300 мс, TLS-рукопожатие на каждый запрос | Одно рукопожатие, дальше — сырой TCP-фрейм | Постоянная сессия поверх TCP |
| Объём данных | JSON + HTTP-заголовки на каждое сообщение | JSON или бинарный фрейм без заголовков | Компактный формат с тегами |
| Доставка обновлений | Клиент опрашивает (pull) | Сервер отправляет (push) | Сервер отправляет (push) |
| Типовые потребители | Скрипты, интеграции, аналитика | Торговые боты, интерфейсы бирж, маркет-мейкеры | Институциональные дески, прайм-брокеры |
| Обнаружение разрывов | Нет состояния — нечего терять | Через sequence numbers в дельтах | Встроено в протокол (MsgSeqNum) |
| Стоимость интеграции | Любой HTTP-клиент | WebSocket-клиент и логика переподключения | FIX-движок, сертификация на стороне биржи |
Для нас как архитекторов здесь важен конкретный урок: выбор протокола — это не вкусовщина, а следствие того, с какой задержкой и с какой частотой вы собираетесь читать данные. Если у вас аналитический конвейер, который раз в минуту снимает тикеры по сотне символов, — REST. Если у вас торговый бот, который держит локальную копию стакана и реагирует на движения в реальном времени, — WebSocket. Если вы — институциональный торговый деск, у которого уже есть FIX-движок, — FIX.
Snapshot + delta: как клиент собирает стакан
Вот главный вопрос, на который отвечает этот раздел: как клиент держит у себя синхронизированную копию стакана, если сам стакан живёт где-то в matching engine биржи и меняется тысячи раз в секунду?
Отправлять клиенту полный стакан при каждом изменении — нельзя. Полный стакан BTC/USDT на Binance — это тысячи уровней цен. При частоте в 100 изменений в секунду это были бы мегабайты трафика в секунду на одного клиента. При миллионе клиентов — терабайты. Нереально.
Решение — паттерн snapshot + delta:
- Клиент подключается к WebSocket и подписывается на канал стакана.
- Биржа один раз присылает полный snapshot — текущее состояние книги на момент T с уникальным номером последнего обновления (
lastUpdateIdу Binance,sequenceу Coinbase). - Дальше биржа шлёт только дельты — «изменение цены P на стороне S, новый объём Q». Каждая дельта несёт свой sequence number, монотонно возрастающий.
- Клиент применяет дельты к локальной копии по очереди, отбрасывая те, что старше
lastUpdateIdиз snapshot'а.
Вот главное в этой схеме: клиент должен проверять, что дельты приходят без пропусков. Если клиент только что применил seq=1002, а следующее сообщение несёт seq=1004 — это gap. Что-то потерялось между биржей и клиентом: разрыв соединения, перегрузка промежуточного узла, переключение маршрутов в сети. Локальная копия стакана теперь не соответствует реальности. Торговать по такому стакану нельзя — это прямой путь к тому самому исполнению по невыгодной цене из нашего вступления.
Как чинить gap? Универсальный ответ — сбросить всё и пересобрать:
- Очистить локальный стакан.
- Запросить новый snapshot (через REST или специальное WS-сообщение).
- Начать заново применять дельты с номера
snapshot.lastUpdateId + 1.
Поиграйте со следующей визуализацией: нажимайте «следующее сообщение», чтобы шаг за шагом применить поток к локальному стакану. Затем нажмите «потерять seq 1003» и пройдите тот же путь ещё раз — обратите внимание, как клиент обнаруживает gap по разрыву в последовательности.
Готовый минимальный клиент на TypeScript, который реализует этот же паттерн:
type Delta = {
u: number // последний seq в этом обновлении
U: number // первый seq в этом обновлении
b: [string, string][] // bids: [[price, qty], ...]
a: [string, string][] // asks: [[price, qty], ...]
}
class DepthClient {
private bids = new Map<string, string>()
private asks = new Map<string, string>()
private lastUpdateId = 0
private buffer: Delta[] = []
private ready = false
async connect(symbol: string) {
const ws = new WebSocket(`wss://stream.binance.com:9443/ws/${symbol}@depth`)
ws.onmessage = (evt) => this.handleDelta(JSON.parse(evt.data))
const snap = await fetch(`/api/v3/depth?symbol=${symbol.toUpperCase()}&limit=1000`)
.then((r) => r.json())
this.applySnapshot(snap)
for (const d of this.buffer) {
if (d.u <= this.lastUpdateId) continue
if (d.U > this.lastUpdateId + 1) {
return this.resync(symbol)
}
this.applyDelta(d)
}
this.buffer = []
this.ready = true
}
private handleDelta(d: Delta) {
if (!this.ready) {
this.buffer.push(d)
return
}
if (d.U !== this.lastUpdateId + 1) {
return this.resync((this as any).symbol)
}
this.applyDelta(d)
}
private applySnapshot(snap: { lastUpdateId: number; bids: [string, string][]; asks: [string, string][] }) {
this.bids = new Map(snap.bids)
this.asks = new Map(snap.asks)
this.lastUpdateId = snap.lastUpdateId
}
private applyDelta(d: Delta) {
for (const [p, q] of d.b) q === '0' ? this.bids.delete(p) : this.bids.set(p, q)
for (const [p, q] of d.a) q === '0' ? this.asks.delete(p) : this.asks.set(p, q)
this.lastUpdateId = d.u
}
private async resync(symbol: string) {
this.ready = false
this.bids.clear()
this.asks.clear()
this.buffer = []
await this.connect(symbol)
}
}
Обратите внимание на buffer и ready — это не абстракция ради абстракции, а решение реального состояния гонки: WebSocket-подключение начинает слать дельты до того, как клиент успел получить snapshot по HTTP. Без буферизации ранние дельты либо потерялись бы, либо были бы применены поверх пустого стакана с несогласованным sequence. Паттерн «буферизуем всё, пока не пришёл snapshot; затем проигрываем буфер с фильтром по lastUpdateId» — канонический для всех бирж, и Binance напрямую описывает его в документации.
Rate limits: почему у биржи есть бюджеты для каждого клиента
Каждый публичный API биржи защищён rate limit'ом — лимитом числа запросов, которые один клиент может сделать за единицу времени. Это первое, о чём вспоминает инженер, который забыл поставить sleep в цикле и получил HTTP 429 Too Many Requests на десятом запросе.
Rate limit — это не просто «60 запросов в минуту». Большие биржи давно ушли от простой модели «по числу запросов» к взвешенным лимитам — «по весу запросов». Каждому эндпоинту присваивается вес, пропорциональный нагрузке, которую он создаёт на бэкенд:
GET /ticker— вес 1. Одно чтение, одна ячейка памяти.GET /depth?limit=100— вес 5. Пачка уровней стакана, копия нужной глубины.GET /depth?limit=5000— вес 50. Почти весь стакан целиком.GET /allOrders— вес 20–40. Полная история аккаунта, тяжёлый запрос к базе.
У клиента есть общий бюджет, скажем, 6000 весовых единиц в минуту. Он может сделать 6000 лёгких тикеров, 120 тяжёлых запросов глубины стакана, или любую комбинацию, которая суммарно укладывается в бюджет. Как только бюджет исчерпан — HTTP 429, а при повторных нарушениях — временный бан по IP.
Зачем вообще нужны эти лимиты? Три причины, в порядке важности:
- Защита от DoS. Без rate limit'а один клиент с битым циклом способен положить публичный API. Биржи живут в состоянии постоянной борьбы со «случайным DoS» — и почти никогда с намеренным.
- Справедливое распределение. Публичные данные должны быть равно доступны всем. Если один клиент забирает 100% пропускной способности — остальные получают деградированный сервис.
- Бизнес-модель. Платные тарифы API существуют именно потому, что у лимитов есть цена. Институциональные клиенты платят за расширенные квоты по весу запросов.
Практическое следствие для инженера: всегда читайте заголовки X-MBX-USED-WEIGHT (Binance), CB-RATELIMIT-REMAINING (Coinbase) или их аналоги, и снижайте частоту запросов, когда приближаетесь к лимиту. Не ждите 429 — к моменту, когда он прилетел, вы уже близки к бану по IP.
Уровни задержки: почему розница и маркет-мейкер видят разную биржу
Формально у всех клиентов одно и то же API. Фактически — задержки разные на порядки. И это не баг, а осознанная архитектура.
Розничный клиент сидит где-нибудь в Алматы, подключается к Binance через публичный интернет, и round-trip time до биржи составляет 80–250 мс. На такой задержке нельзя торговать высокочастотно, но можно комфортно размещать рыночные и лимитные ордера «по интуиции».
Торговый бот в облаке поднимается в той же облачной зоне, что и биржа — скажем, AWS ap-northeast-1 для Binance. RTT падает до 5–20 мс. Это уже серьёзная позиция: можно реагировать на изменения рынка за десятки миллисекунд, удерживать котировки в нижнем слое стакана и играть на разнице между биржами.
Колокация маркет-мейкера — это физическая стойка в дата-центре рядом с matching engine, соединённая с ним по приватной сети с гарантированной задержкой. RTT — сотни микросекунд. На таких задержках работают профессиональные маркет-мейкеры, HFT-фирмы и арбитражёры, которые платят бирже (или её провайдеру) за близость.
Здесь — ещё один урок, который не сформулирован ни в одной документации: биржа не равна своему API-эндпоинту. «Равный доступ для всех» означает одинаковые права, а не одинаковую скорость. Если вы строите торговую стратегию, первым делом оцените, на каком из этих уровней задержки вы будете её исполнять — и решите, имеет ли стратегия смысл на таком RTT.
Аутентификация API: ключ, секрет и HMAC
Любой приватный запрос к бирже должен быть подписан. Просто потому, что HTTPS доказывает, что вы общаетесь с биржей, а не с посредником, — но не доказывает, что запрос действительно отправили вы.
Типовая схема — HMAC-SHA256 с симметричным секретом:
- В личном кабинете биржи вы создаёте пару API key + API secret. Первый — публичный идентификатор, второй — строго секретный, биржа показывает его ровно один раз при создании.
- Для каждого запроса клиент собирает каноническую строку — запрос в нормализованном виде: метод, путь, query-параметры, тело, обязательный
timestamp. - Клиент вычисляет
signature = HMAC-SHA256(secret, canonical)и шлёт подпись в заголовкеX-SIGNATURE(или как дополнительный query-параметр). - Биржа по
X-API-KEYнаходит тот же секрет у себя, повторяет вычисление и сравнивает результат в константное время. Совпало — запрос авторизован.
POST /api/v3/order
symbol=BTCUSDT&side=BUY&quantity=0.01timestamp=1712755200000
&symbol=BTCUSDT&side=BUY…X-API-KEY: abc123…
X-SIGNATURE: 7f4e…X-API-KEY — достать secret из базы, проверить разрешения и IP-whitelist.|server_now − timestamp| ≤ recvWindow (обычно 5 секунд). Иначе — 401.401.timestamp, а сервер отвергает запросы старше нескольких секунд.Несколько практических деталей, которые легко упустить в первой интеграции:
- Timestamp обязателен. Без него подпись можно перехватить и переиспользовать («replay attack»). Биржа отвергает запросы, у которых
|server_now − timestamp| > recvWindow— обычно несколько секунд. - IP whitelist. Даже при утечке секрета злоумышленник не сможет выполнить запрос, если его IP не в списке разрешённых. Стоит включать всегда, когда это возможно.
- Whitelist адресов вывода. Отдельный флаг на ключе, разрешающий выводы только на заранее заданные адреса. Утёкший ключ без права на вывод — это украденная торговая история, а не украденные деньги.
- Никогда не логируйте каноническую строку вместе с подписью. Это эквивалент логирования пароля.
Более современные схемы на крупных биржах уже работают с Ed25519 (асимметричной подписью): клиент хранит приватный ключ, биржа — публичный, секрет никогда не покидает клиент даже в зашифрованном виде. Механика аутентификации при этом не меняется — меняется только алгоритм подписи.
Рыночные стандарты: ITCH, FAST, FIX
Криптобиржи не родились в вакууме. Механика snapshot+delta, sequence numbers, канонические форматы — всё это унаследовано из традиционных финансов, где проблемы раздачи рыночных данных решают с 70-х годов.
FIX (Financial Information eXchange) — самый узнаваемый из стандартов. Появился в 1992 году как протокол обмена торговыми сообщениями между брокерами и биржами, быстро стал индустриальным стандартом. Текстовый формат (8=FIX.4.4|9=176|35=D|...), встроенная сессия с MsgSeqNum, официальные реализации для всех популярных языков. Сегодня FIX поддерживают Kraken, Coinbase, Binance и большинство институциональных торговых десков. Спецификации открыты — см. fixtrading.org.
ITCH — бинарный протокол раздачи market data, разработанный Nasdaq. Минималистичный, ориентированный на максимальную пропускную способность: каждое сообщение — жёсткая бинарная запись длиной 20–50 байт, никакого парсинга, никакой сериализации. ITCH раздаёт все события стакана и сделок в реальном времени — то, что криптобиржи делают через WebSocket, Nasdaq делает через ITCH. Если вы увидите в вакансии «ITCH feed handler» — это значит, что команда строит биржу, а не пользуется ею.
FAST (FIX Adapted for STreaming) — компромисс: сообщения всё ещё в духе FIX (поля с тегами), но закодированы в компактный бинарный формат с дифференциальным кодированием. Используется, когда нужна семантика FIX и пропускная способность ITCH.
Криптобиржи не обязательно реализуют ITCH или FAST напрямую, но знают о них, и их WebSocket-API часто напоминают «ITCH в JSON»: тот же snapshot+delta, те же sequence numbers, то же требование держать на клиенте буфер и детектор пропусков. Если вы понимаете ITCH — вы понимаете, откуда пришёл поток глубины стакана у Binance.
Закрывая раздел
Вернёмся к боту из Токио, с которого начинался раздел. Теперь мы можем проследить, что именно происходит за те 200 мс тишины, и почему их достаточно, чтобы превратить прибыль дня в убыток.
Бот держит на бирже 48 тысяч лимитных ордеров. Каждый из них — обещание купить или продать по конкретной цене, и это обещание основано на картине рынка, которую бот перестраивает по потоку обновлений стакана. Пока поток идёт, бот видит, что лучший bid сдвинулся с 68 420 на 68 430, и за миллисекунды переставляет свои ордера вслед. Если поток замолк, бот не знает, что рынок ушёл: его внутренняя картина замерла на старой цене, а его ордера — на старых уровнях. Дальше открываются два окна уязвимости одновременно.
Первое: пока бот смотрит в замороженный стакан, по-настоящему быстрые участники рынка (у которых поток не прерывался) уже видят новую цену и бьют по его ордерам, оставшимся на прежних уровнях. Это классическое adverse selection: исполняется именно то, что бот хотел бы убрать в первую очередь. Второе: даже когда поток возобновится, бот не имеет способа узнать, что он пропустил обновление, если не сверяет sequence numbers. Без них он продолжит торговать по устаревшей картине, даже не подозревая об этом. Именно здесь snapshot + delta и контроль последовательности перестают быть академической деталью и становятся единственной линией обороны.
Масштаб поражения задают остальные механизмы этого раздела. Rate limits ограничивают, как быстро бот может отменить все 48 тысяч ордеров: бюджет весов — конечный, и если на «холодной» стороне (чтение market data) оставлено слишком мало запаса, на «горячую» сторону (массовая отмена) его просто не хватит. Уровень задержки определяет, сколько участников успеют ударить по устаревшим ордерам за те же 200 мс: колоцированный маркет-мейкер за это время проведёт сотни раундов матчинга.
Это тот слой, на котором внутренняя архитектура биржи — matching engine, ledger, wallet service — встречается с реальным клиентом, который ей пользуется. В следующем разделе мы посмотрим на ту же систему ещё раз, но уже глазами дежурного инженера: как её держат живой, как защищают и что происходит, когда что-то идёт не так.
Дополнительное чтение
- Binance Spot API — developers.binance.com — канонический справочник по REST и WebSocket, включая страницу
@depth-потока и правила rate limit. - Coinbase Advanced Trade API — официальная документация REST и WebSocket, канал
level2для сборки стакана. - Kraken WebSocket API — хорошо описанный snapshot+delta протокол, удобен для первого знакомства.
- Nasdaq ITCH 5.0 specification — PDF спецификации ITCH, первоисточник «бинарного feed'а» для market data.
- FIX Trading Community — стандарты FIX, примеры сообщений, сертификационные тесты.