Мы перезапустили бумажную стратегию на полпути сделки и получили последовательность заявок, отличавшуюся от той, что была до перезапуска. Те же рыночные данные, та же версия стратегии, тот же баланс счёта. Расчёт сигнала совпал. А вот представления стратегии о позиции и ожидающих исполнения заявках — нет.
Мы разбирали это расхождение целый день, повторно воспроизводя сценарий. Вывод оказался простым: для вашей программы перезапуск — это рыночное событие. Если не проверить, как стратегия восстанавливает состояние, чистый бэктест может скрыть проблему: бумажная система забывает, чем владеет.
09:10 — Мы выбрали самую обычную позицию
Тестовая стратегия торговала ликвидным бессрочным контрактом: открывала длинную позицию, когда короткая скользящая средняя пересекала длинную снизу вверх, и закрывала её при обратном пересечении. Мы начали повторное воспроизведение с уже открытой небольшой позицией и выставленной лимитной заявкой reduce-only на её сокращение. Для восстановления нужно было учесть два факта: чем мы владели и что уже поручили бирже сделать.
На контрольной точке на счёте было 0.04 контракта. Заявка на 0.01 оставалась открытой. Процесс стратегии хранил оба значения в памяти, но при запуске запрашивал только позицию. Он считал, что сохранённой в памяти заявки больше нет.
09:25 — Появилась первая дублирующая заявка
После перезапуска стратегия увидела существующую длинную позицию, выполнила логику сигналов и отправила ещё одну заявку на сокращение на 0.01. Теперь на бумажной площадке было две активные заявки. Каждая по отдельности была корректной. Вместе же они могли продать вдвое больше нужного, если бы исполнились обе.
Сначала мы обвинили цикл обработки сигналов. Но проблема была не в нём, а в неполном снимке состояния. Стратегия спрашивала: «Какая у меня позиция?», но не спрашивала: «Какие заявки всё ещё активны?»
| Состояние после перезапуска | Что считал процесс | Что было на счёте |
|---|---|---|
| Позиция | Длинная 0.04 | Длинная 0.04 |
| Открытые заявки на сокращение | Нет | Две по 0.01 каждая |
| Планируемая экспозиция после одного исполнения | Длинная 0.03 | Может стать длинной 0.02 |
10:00 — Мы исправили восстановление, а затем нашли проблему со временем событий
Мы изменили запуск: теперь перед тем, как разрешить принимать новые решения, стратегия восстанавливает состояние по позициям и открытым заявкам на счёте. Дублирование исчезло. Затем мы усложнили сценарий отключения: одна заявка исполнилась, пока стратегия была офлайн, а уведомление об исполнении пришло уже после переподключения.
Снимок счёта уже отражал исполнение. Позднее уведомление уменьшило локальную позицию ещё раз. Несколько секунд стратегия считала, что у неё 0.02 контракта, тогда как на счёте было 0.03. Следующая ребалансировка основывалась на несуществующем дефиците.
Мы добавили правила сверки: начинать с состояния в снимке счёта, отбрасывать исполнения, уже учтённые в нём, по идентификаторам событий и не отправлять заявки до завершения начальной синхронизации. Уведомление может прийти с опозданием или повториться. Восстановление должно справляться с обоими случаями.
13:40 — Повторное воспроизведение выявило незаметное расхождение
Мы повторили один и тот же ценовой сценарий для исходного запуска и запуска после перезапуска. Сравнение итогового P&L не выявило бы проблему: после разворота рынка обе версии завершили работу с одинаковой позицией. Расхождение обнаружилось при сравнении событий заявок.
Мы записывали каждое решение вместе с прочитанным состоянием: позицией, открытыми заявками, идентификатором последнего обработанного исполнения, значением сигнала и версией стратегии. Стало понятно, почему возникло первое расхождение. В одном запуске стратегия видела активную заявку, в другом — пустой список. Позже один запуск применил исполнение дважды.
Совпадение итоговых балансов не доказывает, что поведение было одинаковым. Сравнивайте последовательности решений и заявок, особенно при восстановлении после сбоев.
16:20 — Что мы сделаем иначе в следующий раз
Мы слишком долго повторно воспроизводили ценовые данные, прежде чем проверили изменения состояния счёта. В следующий раз сначала смоделируем сбои и почти не будем менять рыночные данные. Так ошибку в программе будет проще заметить: резкое движение рынка не отвлечёт от причины.
- Перезапустите систему с открытой позицией и частично исполненной заявкой.
- Отключитесь после отправки заявки, затем переподключитесь до того, как придёт уведомление об исполнении.
- Передайте одно и то же событие исполнения дважды и убедитесь, что состояние изменится только один раз.
- Не разрешайте отправку новых заявок, пока не будут сверены позиции и открытые заявки.
- Сравните журналы решений и заявок в непрерывном запуске и запуске после перезапуска.
Мы также научились сохранять снимок состояния для восстановления вместе с версией стратегии и журналом событий. Благодаря этому сбой можно было воспроизвести за минуты, не полагаясь на чью-то память о точной последовательности переподключения.
Бумажная стратегия, которая работает правильно, только пока её процесс не останавливается, ещё не прошла полноценную проверку. Перезапустите её с открытой позицией, задержите поступление исполнений и проверьте каждую последующую заявку. Цель не в том, чтобы доказать, что сбоев не бывает. Нужно сделать восстановление наблюдаемым до того, как бумажный счёт неожиданно преподаст вам тот же урок.
← Все статьи


