Что мы строим

Прогноз развития зоны MACD — и честный ответ о том, чему в нём можно верить

Задача

Цена не движется равномерно. На индикаторе MACD она распадается на зоны — участки, где гистограмма держит один знак. Зона рождается, живёт какое-то число баров, внутри неё цена ходит ногами (импульс — откат — импульс), и в какой-то момент зона заканчивается.

Вопрос, на который мы отвечаем, звучит так:

Мы стоим внутри живой зоны, на баре k. Известно только то, что было до этого бара. Что будет дальше — и насколько мы в этом уверены?

Что именно является изделием

Не «предсказание цены». Изделие — ансамбль целостных сценариев продолжения.

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

Из этого одного ансамбля получаются все ответы разом, и они согласованы по построению:

  • полоса пути P10–P90 — коридор, в котором цена окажется;
  • сколько ног зоне осталось;
  • доживёт ли зона до бара k + n;
  • как поведёт себя ближайшая пара ног.
ImportantОтказ — это результат, а не ошибка

Система имеет право сказать «не знаю», и это её штатное поведение, а не сбой.

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

Молчание при этом считается дефектом: отказ обязан быть назван и посчитан. Прогноз, который всегда что-то отвечает, честным быть не может.

Как это выглядит

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

Сначала — только то, что модель видела

показать код, которым посчитано
# Страница читает АРТЕФАКТ прогноза и рисует его нашим же рендером. Пайплайн она не зовёт и не
# умеет: замыкание этого импорта — два листовых модуля и ни одного скорингового. Сторож это
# МЕРЯЕТ запуском в отдельном процессе, а не перечисляет запрещённые имена.
from dataclasses import replace
from forecast_artifact import dict_to_view, render_panel as fc_panel

snap = artifact("snap_showcase.json")
fc = dict_to_view(snap)

# ФАКТ — отдельный слой, а не часть прогноза. Убрать его — значит убрать одно поле модели вида;
# рисуется та же картинка тем же кодом. Именно поэтому «причинность» здесь не обещание в тексте:
# видно, что прогноз существует БЕЗ знания об исходе.
blind = replace(fc, actual=None, actual_pivots=None)
show(fc_panel(blind, "fan", theme="light", height=430))
Figure 1: Прогноз на баре k. Ничего из будущего здесь нет — ни одной точки правее вертикали.

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

Теперь — что случилось на самом деле

Figure 2: Та же картинка, тем же кодом, плюс один слой: как зона пошла на самом деле.

Факт лёг поверх прогноза — но он не участвовал в его построении и появился на картинке отдельным слоем, из отдельного поля. Это важно проговорить: ретроспективный пример убеждает ровно настолько, насколько читатель верит, что ответ не подглядели. Здесь верить не нужно — верхняя картинка построена из тех же данных с одним выключенным полем.

Одна зона ничего не доказывает и доказывать не должна. Доказательство — ниже: обещание полосы, проверенное на данных, которых модель не видела.

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

Отгружаемая сборка учится на всей истории — ей не нужно ничего никому демонстрировать, ей нужно быть как можно точнее на завтрашнем баре.

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

Ансамбль: 80 сценариев. Нарисовано 60 самых весомых — они несут 75 % веса.

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

Figure 3: Распределение числа оставшихся ног. Ноль — зона заканчивается прямо здесь.
Figure 4: Вероятность, что зона ещё жива на баре k + n.

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

Как изделие себя проверяет

Полоса P10–P90 обещает накрыть факт в 80 % случаев. Обещание проверяется на данных, которых модель не видела, отдельно по каждому периоду истории — включая слом режима 2022 года.

показать код, которым посчитано
from reliability_view import from_evidence, render_panel as rel_panel, gate_line

# Числа И ВЕРДИКТ берутся ИЗ ЗАПИСИ ПРОГОНА — того же артефакта, по которому продукт судили.
# Страница не пересчитывает ни то, ни другое: пересчёт завёл бы вторую версию правды.
evidence = artifact("evidence.json")
view = from_evidence(evidence)
show(rel_panel(view, "coverage", theme="light", height=340))
Figure 5: Покрытие 80 %-полосы по периодам. Пунктир — обещанные 0.80, полоса — допуск ±0.04.

Вердикт по этим числам:

калибровка ПРОЙДЕНА · coverage80_error 0.0068 <= 0.04 · cover80_folds_in_band_frac 1.0000 >= 1.0

WarningВажная оговорка, добавленная 27 августа 2026

Числа выше описывают объект, который мы измеряли. Мы обнаружили, что он отличается от того, который отгружается, в одной существенной детали: у измеряемого объекта поправка к полосе обновляется по ходу, а у отгружаемого она фиксируется в момент сборки.

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

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

Мы оставляем эти цифры на месте, а не убираем задним числом. Витрина обязана показывать и то, что нас не красит, — иначе она перестаёт быть свидетельством и становится рекламой.

WarningПоправка к оговорке, 28 августа 2026: предполагаемое лечение не сработало

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

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

Но обещание оно не возвращает. Ни в одном из трёх вариантов полоса не вышла на допустимый промах: 0.064, 0.068 и 0.066 при допустимых 0.03. Длина отданной истории на это не влияет — у самого «щедрого» варианта промах такой же, как у самого скупого. Промах при этом односторонний: полоса везде слишком узкая, ни разу не слишком широкая.

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

WarningНа каких барах измерено обещание полосы

Число выше посчитано не на всех барах, на которых изделие выдаёт прогноз. Проверка исторически идёт по фиксированному набору контрольных баров — четырнадцати точкам плюс первый бар зоны, — а изделие отвечает на каждом баре подряд. То есть судится примерно треть того, что отдаётся.

