При trading platform order management development опасно сводить заявку к записи со статусами «создана», «исполнена» и «отменена». В рабочем контуре заявка может быть принята брокером, исполнена частично, получить ещё одно исполнение во время ожидания отмены, получить отказ на замену или обновиться отложенным сообщением. Поэтому надёжная платформа хранит не только текущую карточку заявки, но и неизменяемую историю подтверждённых событий.

Это критично для терминалов с брокерским подключением, внутренних dealing-систем, execution workflow и операционных платформ инвестиционных команд. Задача не ограничивается корректной надписью на экране: позиция, остаток, средняя цена исполнения, аудит и данные брокера должны описывать одну и ту же экономическую реальность.

Заявка — это жизненный цикл, а не один объект

После нажатия «Купить 1 000» происходят отдельные процессы: платформа фиксирует намерение, отправляет запрос брокеру, брокер или торговая площадка его обрабатывает, затем приходят подтверждения и сделки. Эти процессы асинхронны. Они могут завершиться сбоем независимо друг от друга, а сообщения не обязаны прийти в ожидаемом порядке.

Полезно разделить две сущности:

  • Намерение по заявке: инструмент, сторона, объём, тип заявки, лимитная цена, time-in-force, счёт и иные инструкции пользователя.
  • Факты исполнения: наблюдаемые подтверждения того, что брокер принял, отклонил, исполнил, заменил, отменил или скорректировал заявку.

В интерфейсе это одна заявка, но в backend обычно нужны внутренний platform order ID, уникальный идентификатор каждого исходящего запроса, broker order ID, цепочка предыдущих идентификаторов при replace и execution ID для отдельных сделок. Не стоит делать идентификатор брокера единственным бизнес-идентификатором: он внешний, может прийти с задержкой и иметь другую семантику жизненного цикла.

Модель состояний должна отражать и неопределённость

Order state machine trading platform должна описывать как бизнес-состояние, так и операции, которые ещё выполняются в интеграции. Практический начальный набор может включать:

  • Draft / validation failed: заявка не отправлена либо заблокирована локальной валидацией или risk-проверкой.
  • Pending submission: запрос надёжно поставлен в очередь доставки, но результат отправки ещё неизвестен.
  • Pending new / working: брокер обрабатывает заявку либо уже принял и разместил её.
  • Partially filled: часть объёма исполнена, часть остаётся открытой.
  • Pending cancel / pending replace: запрос на отмену или изменение в пути; исходная заявка ещё способна получить исполнение.
  • Filled, cancelled, rejected, expired: финальные результаты жизненного цикла.
  • Unknown / reconciliation required: имеющихся свидетельств недостаточно, чтобы безопасно определить итог.

Не переносите в UI протокольные значения без интерпретации. Например, в FIX поле ExecType описывает назначение конкретного execution report, а OrdStatus — актуальный статус заявки. Исполнение может прийти, пока замена ожидает обработки. Система должна сохранить оба факта, а не перезаписать историю последней меткой статуса.

Остаток — расчётная проекция, а не первичный факт

В простом случае leaves quantity = accepted order quantity − cumulative executed quantity. Но подтверждённый объём может измениться после replace, а correction или trade cancel способны скорректировать историю исполнения. Храните каждую сделку отдельно: объём, цену, время, execution ID и источник. Тогда cumulative quantity, остаток и средняя цена становятся воспроизводимой проекцией из дедуплицированных фактов и подтверждённых snapshot-ов брокера.

Partial fill handling: каждое исполнение имеет значение

Лимитная заявка на 1 000 единиц может исполниться как 250, затем 300 и затем 450. Уже после первого исполнения пользователь должен видеть «исполнено 250, открыто 750», а расчёт позиции, экспозиции и резервирования должен учитывать эти 250. Ожидание финального Filled создаёт неверную картину для операций и риска.

Каждый execution report нужно применять идемпотентно. Если брокер повторит сообщение, платформа не должна создать вторую сделку или дважды увеличить cumulative quantity. Лучший ключ дедупликации — уникальный execution ID от брокера. Если его нет, согласуйте со стороной интеграции документированный стабильный составной ключ; одних timestamp и цены недостаточно.

Повторы: timeout не означает отказ

Сетевой timeout говорит лишь о том, что платформа не получила ответ. Он не доказывает, что брокер не принял заявку. Повторная отправка с новым идентификатором может создать вторую активную заявку.

  1. Создавайте неизменяемый client request ID для действия пользователя.
  2. Используйте outbox pattern, чтобы намерение и исходящее сообщение фиксировались одной локальной транзакцией.
  3. Применяйте idempotency key или client-order-ID брокера, если он поддерживается.
  4. До новой отправки запрашивайте статус, проверяйте execution feed или историю запросов брокера.
  5. Показывайте честное состояние: «отправка подтверждается», а не выдавайте неопределённость за отказ.

