Создание высокоскоростного фид-сервера Binance на C++

В быстро меняющемся мире финансовых рынков каждая доля секунды на счету и может совершить или разорвать сделку. Это не проблема: наличие быстрых и надежных рыночных данных — это название игры. Торговые операции разрастаются по сложной сети компьютерных процессов по всему миру, и все они жаждут последней рыночной информации. В этой обширной сети компании используют различные инструменты и технологии для обеспечения бесперебойной работы. Чтобы сохранить все это вместе, гибкость и способность работать с различными технологиями имеют решающее значение. И помните, что трейдинг — это, по сути, конкурентный научный эксперимент. Способность платформы рыночных данных быстро собирать и предоставлять данные для исследований и моделирования является главным приоритетом для разумного реагирования на изменения рынка.

Именно жонглирование требованиями к торговым технологиям делает алгоритмическую торговлю такой захватывающей и сложной областью. Наша миссия в этой серии блогов состоит в том, чтобы придумать платформу рыночных данных, отвечающую всем этим сложным требованиям. В этом первом посте мы углубимся в потребность в скорости в торговле. Мы создадим молниеносный фид-сервер Binance на C++, настроим сразу несколько серверов для достижения наилучшей производительности, проанализируем, как работает фид-сервер, и закончим созданием базового торгового плоттера с использованием Python API.

Но почему быстрые рыночные данные так важны в торговле? Быстрая реакция на изменения рынка — это все. Вялая торговая стратегия рискует использовать устаревшие данные, что может снизить ее эффективность к тому времени, когда ордера попадут на рынок. Сегодняшний торговый мир беспощаден, и возможность немедленно действовать по прибыльным сделкам является ключевой. С другой стороны, медленные стратегии рискуют отстать и довольствоваться сделками, которые другие уже пропустили. Задержки данных могут привести к потере информации, исказить ваше представление о рынке и потенциально вызвать накопление данных. Эффективная система рыночных данных также экономит деньги. Подумайте о финансовой разнице между запуском пяти серверов по сравнению с двадцатью — это большое дело как для маленьких, так и для крупных игроков на рынке.

Почему Binance?

Binance выделяется своим доступным публичным интерфейсом и удобным API. В распоряжении многочисленных обработчиков каналов с открытым исходным кодом, разработанных для Binance и оптимизированных для низкой задержки. В то же время принципы низкой задержки и элементы, касающиеся распределения и захвата, в значительной степени являются синонимами в потоках данных по акциям, фьючерсам и валютному рынку.

Libwebsockets

Libwebsockets выделяется как проворная, чистая библиотека C, предназначенная для использования современных сетевых протоколов без хлопот. Библиотека занимает мало места и использует неблокирующий цикл событий. Что особенно важно для наших целей, он хорошо подходит для управления единичным соединением, подчеркивая задержку каждого сообщения. Важно отметить, что библиотека предоставляет подробный пример доступа к рыночным данным Binance, которые служат нашей основой.

Полнометражный Yamal

Yamal — это библиотека с открытым исходным кодом, предназначенная для транзакционного IPC с малой задержкой и сбора данных. Он используется для создания систем, в которых данные передаются и собираются между различными процессами очень быстро, с акцентом на обеспечение согласованности и надежности этих данных. Это особенно важно в средах, где важна быстрая и надежная передача и хранение данных, таких как финансовые торговые платформы или системы аналитики в реальном времени. Ключевые особенности Ямала для нашего контекста:

  • Производительность: поразительно низкая задержка — 100 нс (медиана) и 1 мкс (макс.) на Ryzen 7950X.
  • Атомарность: гарантирует, что все обновление шины либо выполнено, либо не выполнено вообще.
  • Согласованность: обеспечивает согласованность данных в различных процессах.
  • Устойчивость: В случае сбоев приложения данные не теряются.
  • Нулевое копирование: воздерживается от копирования данных во время чтения/записи.
  • Простота: Может похвастаться элементарным C API и Python API.

Используя эти функции, мы можем легко разработать фид-сервер для распределения рыночных данных по другим процессам в той же системе с головокружительной скоростью. Для более глубокого понимания Ямала посетите https://github.com/featuremine/yamal.

Создание фид-сервера Binance

Начало работы

Чтобы облегчить работу с этим руководством, мы создали репозиторий, содержащий весь соответствующий код. Прежде чем приступить к работе, убедитесь, что вы установили gitCMake, набор инструментов компилятора C++ и настроили свою любимую среду разработки. Например, вот справочник по настройке Visual Studio Code. Этот проект совместим с современными установками Linux и MacOS. В MacOS вам может потребоваться установить некоторые зависимости разработки с помощью homebrew:brew install pkg-config cmake openssl

Пользователи Windows могут использовать подсистему Windows для Linux (WSL) или контейнер Docker.

