Hebbian-фактор был мёртв 8 версий: как я нашёл баг в своём индикаторе

Восемь версий. Ни одного предупреждения компилятора. Индикатор исправно рисовал на графике, советник открывал сделки - а один из факторов внутри всё это время не работал. Тихо. Я даже гордился - такой чистый код. Фактор, судя по всему, тоже был доволен.

Содержание

Это история про самый коварный класс багов в разработке торгового индикатора: тот, что не роняет программу, не пишет «ошибку» в лог и не ломает компиляцию. Он просто молча отдаёт неправильное число. А ты об этом не подозреваешь - потому что всё остальное «работает».

Рассказываю, как мёртвый фактор прожил в AlgoSynergy GOLD от версии 1.01 до 1.19, почему я не видел этого восемь релизов и как один диагностический Print() вскрыл проблему за минуту.

Что такое Hebbian-акселератор и зачем он нужен

В индикаторе AlgoSynergy GOLD решение по каждому таймфрейму собирается из нескольких компонентов: тренд (EMA), VWAP, триггер-линия и так называемый Hebbian-акселератор. Идея последнего простая: измерять силу отклонения цены от своей средней. Не «выше или ниже» (это и так дают EMA), а насколько далеко и резко цена ушла от равновесия.

Чем сильнее отклонение - тем ближе значение фактора к краям диапазона 0…1. Около 0.5 - цена у средней, скучно. Выше 0.70 - сильный бычий импульс, ниже 0.30 - сильный медвежий. Эти пороги и поднимали сигнал из «обычного» в «сильный».

Так было задумано. На практике фактор не выходил за 0.500-0.505 никогда.

Как баг выглядел в коде

Нормировка отклонения была написана так (упрощённо):

diff = (close - mean) / mean;

Логика на первый взгляд разумная: отклонение в долях от среднего. На акции по $50 или паре EURUSD это дало бы вменяемые числа.

Но GOLD торгует золото. Цена - около 3300-4000. Типичное отклонение закрытия от скользящей средней на M5 - несколько пунктов, ну десятки. Делим, скажем, 8 пунктов на 3700:

8 / 3700 ≈ 0.00216   // две десятых процента

После прогона через сигмоиду такое микроскопическое отклонение давало на выходе 0.500…0.505. Всегда. Пороги 0.70 и 0.30 были недостижимы в принципе - как требовать прыжок на три метра от того, кто привязан к полу. «Сильный сигнал» от Hebbian не возникал ни разу за восемь версий.

Почему я не замечал это 8 версий

Вот что делает такие баги опасными: ничто не сигналит о поломке.

  • Компилятор молчал - синтаксически код идеален.
  • Индикатор рисовал панель, стрелки, уровни - всё на месте.
  • Советник открывал сделки - решение собирается из нескольких компонентов, и трёх оставшихся (EMA, VWAP, триггер) хватало, чтобы система жила.
  • Бэктесты шли, сделки закрывались в плюс и в минус - статистика выглядела «нормальной».

Фактор не отсутствовал - он присутствовал и отдавал 0.5. А 0.5 - это «нейтрально», валидное значение. Никакого NaN, никакого нуля, который бросился бы в глаза. Просто тихая константа, маскирующаяся под работу. Я смотрел на график, видел, что «всё считается», и шёл дальше - восемь раз подряд.

Это ровно тот случай, когда доверять ощущениям нельзя. «Выглядит рабочим» и «работает» - разные вещи, и разделяет их только измерение.

Как нашёл: один Print() вскрыл всё

Я не угадал баг. Я его измерил.

Готовя версию 1.20, я добавил диагностический отчёт - прогнать фактор по последним 2000 барам каждого таймфрейма и вывести распределение: минимум, перцентили, максимум. Один Print() с десятком чисел.

p10=0.000  p50=0.500  p90=0.505  max=0.505

