Всем привет!
Сегодня расскажу, как все устроено в коде нового коннектора.
OsEngine – проект с открытым кодом, поэтому посмотреть раздел, относящийся к коннектору можно прямо сейчас онлайн по адресу https://github.com/AlexWan/OsEngine/tree/master/project/OsEngine/Market/Servers/MoexFixFastSpot
Также можно просто скачать весь проект и открыть его в Visual Studio, чтобы смотреть более наглядно.
Если вы еще не в курсе, то весь OsEngine написан на языке C#, который сейчас считается одним из самых популярных для финтех приложений в нашей стране и в мире.
Разбор FIX-сообщений
В папке FIX находятся вспомогательные классы с разными полезностями для разбора и формирования FIX-сообщений.
Например, в абстрактном классе заголовка можно видеть стандартные поля типа SenderCompID (идентификатор отправителя) или MsgType (тип сообщений), а также перегруженную функцию ToString, отвечающую за формирование правильного байтового представления сообщения при помощи строковых функций стандартной библиотеки C#.
Второй более сложный пример – класс, представляющий тело сообщения типа “New Order Single” (новая заявка) и в его методе ToString формируется строка со всеми полями в необходимом порядке. Все теги (параметры) разделены символом SOH, который в юникоде обозначается как “u0001”.
За самую важную часть, разбор входящих FIX-сообщений, отвечает класс FIXMessage.
Здесь все поля сообщения разбиваются на пары тег=значение и помещаются в словарь для простого доступа по имени.
Например, fixMessage.Fields[“TestReqID”] даст значение поля с тегом TestReqID.
Можно было бы сделать все гораздо более абстрактно, но хотелось соблюсти баланс между универсальностью и простотой.
Всего в коннекторе 11 регионов кода, а также несколько дополнительных сервисных классов.
В начале при включении коннектор читает параметры конфигурации.
IP-адреса, порты, идентификаторы FIX-серверов, логины, пароли и счет пользователя задаются именно тут. Настройки рыночных данных, а именно конфигурация FIX/FAST Multicast UDP и шаблоны FAST-сообщений задаются в отдельной папке, путь к которой тоже есть в параметрах.
Далее мы подключаемся к MFIX Trade, MFIX Trade Capture по TCP для управления ордерами и получения отчетов об исполнении.
Рыночные данные по FAST
Для получения рыночных данных мы подключаемся к прослушиванию UDP Multicast потоков.
- 2 дублирующихся потока обновлений ордеров;
- 2 дублирующихся потока снэпшотов ордеров;
- 2 дублирующихся потока обновлений сделок;
- 2 дублирующихся потока снэпшотов сделок;
- 2 дублирующихся потока определений инструментов.
Потоки статистики и статусов инструментов (ISF, не показан на схеме) не используются. Подключение к серверу TCP Replay происходит по мере необходимости, в случае если произошла потеря данных.
Так что мы храним 10 UDP сокетов, по которым постоянно идут рыночные данные и определения инструментов.
Разбор FAST-сообщений
Для разбора FAST-сообщений использована библиотека OpenFAST.net, а точнее ее порт на C#, так как оригинальная библиотека написана на Java.
В документации описано аж два формата данных, передаваемых от FAST-серверов Мосбиржи. И это разные форматы для UDP и TCP.
В данных, передаваемых по UDP первые 4 байта соответствуют значению поля MsgSeqNum (номер сообщения) и за счет специфики UDP мы всегда читаем сообщение целиком. В декодер OpenFAST мы передаем прочитанные байты, начиная с 4го. На выходе получаем декодированное FIX-сообщение.
Пример чтения сообщения, поступившего по одному из UDP сокетов.
Виды потоков данных
Данные по UDP идут в двух основных формах. Рассмотрим на примере данных о сделках (тики, трейды).
- В потоках обновлений идут данные в реальном времени. Это данные вида «Сбербанк продажа объем такой-то по цене такой-то + время + еще множество данных».
- В потоках восстановления идут, так называемые, снэпшоты данных – это просто по кругу идут блоки данных, содержащие все обновления по всем инструментам с начала торгового дня до текущего момента. Например, это может быть «Сбербанк и дальше перечисление массива сделок, которые состоялись сегодня».
Поэтому при подключении коннектор первым делом собирает данные из снэпшотов (что сегодня уже успело произойти к настоящему моменту), а потом применяет к этим снэпшотам обновления.
Таким образом, можно работать, когда получили всю информацию о том, что уже произошло и начали получать обновления в реальном времени о том, что происходит именно сейчас.
Восстановление пропущенных данных
Если в номерах сообщений обнаруживается пробел, то пропущенные сообщения необходимо восстанавливать. В документации описано два способа:
- Восстановление из снэпшотов.
- Восстановление по TCP.
У первого способа, из снэпшотов, есть существенный недостаток – снэпшоты транслируются по всем инструментам и время ожидания формирования снэпшота данных, содержащего пропущенные сообщения, может быть весьма значительным.
Второй способ, восстановление по TCP, позволяет подключиться к специальному серверу восстановления и запросить пропущенные данные.
Специфика там такая, что мы сообщения серверу отправляем в FIX-формате, а он нам отвечает в FAST-формате. Причем формат сообщений тут отличается от тех, что приходят по UDP. В первых 4 байтах тут содержится длина очередного FAST-сообщения. Зная эту длину, следует читать из сокета именно такое количество байт и только после этого приступать к декодированию и разбору FAST-сообщения.
Информация о бумагах
Информация о торгуемых инструментах передается в цикле безо всяких запросов, то есть мы не можем управлять началом передачи и должны просто слушать поток, пока не получим всю информацию. И даже на тестовом сервере прослушивание полного цикла информации занимало 11 минут. В связи с этим, при первом запуске коннектор дожидается получения всей этой информации и сохраняет ее в базу данных на диске (LiteDB). При повторном запуске информация из кэша (БД) моментально загружается, и нет необходимости ждать ее получения.
Параллельная обработка
Потоки данных по UDP идут по дублированным каналам. То есть, например, есть два потока «обновления по сделкам А» и «обновления по сделкам B», и сделано это для минимизации потерь данных (то есть, если вдруг потеряли пакет с номером Х в потоке А, то скорее всего пакет с номером X придет в потоке B). Поэтому данные из обоих таких потоков читаются, и контролируется порядок номеров сообщений. Таким образом мы избегаем дублей данных и можем обнаруживать пропуски в случаях, когда какой-то пакет потерялся в обоих потоках.
Далее сообщения складываются в общую очередь, которую разбирает уже другой поток. Это позволяет считывать данные без задержек, так как прием и обработка распараллелены.
Многопоточная архитектура
Коннектор создает множество дополнительных потоков:
- Поток контроля соединения с серверами – контроль времени поступления последних данных для FAST и отправка Test Request/ Heartbeat для FIX-серверов;
- Потоки чтения данных ордеров (Orders Incremental A+B, Orders Snapshots A+B);
- По аналогии – чтение данных сделок;
- Поток чтения данных доступных инструментов;
- Отдельные потоки разбора очередей сообщений с данными по ордерам и сделкам. Тут происходит контроль пропусков и восстановление данных при необходимости;
- Два потока для MFIX Trade, MFIX Trade Capture – управление и контроль ордеров;
- Поток восстановления данных через TCP Historical Replay сервер.
Реализованные команды
В коннекторе реализованы все доступные в FIX-интерфейсе виды сообщений (команд).
В том числе Order Cancel Replace Request, более известный как Move. Если не знаете, то при помощи этой команды можно быстро отменить уже существующий ордер и создать новый взамен с другими параметрами цены и объема. Биржа создает новый ордер, отменяет старый, но в OsEngine это происходит как изменение цены и/или объема существующего ордера, отсюда и Move (от англ. «перемещение»).
Что не реализовано, и ограничения коннектора.
К сожалению, Мосбиржа исключила из FIX-интерфейса команду для запроса состояния ордера Order Status Request.
В документации оно просто зачеркнуто, поэтому у коннектора нет возможности запросить список активных ордеров в случае потери соединения. Они эту проблему решили добавлением функциональности Cancel On Disconnect, то есть все активные ордера будут сняты биржей при обрыве связи.
Нет возможности получить информацию о портфеле. Через FIX или FAST подключения информации о вашем торговом счете просто нет. Поэтому за состоянием счета участника торгов придется смотреть либо через ЛК/терминал брокера, либо через другой коннектор OsEngine или терминал Мосбиржи.
Реализованы только основные режимы торгов фондовой секции:
При необходимости, конечно, все легко расширить до поддержки всех остальных доступных режимов. Такое решение было принято, так как OsEngine не поддерживает выбор режима торгов, а на Мосбирже пара данных «символ инструмента – режим торгов» является отдельным инструментом. То есть SBER@TQBR и SBER@SMAL – это разные инструменты.
Кто не знал, у того же Сбера список режимов торгов не уместился на экране (https://www.moex.com/ru/issue.aspx?board=TQBR&code=SBER).
Заключение
Коннектор MoexFixFastSpot в OsEngine реализует весь основной функционал торгов через прямой доступ к рынку Московской Биржи на фондовой секции.
Это подтверждено его сертификацией от специалистов Мосбиржи.
Реализация надежна и неоптимальна, что предполагает возможность последующих улучшений.
При желании может быть легко расширен и для поддержки специальных режимов торгов или использования дополнительных параметров.
Спасибо за внимание и удачных алгоритмов!
OsEngine: https://github.com/AlexWan/OsEngine
FAQ: https://o-s-a.net/os-engine-faq
Поддержка OsEngine: https://t.me/osengine_official_support
Регистрируйся в АЛОР и получай бонусы: https://www.alorbroker.ru/open
Сайт АЛОР БРОКЕР: https://www.alorbroker.ru
Раздел «Для клиентов»: https://www.alorbroker.ru/openinfo/for-clients
Программа лояльности от АЛОР БРОКЕР и OsEngine: https://smart-lab.ru/company/os_engine/blog/972745.php