Лимиты позиций, маржа, fat-finger границы и kill switch обязаны ответить на каждой заявке до того, как она уйдёт с гейтвея. Вот как эти проверки живут внутри пути заявки, а не рядом с ним: какое состояние лежит в памяти, что пересчитывается инкрементально и что происходит после перезапуска. Сначала — ограничения.
Заявка приходит на гейтвей. Прежде чем уйти на площадку, что-то должно решить, разрешено ли счёту её отправлять. Это решение принимается по каждой заявке, включая подавляющее большинство совершенно нормальных, поэтому его цену платит весь обычный трафик, а не только отказы.
Это ограничение стоит назвать первым. Риск-проверка, поставленная в путь заявки, — налог на ввод заявок. Инженерный вопрос не в том, как сделать проверку умной, а в том, как сделать её достаточно маленькой, чтобы трейдеры её не чувствовали, и достаточно честной, чтобы она всё же отклоняла то, что обязана отклонить.
Горячий путь отвечает ровно на один вопрос: можно ли отправить эту заявку прямо сейчас, исходя из того, что мы знаем об этом счёте сейчас. Проверки, отвечающие на этот вопрос, остаются. Проверки, отвечающие на другой, уезжают наружу.
Всё остальное работает рядом с путём, на том же состоянии, не удерживая заявку. Оно определяет лимиты, которые применяет горячий путь, но не стоит между трейдером и площадкой.
Разделяет не категория, а вопрос. В пути: можно ли отправить эту заявку. Рядом с путём: какими должны быть лимиты. Всё, что отвечает на второй вопрос и при этом задерживает заявку, — ошибка проектирования, какой бы важной проверка ни была.
Состояние, нужное предторговой проверке, — текущие позиции, рабочие заявки, использованная и доступная маржа, конфигурация лимитов — лежит в памяти того процесса, который принимает решение. Не в кеше перед базой, не за сетевым вызовом. В процессе.
Дело не только в скорости, хотя запрос на порядки дороже поиска в локальном массиве. Дело в корректности. База хранит позицию такой, какой её записали. Риск-проверке нужна позиция с учётом заявок, отправленных мгновение назад и ещё не исполненных, не подтверждённых и не сохранённых. Читая из хранилища, вы проверяете себя против прошлого, которое уже обогнал ваш собственный поток.
На практике это формирует процесс так же, как формируется любой low latency компонент:
База — это место, где позиция записана. Это не место, где позиция известна.
Полный пересчёт экспозиции и маржи счёта обходит каждую позицию и каждую рабочую заявку. Эта стоимость растёт вместе с размером стакана, то есть риск-проверка замедлялась бы ровно для тех клиентов, кто торгует больше всех. Поэтому горячий путь ничего не пересчитывает. Он применяет дельту.
Счёт несёт текущие агрегаты — чистая и валовая экспозиция по инструменту и по группе, использованная маржа, номинал в полёте. Входящая заявка даёт небольшое изменение этих агрегатов, изменённые значения сравниваются с лимитами, и заявка принимается или отклоняется. Работа пропорциональна заявке, а не портфелю.
Полный пересчёт никуда не девается — он идёт по расписанию, при смене параметров маржи и как периодическая самопроверка против инкрементального результата. Он выполняется вне пути, на копии, и его результат либо подменяет текущий, либо поднимается как расхождение. Инкрементальное состояние, тихо уезжающее от истинного, хуже, чем отсутствие проверки, поэтому сравнение не опционально.
На риск-пути, который мы строим, сама предторговая проверка укладывается в <1 ms. Эта цифра описывает решение на состоянии в памяти, а не весь путь заявки от клиента до площадки и обратно.
Kill switch используется ровно тогда, когда уже что-то не так. Значит, строить его поверх механики, которая сама может быть источником проблемы, нельзя. Это отдельный путь со своими правилами.
Остановить новые заявки — лёгкая половина. Трудная — что kill switch делает с заявками, уже стоящими на площадке: снятие котировок и отмена рабочих заявок должны быть возможны при отключённом пути отправки. Этот путь отмены заслуживает отдельного тестирования, потому что его используют в худший день, а не в обычный.
Состояние в памяти — производный вид от долговечной записи. Именно это делает перезапуск переживаемым. Каждое событие, меняющее риск-состояние — принятая заявка, снятое резервирование, применённое исполнение, изменённый лимит, включённый kill switch — дописывается в журнал на локальной машине до того, как по нему что-то сделают дальше по цепочке.
Время восстановления тогда зависит от длины журнала и доступности drop copy, а не от размера книги позиций, и режим отказа при любой неизвестности один: не давать счёту торговать.
Честные ограничения стоит назвать прямо: именно они решают, подходит ли такая архитектура вообще:
amBrain строит такие предторговые риск-пути для брокеров и проп-фирм, с горячими путями на Rust. Команда работает над торговой инфраструктурой из Еревана, Армения, с 2019 года.
Наша инженерная команда специализируется на решениях FinTech. Давайте обсудим, как воплотить ваш проект в жизнь.