# CM-Balance: план приведения в рабочий вид

Составлен 4 августа 2026 после сверки кода с `docs/CM_BALANCE_ARCHITECTURE.md`
(задуманная архитектура) и с исходным коммитом на GitHub.

---

## Короткий вывод

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

Данные при этом в порядке: часовые бары собираются автоматически, 4H строим
сами, глубина истории доведена до декабря 2024. Чинить нужно слои и логику
поверх них.

---

## Поправка к прежнему выводу

Ранее строка выбора шага

```python
sc = cfg.get('backup', cfg.get('primary'))
```

была названа опечаткой — мол, `.get` с дефолтом работает наоборот задуманного.
**Архитектурный документ говорит обратное:** «Берёт STEP из реестра
(backup/wide для production)» и «Wide STEP в production: backup/EXPL STEP для
снижения шума». Это осознанный выбор.

Значит проблема не в выборе шага, а в **несогласованности**: часть цепей
построена на `primary`, часть на `backup`, а продлеваются все на `backup`.
Лечится приведением историй к производственному шагу, а не переключением
продления на `primary`.

---

## Что уже совпадает с задумкой

Ресэмпл 60M→4H, append-only цепи, отказоустойчивость по тикерам, слой TLT,
форматы вывода — работают как описано.

Три вещи переросли задумку в лучшую сторону:

- документ пишет «пайплайн НЕ качает данные сам» — теперь качает, через
  gRPC Finam, ежечасно;
- в CSV попадают только полностью закрытые бары;
- цепь продлевается инкрементально по новым барам, а не переигрывается заново.

---

## Где расходится

| Слой | Задумано | Как есть |
|---|---|---|
| Данные | 60M вручную из терминала | автосбор Finam, глубина до 12.2024 ✅ |
| Цепи | единый производственный шаг | смесь primary/backup, перелом 27.07–03.08 |
| Слой 1 RTSI | недельная цепь из Finam CSV | **исходников нет**, `segments` и `reassessment` ведутся руками |
| Слой 2 TLT | Yahoo, автоматически | работает ✅ |
| Слой 3 OI | EXPLOSION при OI ≥ 49160 | сборщик пишет `regime`, движок читает `oi_regime` — и получает ACTIVE |
| Матрица | 6 состояний | 2 недостижимы, override без срока годности, CM-aligned не написан |
| CM | перекос лонгов против шортов | всегда 100% — сканируется одно направление |

### Подробности по трём главным

**Слой OI — два ключа.** `oi_updater.py` пишет пороги в `oi_thresholds`, а
вычисленный режим в `regime`. Движок `_get_oi_signal` читает `oi_regime` —
рукописный блок, где у EXPLOSION стоит заглушка `999999`. При текущем
`current_oi = 72420` сборщик докладывает **EXPLOSION**, движок — **ACTIVE**.
Тот же класс ошибки, что раньше обнулял секции брифинга: writer и reader
разошлись в имени ключа.

**Слой RTSI — нет источника.** Файлов `RTSIWeekly1.csv` / `RTSIWeekly2.csv`,
из которых строилась цепь, на диске нет. `segments` (13 событий) и
`reassessment` не пишет ни одна строка кода — `rotation-tlt` обновляет только
под-словарь `tlt_130d`. Поле `distance_to_long_key_pct = 3.7`, от которого
зависит направление слоя, заморожено на 1 августа.

Хорошая новость: `RTSI@MISX` отдаётся Finam на недельном таймфрейме — 82 бара
за 19 месяцев одним запросом. В самом JSON лежат все параметры цепи:
`step = 48.5` (единый, утверждён 01.08) и `structural_s0_start = 754.0`.

**Матрица — две ветки мертвы.** `layers_agree` принимает только значения 0 и 2,
промежуточного 1 не бывает. Перебор всех 270 комбинаций входов:

| Вердикт | Достижим |
|---|---|
| NONE — no agreement | 175 |
| REVERSAL — RTSI segment completed | 45 |
| FULL — 3/3 layers aligned | 30 |
| HALF — 2/3 layers (OI not confirming) | 20 |
| HALF — 2/3 layers (no RTSI confirmation) | **0** |
| EARLY — 1/3 layers only | **0** |

