Я использовал язык программирования D для реализации платформы высокочастотного трейдинга (HFT). Я был вполне удовлетворен полученным опытом и решил поделиться тем, как я пришел к этому. Этот путь был тернист.


В 2008 году на Amazon я наткнулся на книгу под названием "Learn to Tango with D". Это вызвало у меня любопытство, и я решил изучить D подробнее. Это привело меня к Digital Mars и Уолтеру Брайту. Впервые я услышал об Уолтере, когда узнал о Zortech C++, первом нативном компиляторе C++. Его работа оказала огромное влияние на мой путь изучения C++. Поэтому я сразу же заинтересовался языком только потому, что он его автор, и был рад узнать, что он вместе с Андреем Александреску работает над второй версией. Тем не менее, я решил подождать, пока они продвинутся дальше в работе над новой версией, прежде чем окунуться в проект.

В 2010 году я купил книгу Андрея "The D Programming Language", как только она была опубликована, и начал читать. В то время я работал в BNP Paribas, используя C++ для оптимизации своей HFT-платформы, поэтому высокая производительность доминировала в моих мыслях. Когда я увидел, что классы в D являются ссылочными типами, а их функции по умолчанию виртуальные, я был разочарован. Я не понимал, как это может быть полезно для программирования с низкими задержками (латентностью). В то время я был слишком занят работой, чтобы углубляться в изучение, поэтому я отложил книгу и язык в сторону.

В 2014 году я начал готовиться к новому начинанию. В рамках этого я начал работать над новым фреймворком обработчика входных данных (Feed-Handler) на C++, используя свою собственную давно поддерживаемую библиотеку C++ низкоуровневых компонентов, полезных в высокопроизводительных приложениях с низкими задержками. Книга Андрея снова попалась мне на глаза, и я решил взглянуть на нее еще раз.

На этот раз я дочитал книгу до конца и понял, что мое первоначальное впечатление было ошибочным. Я обнаружил, что мне нравятся возможности метапрограммирования в D и его поддержка программирования в функциональном стиле. К концу книги я был готов дать шанс D.

Я начал с переноса своей библиотеки C++ и обработчика входных данных на D. Это было несложно. В своем коде на C++ я использую очень мало наследования, предпочитая композицию и конкретные классы (Concrete class). Я обнаружил, что довольно продуктивно работаю со структурами, шаблонами и миксинами (mixin) D. При этом я внимательно следил за показателями производительности. Когда оказалось, что D дает такую же производительность, как и мой код на C++, я был куплен с потрохами. Я обнаружил, что D намного элегантнее, чище, читабельнее и проще в поддержке. Я перешел на D и больше не оглядывался назад.

Моей целью было разработать законченную систему HFT с использованием D. Система должна была состоять из различных подсистем:

  1. Feed-Handler Framework: получает рыночные данные от бирж; создает журналы для всех ценных бумаг; публикует обновления для других подсистем.

  2. Strategies Framework: получает обновления рыночных данных от обработчика входных данных п.1; облегчает связь с системой управления заказами п.3; позволяет подключать к ней стратегии, принимающие решения о торговле акциями.

  3. Order Management System: взаимодействует с биржей и системой стратегий п.2; поддерживает базу данных ордеров.

  4. Signal Generator: получает обновления рыночных данных от от обработчиков входных данных п.1; генерирует различные сигналы в виде значений индикаторов, прогнозов цен на акции и т.д.; посылает различные сигналы стратегиям п.2.

В конце концов, я нашел новую структуру данных и наилучший дизайн для моего обработчика входных данных. Я разработал новую версию полностью на D. Эта реализация в существенной степени использует шаблоны. Мне нравится синтаксис шаблонов в D, и в целом я нахожу сообщения об ошибках более понятными, чем сложные сообщения об ошибках, к которым я привык в C++. Мне нужно было опуститься до ассемблера для некоторых специфических инструкций x86, и в D это было легко сделать.

