Прогноз развития зоны MACD — и честный ответ о том, чему в нём можно верить
Задача
Цена не движется равномерно. На индикаторе MACD она распадается на зоны — участки, где гистограмма держит один знак. Зона рождается, живёт какое-то число баров, внутри неё цена ходит ногами (импульс — откат — импульс), и в какой-то момент зона заканчивается.
Вопрос, на который мы отвечаем, звучит так:
Мы стоим внутри живой зоны, на баре k. Известно только то, что было до этого бара. Что будет дальше — и насколько мы в этом уверены?
Что именно является изделием
Не «предсказание цены». Изделие — ансамбль целостных сценариев продолжения.
Мы находим в истории зоны, похожие на текущую по тому, как они себя вели до этого же бара, и берём их дальнейшие пути целиком. Не усредняем — именно берём целиком, потому что усреднение порождает траекторию, которой не было ни у одной реальной зоны.
Из этого одного ансамбля получаются все ответы разом, и они согласованы по построению:
полоса пути P10–P90 — коридор, в котором цена окажется;
сколько ног зоне осталось;
доживёт ли зона до бара k + n;
как поведёт себя ближайшая пара ног.
ImportantОтказ — это результат, а не ошибка
Система имеет право сказать «не знаю», и это её штатное поведение, а не сбой.
Если похожих зон в истории слишком мало, если режим рынка ушёл за пределы того, на чём модель училась, или если способность не прошла проверку качества — на выходе будет названная причина вместо правдоподобной картинки.
Молчание при этом считается дефектом: отказ обязан быть назван и посчитан. Прогноз, который всегда что-то отвечает, честным быть не может.
Как это выглядит
Ниже — один настоящий прогноз, а не иллюстрация. Мы стоим внутри живой зоны на баре k, и всё, что нарисовано справа от вертикали, посчитано только по данным до неё.
Сначала — только то, что модель видела
показать код, которым посчитано
# Страница читает АРТЕФАКТ прогноза и рисует его нашим же рендером. Пайплайн она не зовёт и не# умеет: замыкание этого импорта — два листовых модуля и ни одного скорингового. Сторож это# МЕРЯЕТ запуском в отдельном процессе, а не перечисляет запрещённые имена.from dataclasses import replacefrom forecast_artifact import dict_to_view, render_panel as fc_panelsnap = artifact("snap_2923_k14.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: Та же картинка, тем же кодом, плюс один слой: как зона пошла на самом деле.
Факт лёг поверх прогноза — но он не участвовал в его построении и появился на картинке отдельным слоем, из отдельного поля. Это важно проговорить: ретроспективный пример убеждает ровно настолько, насколько читатель верит, что ответ не подглядели. Здесь верить не нужно — верхняя картинка построена из тех же данных с одним выключенным полем.
Одна зона ничего не доказывает и доказывать не должна. Доказательство — ниже: обещание полосы, проверенное на данных, которых модель не видела.
NoteВеер показывает не весь ансамбль — и об этом сказано числом
Ансамбль: 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 6: Скользящая поправка существует не на каждом баре — сперва ей нужен разогрев.
Две величины разведены намеренно. Калибратор, который молчит, и калибратор, который ответил «поправка ноль», дают численно одну и ту же полосу — но это разные утверждения: первый не знает, второй проверил.
Figure 7: Ошибка модели относительно безусловной базы. Ниже 1.0 — модель точнее.
Покрытие легко получить нечестно: полоса от минус бесконечности до плюс бесконечности накрывает факт всегда. Поэтому рядом с покрытием обязана стоять острота — иначе «честная» полоса окажется просто неинформативной.
NoteПочему числа на этой странице нельзя подкрутить
Страница ничего не вычисляет. Она читает запись прогона — тот же файл, по которому вынесен вердикт промоушена, — и раскладывает его по панелям. Код, которым это сделано, раскрывается кнопкой над каждым графиком.
Правило записано в самом модуле и защищено тестом: если бы вид считал покрытие сам, у продукта появилась бы вторая версия числа, и через месяц никто не сказал бы, какая из двух судила изделие.
Чего здесь нет
Данных, сырых прогонов и внутренней кухни исследования. Это витрина смысла, а не рабочий журнал: лаборатория, где всё это живёт, приватная.