Начните с клонирования репозитория учебника, настройки каталога сборки и запуска сборки:git clone —recurse-submodules https://github.com/featuremine/tutorials
cd tutorials
cmake -B release -DCMAKE_BUILD_TYPE=Release
cmake —build release

После сборки вы сможете найти двоичные файлы учебника в release/market-data01-feedhandler. Все соответствующие источники находятся в каталоге market-data01-feedhandler репозитория.

Если вы хотите сразу перейти к результатам и избежать подробного обсуждения кода, вы можете перейти к разделу Проверка и оценка производительности.

Расшифровка примера libwebsockets Binance

Мы упростили этот процесс, добавив пример Binance из libwebsockets в наш репозиторий руководств. Вы захотите проверить файл с именем minimal-ws-client-binance.c.

Позже будет важно настроить путь WebSocket в зависимости от каналов, которые мы хотим получать. Итак, давайте рассмотрим вопрос о том, как указываются детали подключения в примере в строке 130:i.address = «fstream.binance.com»;
i.path = «/stream?streams=btcusdt@depth@0ms/btcusdt@bookTicker/btcusdt@aggTrade»;

Кроме того, нам нужно будет изменить способ обработки данных, полученных от Binance. Это происходит на линии 247. Несмотря на то, что Binance находится в формате JSON, структура сообщений Binance остается неизменной, что устраняет необходимость в комплексном синтаксическом анализаторе JSON. Если вы хотите получить более подробную информацию, ознакомьтесь с документацией по API Binance. В этом примере функция lws_json_simple_find поиска ключа JSON в сообщении:case LWS_CALLBACK_CLIENT_RECEIVE:
// …
p = lws_json_simple_find((const char *)in, len, «\»depthUpdate\»», &alen);

Эта функция довольно проста, и мы будем использовать ее и в нашем приложении.

Интеграция Yamal

Сначала мы скопировали пример libwebsockets в binance-feed-handler.cpp чтобы наши дополнения, связанные с Yamal не мешали исходному коду. Мы также изменили язык на C++, чтобы мы могли использовать стандартную библиотеку C++.

Далее, в строке 313, мы ввели обработку аргументов командной строки. Здесь пригодится fmc_cmdline_opt_proc, полученная из нашей общей библиотеки libfmc. Эта библиотека имеет C API и содержит различные кроссплатформенные служебные функции. Он распространяется вместе с libytp, а также доступен из репозитория «Ямал».

После того, как мы добавили разбор аргументов, мы добавили код для загрузки ценных бумаг из файла с дополнительным шагом, чтобы исключить потенциальные дубликаты:// load securities from the file
vector<string> secs{istream_iterator<string>(secfile), istream_iterator<string>()};
// sort securities
sort(secs.begin(), secs.end());
// remove duplicate securities
auto last = unique(secs.begin(), secs.end());
secs.erase(last, secs.end());

Последующие шаги включали в себя логику открытия файла YTP для чтения и записи в строке 351 и создание экземпляра объекта Yamal с использованием функции ytp_yamal_new.mco.yamal = ytp_yamal_new(fd, &error);
if (error) {
lwsl_err(«could not create yamal with error %s\n», fmc_error_msg(error));
return 1;
}

Экземпляр «Yamal» сохраняется в контексте подключения, что обеспечивает его доступность во время обратного вызова получения WebSocket. Важно отметить, что мы проверяем указатель error, чтобы убедиться, что произошла ошибка. Такая методология последовательно применяется как в libfmc, так и в libytp. Внедрение этого подхода упрощает управление ошибками в этих библиотеках, смягчая типичные ошибки обработки ошибок, присущие библиотекам C.

«Ямал», по сути, представляет собой серию связанных списков с отображением в памяти. Эта архитектура обеспечивает впечатляющую производительность, сохраняя при этом адаптивность. Первый список используется для данных, а второй определяет логическую сегментацию данных по streamsstream фактически представляет собой комбинацию peer и channel. Здесь peer указывает издателя данных, а channel представляет глобальную категорию данных. В YTP или Ямальском транспортном протоколе прописан порядок распределения данных по потокам и объявления этих потоков на территории Ямала.

Для нашего проекта каждый поток Binance направляется в отдельный канал YTP. Для создания объявления потока требуется экземпляр объекта streams, который мы создаем с помощью функции ytp_streams_new.auto *streams = ytp_streams_new(mco.yamal, &error);

Впоследствии, для каждой ценной бумаги и обязательного фида Binance (в частности, bookTicker и trade) объявляется соответствующий поток YTP с ytp_streams_announce, который возвращает идентификатор потока:auto stream = ytp_streams_announce(streams, vpeer.size(), vpeer.data(),
chstr.size(), chstr.data(),
encoding.size(), encoding.data(),
&error);

