Инвестиции с нуля

От первого шага до первой прибыли

Робот Scatter&Gather: Время разбрасывать камни и время собирать камни

Ошибка в роботе Scatter&Gather v10.4­_medium: Как робот «забыл» о сделке и что из этого вышло

Приветствую, коллеги-трейдеры и разработчики. Сегодня я хочу разобрать одну досадную, но поучительную историю из жизни моего торгового робота Scatter&Gather. Речь пойдет о версии v10.4_medium, в которой затаился коварный баг. Причем я хочу не просто сообщить о фиксе, а провести полноценный разбор полетов. Ведь каждая ошибка — это не провал, а бесплатный урок. И этот урок был о том, как тонкое архитектурное изменение может привести к неожиданным последствиям, если не учесть все нюансы управления состоянием системы.

Архитектурный сдвиг: От лимиток к виртуальным заявкам

Когда я задумывал переход от классических лимитных заявок к системе предзаявок (виртуальных заявок), я руководствовался желанием повысить гибкость. Раньше робот напрямую выставлял лимитные приказы в стакан. Это надежно, но несколько грубовато. В новой схеме робот сначала создает предзаявку — `pending_orders` — виртуальный план действий в собственной памяти. И только когда рынок достигает нужной цены, этот план материализуется в моментальную рыночную заявку с реальным `trans_id` в QUIK.

Почему я считаю это правильным? Это давало полное исполнение заявки, исключало «проскальзывание» и позволяло работать с более сложной логикой входа. Представьте разведчика, который не лезет на передовую, а сначала ставит метку на карте. Когда цель в зоне досягаемости, он мгновенно вызывает авиаудар. Элегантно, правда? Но я упустил один критически важный аспект: что происходит с этим «авиаударом», если в самый неподходящий момент разведчика вдруг срочно отзывают на переподготовку?

Именно это и случилось. В моей архитектуре есть подсистема `tracked_orders`, которая, как верный пес, следит за исполнением уже отправленных в QUIK рыночных заявок. И есть функция `StopTrade()`, которую я еще в более ранних версиях запрограммировал на полную «зачистку» перед обучением (тестированием на исторических данных) шага сделки в %.

Момент катастрофы: Когда обучение системы стирает её память

Давайте смоделируем роковую последовательность событий на примере с «Озон Фармацевтика». Робот стартовал на дивидендном гэпе — ситуация динамичная, нервная. Сработала предзаявка, мгновенно превратилась в рыночный приказ на покупку и улетела в биржу. И в этот самый миллисекундный промежуток между «отправил» и «исполнился» срабатывает триггер автообучения шага сделки.

Что делает робот? Он честно выполняет протокол: «Стоп торговля! Начинаем перекалибровку!». Функция `StopTrade()` принимается за работу. Она исправно очищает очередь виртуальных предзаявок (`pending_orders`) — зачем нам новые планы, если мы на паузе? Логично. Но затем она, следуя устаревшему шаблону, также обнуляет список `tracked_orders`. Вот он — фатальный сбой. Робот не просто останавливает торговлю, он стирает из оперативной памяти факт существования всех заявок. При лимитных заявках все работало верно — заявки снимаются и отслеживать нечего. А в системе предзаявок выставленная заявка не снимается, у нее только один путь — исполнение. Вопрос лишь в цене, по которой заявка будет исполнена.

А что в это время делает биржа? Она-то ничего не знает о наших внутренних обучениях. Заявка уже в стакане, и она благополучно исполняется. Но робот, закончив обучение, стартует торговлю заново с новым шагом сделки. Он чист, как белый лист. Он понятия не имеет, что у него уже есть открытая позиция по этой бумаге. Это все равно что отправить курьера с важной посылкой, а потом, переехав в новый офис, забыть о его существовании. Курьер-то придет, но к пустому помещению.

Как не странно это звучит, но подобная ошибка за месяцы тестирования не проявила себя ни разу (за исключением последнего случая с «Озон Фармацевтика». 

Философия исправления: Разделение ответственности в коде

Осознав корень проблемы, я взялся за ее исправление. Ошибка была концептуальной: функция `StopTrade()` выполняла две разные работы под одним названием.

1. Управление активностью (остановка генерации новых сигналов).

2. Управление памятью (очистка данных о текущих процессах).

Их нужно было жестко разделить.

В версии v10.5_medium реализовано следующее правильное поведение:

  • `pending_orders` очищаются при остановке. Это правильно. Новых предзаявок в режиме обучения быть не должно. Сносим карту, по которой разведчик больше не работает.
  • `tracked_orders` НЕ ТРОГАЕМ НИКОГДА. Эта подсистема становится независимой. Её задача — мониторить всё, что уже ушло в биржу, до логического завершения (исполнения или отмены самой биржей). Она должна быть слепой к тому, находится ли основная стратегия в режиме торговли или обучения.
  • Блокировка новых покупок и продаж во время обучения. Пока идет перекалибровка, система не имеет права инициировать новые сделки. Это страховочный пояс.
  • Аккуратная уборка. Очистка `tracked_orders` происходит теперь только после того, как заявка в нем перешла в статус «Исполнена». Убрали со стола только ту тарелку, которая уже пуста.

Заключение

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

Предлагаю скачать исправленную версию Scatter&Gather v10.5_medium. Пусть ваша торговля будет не только прибыльной, но и, в первую очередь, предсказуемой и контролируемой. Удачи на рынках.

Скачать робота:

Scatter&Gather v10.5_medium (download)
Downloaded: 4. Size: 294.5 KB. Date: 13 Янв. 2026