# 19. Кінець ноги 2.3

Пара: `BTC_USDT`, зібрана до `2026-08-29` (`BTC_USDT_29`). Клас фіч:
`клас210х14х4ц`.

## Ціль

Навчити мережу **з памʼяттю** повідомляти про **кінець ноги** зігзага
2.3 %. Мережа отримує ногу подія за подією від її початку, памʼятає все,
що бачила, і на кожному кроці каже одне число — ймовірність того, що
нога закінчується тут. Кінець ноги і є розворот.

Це інша постановка, ніж у дослідженнях 16–18. Там подія оцінювалась
вікном фіксованої довжини (`K` подій позаду) і питання було «де ми в
нозі». Тут одиниця — **ціла нога**, від півота до півота, і питання
одне: чи вона вже закінчилась.

## Дані

| Що | Значення |
| -- | -------- |
| Клас фіч | [`клас210х14х4ц`](../../rules/01-data-and-db.md#клас210х14х4ц) |
| Форма події | `(15, 4)` — 15 блоків по 14 с × 4 фічі, у мережу 60 чисел |
| Фічі | `balance`, `d_balance`, `vol_balance`, `price_balance` |
| Скейлер | **розподіл** — сирі значення, вони вже безрозмірні в `[-1, 1]` |
| Джерело | `data/datasets/BTC_USDT_29/class210x14x4c/` |
| Подій | 43 859 по 210 с, `2026-02-14 17:58` … `2026-08-29 10:22` UTC |
| Ціль | зігзаг **2.3 %** на `ема90` ціни події (`price_open`) |
| Півотів · ніг | 50 · 49 |
| Одиниця навчання | нога, послідовність своєї власної довжини |

Датасет із правил (`BTC_USDT`) закінчується 2026-08-16, а валідація тут
стоїть на свіжому відрізку **до 29.08**, тому дані зібрані заново тим
самим пайплайном і в окрему теку — наявні датасети інших досліджень не
зачеплені.

### Скейлер: чому розподіл

`робаст` і `квантиль` рахуються з самого ряду і **міняють сенс країв**.
У цих чотирьох фіч край — це не викид, а стан: `+1` означає «працює
тільки опір», `−1` — «тільки підтримка». Рангове перетворення розтягує
середину і зливає краї, тому береться `розподіл` — значення як є. Той
самий вибір, що в дослідженні 18 («скейлера немає: фічі вже
безрозмірні»).

Пропуск у фічі (`NaN`, ділення на нуль) заходить у мережу **нулем**:
нуль тут і є «немає перекосу», і подія не викидається.

## План роботи

1. Порізати ряд на ноги зігзага 2.3 % і розмітити зону кінця ноги:
   10 % ноги по часу разом з 10 % по ціні.
2. Розбити за датою: усе до `2026-08-14 15:27` — train, свіжий хвіст —
   val. Нога цілком по один бік межі.
3. Мережа з памʼяттю: LSTM уздовж ноги плюс причинна увага; памʼять
   обнуляється на кожній нозі.
4. Грід ручок під однаковим бюджетом, краща модель — на val.
5. Показати результат на графіку: ціна, зона кінця і видача моделі.
6. Перевірити готову модель поза її часом: відрізки, на яких вона
   вчилась, і свіже вікно на всіх шести монетах.

## Розділ 1. Ноги і зона кінця · `підтверджено`

### Метод

[`scripts/leg_end_data.py`](scripts/leg_end_data.py).

```
ема90(price_open) -> півоти зігзага 2.3 % -> ноги -> зона кінця ноги
```

Нога — відрізок від події **після** попереднього півота до самого
півота включно. Зона кінця міряється **самою ногою**, а не спільним
порогом:

```
по часу:  останні 10 % подій ноги
по ціні:  суцільний окіл півота, де ціна не далі за 10 % розмаху ноги
мітка 1 = обидві множини разом
```

- Розмах ноги — `|ціна півота − ціна попереднього півота|`, тому «10 %
  по ціні» на довгій нозі ширше, ніж на короткій, — рівно як і «10 %
  по часу».
- Окіл по ціні саме **суцільний**: ціна може вернутись на той самий
  рівень і в середині ноги, але то вже інше місце.
- Зона обрізається початком своєї ноги і самим півотом, тому нога
  читається `0 … 0 1 … 1` і завжди починається з нуля. Події після
  півота належать уже наступній нозі.

### Числа розмітки

| Що | Значення |
| -- | -------- |
| Подій | 43 859 |
| Півотів · ніг | 50 · 49 |
| Одиниць у мітці | 5 202 — 11.86 % подій |
| Довжина ноги | медіана 709, середня 891, від 226 до 2 430 подій |
| Медіана зони: час · ціна · разом | 71 · 65 · 87 подій |
| Ніг, де ціна дала ширшу зону за час | 24 з 49 |

Дві умови дають майже однакові зони (71 і 65 подій), але не збігаються:
на 24 ногах з 49 ширшою була ціна. Разом вони дають 87 подій — кожна
умова додає до іншої приблизно чверть зони.

### Розбиття за датою

```
нога, чий півот <= 2026-08-14 15:27  ->  train
решта                                ->  val
```

| Вибірка | Ніг | Подій | Одиниць | Від, UTC | До, UTC |
| ------- | --: | ----: | ------: | -------- | ------- |
| train | 42 | 39 001 | 4 519 | 2026-02-15 09:23 | 2026-08-10 09:23 |
| val | 7 | 4 667 | 683 | 2026-08-10 09:32 | 2026-08-29 09:10 |

Нога, всередину якої потрапила межа, іде **цілком** у val: її півот
пізніший за межу. Тому val починається з 2026-08-10, а не рівно з межі —
інакше нога була б розірвана між вибірками.

**Одиниць навчання тут 49, а не 43 859.** Незалежна одиниця — нога
(`rules/03-models.md` → Розмір вибірки в незалежних одиницях), і в val
їх 7: похибка частки `se = 0.5/√7 = 0.19`. Це головне обмеження
дослідження, і воно стоїть тут, а не у висновках.

## Розділ 2. Мережа з памʼяттю · `підтверджено`

[`scripts/leg_end_net.py`](scripts/leg_end_net.py).

```
вхід  (B, L, 60)   ->  LSTM(60 -> h)       памʼять: стан несе всю ногу
      (B, L, h)    ->  причинна увага      погляд назад на всі події
      (B, L, 2h)   ->  Linear(2h -> 1)     логіт кроку
```

### Памʼять обнуляється на кожній нозі

Кожна нога — окрема послідовність із `h0 = c0 = 0`, стан з ноги в ногу
не переноситься. **Паддинг у LSTM не заходить узагалі**:
`pack_padded_sequence` веде рекурсію рівно по довжинах ніг, тому перша
подія ноги завжди бачить чисту памʼять — скільки б паддингу не стояло
поруч у батчі. Це перевіряє тест
`test_padding_never_enters_the_memory`: та сама нога з різним паддингом
дає ті самі числа.

### Чому ще й увага

Стан LSTM — один вектор: на нозі у 2 430 подій початок у ньому
розмивається, а ноги тут різняться вдесятеро (226 … 2 430). Увага на
кожному кроці дивиться на всі попередні виходи LSTM напряму, тому
початок довгої ноги лишається доступним.

Увага **причинна**: крок `t` бачить кроки `1 … t` і жодного правішого,
паддинг у ключі не потрапляє. Інакше в мітку кроку заходило б майбутнє
тієї самої ноги — на живому ринку такої моделі не буває. Тест —
`test_future_never_changes_the_past`.

### Ручки

| Ручка | Значення | Звідки |
| ----- | -------- | ------ |
| ширина | 32 або 64 | грід, розділ 3 |
| lr | 1e-3 або 3e-4 | грід, розділ 3 |
| увага | є або немає | грід, розділ 3 |
| голови уваги | 2 | на 42 ногах різницю між 2 і 4 не видно |
| шарів LSTM | 1 | 42 ноги train — більше нема на чому вчити |
| dropout | 0.1 | дослідження 17, `picked_2_3.json` |
| батч | 8 ніг, зібраних за довжиною | паддингу в батчі майже немає |
| епох | 20, рання зупинка 8 | бюджет однаковий на всі прогони |
| seed | 0 | `leg_end_train.SEED` |
| скейлер | немає: фічі вже безрозмірні `[-1, 1]` | розділ 1 |

## Розділ 3. Грід ручок · `підтверджено`

[`scripts/leg_end_train.py`](scripts/leg_end_train.py) →
[`results/grid_2_3.json`](results/grid_2_3.json).

Вісім прогонів: ширина × швидкість × наявність уваги. Усі під однаковим
бюджетом і з одним seed, тому порівнюються рівно ці ручки. Краща модель
обирається **на val**: спершу скільки ніг вона вгадала, потім F1 по
подіях. Разом **7.6 хв** на `mps`.

| Прогін | Ваг | Епох (краща) | Хв | val F1 | влучила з 7 | пік у зоні |
| ------ | --: | -----------: | -: | -----: | ----------: | ---------: |
| **ширина 64 · увага 2 гол. · lr 1e-3** | 45 121 | 9 (1) | 0.7 | **0.279** | 0 | **2** |
| ширина 64 · увага 2 гол. · lr 3e-4 | 45 121 | 10 (2) | 0.9 | 0.262 | 0 | 2 |
| ширина 32 · без уваги · lr 1e-3 | 12 129 | 16 (8) | 1.0 | 0.243 | 0 | 1 |
| ширина 32 · без уваги · lr 3e-4 | 12 129 | 20 (20) | 1.2 | 0.255 | 0 | 1 |
| ширина 64 · без уваги · lr 1e-3 | 32 449 | 11 (3) | 0.8 | 0.255 | 0 | 1 |
| ширина 32 · увага 2 гол. · lr 3e-4 | 15 393 | 11 (3) | 0.8 | 0.255 | 0 | 0 |
| ширина 32 · увага 2 гол. · lr 1e-3 | 15 393 | 10 (2) | 0.7 | 0.245 | 0 | 0 |
| ширина 64 · без уваги · lr 3e-4 | 32 449 | 16 (8) | 1.1 | 0.253 | 0 | 0 |

Читається з цієї таблиці рівно три речі:

1. **Жоден прогін не влучив у зону кінця жодної ноги val.** Ручки тут ні
   до чого — різниця між ними менша за похибку `se = 0.19`.
2. **Рання зупинка спрацьовує на 1–8 епосі** з двадцяти. Val починає
   рости майже одразу: на 42 ногах train мережа запамʼятовує їх швидше,
   ніж встигає щось узагальнити.
3. **Увага дає більше «піків у зоні»** (2 проти 0–1), і обидві верхні
   позиції — з нею. Але 2 з 7 при `se = 0.19` — це не результат, а
   напрямок.

## Розділ 4. Краща модель · `підтверджено`

`ширина 64 · увага 2 гол. · lr 1e-3`, 45 121 вага, ваги з **1-ї** епохи,
навчання 0.7 хв.

### Штраф

Зважена бінарна перехресна ентропія по кроках, які маска визнала
справжніми:

```
L = Σ_маска  w·BCE(логіт, мітка) / Σ_маска
w = pos_weight на одиницях
```

`pos_weight` — відношення нулів до одиниць у train (7.63). Рахується
**тільки на train**, як усе, що агрегує датасет.

### Як читається видача

| Мірка | Що це |
| ----- | ----- |
| **сигнал** | перший крок ноги, де `p ≥ поріг` |
| **влучила** | сигнал стоїть усередині зони кінця ноги |
| **пік у зоні** | найбільша ймовірність ноги припала на зону — мірка без порога |
| **хибний** | сигнал був, але поза зоною |
| **мовчала** | сигналу на нозі не було зовсім |

Поріг береться той, що дає найкращу F1 **на train** (0.55), і
застосовується до val як є. Базовий прогноз (`rules/03-models.md`):
«кінець, щойно нога перевищила медіанну довжину train» — 511 подій.

### Результат

| Вибірка | точність | повнота | F1 | ніг | сигналів | влучила | пік у зоні | хибних | мовчала | сигнал за N подій | базовий влучив | похибка |
| ------- | -------: | ------: | -: | --: | -------: | ------: | ---------: | -----: | ------: | ----------------: | -------------: | ------: |
| train | 0.123 | 0.801 | 0.214 | 42 | 42 | **0** | 8 | 42 | 0 | 765 | 3 з 42 | ±0.077 |
| val | 0.166 | 0.874 | 0.279 | 7 | 7 | **0** | 2 | 7 | 0 | 435 | 0 з 7 | ±0.189 |

### Висновки

1. **Модель кричить «кінець» майже весь час.** Повнота 0.87 при
   точності 0.17: під вагою `pos_weight = 7.6` найдешевша відповідь —
   тримати ймовірність вище порога скрізь. На нозі 877 подій вона
   перетнула поріг 550 разів.
2. **Тому «влучила» дорівнює нулю на обох вибірках.** Сигнал за
   правилом «перше перетинання» приходить на 435-й події до півота на
   val і на 765-й на train — тобто майже на початку ноги. Базовий
   прогноз бʼє модель на train (3 влучання з 42 проти 0).
3. **Єдине, що вище випадковості, — пік у зоні.** 2 ноги з 7 на val
   (28.6 %) при частці зони 11.9 %, і 8 з 42 (19 %) на train. Тобто
   найвища ймовірність ноги трохи тяжіє до її кінця — але при
   `se = 0.19` це спостереження, а не результат.
4. **Рання зупинка бере першу епоху.** Val росте з другої: 42 ноги
   train — це занадто мало, щоб мережа з 45 тисячами ваг щось із них
   винесла, і вона просто їх запамʼятовує.
5. **Обнулення памʼяті полагоджено і перевірено.** Паддинг більше не
   заходить у LSTM (`pack_padded_sequence`), тому підказки «скільки
   лишилось до кінця» через довжину паддингу немає — і результат від
   цього не покращився, тобто на попередньому прогоні модель нею й не
   користувалась.
6. **Наступний крок** (окремою командою, не в цьому прогоні): ті самі
   ноги на **всіх шести монетах** — 369 ніг замість 49, як у
   дослідженні 18, розділ 6. Доти будь-яке порівняння ручок упирається
   в `se = 0.19` і нічого не доводить.

## Розділ 5. Результат на графіку · `підтверджено`

**Два окремі малюнки**: train і val. Кожен — три полотна на своїй
спільній осі: ціна з зігзагом 2.3 %, зона кінця ноги і видача моделі з
горизонтальним порогом. Розділяти обовʼязково: train — це те, на чому
модель вчилась, val — свіжий відрізок, якого вона не бачила, і на
одному малюнку їх видно як один ряд.

## Розділ 6. Десятини ноги на ема90 · `підтверджено`

Та сама постановка, що в розділах 1–5, але мітка інша: замість одного
класу «кінець ноги» — **десять сходинок**.

```
нога ділиться на 10 рівних частин за часом
1 — перші 10 % подій ноги, 2 — 11-20 %, ... 10 — 91-100 %
```

Вихід мережі — 10 логітів, штраф — перехресна ентропія по класах, ваг
класів немає: десятини рівні за побудовою. «Кінець ноги» — це клас
**10**, і сигнали рахуються по його ймовірності, тому таблиці читаються
поруч із розділом 4.

Дані ті самі: 49 ніг, train 42 / val 7, межа `2026-08-14 15:27`. Подій
у класі 10 — 4 347 (9.91 %).

### Грід

Вісім прогонів, разом **11.2 хв**.

| Прогін | Ваг | Епох (краща) | val F1 | влучила з 7 | пік | влучність класу | відстань |
| ------ | --: | -----------: | -----: | ----------: | --: | --------------: | -------: |
| **ширина 64 · увага 2 гол. · lr 3e-4** | 46 282 | 20 (18) | **0.196** | 0 | 1 | 0.134 | 3.20 |
| ширина 64 · увага 2 гол. · lr 1e-3 | 46 282 | 20 (14) | 0.190 | 0 | 1 | 0.165 | 2.48 |
| ширина 64 · без уваги · lr 3e-4 | 33 034 | 20 (19) | 0.187 | 0 | 1 | 0.107 | 3.13 |
| ширина 32 · увага 2 гол. · lr 3e-4 | 15 978 | 18 (10) | 0.186 | 0 | 1 | 0.107 | 3.28 |
| ширина 64 · без уваги · lr 1e-3 | 33 034 | 15 (7) | 0.184 | 0 | 1 | 0.109 | 3.13 |
| ширина 32 · без уваги · lr 1e-3 | 12 426 | 19 (11) | 0.181 | 0 | 1 | 0.104 | 3.18 |
| ширина 32 · увага 2 гол. · lr 1e-3 | 15 978 | 12 (4) | 0.183 | 0 | 0 | 0.106 | 3.39 |
| ширина 32 · без уваги · lr 3e-4 | 12 426 | 20 (20) | 0.181 | 0 | 0 | 0.106 | 3.14 |

### Результат кращої моделі

Поріг на ймовірність класу 10 — **0.10**, ваги з 18-ї епохи.

| Вибірка | точність | повнота | F1 | ніг | влучила | пік у зоні | базовий | влучність класу | відстань | похибка |
| ------- | -------: | ------: | -: | --: | ------: | ---------: | ------: | --------------: | -------: | ------: |
| train | 0.158 | 0.736 | 0.260 | 42 | **0** | 13 | 3 з 42 | 0.237 | 2.25 | ±0.077 |
| val | 0.110 | 0.912 | 0.196 | 7 | **0** | 1 | 0 з 7 | 0.134 | 3.20 | ±0.189 |

### Висновки

1. **Десять класів не додали влучань.** Нуль ніг на обох вибірках, як і
   в мітці-зоні: сигнал по класу 10 приходить так само рано.
2. **Влучність класу на val 0.134 при випадковій 0.10.** Середня
   помилка — 3.2 класи з 10, тобто модель промахується більш ніж на
   третину ноги.
3. **Розрив train/val великий**: 0.237 проти 0.134 і відстань 2.25
   проти 3.20. На 42 ногах train мережа з 46 тисячами ваг
   запамʼятовує, а не узагальнює.

## Розділ 7. Зона кінця ноги на ема10 · `підтверджено`

Той самий зігзаг 2.3 %, але побудований на **ема10** ціни замість
ема90. Згладжування коротше, тому півотів більше і ноги коротші:

| Що | ема90 | ема10 |
| -- | ----: | ----: |
| Півотів · ніг | 50 · 49 | **88 · 87** |
| Довжина ноги, подій | 709 / 891 / 226 … 2 430 | **408 / 500 / 29 … 1 644** |
| Ніг train · val | 42 · 7 | **81 · 6** |
| Подій у зоні кінця | 5 202 (11.86 %) | 4 553 (10.38 %) |

Мітка — та сама зона: 10 % ноги по часу разом з 10 % розмаху по ціні.
Ніг у train стало **вдвічі більше**, і це головне, заради чого коротша
ЕМА тут і стоїть.

### Грід

Вісім прогонів, разом **6.8 хв**.

| Прогін | Ваг | Епох (краща) | val F1 | влучила з 6 | пік |
| ------ | --: | -----------: | -----: | ----------: | --: |
| **ширина 32 · без уваги · lr 3e-4** | 12 129 | 20 (15) | 0.209 | 0 | **2** |
| ширина 64 · без уваги · lr 1e-3 | 32 449 | 11 (3) | 0.199 | 0 | 2 |
| ширина 64 · увага 2 гол. · lr 3e-4 | 45 121 | 16 (8) | **0.269** | 0 | 1 |
| ширина 32 · без уваги · lr 1e-3 | 12 129 | 11 (3) | 0.207 | 0 | 1 |
| ширина 64 · без уваги · lr 3e-4 | 32 449 | 16 (8) | 0.200 | 0 | 1 |
| ширина 64 · увага 2 гол. · lr 1e-3 | 45 121 | 13 (5) | 0.268 | 0 | 0 |
| ширина 32 · увага 2 гол. · lr 1e-3 | 15 393 | 11 (3) | 0.206 | 0 | 0 |
| ширина 32 · увага 2 гол. · lr 3e-4 | 15 393 | 17 (9) | 0.178 | 0 | 0 |

Порядок у таблиці — правило вибору: спершу влучання, потім пік у зоні,
і аж потім F1. Тому нагорі стоїть прогін з F1 0.209, а не 0.269.

### Результат кращої моделі

Поріг **0.50**, ваги з 15-ї епохи.

| Вибірка | точність | повнота | F1 | ніг | влучила | пік у зоні | базовий | похибка |
| ------- | -------: | ------: | -: | --: | ------: | ---------: | ------: | ------: |
| train | 0.130 | 0.666 | 0.217 | 81 | **0** | 11 | 0 з 81 | ±0.056 |
| val | 0.127 | 0.585 | 0.209 | 6 | **0** | 2 | 1 з 6 | ±0.204 |

### Висновки

1. **Ніг удвічі більше, влучань так само нуль.** Ані на train (81
   нога), ані на val (6).
2. **Базовий прогноз на ема10 виграє**: 1 влучання з 6 на val проти 0 у
   моделі. На коротких ногах «кінець після медіанної довжини» працює
   краще, ніж мережа.
3. **Пік у зоні тримається на 2 з 6** (33 % при частці зони 10 %) — те
   саме, що на ема90; ця мірка єдина, яка щоразу вища за випадкову.

## Розділ 8. Десятини ноги на ема10 · `підтверджено`

Мітка розділу 6 на зігзагу розділу 7: **десять сходинок на коротких
ногах**. Ніг 87, у класі 10 — 4 315 подій (9.84 %).

### Грід

Вісім прогонів, разом **17.1 хв**.

| Прогін | Ваг | Епох (краща) | val F1 | влучила з 6 | пік | влучність класу | відстань |
| ------ | --: | -----------: | -----: | ----------: | --: | --------------: | -------: |
| **ширина 64 · без уваги · lr 1e-3** | 33 034 | 20 (18) | **0.245** | 0 | **2** | 0.136 | 2.05 |
| ширина 64 · без уваги · lr 3e-4 | 33 034 | 20 (17) | 0.185 | 0 | 1 | 0.099 | 2.49 |
| ширина 32 · без уваги · lr 3e-4 | 12 426 | 20 (20) | 0.166 | 0 | 1 | 0.106 | 2.49 |
| ширина 64 · увага 2 гол. · lr 1e-3 | 46 282 | 17 (9) | 0.227 | 0 | 0 | 0.150 | 1.96 |
| ширина 32 · увага 2 гол. · lr 3e-4 | 15 978 | 20 (19) | 0.216 | 0 | 0 | 0.129 | 2.23 |
| ширина 32 · увага 2 гол. · lr 1e-3 | 15 978 | 19 (11) | 0.211 | 0 | 0 | 0.128 | 2.18 |
| ширина 64 · увага 2 гол. · lr 3e-4 | 46 282 | 20 (19) | 0.208 | 0 | 0 | **0.153** | **1.95** |
| ширина 32 · без уваги · lr 1e-3 | 12 426 | 20 (19) | 0.203 | 0 | 0 | 0.111 | 2.22 |

### Результат кращої моделі

Поріг **0.15**, ваги з 18-ї епохи.

| Вибірка | точність | повнота | F1 | ніг | сигналів | влучила | пік у зоні | мовчала | базовий | влучність класу | відстань | похибка |
| ------- | -------: | ------: | -: | --: | -------: | ------: | ---------: | ------: | ------: | --------------: | -------: | ------: |
| train | 0.247 | 0.400 | 0.305 | 81 | 78 | **5** | 38 | 3 | 0 з 81 | 0.131 | 1.87 | ±0.056 |
| val | 0.193 | 0.333 | 0.245 | 6 | 6 | **0** | 2 | 0 | 1 з 6 | 0.136 | 2.05 | ±0.204 |

### Висновки

1. **Перші влучання зʼявились**: 5 ніг з 81 на train — сигнал уперше
   потрапив усередину останньої десятини, а не на початок ноги. На val
   їх усе ще нуль з шести.
2. **Пік у зоні на train — 38 ніг з 81 (47 %)** при частці класу 10
   близько 10 %. Це найсильніше число всього дослідження, але воно на
   train; на val — 2 з 6.
3. **Відстань у класах найменша тут**: 1.95 … 2.05 проти 3.20 на ема90.
   Коротка ЕМА і короткі ноги дають рівнішу сходинку: помилка близько
   двох десятин замість трьох.
4. **Увага не виграла жодного з двох гріда на ема10.** Нагорі —
   прогони без неї, хоча найкраща влучність класу (0.153) і найменша
   відстань (1.95) все ж у прогонів з увагою. Різниця скрізь менша за
   похибку `se = 0.20`.

## Розділ 9. Сходинки на ема10: усі монети в train · `підтверджено`

Три зміни разом до розділу 8: **ноги інших монет у train**, **штраф за
стрибок і за пониження** і **згладжена видача**.

### Ноги всіх монет

У train додані ноги пʼяти монет — тільки ті, що закінчились до межі
`2026-08-14 15:27`. Val лишається BTC-івським свіжим відрізком, тому
порівняння з розділами 6–8 чесне.

| Монета | Ніг у train |
| ------ | ----------: |
| BTC_USDT (до 29.08) | 81 |
| ETH_USDT | 115 |
| SOL_USDT | 121 |
| XRP_USDT | 92 |
| BNB_USDT | 86 |
| DOGE_USDT | 107 |
| **разом** | **602** |
| val (BTC, після межі) | 6 |

Альткоїни беруться із заповненими пропусками часу (`*_USDT_fill`): без
заповнення в них лишається 4–9 тисяч подій з 60 тисяч
(`rules/01-data-and-db.md` → Пропуски часу). Вікно ніколи не переходить
з монети на монету: кожна нога цілком належить своїй.

### Штраф за сходинку

Мітка йде сходинками 1 → 10 і ніколи не падає. Того самого вимагаємо
від видачі:

```
E_t = Σ_i p_i · i               очікуваний клас, диференційований
Δ_t = E_t − E_{t−1}

стрибок   = max(Δ − 1, 0)       сказала +2, +3, +4 — штраф 5
пониження = max(−Δ, 0)          сказала «вниз» — штраф 25

L = CE + 5·сер(стрибок²) + 25·сер(пониження²)
```

- Пониження дорожче за стрибок навмисно: правильний клас, раз названий,
  понижуватись не має.
- Обидва штрафи рахуються тільки по сусідніх кроках усередині ноги;
  паддинг у них не входить.
- Видача згладжується **причинною ЕМА 10 подій** і аж потім
  округлюється в клас: одна смикана подія сходинку не робить.

Перевірка на папері (тест `test_step_penalty`): смикана видача
1 → 2 → 10 → 1 коштує **664.8**, та сама мережа без цих штрафів — 2.56.

### Прогін

Два прогони по 5 епох (одна епоха на 602 ногах — 1.1 хв), разом
**16.7 хв**. Краща — `ширина 64 · увага 2 гол. · lr 1e-3`, ваги з 4-ї
епохи, поріг 0.10.

### Результат

| Вибірка | точність | повнота | F1 | ніг | влучила | пік у зоні | базовий | влучність класу | відстань |
| ------- | -------: | ------: | -: | --: | ------: | ---------: | ------: | --------------: | -------: |
| train | 0.108 | 0.999 | 0.194 | 602 | 3 | 76 | 24 з 602 | 0.106 | 2.18 |
| val | 0.107 | 1.000 | 0.193 | 6 | 0 | 0 | 1 з 6 | 0.101 | 2.14 |

Сходинки видачі — те, заради чого штрафи й ставились:

| Вибірка | кроків +1 | стрибків +2 і більше | кроків вниз | ніг з рухом вниз | частка чистих кроків |
| ------- | --------: | -------------------: | ----------: | ---------------: | -------------------: |
| train | 1 808 | **0** | 18 | 17 з 602 | **99.0 %** |
| val | 18 | **0** | 1 | 1 з 6 | 94.7 % |

### Висновки

1. **Штрафи роблять рівно те, що просили.** Жодного стрибка через клас
   ні на train, ні на val; рухів вниз 18 з 1 826 кроків (1 %). Видача
   ходить по `+1` і не повертається назад.
2. **Але сходинка стала лінійкою часу.** Влучність класу 0.106 на train
   і 0.101 на val — це рівно випадкове вгадування при десяти рівних
   класах. Модель навчилась рівномірно рахувати від початку ноги, а не
   читати ринок: рівна сходинка мінімізує штраф незалежно від фіч.
3. **Повнота 1.0 при точності 0.107** — ймовірність класу 10 перетинає
   поріг на кожній нозі, тому сигнал є скрізь і влучань майже немає
   (3 з 602 на train, 0 з 6 на val). Базовий прогноз бʼє модель:
   24 з 602 і 1 з 6.
4. **602 ноги в train не зняли проблему.** Порівняно з розділом 8 (81
   нога) влучність класу впала з 0.136 до 0.101, а «пік у зоні» з 2 з 6
   до 0 з 6. Ноги інших монет дали більше прикладів того самого
   рівномірного руху, а не більше сигналу.
5. **Прогін не доучений.** Бюджет — 5 епох, і краща епоха четверта, тобто
   val ще йшов униз, коли навчання скінчилось. Ціна повного гріда на
   602 ногах — близько години на конфігурацію, і це наступний крок, а
   не висновок цього прогону.

## Розділ 10. Свіжий відрізок усіх монет · `підтверджено`

Готова модель розділу 9 читає час, якого не бачила, на **кожній** з
шести монет. Тут нічого не вчиться: ваги ті самі —
`models/leg_end_steps_ema10_2_3/model.pt`, `ширина 64 · увага 2 гол. ·
lr 1e-3`, 46 282 ваги, епоха 4, поріг 0.10.

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

### Дані

Монети зібрані наново **по поточний час** в окремі теки `<PAIR>_now` —
щоб не чіпати датасети, на яких стоять розділи 1–9. Теки котяться за
часом: останній добовий файл джерела читається таким, яким він є на
момент збірки, і та сама команда перезбирає їх на будь-яку пізнішу
мить. Пайплайн той самий, що й у розділі 9: BTC без заповнення
пропусків (як `BTC_USDT_29`), альткоїни із заповненням `--fill-gaps 60`
(як `*_USDT_fill`).

Прогін нижче зібрано `2026-09-01 21:33` UTC.

| Датасет | Файлів | Подій | Останнє, UTC | Заповнення |
| ------- | -----: | ----: | ------------ | ---------: |
| `BTC_USDT_now` | 200 | 44 753 | 2026-09-01 21:26 | немає |
| `ETH_USDT_now` | 201 | 69 771 | 2026-09-01 21:24 | 60 с |
| `SOL_USDT_now` | 201 | 69 264 | 2026-09-01 21:24 | 60 с |
| `XRP_USDT_now` | 182 | 62 581 | 2026-09-01 21:28 | 60 с |
| `BNB_USDT_now` | 201 | 69 568 | 2026-09-01 21:28 | 60 с |
| `DOGE_USDT_now` | 182 | 62 580 | 2026-09-01 21:28 | 60 с |

### Відрізки часу, на яких вона вчилась

Ноги train розділу 9 перераховані тим самим викликом, що й там, тому
таблиця описує рівно ту вибірку, на якій стоять ваги. Межа —
`2026-08-14 15:27` UTC: нога, чий півот не пізніший за неї, іде в train.

| Монета | Датасет | Ніг | Подій | Подій у класі 10 | Медіана ноги | Від, UTC | До, UTC | Днів |
| ------ | ------- | --: | ----: | ---------------: | -----------: | -------- | ------- | ---: |
| BTC_USDT | `BTC_USDT_29` | 81 | 39 905 | 3 952 | 408 | 2026-02-15 06:15 | 2026-08-14 14:23 | 180.3 |
| ETH_USDT | `ETH_USDT_fill` | 115 | 62 258 | 6 173 | 435 | 2026-02-14 19:25 | 2026-08-14 14:26 | 180.8 |
| SOL_USDT | `SOL_USDT_fill` | 121 | 60 822 | 6 036 | 351 | 2026-02-14 19:53 | 2026-08-12 12:28 | 178.7 |
| XRP_USDT | `XRP_USDT_fill` | 92 | 54 030 | 5 365 | 451 | 2026-03-05 11:09 | 2026-08-12 02:08 | 159.6 |
| BNB_USDT | `BNB_USDT_fill` | 86 | 60 848 | 6 048 | 449 | 2026-02-15 06:10 | 2026-08-12 02:08 | 177.8 |
| DOGE_USDT | `DOGE_USDT_fill` | 107 | 55 028 | 5 454 | 372 | 2026-03-05 11:06 | 2026-08-14 13:02 | 162.1 |
| **разом** | — | **602** | **332 891** | 33 028 | — | 2026-02-14 19:25 | 2026-08-14 14:26 | — |
| val розділу 9 | `BTC_USDT_29` | 6 | 3 663 | — | — | 2026-08-14 14:27 | 2026-08-28 22:05 | — |

- Останній день навчання — **14.08**, і у XRP з BNB навіть **12.08**:
  їхня остання нога закінчилась раніше за межу.
- XRP і DOGE почались 05.03, решта — 14–15.02: у джерелі цих монет
  раніших файлів немає.
- Свіжий відрізок нижче стоїть праворуч від межі на всіх шести монетах
  одразу, а не тільки на BTC, як val розділу 9.

### Вікно

`2026-08-14 14:00 … 2026-09-02 00:00` UTC — 18.4 доби. Праву межу ряд
не дістає: дані закінчуються на `2026-09-01 21:24 … 21:28` — це остання
секунда джерела на момент прогону.

Нога заходить у мережу **цілком від свого початку** — інакше памʼять
почалась би з середини. Показана з неї тільки та частина, що лежить у
вікні.

Крім закритих ніг береться **відкрита** — події після останнього
півота, кінець яких ще не настав. Мітки в неї немає, тому в метрики по
класах вона не входить: тільки у видачу на полотні. На цьому прогоні
таких ніг пʼять — у всіх монет, крім XRP: там остання нога встигла
закінчитись півотом.

### Що з цього вікна модель уже бачила

Вікно починається за півтори години до межі train, а val розділу 9 —
це BTC після цієї самої межі. Тому частину вікна модель бачила:

| Як бачила | Ніг | Де |
| --------- | --: | -- |
| **нога train** — на ній рахувався градієнт | 2 | BTC 10.08 … 14.08 14:23, ETH до 14.08 14:26 |
| **нога val розділу 9** — на ній обиралась епоха і конфігурація | 5 | BTC 14.08 14:27 … 28.08 01:36 |
| нічого — справді свіжа закрита нога | 84 | усі шість монет |
| нічого — відкрита нога | 5 | усі, крім XRP |

Обидва види впізнаються за парою «початок, кінець» ноги і **в метрики
свіжого відрізка не йдуть**. На полотні вони лишаються — інакше ціна
мала б розрив — і межа побаченого малюється пунктиром.

**У BTC свіжа закрита нога одна** — `28.08 01:40 … 01.09 19:03`, та
сама, що на попередніх прогонах була відкритою: вона нарешті
закінчилась півотом. Решта шість ніг BTC у вікні — це нога train і пʼять
ніг val розділу 9. Одна нога нічого не важить сама по собі
(`se = 0.5`), тому рядок BTC у таблиці результату читається тільки
разом з рештою монет.

### Результат

Рахується **тільки по свіжих закритих ногах**: бачені й відкриті з
метрик виключені. Стовпець «бачених» — скільки ніг вікна відпало і як
саме (train + val розділу 9).

| Монета | Подій | Свіжих ніг | Бачених | Влучність класу | Відстань | Точність | Повнота | F1 | Сигналів | Влучила | Пік у зоні | Мовчала | Базовий | Кроків +1 | Стрибків | Вниз | Чистих кроків |
| ------ | ----: | ---------: | ------: | --------------: | -------: | -------: | ------: | -: | -------: | ------: | ---------: | ------: | ------: | --------: | -------: | ---: | ------------: |
| BTC_USDT | 4 715 | 1 | 1+5 | 0.100 | 2.37 | 0.102 | 1.000 | 0.185 | 1 | **0** | 0 | 0 | 0 | 3 | 0 | 0 | 1.000 |
| ETH_USDT | 7 495 | 10 | 1+0 | 0.109 | 2.19 | 0.107 | 1.000 | 0.193 | 10 | **0** | 1 | 0 | 0 | 31 | 0 | 0 | 1.000 |
| SOL_USDT | 7 496 | 15 | 0 | 0.116 | 2.11 | 0.119 | 1.000 | 0.213 | 15 | **0** | 4 | 0 | 0 | 43 | 0 | 0 | 1.000 |
| XRP_USDT | 7 495 | 31 | 0 | 0.127 | 2.08 | 0.128 | 0.988 | 0.227 | 27 | **0** | 15 | 4 | 2 | 72 | 0 | 1 | 0.986 |
| BNB_USDT | 7 496 | 7 | 0 | 0.114 | 2.22 | 0.116 | 1.000 | 0.208 | 7 | **0** | 0 | 0 | 0 | 20 | 0 | 0 | 1.000 |
| DOGE_USDT | 7 495 | 20 | 0 | 0.111 | 2.17 | 0.111 | 0.997 | 0.200 | 19 | **0** | 8 | 1 | 1 | 57 | 0 | 1 | 0.983 |
| **разом** | **42 192** | **84** | **7** | **0.115** | **2.16** | — | — | — | 79 | **0** | 28 | 5 | 3 | 226 | **0** | 2 | **0.991** |

Похибка частки по ногах — `se = 0.5/√84 = 0.055`. Випадкове вгадування
при десяти рівних класах — `0.10`. Свіжа нога BTC одна, і влучність
класу на ній рівно `0.100` — випадкова.

**Точність і повнота — по подіях класу 10** при порозі 0.10: повнота
рівно `1.0` на чотирьох монетах з шести означає, що ймовірність класу
10 стоїть вище порога на **кожній** події зони кінця, а точність
`0.10 … 0.13` — що вона стоїть там і скрізь поза нею. Тому F1 близька до 0.2 незалежно
від монети, а «влучила» дорівнює нулю.

У BTC подій менше за решту (4 715 проти ≈7 495) не через коротший
відрізок, а через пропуски: BTC тут без заповнення, як і в навчанні.

### Пік у зоні — це довжина ноги, а не сигнал

«Пік у зоні» 28 з 84 (33 %) при частці класу 10 близько 10 % виглядає
як сигнал, поки не подивитись, де саме він стоїть:

| Монета | Медіана свіжої ноги | Медіана ноги у train | Пік у зоні |
| ------ | ------------------: | -------------------: | ---------: |
| XRP_USDT | **105** | 451 | 15 з 31 |
| DOGE_USDT | **168** | 372 | 8 з 20 |
| SOL_USDT | 404 | 351 | 4 з 15 |
| ETH_USDT | 546 | 435 | 1 з 10 |
| BNB_USDT | 889 | 449 | 0 з 7 |
| BTC_USDT | 1 266 | 408 | 0 з 1 |

23 з 28 піків зібрані на двох монетах — XRP і DOGE, — і рівно тих, де
ноги вікна **у два-чотири рази коротші за ті, на яких модель вчилась**.
Там, де ноги довші за train, піків майже немає: 1 з 10 у ETH, 0 з 7 у
BNB і 0 з 1 у BTC. Пояснення пряме і збігається з висновком 2 розділу 9: видача —
рівномірний лічильник часу від початку ноги з темпом train. На короткій
нозі лічильник до кінця ще росте, тому найбільша ймовірність припадає
на останню подію — тобто на зону. На довгій він упирається у свою
стелю задовго до півота, і пік стоїть у середині.

Те саме видно і по влучності класу: найвища вона у XRP (0.127) —
монети з найкоротшими ногами, найнижча у BTC (0.100) і ETH (0.109), де
ноги вікна найдовші.

### Що з видачею

- **Влучань нуль на всіх свіжих ногах.** Сигнал (перше перетинання
  порога 0.10) приходить майже на початку ноги: ймовірність класу 10
  стоїть вище порога майже скрізь, тому «перше перетинання» нічого не
  означає. Базовий прогноз дає 3 влучання з 84.
- **Сходинки тримаються поза навчанням.** Жодного стрибка через клас на
  всіх шести монетах, рухів вниз два з 228 кроків — 99.1 % чистих. Штрафи
  розділу 9 перенеслись на нові дані без змін.
- **Сказаний клас не доходить до 10.** На всьому вікні видача лишається
  в межах 2 … 6 (на SOL, XRP і DOGE — від 2): модель ніколи не називає
  ногу закінченою, вона тільки рахує від її початку.

### Масштаб полотен

Ймовірність класу 10 живе біля порога `0.10`, а сказаний клас — у
кількох сусідніх десятинах. Шкали `0 … 1` і `1 … 10` показали б пряму
лінію, тому обидва полотна масштабуються під свій ряд:

```
видача:       від floor(min) - 0.5 до ceil(max) + 0.5, у межах 0.5 ... 10.5
ймовірність:  [min, max] + 5 % запасу з кожного боку,
              поріг завжди всередині, ширина не менша за 0.02
```

Шкала ймовірності **спільна на всі шість монет** — інакше однакова
видача виглядала б по-різному від монети до монети. На цьому прогоні
вона `0.0281 … 0.1301`. Шкала видачі — своя на монету, бо це цілі
класи: `2.5 … 6.5` на BTC і ETH, `1.5 … 6.5` на SOL, XRP, BNB і DOGE.

### Полотна

Розділ живе на власній вкладці —
[Дослідження та тест найкращої моделі](http://localhost:8080/besttest),
окремо від гріда й кривих навчання: тут нічого не вчиться.

Шість малюнків, по одному на монету, у кожного **три полотна** на
спільній осі: ціна із зігзагом 2.3 % на ема10, видача моделі й
ймовірність класу 10 з горизонтальним порогом. Полотна мітки на вкладці
немає: мітка — це те, що модель має вгадати, і на полотні вона тільки
відповідає замість неї. Мітка лишається в таблиці результату — там вона
рахується, а не малюється.

Вертикальний пунктир — **межа побаченого**: усе лівіше від нього модель
уже бачила (нога train або нога val розділу 9). На BTC він стоїть на
28.08 01:36, на ETH — на 14.08 14:26, на решті монет його немає зовсім.
Правий край полотен — остання подія джерела на момент прогону:
`01.09 21:24 … 21:28`.

### Висновки

1. **Модель вчилась до 14.08 і на всіх шести монетах** — 602 ноги,
   332 891 подія, від 14.02 (XRP і DOGE — від 05.03).
2. **Градієнт ніколи не торкався val.** Спільних подій між train і val
   нуль, остання подія train — 14.08 14:23, перша подія val —
   14.08 14:27, ніг train із півотом після межі нуль. Фічі
   `клас210х14х4ц` рахуються всередині своїх 210 секунд і скейлера не
   мають, тому статистики всього ряду в них не заходить; поріг,
   `pos_weight` і базовий прогноз узяті **тільки з train**.
3. **Але val обирав епоху і конфігурацію.** Рання зупинка взяла епоху 4
   за найменшим штрафом на val, і краща з двох конфігурацій гріда теж
   обрана на val. Ваг на val не рахували, а вибирали ними — тому val
   розділу 9 це вибірка розробки, а не чистий холдаут.
4. **Тому вікно розділу 10 чистять від побаченого.** З 96 ніг вікна
   7 модель уже бачила: 2 ноги train (BTC, ETH) і 5 ніг val розділу 9
   (BTC). Свіжих закритих лишається 84, з них одна на BTC.
5. **Поза своїм часом модель поводиться так само, як на val розділу 9.**
   Влучність класу 0.115 при випадковій 0.10, відстань 2.16 класи,
   влучань нуль з 84 ніг. Шість монет замість однієї і 84 ноги замість
   6 нічого не змінили — підтвердилось те саме.
6. **«Пік у зоні» пояснено і як мірка більше не годиться.** 23 з 28
   зібрані на XRP і DOGE — монетах з найкоротшими ногами; там, де ноги
   довші за train, піків майже нуль. Це властивість лічильника, а не
   читання ринку.
7. **Штрафи за сходинку тримаються на нових даних**: 0 стрибків, два
   рухи вниз з 228 кроків. Це єдине, що переноситься за межі навчання
   без втрат.
8. **Наступний крок** — не нові монети й не довший відрізок: їх тут уже
   по шість і по 18 діб, і вони дали те саме. Лічильник треба ламати
   самою постановкою — міткою, у якій рівномірний рух від початку ноги
   не мінімізує штраф.

## Розділ 11. Хід без зігзага: памʼять обнуляє сама мережа · `підтверджено`

У розділі 10 памʼять обнуляв **зігзаг**: ряд різався на ноги по півотах,
і кожна нога заходила в мережу з чистим станом `h = c = 0`. Півот
зігзага 2.3 % підтверджується заднім числом — тільки коли ціна відійшла
на 2.3 % від екстремуму. Отже «чистий старт рівно на першій події ноги»
— це знання, якого наживо немає, і саме його модель отримувала
безкоштовно.

Тут його немає. Питання одне: **чи знайде мережа півот сама**.

### Метод

[`scripts/leg_end_walk.py`](scripts/leg_end_walk.py). Ваги ті самі, що в
розділі 10, нічого не вчиться.

```
подія за подією підряд, без розрізання ряду:
  крок t -> LSTM(стан) -> причинна увага на памʼять від обнулення
         -> сказаний клас десятини 1 ... 10 (ЕМА10, як у розділі 9)
  сказала 10  ->  тут півот  ->  h, c = 0, памʼять порожня
                  наступна подія починає нову ногу з класу 1
```

Мітка не змінилась: перші 10 % подій ноги — клас 1, 10-20 % — клас 2,
і так далі; останні 10 % — клас 10. **Окіл кожного класу — його власна
десятина**, однаковий для всіх десяти.

- **Запобіжник.** Якщо мережа не скаже 10 ніколи, памʼять не обнулиться
  сама. Тому стоїть стеля `LENGTH = 3000` подій: обнулення примусове і
  рахується **окремо** від своїх.
- **Розгін.** Перед вікном мережа проходить 3000 подій, інакше перші
  події вікна читались би з чистого стану — тобто з того самого
  подарунка, від якого тут відмовились.
- **Крок у крок дорівнює прогону цілим відрізком.** Ходок веде LSTM по
  одній події і тримає буфер виходів від останнього обнулення; увага
  дивиться рівно на цей буфер. Поки обнулень немає, обидва шляхи дають
  ті самі числа — це перевірено тестом.
- **Де рахується.** На CPU: хід іде по одній події, і на прискорювачі
  кожен крок коштує запуску ядра — виходить у 25 разів довше.

### Витік даних: чого тут немає

| Канал | Чому закритий |
| ----- | ------------- |
| зігзаг у вході | півоти у вхід не подаються взагалі; фічі `клас210х14х4ц` рахуються всередині своїх 210 с |
| скейлер по ряду | його немає — статистики всього ряду у фічі не заходить |
| чистий старт на півоті | прибраний: памʼять обнуляє тільки сама мережа або стеля |
| ноги train і val розділу 9 | впізнані парою «початок, кінець» і **з метрик виключені**, як у розділі 10 |
| майбутнє всередині ноги | увага причинна: крок `t` бачить `1 … t` |

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

### Результат

Вікно те саме, що в розділі 10: `2026-08-14 14:00 … 2026-09-02 00:00`
UTC на датасетах `<PAIR>_now`. Прогін зібрано `2026-09-01 22:10` UTC.

| Монета | Подій | Свіжих ніг | Бачених | Своїх обнулень | Стеля | У зоні кінця | Ніг без обнулення | Влучність класу | Відстань |
| ------ | ----: | ---------: | ------: | -------------: | ----: | -----------: | ----------------: | --------------: | -------: |
| BTC_USDT | 4 715 | 1 | 6 | **0** | 1 | 0 | 1 | 0.100 | 2.50 |
| ETH_USDT | 7 495 | 10 | 1 | **0** | 2 | 1 | 8 | 0.102 | 2.46 |
| SOL_USDT | 7 496 | 15 | 0 | **0** | 2 | 0 | 13 | 0.116 | 2.43 |
| XRP_USDT | 7 495 | 31 | 0 | **0** | 2 | 0 | 29 | 0.107 | 2.42 |
| BNB_USDT | 7 496 | 7 | 0 | **0** | 2 | 1 | 5 | 0.127 | 2.34 |
| DOGE_USDT | 7 495 | 20 | 0 | **0** | 2 | 1 | 18 | 0.101 | 2.48 |
| **разом** | **42 192** | **84** | **7** | **0** | **11** | 3 | 74 | **0.110** | **2.43** |

**Своїх обнулень нуль.** Клас 10 не вимовляється **жодного разу** на
42 192 подіях шести монет. Усі 11 обнулень — це стеля памʼяті, і три з
них випадково впали в зону кінця своєї ноги: 3.6 % ніг при тому, що
зона займає 10 % кожної ноги, — це менше за випадкове влучання.

Влучність класу `0.110` при випадковій `0.10`, відстань `2.43` класи —
**гірше, ніж 0.115 і 2.16 у розділі 10**, де ноги різав зігзаг. Різниця
і є та частина результату розділу 10, яку давав не прогноз, а сам
зігзаг.

### Висновки

1. **Мережа не знаходить півот сама.** Нуль своїх обнулень на 84 свіжих
   ногах і 42 192 подіях. Те, що в розділі 10 виглядало як лічильник
   часу від початку ноги, тут не має початку — і не зʼявляється нічого.
2. **Клас 10 недосяжний за побудовою навчання.** Модель училась на
   ногах, кожна з яких **починалась з чистої памʼяті на першій події**;
   без цієї події її стан ніколи не приходить у ту область, де софтмакс
   ставить максимум на десятий клас. Це не поріг і не згладжування —
   видача просто не доходить до 10, як і на всьому вікні розділу 10.
3. **Метрики розділу 10 завищені саме на розмір цього подарунка.**
   0.115 → 0.110 по влучності і 2.16 → 2.43 по відстані — усе, що між
   ними, давав зігзаг, а не мережа.
4. **Постановку треба міняти, а не ручки.** Мітка «десятина ноги»
   вимірюється довжиною ноги, яку знає тільки зігзаг. Поки ціль
   означена через уже відомий кінець, найкраща стратегія мережі —
   рівномірно рахувати від відомого початку, а обидва кінці наживо
   невідомі.

### Відтворення

```bash
# ті самі датасети <PAIR>_now, що в розділі 10
python3 research/19-leg-end-lstm/scripts/leg_end_walk.py

# інше вікно або одна монета
python3 research/19-leg-end-lstm/scripts/leg_end_walk.py \
    --from "2026-08-20 00:00" --pairs BTC_USDT_now

# тести ходу
python3 -m unittest discover -s tests -t tests -p "test_leg23.py"
```

Вивід — [`results/walk_steps_ema10_2_3.json`](results/walk_steps_ema10_2_3.json),
на вкладці — `/api/besttest/walk`.

## Відтворення

```bash
# дані до 2026-08-29 (окрема тека, наявні датасети не чіпаються)
python3 rules/scripts/build_dataset.py --pair BTC_USDT --end-date 2026-08-29 \
    --out data/datasets/BTC_USDT_29/class210x14x4
python3 rules/scripts/build_class210x14x5.py --pair BTC_USDT \
    --split data/datasets/BTC_USDT_29/class210x14x4 \
    --out data/datasets/BTC_USDT_29/class210x14x5
python3 rules/scripts/build_class210x14x3.py \
    --in data/datasets/BTC_USDT_29/class210x14x5 \
    --out data/datasets/BTC_USDT_29/class210x14x3
python3 rules/scripts/build_class210x14x4c.py --pair BTC_USDT \
    --in data/datasets/BTC_USDT_29/class210x14x3 \
    --out data/datasets/BTC_USDT_29/class210x14x4c

# розділ 1: ноги, зона кінця, розбиття за датою
python3 research/19-leg-end-lstm/scripts/leg_end_data.py

# розділи 2-5: грід і краща модель (зона кінця, ема90)
python3 research/19-leg-end-lstm/scripts/leg_end_train.py

# розділ 6: десятини ноги на ема90
python3 research/19-leg-end-lstm/scripts/leg_end_train.py --mode decile

# розділ 7: зона кінця на ема10
python3 research/19-leg-end-lstm/scripts/leg_end_train.py --period 10

# розділ 8: десятини ноги на ема10
python3 research/19-leg-end-lstm/scripts/leg_end_train.py --mode decile --period 10

# розділ 9: сходинки — усі монети в train, штрафи 5/25, ЕМА видачі 10
python3 research/19-leg-end-lstm/scripts/leg_end_train.py --steps \
    --configs 2 --epochs 5 --patience 3

# ті самі штрафи вручну, на будь-якому прогоні
python3 research/19-leg-end-lstm/scripts/leg_end_train.py --mode decile \
    --period 10 --extra всі --jump 5 --down 25 --smooth 10

# один прогін ручками за замовчуванням, без гріда
python3 research/19-leg-end-lstm/scripts/leg_end_train.py --one

# інша зона або інша межа — тими самими прапорцями
python3 research/19-leg-end-lstm/scripts/leg_end_train.py --share 0.2 \
    --cut "2026-08-01 00:00"

# розділ 10: датасети всіх монет по поточний час в окремі теки <PAIR>_now
#   --end-date — сьогоднішня дата; останній добовий файл читається таким,
#   яким він є на момент збірки, тому та сама команда котить теки далі.
#   BTC — без заповнення пропусків, альткоїни — з --fill-gaps 60
for pair in BTC_USDT:0 ETH_USDT:60 SOL_USDT:60 XRP_USDT:60 BNB_USDT:60 \
            DOGE_USDT:60; do
  name=${pair%:*}; fill=${pair#*:}; out=data/datasets/${name}_now
  python3 rules/scripts/build_dataset.py --pair $name --end-date 2026-09-02 \
      --fill-gaps $fill --out $out/class210x14x4
  python3 rules/scripts/build_class210x14x5.py --pair $name --fill-gaps $fill \
      --split $out/class210x14x4 --out $out/class210x14x5
  python3 rules/scripts/build_class210x14x3.py --pair $name \
      --in $out/class210x14x5 --out $out/class210x14x3
  python3 rules/scripts/build_class210x14x4c.py --pair $name --fill-gaps $fill \
      --in $out/class210x14x3 --out $out/class210x14x4c
done

# розділ 10: готова модель розділу 9 на свіжому відрізку всіх монет
python3 research/19-leg-end-lstm/scripts/leg_end_fresh.py

# інше вікно або інші монети — тими самими прапорцями
python3 research/19-leg-end-lstm/scripts/leg_end_fresh.py \
    --from "2026-08-20 00:00" --to "2026-09-02 00:00" --pairs BTC_USDT_now

# тести
python3 -m unittest discover -s tests -t tests -p "test_leg23.py"
python3 -m unittest discover -s tests -t tests -p "test_dashboard.py"
```

Нічого зовнішнього. Збірка датасету — близько 7 хвилин, розділ 1 —
секунди, грід — 7.6 хвилини на `mps`, розділ 10 — 10 секунд: він нічого
не вчить.

## Файли

| Файл | Що |
| ---- | -- |
| [`scripts/leg_end_data.py`](scripts/leg_end_data.py) | ряд подій, півоти, зона кінця, ноги, маска, розбиття за датою |
| [`scripts/leg_end_net.py`](scripts/leg_end_net.py) | мережа: LSTM без паддингу в памʼяті + причинна увага |
| [`scripts/leg_end_train.py`](scripts/leg_end_train.py) | грід, навчання, метрики по ногах, базовий прогноз, ряди |
| [`scripts/leg_end_fresh.py`](scripts/leg_end_fresh.py) | готова модель на свіжому відрізку: відрізки навчання, вікно всіх монет, масштаб полотен |
| [`scripts/leg_end_walk.py`](scripts/leg_end_walk.py) | розділ 11: хід подія за подією, памʼять обнуляє сама мережа по класу 10 |
| `results/legs<тег>.json` | розмітка: зони, ноги, розбиття |
| `results/grid<тег>.json` | грід: рядок на прогін |
| `results/train<тег>.json` | краща модель: крива, метрики, ряди train і val |
| `models/leg_end<тег>/model.pt` | ваги кращої епохи кращого прогону |
| [`results/fresh_steps_ema10_2_3.json`](results/fresh_steps_ema10_2_3.json) | розділ 10: відрізки навчання, вікно і ряди шести монет |

Тег — це прогін: `_2_3` (зона кінця, ема90), `_dec_2_3` (десятини,
ема90), `_ema10_2_3` (зона кінця, ема10), `_dec_ema10_2_3` (десятини,
ема10), `_steps_ema10_2_3` (сходинки на ема10 з ногами всіх монет).
| [`tests/test_leg23.py`](../../tests/test_leg23.py) | тести розмітки, масок, чистої памʼяті, причинності уваги, гріда і метрик |

## Вкладки

Дослідження показують **дві** вкладки.

[Кінець ноги 2.3](http://localhost:8080/leg23) — пʼять розділів, ті самі,
що в цьому файлі.

Вкладка показує **пʼять прогонів** підряд, у кожного той самий набір
блоків: розмітка, мережа, грід, краща модель і **два малюнки** —
train і val окремо.

| Розділ вкладки | Що показує |
| -------------- | ---------- |
| 1. Зона кінця ноги на ема90 | розділи 1–5 цього файлу |
| 2. Десятини ноги на ема90 | розділ 6 |
| 3. Зона кінця ноги на ема10 | розділ 7 |
| 4. Десятини ноги на ема10 | розділ 8 |
| 5. Сходинки на ема10: усі монети в train | розділ 9 |

Дані — `/api/leg23<ключ прогону>` і `/api/leg23/<ключ>/grid`.

[Дослідження та тест найкращої моделі](http://localhost:8080/besttest) —
**розділ 10** цього файлу: краща модель розділу 9 поза своїм часом.
Тут нічого не вчиться, тому вкладка стоїть окремо від гріда й кривих
навчання.

| Блок вкладки | Що показує |
| ------------ | ---------- |
| Вікно прогону | межі вікна, скільки діб, скільки монет і коли зроблено прогін |
| Відрізки часу, на яких вона вчилась | таблиця train розділу 9 по монетах |
| Полотна | шість малюнків, у кожного три полотна: ціна із зігзагом, видача моделі й ймовірність класу 10 з порогом; вертикальний пунктир — межа побаченого |
| Хід без зігзага (розділ 11) | таблиця обнулень по монетах і ті самі три полотна, де вертикаль — власне обнулення памʼяті |

Полотна доходять до останньої події джерела на момент прогону — правий
край підписаний у заголовку кожної монети.

**Перехрестя.** Наведена мишка веде за собою пунктир: вертикаль іде
наскрізь усіма полотнами монети і стає на подію під курсором,
горизонталь — тільки в тому полотні, над яким стоїть мишка. На нижній
шкалі підписується час цієї події (UTC, з секундами), на лівій — значення
саме цього полотна, у знаках, які його шкала розрізняє: ціна цілими,
ймовірність класу 10 біля порога 0.1 — до тисячних. Осторонь ряду —
у лівому полі під підписи шкали — перехрестя ховається.

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

Метрики вікна на вкладці не показуються — вони живуть у розділі 10
цього файлу («Результат»). Вкладка показує, **де** модель вчилась і
**що вона малює** поза цим часом.

Дані — `/api/besttest`.