Позже мне понадобилось работать с конфигурационными файлами. Я предпочитаю писать свои файлы конфигурации на Lua, легковесном скриптовом языке, который легко интегрировать в программу в качестве расширения через его C API. Для этого я нашел привязку D Lua под названием DerelictLua. Используя, опять же, средства метапрограммирования D, я разработал очень простой и практичный способ взаимодействия с Lua через DerelictLua. Примечание редактора: с тех пор DerelictLua устарел; новые проекты должны использовать его преемника, bindbc-lua.

Обработчик входных данных на бирже Bats работает с 31 параллельным каналом, поэтому эффективнее использовать многопоточность. Для этого я решил не использовать средства многопоточности, предоставляемые Phobos. Я чувствовал, что мне нужно больше контроля в такой среде с низкой задержкой, особенно возможность привязать каждый поток к определенному ядру. Я предпочел использовать библиотеку pthreads и ее работу с affinity. Благодаря совместимости с C ABI в D это было очень прямолинейно.

Я работаю в FreeBSD. Для своих коммуникационных потребностей я использую функции ядра для очередей и сокетов. Та же функциональность доступна на macOS, моей предпочтительной платформе для разработки. D не помешал мне использовать эти API ни на macOS, ни на FreeBSD. Это было так же просто, как использование функций ядра для очередей из C.

Несколько замечаний о проблемах и ограничениях:

  • Я раз столкнулся с ошибкой компилятора. Я нашел обходной путь, так что это не стало блокирующим фактором. Я смог воспроизвести ее с помощью нескольких строк кода и связался с сообществом D. Они решили проблему и внесли исправление в более позднюю версию компилятора.

  • Я не использовал сборщик мусора (GC) в D. Однако это не является выпадом против D или его GC. В такой системе с низкой задержкой, как эта, даже использование malloc и free может стать дорогостоящим, поэтому я не собираюсь рисковать использовать недетерминированную систему с непредсказуемой задержкой. Вместо этого я использовал свою библиотеку для обработки выделения/деаллокации памяти через списки свободных блоков, с предварительным выделением памяти. Как следствие, я также отказался от использования стандартной библиотеки D.

  • Мне нужно было работать со строками ASCII фиксированного размера, которые не имеют NUL-терминатора, а вместо этого дополнены пробелами в конце. Без стандартной библиотеки мне было проще работать с ними в С-стиле с помощью указателей.

Я был единственным разработчиком в этом проекте, но успешно завершил его за относительно короткий срок. Большая заслуга D и его продуктивности, читабельности и простоте модификаций.

Немного пояснений автора из комментариев (прим.ред):

Не использовались ни IDE, ни отладчик.
Я использовал Sublime Text, который имел плагин для D для отступов и подсветки синтаксиса.
Использовал терминал и систему сборки DUB и dmd и ldc2 в качестве компиляторов.
Для такого рода приложений с огромным количеством данных и многопоточностью отладчик может быть полезен, но чаще всего проблемы удается найти с помощью логов.
В D генерировать логи очень просто, достаточно использовать writeln и структуру, и все работает. В C++ вам нужно написать функцию типа dump() вручную.
Время от времени я пробовал использовать lldb в качестве отладчика, но использовал его нечасто, лишь изредка.

Никакой базы данных, так как никакие базы данных не подходят для такого типа приложений с низкой задержкой.

Все необходимые базы данных создаются на месте и находятся в памяти, в основном это хэш-таблицы.

Имеется только асинхронное архивирование сообщений между OMS и биржей, просто на случай, если что-то пойдет не так, мы можем создать требуемое состояние системы и продолжить работу.

