К содержанию

Три отказа, которые не видно в метриках

Система показывает зелёные графики и при этом не делает свою работу. Разбор трёх случаев из собственной торговой системы и того, что с ними делать.


Самый дорогой отказ — не тот, при котором сервис падает. Падение видно сразу: приходит оповещение, кто-то просыпается, чинит. Дорогой отказ — когда всё зелёное, метрики в норме, а работа не делается.

За несколько лет эксплуатации собственной торговой системы я собрал три таких случая. Все три объединяет одно: мониторинг показывал, что произошло, но ни один график не показывал, что не произошло.

Первый: защита от дублей, которая заблокировала сама себя

В системе было правило: одну позицию нельзя закрывать дважды. Реализовано просто — перед закрытием идентификатор кладётся в множество «закрывается сейчас», а функция закрытия проверяет это множество и выходит, если запись уже есть.

Проблема появилась, когда проверку продублировали в двух местах. Планировщик клал идентификатор в множество и запускал фоновую задачу. Задача вызывала функцию закрытия. Функция смотрела в множество, находила там свою же запись, оставленную секунду назад планировщиком, и выходила.

Ни одна позиция не закрывалась. Ни стоп-лосс, ни тейк-профит, ни закрытие по времени. В журнале — чисто: закрытие «пропущено, уже выполняется». Формально правда.

Общий вывод: у защиты от повторного выполнения должен быть ровно один владелец. Если охранник стоит в двух местах на одном пути, второй охранник видит следы первого и принимает их за чужие.

Второй: уборщик, который не знал, в каком режиме работает

Отдельный механизм сверял позиции в системе с позициями на бирже и удалял «призраков» — записи, которым на бирже ничего не соответствует.

В режиме бумажной торговли позиций на бирже нет по определению. Механизм честно спрашивал биржу, честно не находил там ничего и честно удалял все позиции как рассинхрон. С нулевым результатом, потому что удаление призрака не приносит ни прибыли, ни убытка.

Итог: сделки открывались, «закрывались» уборщиком с нулевым результатом, и статистика показывала идеальный ноль вместо реальных цифр.

Вывод: любая автоматическая уборка обязана проверять режим работы перед тем, как что-то удалить. И почти всегда стоит начинать с режима «показывать, что было бы удалено» вместо реального удаления.

Третий: правильные данные, положенные поверх правильных данных

Панель наблюдения показывала капитал системы. Значение бралось из внутреннего учёта, а затем поверх него записывался баланс биржевого кошелька — «чтобы показывать настоящие деньги».

В бумажном режиме настоящий кошелёк пустой. Панель показывала ноль, риск-контур видел просадку 99,99 процента относительно запомненного пика и останавливал торговлю. Каждая из двух частей работала правильно. Ошибка была в том, что они писали в одно поле.

Вывод: если значение может приходить из двух источников, у поля должен быть один владелец и явное правило подмены. «Заодно обновим отсюда тоже» — начало почти любого расследования, которое потом занимает три дня.

Что с этим делать

Три практики, которые с тех пор стоят у меня по умолчанию в любом проекте — и в клиентском тоже:

Считайте не только события, но и их отсутствие. Метрика «закрыто позиций» бесполезна без метрики «сколько раз закрытие было пропущено и почему». Причина пропуска — обязательная метка.

Проверяйте следствия, а не действия. Не «отправили команду на закрытие», а «через десять секунд позиция действительно исчезла». Расхождение между намерением и результатом — самое дешёвое место, где ловятся молчаливые отказы.

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

Ни одна из трёх практик не требует сложных инструментов. Все три требуют один раз задать себе неудобный вопрос: как я узнаю, что система перестала работать, если она при этом не упала?