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 мс. На такой задержке нельзя торговать высокочастотно, но можно комфортно размещать рыночные и лимитные ордера «по интуиции».
Торговый бот в облаке запускается рядом с инфраструктурой биржи. RTT снижается до 5–20 мс: бот уже может реагировать на изменения рынка за десятки миллисекунд, поддерживать котировки близко к лучшим уровням и работать с межбиржевым арбитражем.
Колокация маркет-мейкера — это физическая стойка в дата-центре рядом с matching engine, соединённая с ним по приватной сети с гарантированной задержкой. RTT — сотни микросекунд. На таких задержках работают профессиональные маркет-мейкеры, HFT-фирмы и арбитражёры, которые платят бирже (или её провайдеру) за близость.
Отсюда следует важный урок: биржа не сводится к своему API-endpoint. Равный доступ означает одинаковые права, но не одинаковую задержку. При проектировании торговой стратегии сначала определите, на каком уровне 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 году для обмена торговыми сообщениями между брокерами и биржами и быстро стал индустриальным стандартом. FIX использует текстовый формат (8=FIX.4.4|9=176|35=D|...), поддерживает сессии с MsgSeqNum и имеет реализации для всех популярных языков. Сегодня его предлагают 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 — спецификация ITCH, первоисточник по бинарному feed для market data.
- FIX Trading Community — стандарты FIX, примеры сообщений, сертификационные тесты.