Учитывая идентификатор потока, мы также можем искать информацию о потоке с помощью ytp_announcement_lookup. Память для потоковой информации, такой как имя канала, сопоставляется с памятью и хранится в самом файле «Ямал». Это удобно, поскольку, чтобы избежать ненужного копирования, мы хотели использовать string_view вместо string в качестве типа ключа в unordered_map C++, используемом для поиска потоков YTP.ytp_announcement_lookup(mco.yamal, stream, &seqno, &psz, &peer,
&csz, &channel, &esz, &encoding, &original,
&subscribed, &error);
mco.streams.emplace(string_view(channel, csz), stream);

Кроме того, для каждой безопасности и обязательного фида Binance мы добавляем соответствующее имя потока Binance в переменную пути i.path, которую мы используем для указания пути подключения WebSocket:i.path = mco->path.c_str();

Мы почти закончили. Нам просто нужно записать данные, которые мы получили от Binance на Ямал. Начиная со строки 210, мы сначала изолируем название потока Binance, а затем фактические данные обновления из сообщения. Затем мы ищем соответствующий поток YTP и записываем данные следующим образом:auto dst = ytp_data_reserve(mco->yamal, data.size(), &err);
// …
memcpy(dst, data.data(), data.size());
ytp_data_commit(mco->yamal, fmc_cur_time_ns(), where->second, dst, &err);

Обратите внимание, что сначала мы резервируем данные с помощью ytp_data_reserve, а затем фиксируем эти данные на Ямале с помощью ytp_data_commit. Данные недоступны для чтения до фиксации, а фиксация является атомарной. Это важно, потому что обеспечивает согласованность данных, гарантируя, что читатели никогда не увидят частично записанные данные.

Наконец, мы внесли несколько незначительных корректировок и улучшений в пример кода, и все готово.

Проверка и оценка производительности

Теперь пришло время протестировать наш обработчик кормов в действии. Для этого упражнения мы подготовили два файла, каждый из которых содержит небольшой список ценных бумаг. Чтобы начать, запустите один экземпляр обработчика канала следующим образом (снимите флаг —--us-region за пределами США):./release/market-data01-feedhandler/binance-feed-handler —us-region —securities market-data01-feedhandler/securities1.txt —peer feed —ytp-file mktdata.ytp

Чтобы проверить контент напрямую, нам понадобятся инструменты Ямала. В этом блоге эти утилиты создаются вместе с учебным проектом. Чтобы установить эти утилиты, вы можете либо загрузить один из выпусков, либо собрать его напрямую из исходного кода. Давайте сначала запустим yamal-tail, чтобы выгрузить содержимое файла на экран./release/dependencies/build/yamal/yamal-tail -f mktdata.ytp

Теперь мы можем запустить еще один обработчик фидов со вторым набором ценных бумаг../release/market-data01-feedhandler/binance-feed-handler —us-region —securities market-data01-feedhandler/securities2.txt —peer feed —ytp-file mktdata.ytp

Мы можем запустить yamal-stats, чтобы увидеть, что потоки, соответствующие второму набору бумаг, появляются и на Ямале../release/dependencies/build/yamal/yamal-stats mktdata.ytp

Наконец, мы можем запустить yamal-local-perf для мониторинга производительности шины Ямала. Этот инструмент прослушивает последние сообщения и отображает гистограмму различий между временем сообщения и временем его получения. В нашем случае, поскольку время сообщения непосредственно перед фиксацией сообщения на Ямале, разница соответствует времени, затраченному на передачу сообщения по Ямале../release/dependencies/build/yamal/yamal-local-perf —ytp-file mktdata.ytp

Интеграция данных для визуализации торговли

Завершая этот урок, мы расскажем вам, как использовать рыночные данные с Ямала. Мы добавили утилиту Python, которая отображает заданное количество сделок вместе с соответствующими лучшими ставками и предложениями, указанными во время сделки. Сценарий хорошо документирован и должен быть простым для понимания. Дополнительную информацию об API Yamal Python см. в документации по Ямалу.

Чтобы настроить среду Python, просто выполните:pip install -r market-data01-feedhandler/requirements.txt

Теперь, чтобы отобразить данные, выполните следующее:python3 market-data01-feedhandler/binance-view.py —ytp-file mktdata.ytp —security btcusdt —points 1000

Если требуется, чтобы серверная часть была явно указана, это можно сделать с помощью переменной среды MLPBACKEND. Вы можете найти дополнительную информацию о доступности бэкендов на веб-сайте документации по бэкендам matplotlib.

Для вашего торгового графика вы должны увидеть что-то вроде этого:

Заключение

Изучив это руководство, у вас теперь есть ноу-хау для разработки быстрого сервера каналов Binance и удобного инструмента для отображения рыночных данных. Включив Yamal в приложение C++, вы сможете эффективно обрабатывать большие объемы данных. Вам рекомендуется изменить этот основной код, чтобы он соответствовал различным торговым приложениям и аналитическим инструментам.

Источник