Причинность бэктеста и порядок модельного исполнения
Редакция от 20.10.2026 для интерфейса 0.11.0 и отдельного движка breadth-allocation-1.0.1. Рыночный срез заморожен на 16.10.2026. Документ дополняет docs/BREADTH_ALLOCATION_RU.md и описывает проверенную последовательность расчёта, единицы журнала и пределы проверки.
Результат и точный объём проверки
node tests/backtest-causality.mjs формирует протокол work/review/causality-audit.json. На проверенной редакции все 16 проверок имеют результат PASS; общий статус — PASS_WITH_DATA_LIMITATIONS, поле temporal_code_checks — PASS.
Это означает: в проверенных сценариях будущие допустимые наблюдения не меняют прошлые оценки, веса и исполнения; новые веса начинают получать доходность после модельного закрытия исполнения. Проверка не подтверждает строгую point-in-time доступность исходных данных и не доказывает возможность реального исполнения по расчётным уровням.
Проверяемая проекция — public/breadth-strategy/data.json:
| Характеристика | Зафиксированное значение |
|---|---|
| Версия данных | 99f1e4afa8abbf45111499c8ae1da6fc935581ae242710f7d5f08aa3fd1b84b0 |
| Физический SHA-256 входного JSON | fcdfd39433cc5b0e440f4abec505294905fce8e3b9cb667ecfdb614cc3e4b3a4 |
| Оценочный календарь | 2 965 сессий, 05.01.2015–16.10.2026 |
| Доступные активы | 18 |
| История месячного индикатора | 226 срезов; ранняя диагностика не означает портфельный бэктест с 2008 года |
| Начальный сценарий проверки | MCFTR / MM_CBR, порог 0 п.п., капитал 1 000 000 рублей, издержки 10 б.п. оборота |
| Исполнения начального сценария | 142 события модельной ребалансировки, включая первоначальное инвестирование |
Финансовый выпуск 0.5.0, движок статического конструктора 0.5.0-rc.2, месячный архив 1.0.0 и принятое исследование breadth 1.3.0 сохраняют свои версии. Исходные данные и числовые результаты архивного сценария 1.3.0 с порогом 20 п.п. не переписываются. Проверка новой страницы не заменяет воспроизведение этого архива и не переносит его условия в новый движок.
Какой сигнал используется
Индикатор обновляется по сохранённому месячному расписанию. Это не внутридневное срабатывание и не ежедневная проверка пересечения порога. signal_date обозначает завершённую сигнальную сессию, предшествующую плановому исполнению. Сигнал с датой, равной плановой дате исполнения либо более поздней, отклоняется при проверке входов.
Состав сигнала — исторические участники IMOEX независимо от выбранной пары инвестиционных активов. Положительная категория включает TRI выше либо равный SMA200; неизвестные сигналы остаются в общем знаменателе:
B = (above + equal) / total
tau = threshold_pp / 100
w_defensive = max(0, 1 - B - tau)
w_primary = 1 - w_defensive
Порог по умолчанию равен 0 п.п. Целевые веса — результат формулы для следующей ребалансировки. Между исполнениями фактические модельные веса изменяются вслед за уровнями активов и могут отличаться от целевых. Отсутствующий индикатор не заменяется нулевой шириной рынка.
Сохранённый вход содержит исторический состав на плановую дату исполнения, а TRI/SMA — на предыдущее закрытие. Доказательства того, что именно этот будущий вступающий в силу состав был опубликован к сигнальному закрытию, в полном объёме отсутствуют. Это отдельное ограничение знания состава на дату сигнала; неравенство дат его не устраняет.
После сигнала: когда и по какому уровню происходит операция
Правило движка — next_eligible_common_close, то есть первая допустимая общая дата закрытия после сигнала и не раньше плановой даты. На каждой дате порядок следующий:
- Переоценить прежние количества условных единиц текущими уровнями. Вся
доходность интервала до этого закрытия принадлежит прежним позициям.
- Выбрать последнее уже наступившее месячное решение с
signal_dateстрого
раньше текущей даты.
- Проверить
can_rebalanceобоих выбранных активов, включая актив с
нулевым целевым весом. При отсутствии допуска любой стороны отложить всю ребалансировку.
- На допустимом закрытии рассчитать оборот обеих сторон, вычесть издержки и
установить новые количества условных единиц по точным уровням этой даты.
- Новые количества получают доходность следующего интервала.
Уровень сигнального закрытия не используется как уровень исполнения. Цена открытия следующей сессии и внутридневной момент сделки не моделируются. Входы и журнал содержат даты без времени суток; термин «закрытие» обозначает выбранную дневную расчётную отметку. Для индекса, синтетической модели и справочного ряда это условная экспозиция по разрешённому уровню.
Если общее исполнение отложено, прежние позиции продолжают действовать. Если до исполнения наступило новое месячное решение, оно заменяет ожидающее старое; отдельная задним числом исполненная операция по старому решению не создаётся. При старте пользовательского окна после плановой даты допускается последнее доступное месячное решение с сигнальной датой раньше фактического старта. Поэтому расстояние от плановой даты до начального исполнения не обязательно означает задержку уже открытого портфеля.
При пропуске оценки после первого наблюдения разрешено оценить открытую позицию последним прошлым уровнем. Такой перенос помечается valuation_carried и не разрешает исполнение. Первое будущее наблюдение не переносится назад для заполнения начальных пропусков. На всех допустимых исполнениях оба признака перенесённой оценки равны false.
input_age_days хранит переданный возраст исходного входа либо null. Он не является исходным временем публикации и сам по себе не доказывает ликвидность или исполнимость уровня. У синтетических и справочных рядов разрешённый уровень может опираться на ранее вступившее в силу значение.
Учёт капитала и издержек
Пусть L_P, L_D — уровни на закрытии исполнения, q_P, q_D — прежние условные количества, cash — ещё не инвестированный начальный капитал.
V_before = q_P * L_P + q_D * L_D + cash
T = abs(w_P * V_after - q_P * L_P)
+ abs(w_D * V_after - q_D * L_D)
cost = costBps / 10000 * T
V_after = V_before - cost
q_P_after = w_P * V_after / L_P
q_D_after = w_D * V_after / L_D
Уравнение для стоимости после издержек решается численно. Издержки относятся к сумме абсолютных изменений обеих позиций, включая первоначальное инвестирование. Целевые веса относятся к капиталу после издержек. После начального инвестирования свободный остаток модели равен нулю; MM_CBR является выбранным активом, а не этим техническим остатком. Налоги и финальная ликвидация не моделируются.
Единицы уровней: почему журнал не показывает цену пая в рублях
Журнал фиксирует точные значения series.levels из принятой проекции на дату исполнения. Масштаб ряда влияет на условное количество, но сам по себе не меняет доходность отношения уровней. Наименование исходного актива не меняет единицы опубликованного расчётного ряда.
| Актив | Единица *_level_unit | Экономический смысл |
|---|---|---|
| MCFTR | index_points | Пункты индекса полной доходности; индекс нельзя купить как пай. Дивиденды уже входят в ряд |
| Реальные БПИФ: SBMX, SBGB, LQDT, GOLD, TGLD | normalized_total_wealth_units | Нормированный уровень полной стоимости, созданный из истории фонда; это не исходная котировка в рублях за пай |
| MM_CBR и другие синтетические модели | normalized_total_wealth_units | Расчётные единицы модели. MM_CBR воспроизводит условное начисление по исторической ставке, а не реальную цену фонда |
| USD_RUB, EUR_RUB, CNY_RUB, GOLD_RUB | normalized_total_wealth_units | Нормированное представление официальных справочных значений Банка России, применённых по датам вступления в силу; это не исполнимая рыночная котировка |
Поле *_execution_basis сохраняет политику исходного паспорта актива: hypothetical_at_observed_index_close, hypothetical_at_admitted_model_level, observed_tradable_fund_close либо hypothetical_at_effective_official_reference. Даже значение observed_tradable_fund_close описывает происхождение допуска фонда; в журнале используется нормированный уровень, а исходная котировка CLOSE в рублях за пай в этой проекции отсутствует. *_units_before и *_units_after — расчётные количества, а не число фактически купленных ценных бумаг.
Из нормированного уровня нельзя восстановить подтверждённую цену заявки, число лотов либо официальный аукционный fill. MOEX отдельно определяет официальную цену закрытия через LEGALCLOSEPRICE; последняя сделка может отличаться от неё. Поэтому дневной ряд нельзя автоматически описывать как гарантированное исполнение на аукционе закрытия. Спрос, предложение, очередь заявок, лотность, ликвидность, спред и внутридневное проскальзывание в этом движке не рассчитываются.
Контракт журнала и манифеста
Каждый элемент result.events — событие расчёта и исполнения месячной ребалансировки, включая первоначальное инвестирование; это не отдельный биржевой fill. Событие может иметь нулевой торговый оборот.
| Поля события | Содержание |
|---|---|
signal_date, planned_date, execution_date | Сигнальное закрытие, плановая и фактическая модельная даты |
execution_model, weights_effective | next_eligible_common_close, after_execution_close |
primary_symbol, defensive_symbol | Точные коды выбранной пары |
primary_level, defensive_level | Точные допущенные уровни на закрытии исполнения |
primary_level_unit, defensive_level_unit | Единицы уровней из таблицы выше |
primary_execution_basis, defensive_execution_basis | Политики допуска активов из входных метаданных |
primary_input_age_days, defensive_input_age_days | Возраст входа из проекции либо null; дата публикации не выводится из возраста |
primary_valuation_carried, defensive_valuation_carried | Признаки переноса оценки; на допустимом исполнении false |
primary_units_before, defensive_units_before, primary_units_after, defensive_units_after | Количества условных единиц до и после операции |
primary_value_before, defensive_value_before, primary_value_after, defensive_value_after | Стоимость каждой позиции в рублях до и после операции |
cash_before, cash_after | Технический остаток начального капитала; после инвестирования равен нулю |
breadth, primary_weight, defensive_weight | Сигнал и целевые веса на капитал после издержек |
value_before, value_after, cost | Общая стоимость до/после операции и удержанные издержки, рублей |
traded_notional, turnover | Суммарный оборот обеих сторон в рублях и его доля в value_before |
delay_days | Календарная разница фактической и плановой дат, без предположения о числе торговых сессий |
result.execution и manifest.execution также сохраняют:
execution_model = next_eligible_common_close
signal_timing = prior_closed_session
weights_effective = after_execution_close
return_interval_rule = pre_trade_holdings_earn_return_through_execution_close
source_timestamp_certification = false
Манифест сохраняет версию движка, версию данных, параметры и фактическое окно. run_sha256 идентифицирует канонические параметры и версии запуска; он не заменяет физические хеши данных, исходного кода и протокола контроля.
Что проверяют 16 тестов причинности
| № | Проверка |
|---|---|
| 1 | Усечение всех будущих входов на шести датах сохраняет доступный прошлый префикс: дневные оценки, целевые и фактические веса, отметки, бенчмарки и события |
| 2 | Допустимые изменения будущих уровней в 32 и 1/32 раза не меняют прошлые оценки и исполнения |
| 3 | Независимость от будущих цен проверяется для всех 18 выбираемых активов в их общем окне |
| 4 | Удаление будущих уровней и разрешений не меняет завершённый более ранний расчёт |
| 5 | Изменение будущих breadth с согласованными счётчиками не меняет прошлые цели и доходность |
| 6 | Удаление будущих сигналов сохраняет прошлый результат |
| 7 | Ручной пример 100 → 200 → 400 подтверждает: сигнальная отметка не является исполнением; прежняя позиция получает доходность до закрытия исполнения, новая — следующий интервал |
| 8 | Сигнальная дата в день планового исполнения или позже отклоняется |
| 9 | Каждая сторона отдельно блокирует исполнение, в том числе при нулевом целевом весе |
| 10 | Чередующиеся пропуски обеих сторон откладывают операцию до общей допустимой даты; будущая отметка не заполняет пропуск назад |
| 11 | Начальный пропуск не заполняется первым будущим наблюдением |
| 12 | Новый наступивший сигнал заменяет ожидающее старое решение без дополнительного прошлого исполнения |
| 13 | Все 142 исполнения начального сценария проходят строго после сигнала на первой общей допустимой дате |
| 14 | В статическом конструкторе календарная ребалансировка происходит после доходности прежних позиций; сигнал breadth в нём не применяется |
| 15 | Будущие котировки не меняют прошлый результат статического конструктора |
| 16 | Происхождение данных явно сохраняет отсутствие строгой point-in-time сертификации |
Даты усечения первого теста: 30.06.2016, 30.12.2019, 24.03.2022, 25.03.2022, 30.09.2024 и 30.09.2026. Сравнивается весь доступный общий префикс; если на конечной дате отсутствует исходная отметка одной стороны, фактическое окно корректно заканчивается раньше.
Проверки статического конструктора выполнены без внешних денежных потоков. Его расписание подтверждённых концов месяцев строится по полному известному календарю; исторические версии публикации торгового календаря не проверены. Валидация всего входа включает будущие записи: некорректная будущая запись может отклонить запуск. Независимость числового прошлого проверена для допустимых изменений будущих данных.
Ограничения исходных данных и допустимый вывод
Строгий point-in-time означает использование именно тех сведений и версий, которые были доступны участнику в соответствующий исторический момент. Порядок вычислений и проверка этой доступности — отдельные задачи.
В текущем срезе остаются открытыми:
- исходные временные метки публикации каждого значения и полная история его
пересмотров, включая ретроспективные корпоративные корректировки;
- доступность состава на плановую дату исполнения уже на предшествующее
сигнальное закрытие;
- неразрешённое сопоставление MTSS/MTSI за июнь–август 2011 года и различия
винтажа исторической идентичности в 37 месяцах. Точные наборы выпусков согласованы для 223 из 226 месячных срезов; принятый индикатор не менялся;
- накопительная интерпретация реальных фондов: полное отсутствие распределений
и вся история корпоративных действий отдельно не сертифицированы;
- условность исполнения индекса, моделей и официальных справочных рядов,
отсутствие симуляции реальных заявок, открытия, ликвидности и проскальзывания.
Протокол не является полным повторным построением исходных архивов, визуальным или браузерным аудитом либо подтверждением фактической торговли. Результат бэктеста остаётся гипотетическим. Перебор порогов является анализом чувствительности на истории, а не доказательством будущей оптимальности.
Корректная краткая формулировка результата:
> В проверенной последовательности расчёта использование будущих наблюдений > для прошлых оценок и исполнения не обнаружено. Строгая историческая > доступность всех исходных данных не подтверждена. Операции моделируются по > допущенным дневным расчётным уровням, а не по подтверждённым ценам реальных > биржевых сделок.
Воспроизведение и сохранение результата
Выполнить из корня репозитория с закреплёнными входами и соответствующей редакцией кода:
node tests/backtest-causality.mjs
Сохранить протокол work/review/causality-audit.json вместе с точным commit, параметрами запуска, версией данных и хешами. Протокол фиксирует физический хеш входного JSON и хеши динамического движка, статического движка и теста. Каталог work/ является рабочим: его файл следует включить в постоянный контрольный комплект, а не считать сам каталог резервной копией.
Для восстановления исходников руководствоваться docs/PROJECT_CONTINUITY_RU.md и docs/PROJECT_DATA_REGISTRY_RU.md. Не загружать источники заново под видом того же винтажа и не заменять принятые хеши для устранения сбоя теста. Для воспроизведения конкретного пользовательского расчёта нужны тот же JSON, код, манифест параметров и фактическое окно. Новый рыночный срез или изменение правил требуют отдельной версии и нового протокола.
Первичные отраслевые источники
Эти материалы поддерживают определения и принципы проверки; они не сертифицируют формулу PortfolioLab и не означают соответствие GIPS.
Полная доходность включает изменение стоимости составляющих и реинвестирование дивидендов. Повторное добавление дивидендов к MCFTR недопустимо в принятом расчёте.
Официальная цена закрытия публикуется как LEGALCLOSEPRICE; её следует отличать от иных дневных показателей.
На страницах 5 и 8 документа разобраны возможное различие последней сделки и официальной цены закрытия и исполнение заявки MOC на аукционе. Эти описания не подтверждают участие модельного портфеля в аукционе; текущее расписание не переносится автоматически на всю историю.
Бэктест приближает реальный инвестиционный процесс: правила, построение портфеля, ребалансировка, последующий результат. Отдельно учитываются look-ahead bias и survivorship bias.
Страницы PDF 18–19 описывают исторический состав инвестиционной вселенной и использование только доступной тогда информации; страница 54 — документацию для воспроизведения данных, тестов, метрик и решений.