Комментарии (11)


  1. MAXH0
    18.12.2021 15:01
    +14

    Статья очень сильно напоминает многие введения к переведенным книгам "Как я добился успеха используя X (икс)". Самое бесячье в таких статьях то, описывается конкретный кейс, где это решение зашло, но не демонстрируется код и не сравниваются альтернативы. Т.е. статью явно бы украсили сровнение кодов C++ vs. D и их эффективности с выразительностью.


    1. Siemargl Автор
      18.12.2021 23:04
      +1

      Ну я не стал переводить все комменты, в т.ч тот, где написано, что код закрыт в рамках стартапа (который сдох ибо не наняли проф.трейдера=)

      Но в целом для меня это очевидно, ибо языки с llvm бекендом при адекватной реализации фронта - плюс минус близки по итоговой скорости.


  1. oleshii
    18.12.2021 15:45

    В C++ вам нужно написать функцию типа dump() вручную.

    Писать почти ничего не надо. Про MSVC/cl не уверен, в gcc/g++/clang имеется intrinsinc
    int backtrace (void **buffer, int size);
    Пример вызова:
    void* buf[10];
    backtrace(buf, 10);
    Если не хочется самому возиться с разбором полученых буферов, есть ещё одна функция:
    void backtrace_symbols_fd (void *const *buffer, int size, int fd);
    Она принимает результат работы первой и дескриптор вывода. Пример:
    backtrace_symbols_fd(buf, 10, STDERR_FILENO);
    В драйверах под OS X использовал char ** backtrace_symbols (void *const *buffer, int size);


    1. bfDeveloper
      18.12.2021 18:53
      +1

      И да, и нет. Для хороших трейсов на C++ backtrace_symbols  совершенно недостаточен. То есть что-то вполне читаемое он выведет, но если сравните это с стеком из gdb, то удивитесь, насколько лучше бывает. backtrace_symbols  содержит только символы, поэтому все неэкспортируемые функции, внезапно, лямбды оказываются совершенно неинформативными. Опять же нет номеров строк.

      Поэтому для хорошего стека приходится тащить libdwarf и по дебажной информации восстанваливать качественнее. Но да, начинается всё с банального backtrace.


      1. oleshii
        19.12.2021 12:39

        приходится тащить libdwarf

        К ядру или его модулю не прицепишь. runtime разный…


    1. Siemargl Автор
      18.12.2021 23:14

      Смысл в неявной декодировке структуры компилятором.

      import std.stdio;
      
      struct StrX {
          int x;
          char [10] text;
          double z;
      }
      
      int main() 
      {
          StrX sx = {7, " fx", 3.6};
          writeln(sx);
          return 0;
      }
      

      Вывод StrX(7, " fx\0\0\0\0\0\0\0", 3.6)

      По-моему, это мелкая но удачная ф-циональность, сглаживающая границы между интерпретируемыми и нативными языками.

      Но смысл немного глубже, это не магия компилятора, а доступно и для юзера.


  1. mrbald
    19.12.2021 11:28
    +1

    Понятно теперь, почему чел работу в марте поменял :-D


  1. vgogolin
    20.12.2021 02:51
    +2

    1. HFT при том, что данные только от BATS...

    2. Кастомное решение, которое должно было помочь "профи", заработать деньги, но он не нашелся?..

    3. Учитывая стоимость данных BATS, наличие только одного разработчика на проекте...

      Больше вопросов, чем ответов.


    1. Siemargl Автор
      20.12.2021 08:52

      the system ran in production in 2019 till Mars 2020, when I was working with a startup. unfortunately they didn’t have winning strategy, even the system was running perfectly, with a very low latency.
      they didn’t hire a professional trader to get the best of the system, so I left that startup, as they didn’t fulfill a lot of promises.

      Стартап, хотели профи заменить программой, но не осилили разработать выигрышную стратегию.


      1. v_sapronov
        20.12.2021 09:06

        Не заменить - нет. В HFT системах трейдер и система нужны дпуг другу. Трейдер должен реагировать на макро сигналы и выставлять параметры от которых "пляшет" система высокочастотной торговли.


    1. v_sapronov
      20.12.2021 09:04

      Работал над AMM системой и системоц алгоритмической торговли в двух инвест банках.

      Попробую ответить:

      1. Далеко не обязательно подключать сразу все источники данных. Многие стратегии можно и нужно делать на одной бирже. В HFT на колоцированных серверах получать данные других бирж не имеет смысла в масштабах микросекунд - просто они будут всегда "опоздавшие" относительно биржи, где стоит сервер.

      2. "Профи" - это тот кто умеет правильно управлять сигналами и писать их анализ для HFT. Такой трейдер умеющий делать стратегии внутри миллисекунды. Это редкий скилл, если не удача...

      3. Часто данные покупают в любом случае для всей организации. Проектов у организации может быть много. Цена коллокации (необходимой для HFT) обычно дороже, чем сами данные.