Я использовал язык программирования 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. Система должна была состоять из различных подсистем:
Feed-Handler Framework: получает рыночные данные от бирж; создает журналы для всех ценных бумаг; публикует обновления для других подсистем.
Strategies Framework: получает обновления рыночных данных от обработчика входных данных п.1; облегчает связь с системой управления заказами п.3; позволяет подключать к ней стратегии, принимающие решения о торговле акциями.
Order Management System: взаимодействует с биржей и системой стратегий п.2; поддерживает базу данных ордеров.
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)
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);bfDeveloper
18.12.2021 18:53+1И да, и нет. Для хороших трейсов на C++ backtrace_symbols совершенно недостаточен. То есть что-то вполне читаемое он выведет, но если сравните это с стеком из gdb, то удивитесь, насколько лучше бывает. backtrace_symbols содержит только символы, поэтому все неэкспортируемые функции, внезапно, лямбды оказываются совершенно неинформативными. Опять же нет номеров строк.
Поэтому для хорошего стека приходится тащить libdwarf и по дебажной информации восстанваливать качественнее. Но да, начинается всё с банального backtrace.
oleshii
19.12.2021 12:39приходится тащить libdwarf
К ядру или его модулю не прицепишь. runtime разный…
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)
По-моему, это мелкая но удачная ф-циональность, сглаживающая границы между интерпретируемыми и нативными языками.
Но смысл немного глубже, это не магия компилятора, а доступно и для юзера.
vgogolin
20.12.2021 02:51+2HFT при том, что данные только от BATS...
Кастомное решение, которое должно было помочь "профи", заработать деньги, но он не нашелся?..
-
Учитывая стоимость данных BATS, наличие только одного разработчика на проекте...
Больше вопросов, чем ответов.
Siemargl Автор
20.12.2021 08:52the 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.Стартап, хотели профи заменить программой, но не осилили разработать выигрышную стратегию.
v_sapronov
20.12.2021 09:06Не заменить - нет. В HFT системах трейдер и система нужны дпуг другу. Трейдер должен реагировать на макро сигналы и выставлять параметры от которых "пляшет" система высокочастотной торговли.
v_sapronov
20.12.2021 09:04Работал над AMM системой и системоц алгоритмической торговли в двух инвест банках.
Попробую ответить:
Далеко не обязательно подключать сразу все источники данных. Многие стратегии можно и нужно делать на одной бирже. В HFT на колоцированных серверах получать данные других бирж не имеет смысла в масштабах микросекунд - просто они будут всегда "опоздавшие" относительно биржи, где стоит сервер.
"Профи" - это тот кто умеет правильно управлять сигналами и писать их анализ для HFT. Такой трейдер умеющий делать стратегии внутри миллисекунды. Это редкий скилл, если не удача...
Часто данные покупают в любом случае для всей организации. Проектов у организации может быть много. Цена коллокации (необходимой для HFT) обычно дороже, чем сами данные.
MAXH0
Статья очень сильно напоминает многие введения к переведенным книгам "Как я добился успеха используя X (икс)". Самое бесячье в таких статьях то, описывается конкретный кейс, где это решение зашло, но не демонстрируется код и не сравниваются альтернативы. Т.е. статью явно бы украсили сровнение кодов C++ vs. D и их эффективности с выразительностью.
Siemargl Автор
Ну я не стал переводить все комменты, в т.ч тот, где написано, что код закрыт в рамках стартапа (который сдох ибо не наняли проф.трейдера=)
Но в целом для меня это очевидно, ибо языки с llvm бекендом при адекватной реализации фронта - плюс минус близки по итоговой скорости.