VIZANIXРазработка торгового ПО
Архитектура ботовИсполнение8 мин чтения

Подтверждение приёма — это не исполнение: машина состояний ордера

Биржа сообщила, что получила ваш ордер, и биржа сообщила, что он исполнился, — это разные события. Боты, которые их смешивают, показывают прибыль, которой не было.

Инженеры Vizanix · об авторе

МАТЕРИАЛ
8 минвремя чтения
РАЗДЕЛ
Архитектура ботов
ОПУБЛИКОВАНО
2026-08-28
ГЛАВ
6
ЧИТАТЬ ДАЛЬШЕ
3
ЯЗЫК
написано на русском
Инженерный разбор, а не пересказ документации.
STATE MACHINEACK ≠ FILLIDEMPOTENCYRECONCILE

Самая значимая строчка в слое исполнения — та, что решает, что значит «ордер прошёл». Ошибитесь в ней, и всё дальше по течению — учёт позиции, риск, P&L, бэктест, с которым вы сравниваете, — тихо стоит на ложной посылке.

Три разные истины

  • HTTP-ответ вернул 200. Ваш запрос дошёл до площадки и принят к обработке. Ничего не исполнилось.
  • Ордер появился в стакане. Он живой и висит. По-прежнему ничего не исполнилось.
  • По приватному потоку пришёл отчёт об исполнении. Что-то исполнилось: конкретный объём по конкретной цене.

Только третье меняет вашу позицию. В нашем микроструктурном движке это записано в проект как правило: подтверждение приёма — не исполнение, а исполнения берутся из приватного потока ордеров и сделок, но никогда — из ответа на запрос постановки.

Состояния, которые действительно нужны

python
class OrderState(Enum):
    PENDING   = auto()   # запрос собран, ещё не отправлен
    SENT      = auto()   # в полёте — исход честно неизвестен
    ACKED     = auto()   # площадка приняла; исполнено ноль
    PARTIAL   = auto()   # часть объёма исполнена, остаток живой
    FILLED    = auto()   # исполнен полностью
    CANCELLED = auto()   # живого остатка нет; частичные исполнения могли быть
    REJECTED  = auto()   # площадка отказала; ничего не исполнилось
    UNKNOWN   = auto()   # таймаут или обрыв — разрешается только запросом

UNKNOWN — состояние, которое чаще всего пропускают, и именно оно важно. Таймаут не равен отказу. Ордер может быть живым. Он может быть исполнен. Считать таймаут неудачей и переотправлять — классический способ получить две позиции.

Разрешение неизвестности

Именно поэтому каждый запрос, меняющий состояние, несёт клиентский ID ордера, сгенерированный до первой попытки и переиспользуемый во всех повторах. Это даёт две вещи: площадка отклонит настоящий дубль вместо его исполнения, и у вас есть ключ, по которому можно спросить, что произошло.

python
async def place(intent) -> Order:
    coid = client_order_id(intent)          # детерминированный, генерируется один раз
    try:
        resp = await api.create_order(intent, client_order_id=coid)
        return Order(coid, OrderState.ACKED, resp)
    except (Timeout, ConnectionError):
        # Мы НЕ знаем, дошло ли до площадки. Спрашиваем.
        return await resolve_unknown(coid)

async def resolve_unknown(coid) -> Order:
    for delay in (0.2, 0.5, 1.0, 2.0, 5.0):
        await asyncio.sleep(delay)
        found = await api.query_order(client_order_id=coid)
        if found:
            return Order.from_venue(found)
    alert("ордер %s не разрешён — останавливаю символ", coid)
    raise Unresolved(coid)

Последние две строки — то, что обычно опускают. Если состояние разрешить не удалось, правильное поведение — прекратить торговать этим символом и позвать человека. Бот, который продолжает с неразрешённым висящим ордером, гадает о собственной позиции.

Отмена — тоже изменение состояния

К отменам отношение такое же. Запрос на отмену, ушедший в таймаут, оставляет ордер в UNKNOWN, а не в CANCELLED. Оптимистично пометить его отменённым — это то, как бот получает висящий ордер, которого, по его мнению, не существует; ордер затем исполняется в наихудший момент в позицию, которой никто не управляет.

И отмена может проиграть гонку исполнению. Ордер способен исполниться в те микросекунды, что лежат между вашим решением отменить и обработкой отмены площадкой. Машина состояний обязана принять отчёт об исполнении по ордеру, который она отменяла, и считать исполнение главнее.

Сверка как страховка

Машины состояний расходятся с реальностью. Потоки теряют сообщения. Процессы перезапускаются. Единственная надёжная коррекция — периодическая сверка с площадкой: забрать открытые ордера и позиции, сравнить с локальным состоянием и разрешить каждое расхождение в пользу площадки.

Запускайте после каждого переподключения, после каждого перезапуска и по медленному таймеру как страховку. Логируйте найденные расхождения, а не чините их молча: сверка, которая начала регулярно что-то находить, сообщает, что выше по течению что-то сломано.

Правило, сказанное прямо

  1. Позиция меняется только по отчётам об исполнении.
  2. Каждый запрос, меняющий состояние, несёт клиентский ID ордера.
  3. Таймаут означает UNKNOWN, а UNKNOWN разрешается запросом.
  4. Неразрешимое состояние останавливает символ и зовёт человека.
  5. Сверка с площадкой при переподключении, перезапуске и по таймеру.

Пять правил. Большинство багов исполнения, которые нас просят починить, — нарушение одного из них.

Статья описывает инженерную практику. Это не инвестиционная рекомендация. Vizanix разрабатывает программное обеспечение и не обещает торговую доходность.

Блог

Читать дальше

Хотите такое у себя?

Мы пишем о том, что строим. Если нужно построить — напишите, разбор бесплатный.

Бриф

Получить оценку проекта

Четыре вопроса и контакт. Предоплата для разговора не нужна — если задача не наша, скажем сразу.

01Что вам нужно
02Биржа
03Рынок
04Стратегия
05Контакты

Проще написать напрямую? Telegram @vx_ceo

Обсудить систему