И override на `TARGET_HIT` срабатывает без проверки даты. Событие датировано
**20 июля**; без override слои дают `rtsi LONG` против `tlt SHORT`, согласия
нет, вердикт был бы «NONE, размер 0, вне рынка». С ним — SHORT на половину
капитала, и так уже две недели.

---

## План

**Этап 1. Слой OI — починить чтение.**
Движок должен брать режим оттуда, куда его пишет сборщик; рукописный блок с
заглушкой убрать.
*Приёмка:* режим в сигнале совпадает с тем, что докладывает `rotation-oi`.

**Этап 2. Слой RTSI — снять с ручного ведения.**
Тянуть `RTSI@MISX` недельками через `finam_provider` (добавить `TIME_FRAME_W`
в таблицу закрытости), строить цепь тем же `SegmentEngine` от `s0 = 754.0`,
`step = 48.5`, а `reassessment` вычислять из состояния цепи. Автоматическая
цепь — единственный источник рабочего RTSI-сигнала; нынешняя ручная история
остаётся архивом и эталоном для сверки.
*Приёмка:* автоматическая цепь воспроизводит нынешние 13 рукописных событий.
При расхождении модель публикует `RTSI_MISMATCH`, не подменяет тихо один
источник другим и считает решение без RTSI-подтверждения до ручного разбора.

**Этап 3. Матрица решений.**
Считать `layers_agree` как реальное число согласных слоёв и дописать правило
CM-aligned. `TARGET_HIT` оставляем бессрочным override: TTL не вводим.
*Приёмка:* перебор комбинаций даёт все шесть вердиктов из таблицы документа.

**Этап 4. Critical Mass.**
Сканировать оба направления, считать CM по обоим, в кандидаты отдавать только
активное направление.
*Приёмка:* CM перестаёт быть константой 100%, порог «CM > 65%» обретает смысл.

**Этап 5. Пересборка цепей.**
Сначала архивировать все 22 текущие цепи в датированный каталог. Затем
пересчитать их одним прогоном на едином `backup`/wide STEP по догруженной
истории. Архив не удалять и не изменять.
*Делать последним:* самый рискованный этап; решение о production STEP принято
в пользу `backup`/wide согласно архитектурному документу.

**Этап 6. Проверка.**
Тесты на матрицу и слои по образцу `tests/test_chain_resume.py`, плюс прогон
«как выглядел бы сигнал» по всей истории — увидеть, выдаёт ли модель когда-либо
FULL или живёт на одном протухшем override.

**Порядок:** этапы 1–2 дают честные входы, 3–4 — честную логику, 5 — честные
цепи. Если начать с 5, пересобирать придётся ещё раз после каждой правки выше.

---

## Принятые решения

1. **STEP цепей:** все 22 цепи пересобираем на `backup`/wide. Перед этим
   сохраняем текущие цепи в неизменяемый архив.
2. **`TARGET_HIT`:** override остаётся бессрочным. TTL не нужен.
3. **RTSI:** автоматическая недельная цепь — источник истины. Ручная история
   сохраняется как архив и эталон для проверки. При несовпадении — статус
   `RTSI_MISMATCH` и ручной разбор, без неявного переключения модели.
4. **`ACTIVE_REVERSAL` и PnL:** это закрытие уже открытой позиции. Остаток
   закрывается по `close` 4H-свечи, вызвавшей разворот; после `PARTIAL_HIT` —
   только оставшиеся 50%, без partial — 100%. В событие пишутся
   `entry_price`, `entry_stop`, `exit_price`, `exit_reason`,
   `closed_fraction`, итоговые `pnl_pct` и `pnl_r`. `pnl_r` — истинный R:
   итоговый процентный результат сделки, делённый на исходный риск от entry
   до stop. Обычный `REVERSAL` остаётся сменой сценария без закрытия, потому
   что активной позиции к этому моменту уже нет.

---

## Чеклист выполнения

- [ ] Зафиксировать снимок текущего состояния: `rotation_state.json`,
      `segment_state.json`, `segment_history_rtsi.json` и 22
      `segment_history_*_4h.json`.
