Andrey Varakin
Andrey Varakin личный блог
30 августа 2026, 00:25

Баг, который принёс 70 тысяч — и почему я его всё равно починил


У меня на срочном рынке работает автомат: собирает данные, считает целевой портфель, сам ставит ордера через API брокера и вечером сверяет позиции со счётом. На прошлой неделе он двое суток держал не тот портфель, который должен был. И заработал на этом 69 762 ₽.

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

Дальше — про экспирацию. Не в смысле «не забудьте дату в календаре», а в смысле «она ломает ровно те места, где вы её не ждёте».

Что произошло

Портфель рыночно-нейтральный: несколько длинных позиций против нескольких коротких. Отдельную позицию дальше называю ногой, так короче. 

Одна из коротких ног — по инструменту с квартальной серией контрактов.

25 августа. Ликвидность переехала на следующий контракт. Активный контракт я выбираю по максимуму дневного объёма, и он честно переключился. Проблема в том, что переключился за день до последнего торгового дня старого. Объём мигрирует когда мигрирует, а не когда мне удобно.

Дальше важная деталь, без неё не понять. Между ребалансами автомат цель не пересчитывает. Это осознанно: целевой вес переводится в целые лоты, и от небольшого движения цены округление скачет. Сегодня 15 лотов, завтра 14, послезавтра снова 15. Пересчитывай каждый день — и портфель начнёт перекладываться туда-обратно просто так, оплачивая комиссией собственный шум. Поэтому цель считается на ребалансе и держится до следующего.

А хранилась она по идентификатору контракта. У брокера это FIGI. То есть привязанной к конкретному контракту, а не к инструменту. Вот тут и легла мина.

26 августа. Контракт отторговался. Брокер списал позицию расчётами. Без сделки.

27 августа. Автомат смотрит: в цели короткая нога есть, на счёте нет. Логично пытается восстановить — и получает 30052 Instrument forbidden for trading by API. И так каждый запуск, до следующего ребаланса.

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

Почему не сработала защита

Самое обидное, что защита ровно от этого у меня была. Логика такая: любой контракт, который есть на счёте, но отсутствует в цели, закрывается в ноль — как старый после ролла.

Не сработало, потому что мёртвый контракт был в самой цели. Условие проверяло «есть на счёте, но нет в цели», а тут и на счёте, и в цели стоял один и тот же покойник. Формально код отработал правильно. И пропустил ровно тот случай, ради которого писался.

Вывод, который я себе записал: защита проверяет своё условие, а не то, что вы имели в виду.

Второе место, где вылезло то же самое

Пока чинил исполнение, полез в учёт и нашёл ту же мину, только тихую.

Там лежала карта «FIGI → инструмент», зашитая точными контрактами. После ролла в ней остался истёкший. А фильтр позиций был такой:

if figi not in FIGI2ROOT:
continue

Нога по новому контракту молча выпадала из учёта. Её результат и комиссия не копились нигде, ночной снимок писал «ног в портфеле: 3» и ноль лотов по инструменту, стоимость счёта занижалась на целую ногу. А от неё, между прочим, зависит решение о размере портфеля.

Ловится это за минуту: в своём учёте три ноги, у брокера четыре.

Если у вас где-то есть словарь, где ключ — конкретный контракт вида FUT<ИНСТРУМЕНТ><ММГГ>0, это мина с известной датой срабатывания. Серия живёт годами, отдельный контракт — квартал.

Теперь про 70 тысяч

Пока ноги не было, рынок ушёл против неё на несколько процентов за день. Короткой ноги нет — платить за этот ход нечем. Разница между живым счётом и моделью за тот день вышла +69 762 ₽ в пользу живого.

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

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

Что проверить у себя

Каждый пункт — минут на десять.

1. Цель у вас хранится по контракту или по инструменту? Если по контракту, на ближайшем ролле она протухнет, и узнаете вы об этом от биржи, а не от своего кода.

2. Поищите словари, где ключ — конкретный контракт. Инструмент надо определять по началу идентификатора, общему для всей серии: оно ролл переживает, полный идентификатор — нет.

3. Что код делает с неизвестным инструментом в позиции? Если continue — однажды потеряете ногу молча. Там должен быть громкий лог.

4. Сверяете состав портфеля или только его стоимость? «Позиций должно быть четыре, а их три» ловится проверкой в одну строку и совершенно не ловится взглядом на кривую доходности.

5. Переживает ли экспирацию ваша сверка? Позиция закрывается расчётами, сделки в журнале нет. Всё, что построено на журнале ордеров, после экспирации разъезжается с реальностью. У меня реконструкция сходилась со снимками счёта 17 дней из 18, и единственное расхождение пришлось ровно на день экспирации.

Как починил

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

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

Проверил на живом: в ближайший же вечер ночной снимок впервые за трое суток посчитал портфель целиком, четыре ноги на всех счетах.

Код проверок, о котором был прошлый пост, лежит тут: gitlab.com/andreyvarakin29/quant-validation — numpy и pandas, данные генерируются, ключей и интернета не нужно, запускается одной командой. На GitHub он остался приватным после истории с флагом аккаунта, поэтому ссылка на GitLab. Сигнала, параметров и состава портфеля там по-прежнему нет, оценивать предлагаю процедуру.

Если у вас крутится автомат на FORTS — проверьте пункт 2 прямо сейчас, это буквально grep по проекту.

Данная публикация является личным мнением автора. Мнение владельца сайта может не совпадать с мнением автора.
9 Комментариев
  • astray
    30 августа 2026, 01:18
    столько текста про ноги и всего лишь из за сраных 70 тыс )

    называю ногой
    коротких ног
    нога есть
    Ноги не было вообще
    Нога по новому
    ног в портфеле
    целую ногу
    три ноги
    ноги не было
    Короткой ноги нет
    потеряете ногу
    размер ноги
    четыре ноги

  • Грядущая тьма
    30 августа 2026, 01:39
    Энергобанк твой кумир?
  • Дмитрий-Димас Ермаков
    30 августа 2026, 06:43
    А что доктор говорит?
    Зачем нам знать о ваших ошибках в коде? Здесь не сайт для питонщиков. Никому ваши ошибки и исправления кода не интересны. Это ваши личные проблемы, никак не связанные с темой сайта. Никто не возьмёт вас на работу. Никому вы не нужны. Никакой пользы ваш пост не несёт. Это бред и мусор. Это типичный самопиар. Самореклама несчастного алготрейдера. В надежде, что ему кто-то доверит деньги.
    1. Автор пришёл сказать, что у него есть робот.
    2. Хотел показать, что он умный. Починил своего робота.
    3. Ждёт наивных подписчиков, инвесторов и спонсоров, клиентов.
    В ЧС
  • Карабас
    30 августа 2026, 07:40
    Кому твой баг нужен на хрен

Активные форумы
Что сейчас обсуждают

Старый дизайн
Старый
дизайн