# Дослідження 31 — спільна складова ноги

| | |
| --- | --- |
| Пара | BTC_USDT |
| Клас | `клас210х10-1х7д/д` — шість відношень похідних сусідніх рівнів плюс похідна ціни |
| Ряд подій | `train` + `val` разом, сітка `t_start`, 70 647 подій |
| Розмітка | зігзаг 2.3 % на ема10 `price_open`, ноги вгору і вниз нарізно |
| Статус | етапи 1–7 підтверджені, 2026-09-14 |
| Вкладка | [`/common`](http://localhost:8080/common) |
| Правила | [`01-data-and-db.md`](../../rules/01-data-and-db.md), [`02-markup.md`](../../rules/02-markup.md), [`05-visualization.md`](../../rules/05-visualization.md) |

## Ціль

Події однієї ноги тримають **спільний рівень**: усередині ноги сім фіч
класу стоять не там, де в сусідній. Цей рівень однаковий для всіх її
подій, тому про саму подію він не каже нічого — він тільки заважає
побачити події, які з ряду **виділяються**.

Питання етапу: зняти цей спільний рівень відніманням і подивитись, що
лишається.

Дві умови, без яких відповідь не рахується:

1. **Ноги вгору й ноги вниз міряються нарізно.** Це різні режими, і
   спільний рівень у них свій.
2. **База, яку віднімають, причинна.** Середнє **своєї** ноги знає все,
   що в нозі сталось, і відняти його наживо не можна: воно відоме тільки
   після кінця ноги. Тому база береться з **попередньої ноги того самого
   напрямку** — вона закінчилась до першої події цієї.

## Дані

| | |
| --- | --- |
| Датасет | `data/datasets/BTC_USDT/class210x10m1x7dd/{train,val}.npz` |
| Побудова | `python3 rules/scripts/build_class210x10x7dc.py` |
| Подія | `(9, 7)` — девʼять блоків по 21 с, сім фіч |
| Фічі | `d_up`, `d_down`, `d_resist_buy`, `d_bal_resist_buy`, `d_support_sell`, `d_bal_support_sell`, `d_price_avg` |
| Період | 2026-02-14 … 2026-09-07 (`t_start` від 1771091656 до 1788738832) |
| Межа train / val | 1786665720 (2026-08-14 UTC) |
| Ноги | знайдено 93, у замір пішло 70 (35 вгору, 35 вниз), з базою 68 |
| Відкинуто | 23 ноги: діра в ряді понад годину або коротші за 20 подій |
| Подій у ногах | 51 391 |
| Довжина ноги | медіана 562 події, від 80 до 3099 |

Інші пари підключаються тим самим скриптом ключем `--pair`.

## Метод

    ряд подій монети -> ема10(price_open) -> півоти зігзага 2.3 % -> ноги
    -> величина події = згортка девʼяти блоків фічі -> ранг по монеті
    -> база ноги = середній рівень попередньої ноги свого напрямку
    -> залишок = рівень події − база

Розмітка ноги — той самий хід, що в дослідженнях 28, 29 і 30: береться
з їхнього [`edge_profile.py`](../28-leg-edge-profile/scripts/edge_profile.py)
(`legs_of`, `HOLE`, `PERIOD`, `THRESHOLD`), згортки блоків і ранги — з
[`inleg_profile.py`](../30-inleg-events/scripts/inleg_profile.py)
(`reduce_blocks`, `ranks`).

### Величина події

Подія класу — це `(9, 7)`. Фіча згортається в одне число згорткою
`--reduce` (за замовчуванням `mean` — середнє по девʼяти блоках;
доступні ті самі шість, що в дослідженні 30).

### Рівень у рангах

Фічі класу — відношення похідних: скінченного розкиду в них немає, і
середнє сирих чисел не значить нічого
([`01-data-and-db.md`](../../rules/01-data-and-db.md) →
`клас210х10-1х7д/д`). Тому величина переводиться **в ранг серед подій
ніг цієї монети**, 0…1, рівень ряду `0.5`. Криві дві:

| крива | що це |
| ----- | ----- |
| `rank` | ранг самої величини: куди схилена фіча |
| `amplitude` | ранг `|величини|`: наскільки подія велика, без напрямку |

`amplitude` і є «розмір події», з якого починалось питання; `rank`
стоїть поруч, бо спільна складова буває і в знаку.

### База ноги і залишок

```
база(нога)     = середній рівень подій попередньої ноги того самого напрямку
залишок(подія) = рівень(подія) − база(її нога)
```

Перша нога кожного напрямку бази не має і в заміри не входить — звідси
68 ніг з базою з 70.

### Чим міряється, скільки зняла база

`rms` відхилення події від бази, у рангах: менше означає, що база забрала
більше спільного. Три бази стоять поруч:

| база | що віднімається | причинна |
| ---- | --------------- | -------- |
| `pool` | рівень ряду 0.5 — не віднімається нічого | — |
| `previous` | середній рівень попередньої ноги свого напрямку | так |
| `own` | середнє **своєї** ноги | ні: відоме тільки після кінця ноги |

`own` — стеля: скільки спільного в нозі взагалі є. `previous` — скільки
з цього можна зняти наживо. Поруч іде **перенос** — Спірмен між рівнем
ноги і рівнем попередньої ноги того самого напрямку по 34 парах: він
каже, чи доживає рівень до наступної ноги взагалі.

## Скрипти

| Скрипт | Що робить |
| ------ | --------- |
| [`scripts/leg_common.py`](scripts/leg_common.py) | етап 1: ноги, рівні, бази, залишки, ряд кожної ноги |
| [`scripts/cohort_link.py`](scripts/cohort_link.py) | етап 2: когорти по 5 % — кожна з кожною і та сама через N ніг |
| [`scripts/opposite.py`](scripts/opposite.py) | етап 3: сусідні різнонаправлені ноги проти однонаправленого контролю |
| [`scripts/zones.py`](scripts/zones.py) | етап 4: зона півота і зона корекції — межі за фічами класу |
| [`scripts/zone_model.py`](scripts/zone_model.py) | етап 5: модель констатації трьох видів події |
| [`scripts/zone_lstm.py`](scripts/zone_lstm.py) | етап 6: LSTM з памʼяттю в кілька ніг на тій самій розмітці |
| [`scripts/zone_grid.py`](scripts/zone_grid.py) | етап 7: грід функцій втрат, прицільного штрафу за плутанину і ручок мережі |

```bash
python3 research/31-leg-common-part/scripts/leg_common.py
python3 research/31-leg-common-part/scripts/cohort_link.py
python3 research/31-leg-common-part/scripts/opposite.py
python3 research/31-leg-common-part/scripts/zones.py
python3 research/31-leg-common-part/scripts/zone_model.py --pairs all \
    --series BTC_USDT
python3 research/31-leg-common-part/scripts/zone_lstm.py --memory 1,3,5,0
python3 research/31-leg-common-part/scripts/zone_lstm.py --memory 1,3,5,0 \
    --align legs
python3 research/31-leg-common-part/scripts/zone_lstm.py --memory 1,3,5,0 \
    --head softmax
python3 research/31-leg-common-part/scripts/zone_lstm.py --memory 0 \
    --head sigmoid --series BTC_USDT
python3 research/31-leg-common-part/scripts/zone_grid.py --epochs 8 \
    --series BTC_USDT
python3 research/31-leg-common-part/scripts/leg_common.py \
    --pair BTC_USDT --class class210x10m1x7dd --reduce mean --part all
python3 -m unittest discover -s tests -t tests -p "test_leg_common.py"
python3 -m unittest discover -s tests -t tests -p "test_leg_cohorts.py"
```

Параметри заміру (у скрипті): `THRESHOLD = 0.023`, `PERIOD = 10`,
`HOLE = 3600` с, `MIN_LEG = 20` подій, `LEVEL = 0.5`, `REDUCE = "mean"`,
`CURVES = ("rank", "amplitude")`. Випадковості в заміру немає, тому seed
не потрібен: ті самі дані дають ті самі числа.

### Вихід

| Файл | Що в ньому |
| ---- | ---------- |
| `results/leg_common_BTC_USDT_class210x10m1x7dd.json` | зведення, перелік ніг, база й рівень кожної |
| `results/leg_common_BTC_USDT_class210x10m1x7dd.meta.json` | те саме без переліку ніг — для картки вкладки «Дослідження» |
| `results/legs/leg_common_BTC_USDT_class210x10m1x7dd_<нога>.json` | ряд однієї ноги: час, ціна, ема, півоти, рівні всіх фіч, база |
| `results/cohort_link_BTC_USDT_class210x10m1x7dd.json` | етап 2: матриці 20 × 20 і драбина сусідніх ніг |
| `results/opposite_BTC_USDT_class210x10m1x7dd.json` | етап 3: пари через півот проти однонаправленого контролю |
| `results/zones_BTC_USDT_class210x10m1x7dd.json` | етап 4: межі зон, розміри, порівняння трьох видів, калібрування |
| `results/zone_model_all_class210x10m1x7dd.json` | етап 5: модель на шести монетах, метрики на val і по кожній монеті |
| `results/zone_series_BTC_USDT_class210x10m1x7dd.json` | ряд val BTC: час, ціна, ема, півоти, зони, мітка і три ймовірності на кожній події |
| `results/zone_lstm_all_class210x10m1x7dd_softmax.json` | етап 6: софтмакс, усі чотири памʼяті, блоки за подіями |
| `results/zone_lstm_all_class210x10m1x7dd_sigmoid.json` | той самий прогін памʼяті «увесь ряд» із трьома незалежними каналами |
| `results/zone_lstm_all_class210x10m1x7dd_legs.json` | той самий прогін із нарізкою за ногами — показує ціну підказки |
| `results/zone_lstm_series_BTC_USDT_class210x10m1x7dd.json` | ряд val BTC із видачею мережі **на кожній епосі**: час, ціна, мітка, півоти, зони і три ймовірності на епоху (незалежні канали) |
| `results/zone_lstm_series_BTC_USDT_class210x10m1x7dd_softmax.json` | той самий ряд із софтмаксної голови — для порівняння |
| `results/zone_grid_all_class210x10m1x7dd.json` | етап 7: усі набори гріду з їхніми метриками на val |
| `results/zone_grid_series_BTC_USDT_class210x10m1x7dd.json` | ряд val BTC із видачею кращого набору, на кожній епосі |

Ряди ніг лежать окремими файлами навмисне: разом сімдесят ніг важать
сім мегабайтів, а вкладці потрібна одна — та, яку вибрали.

## Вкладка [`/common`](http://localhost:8080/common)

Девʼять розділів, кожен зі свого ендпоінта — збій одного не гасить
решту:

1. **Скільки розкиду знімає спільна складова** — таблиця на кожен
   напрямок: фіча × три бази, перенос, рівень ноги, скільки ніг і подій
   за числом. Перемикач кривої — ранг або амплітуда.
2. **Події ноги до і після віднімання** — вибір ноги, напрямку, фічі й
   кривої; чотири вікна на спільній осі X
   ([`05-visualization.md`](../../rules/05-visualization.md)):

   | вікно | що показує |
   | ----- | ---------- |
   | графік | ціна ноги (`price_open` події), зігзаг і обидва півоти |
   | підвал «до» | рівень фічі подія за подією — **смужка на кожну подію**; пунктиром база ноги і рівень ряду |
   | підвал «віднято» | сама спільна складова: база ноги, та сама смужка на кожній події |
   | підвал «після» | залишок — смужка від нуля; точками верхні 5 % ноги за модулем залишку, вгору зелене, вниз червоне |

   Смужки читаються разом: **«до» − «віднято» = «після»**, подія в подію.
   «До» і «віднято» стоять в одній шкалі, порахованій з самих даних, —
   тому видно, яку частину смужки забирає спільна складова.

   Ряд ноги починається з півота, яким вона відкривається: рівні фіч на
   ньому належать попередній нозі, тому в підвалі там розрив.

3. **Когорти по 5 % ноги: кожна з кожною** — матриця 20 × 20 з
   перемикачами напрямку, кривої, етапу (до / після віднімання) і фічі;
   під нею драбина «та сама когорта через 1, 2, 3 ноги свого напрямку»
   з контролем-перемішуванням.
4. **Сусідні різнонаправлені ноги** — AUC «подія ноги вгору проти події
   сусідньої ноги вниз» на кожній фічі, поруч однонаправлений контроль
   і виграш; нижче — різниця по 20 когортах ноги з тим самим контролем.

5. **Зона півота і зона корекції** — розміри зон обома половинами,
   порівняння трьох видів подій із перемикачем «межі за фічами /
   фіксоване вікно», хід несхожості навколо точки розвороту і таблиця
   калібрування порога.
6. **Модель констатації** — три види події на val: AUC, точність
   верхівки, перемикач причинного й центрованого вікна.
7. **Ряд val BTC із видачею моделі** — ціна з зігзагом і смугами зон,
   під нею ймовірності «зона півота» і «зона корекції» на кожній події,
   спільна вісь X, пунктир — поріг верхівки 1 % ряду, точки — події,
   які справді належать зоні.

Зони видно й на графіку ноги (розділ 2): смуга під зігзагом — червона
на зону півота, вохряна на зону корекції.

8. **LSTM з памʼяттю в кілька ніг** — таблиця памʼятей, перемикач
   прогонів «незалежні канали / софтмакс / софтмакс із нарізкою за
   ногами», хід навчання **двома
   графіками** (втрати train і val по епохах; AUC на val із рівнем 0.5
   пунктиром і рискою на обраній епосі) і під ними ряд val BTC із
   видачею цієї мережі: ціна зі смугами зон, `p(зона півота)` і
   `p(зона корекції)` у підвалі на спільній осі X. Видача збережена на
   **кожній епосі**, перемикач епох над графіком — видно, як мережа
   міняла відповідь.

9. **Грід: функція втрат, штраф за плутанину і ручки мережі** — таблиця
   наборів із AUC обох каналів, мірою плутанини й оцінкою; перемикач
   набору міняє графіки епох (втрати, AUC і плутанина) і ряд val
   кращого набору з вибором епохи.

Ендпоінти: `/api/common` — зведення, `/api/common/leg/<нога>` — ряд ноги,
`/api/common/cohorts` — етап 2, `/api/common/opposite` — етап 3,
`/api/common/zones` — етап 4, `/api/common/model` — етап 5,
`/api/common/series` — ряд val BTC із видачею; етап 6 —
`/api/common/lstm/sigmoid`, `/api/common/lstm/softmax`,
`/api/common/lstm/legs` і `/api/common/lstm/series`; етап 7 —
`/api/common/grid` і `/api/common/grid/series`.

## Приклади

Ряд ноги 3 (вгору, 335 подій, база — нога 1):

```
$ python3 -c "import json; d=json.load(open('research/31-leg-common-part/\
results/legs/leg_common_BTC_USDT_class210x10m1x7dd_3.json')); \
print(d['leg'], d['kind'], d['base_leg'], d['events'], d['start']); \
print(d['values']['rank']['d_up'][:4])"
3 up 1 336 1
[None, 0.006, 0.1408, 0.9611]
```

Подій у ряді на одну більше за ноги (336 проти 335): перша — півот, з
якого нога починається, і рівнів вона не має (`None`).

## Результат

Числа — прогін `leg_common.py` на BTC_USDT, згортка `mean`, 34 ноги
кожного напрямку з базою.

### Амплітуда (`|величина|`), rms відхилення від бази

| фіча | ноги вгору: пул → попередня → своя | перенос | ноги вниз: пул → попередня → своя | перенос |
| ---- | ---------------------------------- | ------- | --------------------------------- | ------- |
| `d_up` | 0.299 → 0.305 → 0.278 | 0.450 | 0.275 → 0.279 → 0.260 | 0.368 |
| `d_down` | 0.294 → 0.298 → 0.271 | 0.464 | 0.282 → 0.287 → 0.265 | 0.367 |
| `d_resist_buy` | 0.302 → 0.309 → **0.263** | 0.478 | 0.271 → 0.280 → **0.242** | 0.359 |
| `d_bal_resist_buy` | 0.296 → 0.298 → 0.279 | 0.474 | 0.280 → 0.286 → 0.267 | 0.268 |
| `d_support_sell` | 0.294 → 0.303 → **0.258** | 0.472 | 0.282 → 0.294 → **0.252** | 0.237 |
| `d_bal_support_sell` | 0.290 → 0.293 → 0.276 | 0.512 | 0.287 → 0.297 → 0.273 | 0.198 |
| `d_price_avg` | 0.293 → 0.297 → 0.276 | 0.447 | 0.282 → 0.280 → 0.271 | 0.176 |

### Ранг (величина зі знаком), rms відхилення від бази

| фіча | ноги вгору: пул → попередня → своя | перенос | ноги вниз: пул → попередня → своя | перенос |
| ---- | ---------------------------------- | ------- | --------------------------------- | ------- |
| `d_up` | 0.292 → 0.293 → 0.279 | 0.554 | 0.284 → 0.287 → 0.275 | 0.287 |
| `d_down` | 0.289 → 0.292 → 0.276 | 0.480 | 0.288 → 0.290 → 0.280 | 0.416 |
| `d_resist_buy` | 0.302 → 0.307 → 0.276 | 0.470 | 0.272 → 0.279 → 0.252 | 0.331 |
| `d_bal_resist_buy` | 0.298 → 0.298 → 0.296 | 0.494 | 0.279 → 0.281 → 0.277 | 0.051 |
| `d_support_sell` | 0.295 → 0.302 → 0.270 | 0.483 | 0.282 → 0.289 → 0.261 | 0.233 |
| `d_bal_support_sell` | 0.292 → 0.292 → 0.290 | 0.405 | 0.287 → 0.289 → 0.285 | −0.090 |
| `d_price_avg` | 0.282 → 0.282 → 0.282 | 0.131 | 0.295 → 0.295 → 0.294 | 0.004 |

## Висновки

1. **Спільна складова в нозі є.** Середнє своєї ноги знімає до 14 %
   розкиду: за амплітудою `d_support_sell` падає з 0.294 до 0.258 на
   ногах вгору і з 0.282 до 0.252 на ногах вниз, `d_resist_buy` — з
   0.302 до 0.263 і з 0.271 до 0.242. Це рівні стакана на свій обʼєм;
   на балансах (`d_bal_*`) і на ціні спільного менше — 1…6 %.

2. **Через півот вона переноситься наполовину.** Перенос за амплітудою
   0.45…0.51 на ногах вгору, але 0.18…0.37 на ногах вниз; за рангом на
   ногах вниз він зникає зовсім (`d_bal_support_sell` −0.09,
   `d_price_avg` 0.00). Рівень ноги знає про напрямок більше, ніж про
   свою попередницю.

3. **Тому база з однієї попередньої ноги розкид не знімає, а трохи
   додає**: 0.294 → 0.303 (`d_support_sell`, амплітуда, ноги вгору),
   0.282 → 0.294 (те саме вниз). Віднімання рівня, який не вгадано,
   додає до розкиду власну похибку бази. Рівно на одній фічі
   (`d_price_avg`, ноги вниз) вона працює в нуль: 0.282 → 0.280.

4. **Спільне в нозі й причинно доступне — це поки різні речі.** Стеля
   (`own`) показує, що знімати є що; з одної попередньої ноги цього
   взяти не вийшло. Далі напрошуються дві дороги: інша причинна база
   (кілька попередніх ніг свого напрямку, причинна ЕМА рівня по ряду
   ніг) і зняття рівня вже **всередині** ноги — від підтвердженого
   півота, лічильником подій.

Візуально це видно на вкладці: смужка «віднято» однакова на всіх подіях
ноги, тому «після» повторює форму «до», опущену на цю сталу. Форма ряду
не міняється — база одна на всю ногу; міняється тільки те, від чого
відлічуються сплески.

## Етап 2 — когорти по 5 % ноги

Нога ділиться на **20 когорт по 5 %** довжини. Рівень когорти — середнє
її подій. Питання два:

1. **Когорта з когортою** — Спірмен кожної когорти з кожною, порахований
   **по ногах** (одиниця вибірки — нога, `rules/03-models.md`): чи тягне
   рівень початку ноги рівень її кінця.
2. **Та сама позиція в наступній нозі свого напрямку** — Спірмен між
   когортою `k` ноги і когортою `k` наступної ноги того ж напрямку,
   з контролем: та сама міра на 200 випадкових перестановках ніг
   (seed 0).

Обидва — двічі: **до** віднімання спільної складової і **після**.

Скрипт: [`scripts/cohort_link.py`](scripts/cohort_link.py), вихід
`results/cohort_link_BTC_USDT_class210x10m1x7dd.json`. 34 ноги кожного
напрямку з базою, похибка Спірмена `1/sqrt(34−3)` = **±0.18**.

### Результат: когорта з когортою (амплітуда)

| фіча | вгору: сусідні / усі пари / перша з останньою | вниз: сусідні / усі / перша з останньою |
| ---- | -------------------------------------------- | --------------------------------------- |
| `d_up` | 0.757 / 0.484 / 0.186 | 0.629 / 0.322 / 0.049 |
| `d_down` | 0.767 / 0.515 / 0.209 | 0.590 / 0.324 / 0.126 |
| `d_resist_buy` | 0.804 / 0.508 / 0.168 | 0.697 / 0.368 / 0.024 |
| `d_bal_resist_buy` | 0.711 / 0.398 / 0.081 | 0.596 / 0.272 / −0.051 |
| `d_support_sell` | 0.815 / 0.536 / 0.183 | 0.681 / 0.364 / 0.182 |
| `d_bal_support_sell` | 0.720 / 0.449 / 0.124 | 0.601 / 0.308 / 0.224 |
| `d_price_avg` | 0.733 / 0.463 / 0.055 | 0.526 / 0.272 / −0.051 |

Числа «до» віднімання; «після» дає ту саму картину (±0.05), бо база —
одне число на ногу і порядку всередині ноги не міняє.

### Результат: та сама когорта в наступній нозі свого напрямку

Етап **«до»**, амплітуда; контроль перемішування `p95 ≈ 0.185`:

| фіча | вгору: через 1 / 2 / 3 ноги | p (через 1) | вниз: через 1 / 2 / 3 | p (через 1) |
| ---- | --------------------------- | ----------- | --------------------- | ----------- |
| `d_up` | 0.256 / 0.201 / 0.122 | 0.005 | 0.157 / 0.126 / 0.095 | 0.020 |
| `d_down` | 0.298 / 0.222 / 0.151 | 0.000 | 0.160 / 0.121 / 0.087 | 0.020 |
| `d_resist_buy` | 0.284 / 0.241 / 0.194 | 0.010 | 0.158 / 0.107 / 0.040 | 0.070 |
| `d_bal_resist_buy` | 0.226 / 0.228 / 0.188 | 0.000 | 0.123 / 0.048 / 0.050 | 0.050 |
| `d_support_sell` | 0.281 / 0.243 / 0.167 | 0.000 | 0.140 / 0.134 / 0.044 | 0.100 |
| `d_bal_support_sell` | 0.261 / 0.246 / 0.187 | 0.000 | 0.102 / 0.092 / 0.075 | 0.170 |
| `d_price_avg` | 0.216 / 0.142 / 0.099 | 0.015 | 0.082 / 0.151 / 0.065 | 0.220 |

На етапі **«після»** те саме число виходить від'ємним (−0.12 … −0.26).
Це **артефакт побудови**, а не знахідка: залишок ноги `n` містить
`−рівень(n−1)`, а залишок ноги `n+1` містить `+рівень(n)` з тим самим
знаком навпаки, тому від'ємна кореляція зʼявляється сама. Питання про
повторюваність позиції читається тільки на етапі «до».

### Висновки етапу 2

1. **Рівень усередині ноги — повільна величина.** Сусідні когорти
   тримаються разом на 0.53…0.82, і звʼязок спадає з відстанню: перша
   пʼятірка відсотків з останньою вже 0.02…0.22. Нога — не одне плато,
   а дрейф.
2. **Ноги вгору звʼязані сильніше за ноги вниз** по всіх семи фічах:
   0.59…0.82 проти 0.53…0.70 для сусідніх когорт. Падіння «рваніше».
3. **Позиція повторюється в наступній нозі свого напрямку — слабко, але
   не випадково**: вгору 0.22…0.30 при контролі 0.185 і `p ≤ 0.015`;
   вниз 0.08…0.16, і на трьох фічах це вже в межах контролю. Спад від
   ноги до ноги повільний (через 3 ноги ще 0.10…0.19) — це радше
   загальний режим ринку, ніж памʼять саме про попередню ногу.

## Етап 3 — сусідні різнонаправлені ноги

Зігзаг чергує напрямки, тому дві сусідні ноги завжди різнонаправлені й
діляться спільним півотом: той самий ринок, та сама година, різний
напрямок руху. Порівняння йде **всередині кожної пари**, одиниця
вибірки — пара ніг.

| міра | що це |
| ---- | ----- |
| `AUC` | ймовірність, що випадкова подія ноги вгору вища за випадкову подію сусідньої ноги вниз |
| `by_cohort` | різниця рівнів по 20 когортах (вгору мінус вниз) |
| контроль | те саме на парі **однонаправлених** ніг через одну — та сама часова відстань, напрямку немає |
| виграш | `|AUC−0.5|` пари через півот мінус те саме в контролі |

Скрипт: [`scripts/opposite.py`](scripts/opposite.py), вихід
`results/opposite_BTC_USDT_class210x10m1x7dd.json`. 63 пари через півот
проти 59 однонаправлених.

### Результат (крива `rank`)

| фіча | AUC вгору проти вниз | t | пар вище 0.5 | \|відхід\| | контроль | виграш |
| ---- | -------------------- | - | ------------ | ---------- | -------- | ------ |
| `d_up` | 0.494 | −0.5 | 44 % | 0.073 | 0.077 | −0.004 |
| `d_down` | 0.496 | −0.3 | 46 % | 0.076 | 0.081 | −0.005 |
| `d_resist_buy` | 0.501 | 0.1 | 51 % | 0.108 | 0.114 | −0.006 |
| `d_bal_resist_buy` | 0.510 | 1.5 | 52 % | 0.041 | 0.040 | 0.001 |
| `d_support_sell` | 0.512 | 0.7 | 51 % | 0.116 | 0.120 | −0.004 |
| `d_bal_support_sell` | 0.515 | 2.1 | 63 % | 0.041 | 0.039 | 0.002 |
| **`d_price_avg`** | **0.533** | **15.1** | **100 %** | **0.033** | **0.013** | **0.020** |

На кривій `amplitude` не відрізняється **жодна** фіча: AUC 0.485…0.510,
виграш від −0.008 до −0.001. Розмір події про напрямок ноги не знає.

### Де в нозі різниця: `d_price_avg`, rank

| когорта | 0–5 | 5–10 | 10–15 | 15–20 | 20–25 | 25–30 | 30–40 | 40–60 | 60–70 | 70–85 | 85–90 | 90–95 | 95–100 |
| ------- | --- | ---- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ------ |
| різниця | **0.102** | 0.022 | 0.052 | 0.040 | 0.028 | 0.026 | 0.014…0.022 | ≈0 | 0.010…0.065 | 0.015…0.040 | 0.061 | **0.093** | 0.037 |
| t | 12.1 | 2.1 | 3.9 | 3.6 | 2.5 | 2.7 | 1.5…1.9 | −0.2…0.9 | 0.8…5.1 | 1.5…2.4 | 4.2 | 8.2 | 2.8 |

Контроль на всіх когортах плаский: від −0.007 до +0.006, `|t| ≤ 0.5`.

### Висновки етапу 3

1. **Шість фіч стакана про напрямок сусідньої ноги не знають нічого.**
   AUC 0.49…0.52, і весь відхід від 0.5 пояснюється контролем: дві
   однонаправлені ноги на тій самій відстані розходяться так само.
   Різниця, яку видно, — це час, а не напрямок.
2. **Знає тільки похідна ціни** — `d_price_avg`, AUC 0.533 при контролі
   0.499, `t = 15.1`, і в **кожній** з 63 пар AUC вище 0.5. Ефект
   невеликий, але однозначний.
3. **Він живе на краях ноги**: пік на перших 5 % (різниця 0.102,
   `t = 12.1`) і на 90–95 % (0.093, `t = 8.2`), провал у середині
   (40–60 % — нуль). Це той самий край ноги, що в дослідженнях 28–30, і
   той самий висновок з іншого боку: **нову ногу видно з її початку**,
   а середина ноги напрямку не показує.

## Етап 4 — зона півота і зона корекції

Півот зігзага — оцінка, а не подія: ема зсуває екстремум, ряд подій
проріджений по 210 с, поріг 2.3 % підтверджується заднім числом. Тому
розворот займає **відрізок**, і межі цього відрізка ставляться
**за фічами класу**:

    від точки розвороту йдемо ліворуч, поки подія відрізняється від
    середнього рівня своєї ноги; як тільки події стали звичайними —
    ліва межа знайдена. Так само праворуч, але вже проти середнього
    рівня наступної ноги.

Ліва половина належить хвосту старої ноги, права — голові нової, і
кожна міряється проти **своєї** ноги. Довжини половин не задані наперед.

**Корекція** — те саме правило навколо вкладеного розвороту: всередині
ноги ціна ходить проти неї й повертається. Такі точки знаходить зігзаг
`0.5 %` по тій самій ема10; обидві половини зони корекції міряються
проти однієї ноги.

Ряд стає трьома видами подій: `regular`, `correction`, `pivot`.

### Чим міряється несхожість

```
z_f(t) = |ранг_f(t) − медіана_f(нога)| / IQR_f(нога)
d(t)   = медіана по семи фічах z_f(t),  згладжена 5 подіями
```

Медіана по фічах, а не середнє: одна фіча в хвості не робить зону сама.
Поріг — **квантиль `d` самої ноги** (0.75), а не стале число: розкид `d`
у ніг різний. Зону закривають 3 звичайні події поспіль; довша половина
не буває більшою за чверть ноги. Опора ноги (медіана й IQR) рахується
двічі: спершу по середині ноги (десятини 4–7), потім по її подіях поза
зонами.

### Результат: розміри зон (BTC_USDT)

| вид | зон | ліворуч, подій | праворуч, подій | разом | розмах ціни |
| --- | --- | -------------- | --------------- | ----- | ----------- |
| зона півота | 63 | 5.8 ± 1.3 | 7.3 ± 2.0 | 15.0 | 0.71 % |
| зона корекції | 400 | 5.5 ± 0.8 | 4.9 ± 0.7 | 11.4 | 0.42 % |

Права половина довша за ліву на 1.5 події, але `t = 0.6` — на 63 зонах
це не різниця.

Подій у ногах 51 391: звичайні 47 273 (92.0 %), зона корекції 3 171
(6.2 %), зона півота 947 (1.8 %).

### Результат: чим три види відрізняються

Рівні в рангах і AUC. Ліва половина таблиці — зона, нарізана за фічами;
права — **контроль фіксованим вікном ±5 подій** навколо тієї самої
точки, який про фічі не знає нічого.

| фіча | звичайна | корекція | півот | AUC п/з | AUC п/з, вікно | AUC к/з, вікно | AUC п/к, вікно |
| ---- | -------- | -------- | ----- | ------- | -------------- | -------------- | -------------- |
| `d_up` | 0.512 | 0.375 | 0.336 | 0.324 | **0.338** | 0.423 | 0.415 |
| `d_down` | 0.510 | 0.393 | 0.353 | 0.343 | **0.374** | 0.426 | 0.439 |
| `d_resist_buy` | 0.484 | 0.672 | 0.716 | 0.732 | **0.696** | 0.611 | 0.597 |
| `d_bal_resist_buy` | 0.495 | 0.559 | 0.562 | 0.567 | 0.551 | 0.524 | 0.531 |
| `d_support_sell` | 0.485 | 0.660 | 0.696 | 0.711 | **0.703** | 0.604 | 0.614 |
| `d_bal_support_sell` | 0.496 | 0.546 | 0.551 | 0.555 | 0.556 | 0.516 | 0.546 |
| `d_price_avg` | 0.499 | 0.511 | 0.497 | 0.498 | 0.499 | 0.498 | 0.501 |

### Результат: чи стоїть правило на чомусь

Хід несхожості `d` навколо точки розвороту, медіана по зонах:

| | у точці | за 40 подій | підйом |
| --- | ------- | ----------- | ------ |
| півот | 0.561 | 0.511 | **+9.7 %** |
| корекція | 0.510 | 0.501 | +1.8 % |

Калібрування порога — як розмір зони залежить від квантиля:

| квантиль ноги | звичайні | корекція | півот | подій у зоні півота | у зоні корекції |
| ------------- | -------- | -------- | ----- | ------------------- | --------------- |
| 0.50 | 82.0 % | 12.6 % | 5.4 % | 43.8 | 59.5 |
| 0.70 | 90.1 % | 7.3 % | 2.6 % | 20.9 | 13.8 |
| **0.75** | 92.0 % | 6.2 % | 1.8 % | 15.0 | 11.4 |
| 0.80 | 93.6 % | 4.7 % | 1.7 % | 13.5 | 7.6 |
| 0.90 | 96.4 % | 2.8 % | 0.9 % | 7.1 | 3.6 |

### Висновки етапу 4

1. **Зона розвороту — близько 15 подій** (5.8 ліворуч, 7.3 праворуч і
   сама точка), тобто ~53 хвилини ряду, у яких ціна ходить на 0.71 %.
   Зона корекції коротша (11.4) і вужча (0.42 %).
2. **У розвороту є підпис у класі, і він не від способу нарізки.**
   Рівні стакана на свій обʼєм ростуть (`d_resist_buy` 0.484 → 0.716),
   обʼєм на рівень падає (`d_up` 0.512 → 0.336). Контроль фіксованим
   вікном дає те саме: AUC 0.696 і 0.338. Ціна (`d_price_avg`) у цьому
   не бере участі взагалі — 0.499.
3. **Корекція — той самий підпис, слабший.** У контрольному вікні
   корекція проти звичайної події дає 0.611 і 0.604 там, де півот дає
   0.696 і 0.703, а півот проти корекції — 0.597 і 0.614. Три види
   стоять **на одній осі**, а не в різних місцях: звичайна → корекція →
   півот.
4. **Сама несхожість `d` слабка**: +9.7 % у точці півота й +1.8 % у
   корекції. Тому межа зони мʼяка, а поріг мусить бути відносний
   (квантиль ноги), — на сталому порозі зона в одній нозі з'їдала б
   чверть ноги, а в іншій не існувала б.

## Етап 5 — модель констатації

На розмітці етапу 4 вчиться модель, яка каже, **що зараз**: звичайна
подія, зона корекції чи зона півота.

| | |
| --- | --- |
| Вхід | рівні семи фіч у вікні 11 подій, пʼять чисел на фічу (середнє, останнє, розкид, розмах, сама подія) — **35 колонок** |
| Ранги | зняті **тільки з train** (`rules/01` → Скейлер, порахований на train) |
| Вікно | не виходить за межі своєї ноги: подія сусідньої означала б погляд через півот |
| Модель | градієнтний бустинг (`HistGradientBoostingClassifier`), seed 0 |
| Розбиття | за часом, межа — перший час у `val.npz`; зона на межі відкидається цілком |
| Монети | усі шість: на одному BTC у val лишається **вісім** зон півота |

Два вікна: `causal` (тільки минуле — те, що рахується наживо) і
`centered` (з майбутнім — стеля констатації).

Лічильника від півота у вході **немає**: він знає відповідь на питання,
яке ставиться.

### Результат (val, шість монет, 89 зон півота і 482 корекції)

| вид події | подій у val | частка | AUC, `causal` | AUC, `centered` |
| --------- | ----------- | ------ | ------------- | --------------- |
| звичайна | 41 856 | 89.8 % | 0.705 | 0.699 |
| зона корекції | 3 792 | 8.1 % | 0.679 | 0.679 |
| **зона півота** | 957 | 2.1 % | **0.724** | 0.718 |

Влучність 0.891 проти 0.898 у відповіді «завжди звичайна»: класи рідкі,
тому по `argmax` модель майже мовчить. Читається вона порогом:

| верхівка val за ймовірністю «півот» | подій | точність | підйом | повнота |
| ---------------------------------- | ----- | -------- | ------ | ------- |
| 0.1 % | 46 | **17.4 %** | ×8.5 | 0.8 % |
| 0.5 % | 233 | 9.0 % | ×4.4 | 2.2 % |
| 1 % | 466 | 7.9 % | ×3.9 | 3.8 % |
| 2 % | 932 | 7.8 % | ×3.8 | 7.6 % |
| 5 % | 2 330 | 6.7 % | ×3.3 | 16.3 % |

### Видача по монетах (val, причинне вікно)

Модель одна, навчена на train усіх шести монет; видача рознесена по
монетах окремо — спільне число ховає монету, у якій зон майже немає.

| монета | подій val | AUC «зона півота» | точність верхівки 1 % | підйом |
| ------ | --------- | ----------------- | --------------------- | ------ |
| ETH_USDT | 8 775 | 0.750 | 1.1 % | ×0.7 |
| BNB_USDT | 6 096 | 0.734 | 3.3 % | ×13.5 |
| DOGE_USDT | 8 593 | 0.721 | 11.8 % | ×3.0 |
| **BTC_USDT** | 7 896 | **0.654** | **0.0 %** | ×0.0 |
| SOL_USDT | 8 631 | 0.639 | 4.7 % | ×3.8 |
| XRP_USDT | 6 614 | 0.625 | 13.6 % | ×3.0 |

### BTC_USDT на val окремо

Вікно val: 2026-08-14 00:02 … 2026-09-04 15:06 UTC, 7 896 подій. Мітки:
звичайних 6 969, зона корекції 875, **зона півота 52** (вісім зон).

| клас | AUC на BTC val |
| ---- | -------------- |
| звичайна подія | 0.869 |
| зона корекції | 0.816 |
| зона півота | 0.654 |

Верхівка `p(півот)` на BTC не містить **жодної** справжньої зони півота:
ні в 0.1 %, ні в 5 % (394 події). Де стоять справжні мітки за рангом
`p(півот)`:

| мітка | подій | медіана рангу | скільки у верхніх 10 % |
| ----- | ----- | ------------- | ---------------------- |
| зона півота | 52 | 0.620 | 4 |
| зона корекції | 875 | 0.909 | 472 |

Десять найвпевненіших подій BTC val — вісім зон корекції й дві звичайні;
жодного півота:

```
час UTC                 ціна   p(півот)  p(корекц)  правда
2026-08-21 12:13       76874      0.523      0.051  correction
2026-08-25 02:39       80981      0.446      0.290  regular
2026-08-21 11:59       76697      0.436      0.161  correction
2026-08-19 17:32       67851      0.422      0.068  correction
2026-08-21 12:17       76786      0.405      0.106  correction
2026-08-24 13:05       79045      0.389      0.036  regular
2026-08-21 12:20       76820      0.385      0.086  correction
2026-08-20 15:36       72270      0.376      0.140  correction
2026-08-24 13:54       78201      0.368      0.107  correction
2026-08-21 02:04       74977      0.366      0.125  correction
```

Сам ряд лежить у `results/zone_series_BTC_USDT_class210x10m1x7dd.json`
і показаний на вкладці розділом 7: ціна з зігзагом і смугами зон,
під нею ймовірності моделі на кожній із 7 896 подій.

Читається це так: канал «півот» на BTC ловить **корекції**, а не
розвороти. Це те саме, що показав етап 4 — корекція й півот лежать на
одній осі (AUC півот / корекція 0.597), — але тут воно видно
операційно: там, де зон півота мало, весь верх шкали займають корекції,
яких у сімнадцять разів більше.

### Висновки етапу 5

1. **Три види події розрізняються моделлю, але не порогом argmax.**
   AUC 0.724 на зону півота при частці 2.1 % — сигнал є; найвпевненіші
   0.1 % відповідей містять 17.4 % справжніх зон півота проти 2.1 %
   базових, тобто **у 8.5 раза** більше.
2. **Причинне вікно не гірше за центроване** (0.724 проти 0.718): усе,
   що модель знає про розворот, вона знає з минулого. Для роботи наживо
   це головне.
3. **Зона корекції дається найгірше** (0.679) — і це те саме, що
   показав етап 4: корекція лежить між звичайною подією й півотом, тому
   її межі розмиті з обох боків.
4. **Одна монета цього питання не міряє.** На BTC у val лишається вісім
   зон півота (52 події), AUC 0.654, і у верхівці — самі корекції. Тому
   модель учиться на шести монетах одразу — і це перший етап
   дослідження, де так зроблено.
5. **Головна перешкода — не «розворот проти ринку», а «розворот проти
   корекції».** Звичайні події й корекції модель розділяє добре
   (AUC 0.869 і 0.816 на BTC), а півот від корекції — ні. Наступний
   крок, якщо він буде: вчити не три класи одразу, а окремо «зона
   розвороту проти зони корекції» на самих зонах, де класи рівніші
   (493 проти 2 851), а не 2 % проти 90 %.

## Етап 6 — LSTM з памʼяттю в кілька ніг

Етап 5 питав вікном в 11 подій. Тут те саме питання ставиться мережею,
яка веде ряд подія за подією й не забуває попередні ноги.

| | |
| --- | --- |
| Мережа | LSTM 64 × 2 шари, dropout 0.1, голова на 3 логіти **на кожну подію** |
| Вхід | 8 каналів: рівні семи фіч у рангах з train плюс частка пропущених фіч події |
| Голова | три **незалежні** канали `sigmoid` із власним `pos_weight` (за замовчуванням) або спільний `softmax` |
| Втрата | `BCEWithLogits` на кожен канал; під софтмаксом — зважена крос-ентропія (класи 90 / 8 / 2 %) |
| Памʼять | ряд ріжеться на блоки; усередині блока стан іде далі (TBPTT, чанк 512), на початку блока обнуляється |
| Розбиття | за часом, блок не перетинає межу train\|val; зона на межі не йде нікуди |
| Прогін | 6 монет, 8 епох, lr 0.003, seed 0, CPU, ~34 с на епоху |

Памʼять міряється ногами: 1, 3, 5 і «увесь ряд» (стан не обнуляється
взагалі).

### Де починається блок — і чому це вирішує все

Перший прогін різав блоки **по ногах**: блок починався рівно на першій
події ноги. Вийшла красива драбина — і вона фальшива:

| памʼять | AUC «зона півота», блоки за ногами | те саме, блоки за подіями |
| ------- | ---------------------------------- | ------------------------- |
| 1 нога | **0.823** (верхівка 1 % → 40.5 %, ×15.8) | 0.737 (10.9 %, ×4.3) |
| 3 ноги | 0.758 (21.1 %, ×8.3) | 0.740 (10.5 %, ×4.1) |
| 5 ніг | 0.741 (14.6 %, ×5.7) | 0.741 (11.1 %, ×4.3) |
| увесь ряд | 0.738 (10.9 %, ×4.3) | 0.738 (10.9 %, ×4.3) |

Обнулення стану на початку блока **саме́ повідомляє мережі, що тут
півот**. Чим частіше блоки — тим більше таких підказок: при памʼяті в
одну ногу мережа дістає відповідь на кожному розвороті задарма, при
памʼяті на весь ряд підказок немає зовсім, і обидві нарізки сходяться в
одне число. Тому за замовчуванням блоки ріжуться **за подіями**:
довжина та сама (`N × медіана довжини ноги`), але межа про ноги не знає.

### Результат (чесна нарізка, блоки за подіями)

| памʼять | блоків train / val | AUC півот | медіана епох | AUC корекція | верхівка 1 % |
| ------- | ------------------ | --------- | ------------ | ------------ | ------------ |
| 1 нога | 502 / 85 | 0.737 | 0.732 | 0.684 | 10.9 % (×4.3) |
| 3 ноги | 209 / 48 | 0.740 | 0.722 | 0.700 | 10.5 % (×4.1) |
| 5 ніг | 126 / 35 | 0.741 | 0.729 | 0.702 | 11.1 % (×4.3) |
| увесь ряд | 6 / 6 | 0.738 | 0.732 | 0.634 | 10.9 % (×4.3) |

Поруч — бустинг етапу 5 на тій самій розмітці й тому самому розбитті:
AUC 0.724, верхівка 1 % — 7.9 % (×3.9).

AUC по монетах (памʼять 3 ноги): BNB 0.810, ETH 0.801, DOGE 0.739,
BTC 0.664, XRP 0.630, SOL 0.559.

Хід навчання: найкраща епоха — **перша** в усіх чотирьох прогонах, далі
val повільно гіршає (памʼять 3 ноги: 0.740 → 0.719 за вісім епох). Мережа
бере те, що може, одразу, а потім вчить train напамʼять. На памʼяті
«увесь ряд» те саме видно поепохово на BTC: AUC 0.738 → 0.737 → 0.735 →
0.733 → 0.732 → 0.733 → 0.731 → 0.729.

Ряд val BTC з видачею цієї мережі (памʼять «увесь ряд») збережений на
кожній з восьми епох: 7 746 подій, з них 6 825 звичайних, 876 у зоні
корекції і 45 у зоні півота, десять півотів зігзага і 71 зона.

### Голова: чому канал корекції був пласким

Перші прогони йшли під **софтмаксом** — три канали ділили одиницю. Рідкий
клас у такій голові притискається до сталої: на BTC канал «зона корекції»
стояв у смузі 0.30…0.43 при розкиді 0.065, тобто на графіку це рівна
лінія, з якої нічого не прочитати.

Заміна на **три незалежні канали** (`BCEWithLogits` із власним
`pos_weight` на кожен) знімає конкуренцію між класами:

| канал | софтмакс: діапазон / σ / AUC на BTC | незалежні канали |
| ----- | ----------------------------------- | ---------------- |
| зона корекції | 0.215…0.416 / 0.065 / **0.634** | 0.098…0.709 / 0.189 / **0.700** |
| зона півота | 0.020…0.423 / 0.134 / 0.738 | 0.007…0.773 / 0.231 / 0.736 |

Зона півота від заміни не виграла й не втратила, зона корекції —
виграла і в розкиді (втричі), і в AUC. Заразом виправлена помилка
показу: обидва підвали мали сталі межі осі `0…1`, хоча
[`05-visualization.md`](../../rules/05-visualization.md) вимагає рахувати
межі з самих даних — саме тому вузький канал і виглядав нулем.

На всіх шести монетах разом (памʼять «увесь ряд», епоха 8): AUC зони
півота 0.736 проти 0.738 у софтмакса, зони корекції **0.700 проти
0.634**, верхівка 1 % — 8.9 % (×3.5) проти 10.9 % (×4.3).

### Висновки етапу 6

1. **Памʼять у кілька ніг не дає нічого.** 0.737 / 0.740 / 0.741 / 0.738
   — це один рівень у межах похибки, і верхівка скрізь ×4.3. Ані три
   ноги, ані пʼять, ані весь ряд не додають до того, що видно в межах
   поточної ноги.
2. **Уся «драбина памʼяті» першого прогону — артефакт нарізки.** Різниця
   0.823 проти 0.737 виникла з того, що межа блока збігалася з півотом.
   Це той самий клас помилки, що витік: мережа читала не дані, а спосіб
   їх подачі.
3. **Мережа трохи краща за бустинг**: 0.740 проти 0.724 на зоні півота і
   10.9 % проти 7.9 % у верхівці 1 %. Послідовність дає більше, ніж
   згортки вікна в 11 подій, — але це поправка, а не інший результат.
4. **На корекції памʼять шкодить**: 0.684–0.702 у блоках і 0.634 на
   всьому ряді. Що довший блок, то гірше мережа тримає короткі зони.
5. **Ранжування монет те саме, що в бустингу**: BTC і SOL найгірші,
   BNB і ETH найкращі. Це властивість монети, а не моделі.
6. **Форма голови коштує дорожче за памʼять.** Перехід від софтмакса до
   незалежних каналів підняв зону корекції з 0.634 до 0.700 — більше,
   ніж дала будь-яка зміна памʼяті. Коли класи стоять як 90 / 8 / 2 %,
   спільна одиниця софтмакса сама по собі гасить рідкий канал.

## Етап 7 — функція втрат, штраф за плутанину і ручки мережі

Мережа етапу 6 плутає **зону корекції із зоною півота**: в етапі 4 видно
чому — у них той самий підпис у класі, тільки різної сили. Тут ця
помилка штрафується прицільно й перебираються способи це зробити.

### Прицільний штраф і міра плутанини

На події, де правда — **зона півота**, канал «зона корекції» отримує
додаткову втрату з вагою `confuse`
([`zone_lstm.loss_for`](scripts/zone_lstm.py)).

Міра, яку він має зрушити, — `mixup`: частка подій зони півота, де
**ранг** каналу «корекція» вищий за ранг каналу «півот». Рангова
навмисно: опустити канал корекції вниз цілком — не перемога, ранги від
зсуву не міняються.

### Що перебиралось

По одній зміні від бази (`rules/03-models.md`): база — `bce`,
`confuse 0`, `hidden 64`, 2 шари, `lr 0.003`, памʼять «увесь ряд»,
8 епох, seed 0, шість монет. Дані читаються раз на весь грід
([`zone_grid.py`](scripts/zone_grid.py)).

| набір | епоха | AUC півот | AUC корекція | плутанина | оцінка | допуск |
| ----- | ----- | --------- | ------------ | --------- | ------ | ------ |
| **база: bce** | 8 | 0.736 | 0.700 | 0.356 | **1.080** | так |
| bce + штраф 0.3 | 1 | 0.739 | 0.704 | 0.515 | 0.927 | так |
| bce + штраф 1 | 2 | 0.734 | 0.469 | **0.010** | 1.193 | **ні** |
| bce + штраф 3 | 1 | 0.732 | 0.289 | 0.170 | 0.851 | ні |
| focal | 1 | **0.742** | 0.700 | 0.517 | 0.925 | так |
| focal + штраф 1 | 1 | 0.732 | 0.301 | 0.163 | 0.870 | ні |
| softmax + штраф 1 | 1 | 0.739 | 0.567 | 0.322 | 0.983 | ні |
| ширше: hidden 128 | 8 | **0.753** | **0.717** | 0.417 | 1.053 | так |
| мілкіше: 1 шар | 5 | 0.737 | 0.703 | 0.362 | 1.077 | так |
| повільніше: lr 0.001 | 1 | 0.739 | 0.699 | 0.501 | 0.938 | так |

Оцінка = `AUC(півот) + AUC(корекція) − плутанина`.

**Умова допуску.** Плутанину легко звести до нуля, **зламавши** канал
корекції: штраф тисне його вниз, ранги розсипаються, плутанини немає —
але й сказати «це корекція» модель більше не може. Тому набір, у якого
AUC хоч одного каналу нижче **0.6**, у вибір кращого не йде.

### Висновки етапу 7

1. **Прицільний штраф робить рівно те, що просили, і це не допомагає.**
   При вазі 1 плутанина падає з 0.356 до **0.010** — канал «півот» стоїть
   вище каналу «корекція» майже на кожній події зони розвороту. Але AUC
   корекції падає з 0.700 до **0.469**, тобто нижче випадкового: модель
   купує порядок тим, що перестає розрізняти корекцію взагалі.
2. **Так само поводяться всі ваги й усі втрати зі штрафом**: 0.3 →
   плутанина навіть гірша (0.515), 1 і 3 → канал зламано, `focal` і
   `softmax` зі штрафом — те саме. Жоден набір не зменшив плутанину,
   зберігши канал.
3. **Найкраще з допущених — проста база** (оцінка 1.080) і майже така
   сама мережа в один шар (1.077, і вдвічі швидша).
4. **Найсильніші числа дає ширша мережа**: `hidden 128` — AUC півота
   **0.753** і корекції **0.717**, обидва найкращі в гріді; платить
   гіршою плутаниною (0.417) і вдвічі довшим навчанням.
5. **Форма втрати майже нічого не міняє**: `bce` 0.736, `focal` 0.742,
   `softmax` 0.739 на каналі півота. Усе, що видно, — це ширина мережі й
   кількість епох, а не втрата.
6. Разом із етапом 6 це означає: **зону корекції й зону півота
   розділити не вийшло ні памʼяттю, ні втратою, ні штрафом.** Вони не
   різні події, а одна подія різної сили — саме так, як показав етап 4
   (AUC півот / корекція 0.597 на фіксованому вікні).

## Що можна навчити

Три етапи разом кажуть, що вчити на цьому класі, а що ні. Нижче — тільки
те, що спирається на числа вище; кожне з власним базовим прогнозом
(`rules/03-models.md` → модель без базового прогнозу нічого не означає).

| № | Ціль | Вхід | Базовий прогноз | Що обіцяють числа |
| - | ---- | ---- | --------------- | ----------------- |
| 1 | рівень наступної когорти (5 % ноги) | рівні `K` попередніх когорт цієї ноги + лічильник від півота | перенос числа з поточної когорти | сусідні когорти 0.53…0.82 — базовий буде сильним, модель має його бити, а не повторювати |
| 2 | рівень **усієї** наступної ноги свого напрямку | рівні `M` попередніх ніг обох напрямків, їхні довжини й рухи | рівень попередньої ноги свого напрямку | перенос 0.45…0.51 вгору і 0.18…0.37 вниз: одна нога явно не вся інформація, кілька можуть дати причинну базу, якої етапу 1 забракло |
| 3 | напрямок поточної ноги на її початку | `d_price_avg` перших `N` подій після півота, решта фіч поруч | частка ніг вгору в train | AUC 0.533 на всій нозі й різниця 0.102 на перших 5 % — сигнал є саме на краю |
| 4 | причинна база для віднімання (етап 1 наживо) | вихід моделі 2 | пульний рівень 0.5 | `own` знімає до 14 % розкиду, `previous` — нуль: між ними і є ціна задачі |

Чого на цих даних вчити **не варто**:

- напрямок ноги за шістьма фічами стакана — етап 3 показав AUC 0.49…0.52
  проти контролю, це нуль;
- будь-що з **амплітуди** про напрямок — розмір події напрямку не знає
  (AUC 0.485…0.510);
- модель на залишку етапу 1 з міткою, що залежить від сусідньої ноги:
  залишок несе `−рівень(n−1)` і сам по собі дає від'ємні кореляції, які
  легко прийняти за сигнал.

Розбиття для всіх чотирьох — **по часу** (`rules/03-models.md`): ціль
побудована на згладженому ряді, фічі тягнуть історію. Одиниця вибірки —
нога: 70 ніг на BTC_USDT, тому похибка частки `0.5/sqrt(70)` ≈ 0.06, і
одна монета тут замала — рахувати треба на всіх шести.


## Тести

[`tests/test_leg_common.py`](../../tests/test_leg_common.py) — 30 тестів
етапу 1 на синтетичному ряді й тимчасовому датасеті: відбір ніг, ранги,
база з попередньої ноги свого напрямку, **причинність** (зміна подій ноги
не міняє її бази), три бази поруч, ряд ноги, збереження і CLI.

[`tests/test_zone_lstm.py`](../../tests/test_zone_lstm.py) — 23 тести
етапу 6: блоки памʼяті (не перетинають межу train|val, нарізка за
подіями не йде за ногами, ряд не рветься й не дублюється), канали (сім
рангів плюс прапорець пропуску, жодного NaN), вага рідкого класу зі
стелею, мережа (три логіти на подію, стан переноситься), метрики й
короткий повний прогін.

[`tests/test_leg_zones.py`](../../tests/test_leg_zones.py) — 42 тести
етапів 4 і 5: згладжування, опора ноги, несхожість, ріст зони (терпіння,
обмеження чвертю ноги, спокійний сусід), розмітка (три мітки, півот
сильніший за корекцію, половини не виходять у чужі ноги, вищий поріг
маркує менше), контроль фіксованим вікном, профіль, монотонність
калібрування; для моделі — вікно не перетинає півот, причинне не
потребує майбутнього, точність верхівки на ідеальному й на шумі,
розбиття за часом, збереження і CLI.

[`tests/test_leg_cohorts.py`](../../tests/test_leg_cohorts.py) — 33 тести
етапів 2 і 3: поділ на когорти, `після = до − база`, матриця (одиниці на
діагоналі, симетрія, спільний рівень → всі пари 1.0, шум → нуль),
драбина з контролем, повторюваність прогону за seed, AUC (незчеплені
множини, зв'язки), відбір пар (діра в ряді розриває сусідство, нога
вгору завжди ліва сторона), частка AUC від 0.5, збереження і CLI.

Вкладка — `TestCommonTab`, `TestCommonCohortsTab`, `TestCommonOppositeTab`,
`TestCommonZonesTab`, `TestCommonModelTab`, `TestCommonSeriesTab`,
`TestCommonSeriesMarkup`, `TestCommonWithoutResults`,
`TestCommonSectionsWithoutResults`, `TestCommonZoneSectionsWithoutResults`,
`TestCommonMarkup`, `TestCommonSectionsMarkup`, `TestCommonZonesMarkup` у
[`tests/test_dashboard.py`](../../tests/test_dashboard.py).

## Файл дослідження

[`research/31-leg-common-part/31-leg-common-part.md`](31-leg-common-part.md)