- [x] Скопировать 22 цепи в датированный архив вне рабочего каталога.
- [x] Добавить автоматическую недельную загрузку `RTSI@MISX`; записывать
      исходные свечи, состояние цепи и вычисленный `reassessment`.
- [x] Реализовать сверку автоматической RTSI-цепи с архивными 13 событиями.
      При несовпадении выставлять `RTSI_MISMATCH` и исключать RTSI из
      подтверждающих слоёв до разбора.
- [x] Исправить OI: читать рассчитанный сборщиком `current_regime`; оставить
      контролируемый fallback для старого формата данных только на время
      миграции.
- [x] Переписать матрицу так, чтобы все шесть состояний были достижимы;
      бессрочный `TARGET_HIT` сохранить как явное правило.
- [x] Сканировать LONG и SHORT независимо, считать CM по обеим выборкам и
      публиковать кандидатов только направления итогового решения.
- [x] Пересобрать 22 цепи на `backup`/wide из одинаковой 4H-истории;
      записать новый `chain_state` и не менять архив.
- [x] Сравнить до/после: число событий, текущий S0, направление, уровни,
      кандидаты и итоговый сигнал по каждому инструменту.
- [x] Явно закрывать остаток при `ACTIVE_REVERSAL` по close свечи разворота;
      писать цену выхода, причину и итоговый PnL, включая сделку с partial.
      05.08.2026 рабочая история перед этим пересчётом сохранена в
      `data/archive/cm-balance-chains-20260805-082952/`; 22/22 новых цепей
      затем подтверждены replay.
- [ ] Провести исторический replay и проверить, что модель не опирается на
      скрытые ручные данные или недостижимые ветки.
      Segment-часть выполнена 05.08.2026: 22/22 цепей воспроизводятся;
      отчёт `gcrm-segment-replay-2026-08-05.md`. Пункт остаётся открытым до
      полного GCRM replay с историческим OI RTS/MIX.
- [ ] Закоммитить код, тесты, документацию и только метаданные архива; большие
      данные и секреты в Git не добавлять.

## Обязательные тесты

1. **OI-контракт:** `current_regime=EXPLOSION` при `current_oi=72420` даёт
   `EXPLOSION` и силу `1.0`; legacy-формат даёт ожидаемый fallback.
2. **RTSI-загрузка:** недельные бары Finam приводятся к MSK, формируются только
   из закрытых свечей и сохраняются без дублей.
3. **RTSI-цепь:** зафиксированный набор недельных свечей даёт ожидаемые
   события, S0, направление и `reassessment`.
4. **RTSI mismatch:** несовпадение с архивом выставляет `RTSI_MISMATCH`, не
   меняет архив и не даёт RTSI подтвердить сделку.
5. **Матрица:** параметризованный перебор покрывает `NONE`, обе ветки `HALF`,
   `EARLY`, `FULL` и `REVERSAL`; `TARGET_HIT` остаётся `REVERSAL` независимо
   от возраста события.
6. **CM:** тестовые данные с кандидатами в обе стороны дают CM ниже 100%;
   итоговый список кандидатов содержит только активное направление.
7. **Выбор STEP:** все новые `segment_history_*_4h.json` содержат STEP из
   `backup`; ни один новый event не смешивает STEP с предыдущим состоянием.
8. **Цепь:** полный прогон и инкрементальное продление дают идентичные события
   и финальное состояние (сохранить существующие `test_chain_resume.py`).
9. **Архив:** в нём есть все 22 исходные цепи, а пересборка не может записать
   в архивный каталог.
10. **Replay:** на историческом наборе выход содержателен, все ветки матрицы
    достижимы, а результат не зависит от отсутствующих ручных файлов RTSI.
11. **`ACTIVE_REVERSAL`:** тесты покрывают закрытие 100% без partial и
    оставшихся 50% после partial по close свечи разворота; в обоих случаях
    проверяются `exit_price`, причина и итоговые `pnl_pct`/`pnl_r`.

---

*Связанные документы: `docs/CM_BALANCE_ARCHITECTURE.md` (задумка),
`docs/modules-rotation-pipeline.md` (разбор кода), `TODO.md` (открытые вопросы).*