И всё стало очевидно мгновенно. Восемь версий, сотни часов - и один Print() объяснил мне, что я делал вид, что считаю, уже очень давно. Девяностый перцентиль - 0.505. То есть 90% значений лежат между 0.5 и 0.505. Фактор не просто слабый - он раздавлен в точку. Порог 0.70 от него не видел никто и никогда.

Минута на чтение отчёта - и восьмиверсионная загадка перестала быть загадкой. Данные сказали то, что глаз не ловил.

Как чинил: две калибровки и решение для H4

Диагноз ясен: нормировать отклонение нужно не на цену, а на волатильность. Сколько пунктов - это «много», зависит не от того, $50 актив или $4000, а от того, насколько он обычно ходит. Естественная мера этого - ATR.

diff = (close - mean) / (atr * K);

Теперь 8 пунктов сравниваются не с 3700, а с текущим ATR - и сразу превращаются в осмысленную величину. Осталось подобрать K.

  • Прогон 1, K = 1.0 → p90 = 1.000. Перелёт: я починил мёртвый фактор и немедленно создал противоположную проблему. Теперь фактор бьёт в потолок, «сильным» становится почти всё - так же бесполезно, как раньше, только наоборот.
  • Прогон 2, K = 3.0 → p90 = 0.693. Попали: верхний край распределения сел ровно у порога 0.70, «сильный сигнал» снова стал редким и значимым.

Дальше всплыла деталь. На H4 распределение оказалось смещено вверх - за период золото росло, и старший таймфрейм был устойчиво бычьим. С общим K=3 H4 перегревался. Поэтому в финале v1.20 - пер-TF коэффициенты: K=3.0 для M3-H1 и более мягкий K=5.0 для H4. Каждый таймфрейм нормируется под свою динамику.

Вывод: тихие баги опаснее громких

Громкий баг - это подарок. Он падает, орёт в лог, ломает сборку - ты его чинишь в тот же день.

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

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

Именно так я и строю инструменты AlgoSynergy: не на ощущениях, что «вроде работает», а на данных - этот принцип измеримости лежит в основе всей методологии. А ещё я теперь гораздо осторожнее говорю «у меня это работает». Потому что знаю: иногда это значит просто «у меня это рисует».

Кстати, исправленный фактор теперь работает в проде - увидеть текущие показания консенсуса по золоту и биткойну можно на барометре в реальном времени.

Частые вопросы

Что такое Hebbian-акселератор в индикаторе GOLD?

Это компонент, который измеряет силу отклонения цены от своей средней - не «выше/ниже», а насколько далеко и резко цена ушла от равновесия. После фикса отклонение нормируется на волатильность (ATR), а не на абсолютную цену.

Почему «тихий» баг не замечали восемь версий?

Он ничего не ломал: компилятор молчал, индикатор рисовал, советник торговал, а фактор отдавал валидное «нейтральное» 0.5. Ни ошибки, ни NaN. Такую поломку не видит глаз - её ловит только измерение распределения значений.

Как защититься от подобных тихих багов?

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

Хотите посмотреть, как устроены индикаторы AlgoSynergy для XAUUSD и что у них «под капотом», - пишите в Telegram @AlgoSynergy. Покажу, расскажу, отвечу лично.

Материал носит образовательный характер и не является инвестиционной рекомендацией.

Александр Тригуб - автор AlgoSynergy
Александр Тригуб

Трейдер и разработчик торговых инструментов для золота (XAUUSD) на MetaTrader 5. Пришёл в трейдинг из мира систем и данных: пишу собственные алгоритмы на MQL5 по концепциям структуры рынка, в том числе ICT. Метод измерим - разметка, источник стопа, цели в R; торгую ровно тем, что создаю, без чёрных ящиков и чужих сигналов.

Об авторе · Telegram @AlgoSynergy

Вопрос по инструментам или системе?

Напишите в Telegram - отвечаю лично.

Telegram
Дисклеймер о рисках. Торговля на финансовых рынках сопряжена с высоким риском потери капитала. Прошлые результаты не гарантируют будущих. Материалы сайта носят информационно-образовательный характер и не являются индивидуальной инвестиционной рекомендацией.