Мы пересчитали то же обещание на полном наборе баров, тем же кодом и тем же протоколом. Результат: промах полосы 0.0171 вместо 0.0068 — в два с половиной раза больше. Вердикт «полоса держит обещание» при этом устоял, и, в отличие от прежней нашей публикации, запас до планки на полном наборе теперь тоже есть: 0.0171 против планки 0.03.

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

Так было не всегда. Ещё в начале сентября на полном наборе промах составлял 0.0347, то есть запаса не оставалось вовсе, и мы об этом здесь писали. Разница не в том, что дальние бары стали «лучше»: поправка к ширине полосы раньше была одна на все бары зоны, и одно значение не может подходить и началу зоны, и её глубине. Теперь она считается отдельно для трёх диапазонов возраста бара. Доля отдаваемых баров, лежащих там, где обещание не держится, упала при этом с 26 % до 13 %.

И вторая граница, которую честнее назвать, чем подразумевать. Начиная с некоторой глубины внутри зоны проверка перестаёт быть возможной: контрольная сборка учится на более коротком отрезке истории, чем отгружаемая, и раньше упирается в собственное правило «не отвечать, когда похожих случаев слишком мало». Мест, где не проверил ни один из четырёх контрольных периодов, примерно 4 % того, что изделие отдаёт.

Но гарантию мы с 19 сентября даём строже. Бар считается проверенным, только если его проверили не меньше чем в трёх периодах из четырёх: одного периода мало — он говорит об одном режиме рынка, а обещание мы даём на все. По этому счёту без гарантии не 4 %, а около 8 % отдаваемых баров. Число выросло не оттого, что изделие стало хуже, а оттого, что проверка стала строже.

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

Мы оставляем прежние цифры в тексте, а не убираем задним числом. Витрина обязана показывать и то, что нас не красит, — иначе она перестаёт быть свидетельством и становится рекламой.

Полоса считается только по тем сценариям, у которых зона на этом баре ещё не закрылась. Иначе она врала бы про мир, которого уже нет. Значит её обещание звучит не «цена будет здесь», а «если зона продолжится, цена будет здесь в 80 % случаев». На самой картинке это написано второй строкой подписи, вместе с долей сценариев, доживших до дальнего края горизонта.

Дальше — то, что мы у себя нашли, когда разрезали покрытие по длительности зоны:

длительность зоны фактическое покрытие
3 бара 0.970
4 0.946
5–6 0.881
7–10 0.815
11–20 0.799
21 и больше 0.791

Ширина полосы почти не зависит от того, сколько зоне осталось жить. Она откалибрована по популяции, где преобладают длинные зоны — на них и приходится около 70 % измеренных случаев, — и на коротких зонах систематически перекрывает: обещая 80 %, накрывает до 97 %.

Мы довели этот разрез до самого края, за пределы популяции, на которой изделие судили: на зонах длиной в два бара покрытие 0.991, на зонах в один бар — 0.999. Восемь точек подряд, ни одного излома. Это отдельная популяция, поэтому в таблицу выше она не подмешана.

И два уточнения, ради которых край вообще стоило домерить:

  • на самых коротких зонах продукт не ошибается — он молчит. Зоны там просто не возникает, и прогноз не выдаётся вовсе: это отсутствие ответа, а не неверный ответ. Так устроено намеренно;
  • сам прогноз на первом баре не смещён. Ширина полосы у зон, которым предстоит закончиться немедленно, и у тех, что проживут долго, статистически не различается — модель в этот момент говорит про тех и других одно и то же. То есть цена этой слепоты — в охвате, а не в содержании: она решает, будет ли прогноз выдан, а не искажает его.

Что с этим делать читателю: на короткой зоне полосу читать как консервативную, а не как откалиброванную — реальный риск выхода за неё ниже заявленных 20 %. А на вопрос «продолжится ли зона» отвечает не полоса, а две другие панели выше: дожитие и распределение числа оставшихся ног.

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

Figure 6: Скользящая поправка существует не на каждом баре — сперва ей нужен разогрев.

Две величины разведены намеренно. Калибратор, который молчит, и калибратор, который ответил «поправка ноль», дают численно одну и ту же полосу — но это разные утверждения: первый не знает, второй проверил.

Figure 7: Ошибка модели относительно безусловной базы. Ниже 1.0 — модель точнее.

Покрытие легко получить нечестно: полоса от минус бесконечности до плюс бесконечности накрывает факт всегда. Поэтому рядом с покрытием обязана стоять острота — иначе «честная» полоса окажется просто неинформативной.

Страница ничего не вычисляет. Она читает тот самый файл, по которому вынесен вердикт промоушена, и раскладывает его по панелям. Код, которым это сделано, раскрывается кнопкой над каждым графиком.

Так было не всегда, и об этом стоит сказать прямо. До 27 августа витрина считала эти числа заново — тем же кодом, на тех же данных, но заново. Мы это проверили и обнаружили, что формулировка «тот же файл» была неточной: числа совпадали до последнего знака, а объект был другой. Поэтому связь переделали: витрина берёт судейскую запись по ссылке из самой сборки и отказывается собираться, если такой записи нет или она принадлежит другому прогону. Совпадение больше не нужно проверять — показывать нечего, кроме судейского файла.

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

Чего здесь нет

Данных, сырых прогонов и внутренней кухни исследования. Это витрина смысла, а не рабочий журнал: лаборатория, где всё это живёт, приватная.