Опубликовано как идея TradingView по ETHUSDT. Посмотреть график на TradingView
Если вы когда-нибудь переносили индикатор в Python, чтобы прогнать по нему бэктест, или доверяли скрипту потому, что он шёл с проверкой, то речь здесь о зазоре, который эти две вещи оставляют открытым.
Мы переписали три собственных индикатора, чтобы по ним можно было прогнать бэктест. Во всех трёх переписанных версиях сидела неверная константа. Все три прошли свою проверку. Ни одну не поймал ни один тест.
Зачем вообще переписывать индикатор
Скрипт на Pine работает на вашем графике. Он не проходит в цикле по шести годам истории и не отвечает на вопрос, окупается ли сигнал на самом деле. Чтобы задать этот вопрос, его переписывают на чём-то, что можно запустить тысячи раз.
Именно в этой переписанной версии индикатор тихо перестаёт быть тем же самым индикатором.
Ошибка, которая обошлась дороже всего, и это один бар
Одна из длин внутри Volume Oracle выводится из таймфрейма:
target bars = 36 hours × 60 / 240 minutes = 9
regime length = round(9 × 2.5) = round(22.5)
Функция math.round в Pine разрешает ничьи в сторону от нуля. Она даёт 23. Функция round() в Python 3 применяет банковское округление, которое разрешает ничьи в сторону чётного числа. Она даёт 22.
Обе — корректные реализации округления. Это просто разные соглашения, и ничто не предупреждает вас, что вы перешли из одного в другое.
Каждая ретроспектива, выведенная из этого числа, сдвинулась вместе с ним. Результат на самой обеспеченной данными строке прогона по 36 сигналам:
| версия | преимущество за 48 ч | доля удачных | n |
|---|---|---|---|
| переписано, длина 22 | +0,423 % | 51 | 1216 |
| верно, длина 23 | +0,260 % | 50 | 1223 |
Срез в 38 % из-за соглашения об округлении.
Затем мы неверно записали сам вывод
В нашей заметке было сказано, что это происходит только на 4H. Она была написана из того единственного таймфрейма, который тогда оказался под тестом, и в таком виде пролежала два месяца.
Прогоняя ту же арифметику по всем таймфреймам:
| таймфрейм | длина режима | длина потока | результат |
|---|---|---|---|
| 5m | 45 против 45 | 23 против 22 | расходится |
| 2H | 45 против 45 | 23 против 22 | расходится |
| 4H | 23 против 22 | 12 против 11 | расходится |
| 8H | 53 против 52 | 27 против 26 | расходится |
| 1D | 18 против 18 | 9 против 9 | ничья, совпадает |
| 3D, 1W | 15 против 15 | 8 против 8 | ничья, совпадает |
Четыре из шестнадцати, а не один.
Из этой таблицы выпадают две вещи, которых никакие рассуждения не дали.
Ничьей недостаточно. 1D, 3D и 1W все попадают ровно на ,5 и совпадают. Банковское округление сдвигает только ту ничью, ответ которой при округлении вверх нечётный, поэтому round(17.5) даёт 18 в обоих случаях. Примерно половина всех ничьих безобидна, и именно поэтому выборочная проверка одной из них не доказывает ничего.
Расхождение может прийти на шаг позже. На 5m и 2H первая длина совпадает на 45. Следующая строка, round(45 × 0.5), снова даёт 22,5, и вот она уже расходится. Если вы проверите первую константу и пойдёте дальше, вы напрасно признаете годными оба таймфрейма.
Два других, коротко
Harmonic нормализует осциллятор, обрезая его по скользящей полосе. В исходнике каждый бар обрезается своей собственной полосой. Переписанная версия обрезала целое окно в 200 баров по полосе текущего бара, а это уже другая функция. Измеренное расхождение на 11 497 барах: в среднем 0,47 пункта. Настоящая ошибка переноса без существенных последствий.
Эту мы к тому же раздули, прежде чем измерить. По одному лишь прочтению кода её объявили почти наверняка тем самым отклонением в 9 %. Число оказалось 0,47. Настоящая ошибка и существенная ошибка — разные утверждения, и порядок величины измеряют прежде, чем называют последствие.
Plutus Flow усредняет объём по 50 барам, чтобы находить всплески. Переписанная версия брала 20, потому что это было число, которое мы думали, что знаем. Эффект: меньше десятой доли процентного пункта, смены знака нет. Вывод не сдвинулся, но число было неверным, а теперь верное, и это единственная мерка, которая имеет значение.
Та часть, которая и есть настоящий урок
Вот что тогда сообщала проверка каждой переписанной версии.
| переписанная версия | её собственная проверка | что это значило |
|---|---|---|
| Volume Oracle | 3 из 3 до, 3 из 3 после | не смогла их различить |
| Harmonic | 6 из 8 до, 5 из 8 после | оценила версию с ошибкой выше |
| Plutus Flow | ни разу не запускалась | — |
Над средней строкой стоит посидеть. Проверка предпочла неверный код. Если бы мы дали проверке решать, она доводами вернула бы нас обратно в ошибку.
Проверка, которая сравнивает одно выходное значение, не может обнаружить неверную входную константу. Выход — слишком грубое сито. Множество разных неверных наборов параметров дают сегодня ту же метку режима, то же состояние пересечения, тот же цвет на графике. Различаются они на барах, на которые вы сейчас не смотрите, а именно там и живёт бэктест.
Что делать вместо этого
Выведите каждый производный параметр. Каждую длину, каждый порог, каждый множитель. Сверьте их с исходником строка за строкой, прежде чем что-либо оценивать.
Сохраните проверку выхода. Она ловит другой класс ошибок и она нужна. Она просто далеко не достаточна, и все три прошли мимо неё беспрепятственно.
Честные границы
Код на Pine был верен во всех трёх случаях. Неверен был аппарат, построенный для его проверки, и это более неудобная версия проблемы: измерительный прибор ушёл в сторону, а его собственная самопроверка утверждала, что не ушёл.
Цифра в 38 % — это одна строка, один прогон, один таймфрейм, внутри выборки, и она приведена из нашей собственной записи, а не пересчитана заново. Результат по округлению воспроизводится одной командой, и именно при его исправлении выяснилось, что наша собственная заметка об этом два месяца была неверной.
Опубликовано как идея TradingView по ETHUSDT. Посмотреть график на TradingView
Прошлое измерение нашего собственного инструментария, описанное задним числом. Ничто здесь не является рекомендацией или указанием на будущие результаты.