Что мы строим

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

Задача

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

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

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

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

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

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

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

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

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

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

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

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

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

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

snap = artifact("snap_2923_k14.json")
fc = dict_to_view(snap)
show(fc_panel(fc, "fan", theme="light", height=430))
Figure 1: Веер сценариев и полоса P10–P90. Вертикаль — бар k, на котором сделан прогноз; красная линия — как зона пошла на самом деле.

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

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

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

Figure 2: Распределение числа оставшихся ног. Ноль — зона заканчивается прямо здесь.
Figure 3: Вероятность, что зона ещё жива на баре 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 4: Покрытие 80 %-полосы по периодам. Пунктир — обещанные 0.80, полоса — допуск ±0.04.

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

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

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

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

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

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

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

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

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

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