Для cancel и replace действует та же дисциплина. Это отдельные операции со своим request ID, результатом и связью с цепочкой заявки. Такой подход является базой для FinTech и trading-систем, где надёжность доставки, восстановление и аудит должны проектироваться одновременно.

Гонки cancel/replace — нормальный сценарий

Нажатие Cancel не отменяет заявку мгновенно. Пока отмена не подтверждена брокером и площадкой, оставшийся объём может исполниться. То же относится к cancel/replace, когда меняются цена или объём.

Допустим, в заявке на 1 000 уже исполнено 300. Трейдер отменяет остаток, но во время pending cancel приходит ещё одно исполнение на 100. Корректный итог: исполнено 400, отменено 600 — а не 300 и 700. Если брокер возвращает «too late to cancel», интерфейс должен не только сообщить об отказе отмены, но и обновить финальное известное состояние заявки.

При replace сохраняйте всю цепочку идентификаторов и привязывайте новые исполнения к верному семейству заявок. Сокращение объёма, увеличение объёма и изменение цены могут иметь у брокера разные правила. Интеграционный адаптер должен преобразовать эти различия в единые внутренние команды и события.

Неизменяемые события и быстрая read-модель

Устойчивую архитектуру удобно строить в два слоя:

  • Append-only event ledger: входящие webhook-сообщения, FIX execution report, REST-ответы, действия оператора и результаты сверки сохраняются как факты. Нужны ID исходного сообщения, время получения, время брокера, защищённая ссылка на raw payload и версия parser-а.
  • Order projection: для быстрых экранов и API рассчитываются текущий статус, исполненный и остаточный объём, средняя цена, последнее обновление и флаги исключений.

Если raw-сообщения содержат чувствительные данные, маскируйте поля либо храните зашифрованные payload в аудиторском хранилище с контролем доступа. Главное правило: обновление текущей записи не должно удалять прежние факты. Это делает объяснимыми отложенные callbacks, trade correction, trade cancel и операционные расследования.

Показывайте пользователю понятный статус и доказательства

Трейдеру не нужно расшифровывать протокольные термины. Используйте короткий статус с контекстом: «Частично исполнена — 400 из 1 000», «Отмена запрошена — заявка всё ещё может исполниться», «Ожидается подтверждение брокера — не отправляйте повторно». В деталях должны быть время принятия, последнее обновление брокера, список исполнений, средняя цена, остаток и результат cancel/replace.

Это также задача продуктового дизайна. Иерархия информации, недоступные действия, подтверждения и состояния ошибок определяют безопасность работы с платформой. POLADO объединяет логику исполнения с UI/UX и продуктовым дизайном, чтобы интерфейс отражал реальный lifecycle заявки, а не упрощённую картину API.

Сверка — постоянный контроль, а не только end-of-day

Trading order reconciliation сопоставляет проекцию платформы с авторитетными данными брокера по заявкам и исполнениям. End-of-day файлы могут быть важной частью процесса, но ошибку лучше находить внутри торгового дня. Сверяйте открытые заявки, cumulative и leaves quantity, финальный статус, execution ID и среднюю цену.

При расхождении не перезаписывайте локальные данные молча. Создайте исключение, сохраните величину расхождения, snapshot брокера и локальную цепочку событий, затем выполните контролируемый replay или разрешение уполномоченным оператором. Тихая коррекция может сделать дашборд визуально правильным, но уничтожить объяснение, необходимое для аудита, поддержки и разбора инцидента.

Чек-лист перед релизом

  • Повторный execution report не меняет позицию и исполненный объём?
  • После timeout при отправке платформа выясняет результат до создания новой активной заявки?
  • Корректно ли учитывается fill, пришедший при pending cancel?
  • Обновляют ли replace reject и «too late to cancel» финальное представление заявки?
  • Сохраняются ли сообщения брокера, пришедшие не по порядку, и применяются ли они детерминированно?
  • Попадают ли расхождения сверки в видимую очередь с назначенным владельцем?

POLADO помогает спроектировать broker API, execution workflow, операционные интерфейсы и backend с аудитом как единый продукт. Посмотрите FinTech-контекст проектов FT10 и ESFX. Если вы планируете платформу с брокерской интеграцией, техническая консультация поможет до разработки зафиксировать state model, контракт интеграции и процесс reconciliation.