# 18. Згладжений зігзаг і клас210х210х4ц

Пари: `BTC_USDT` у розділах 1–3, **усі шість** у розділах 4–6. Клас
фіч: `клас210х210х4ц`.

## Ціль

Побачити **згладжений зігзаг** поруч із сирим і порахувати, наскільки за
ним ідуть чотири фічі події `клас210х210х4ц` — тобто подія **без
розкладки по ЕМА**, чотири числа замість 52.

Дослідження 16 і 17 працювали над `клас210х4бема10х10х130`, де кожна фіча
розкладена на 13 періодів ЕМА, і найсильніший звʼязок із ціллю там
`|r| = 0.2936` (`vol_balance ема130`). Питання тут просте: скільки з
цього звʼязку — у самих фічах, а скільки додає згладжування ЕМА.

## Дані

| Що | Значення |
| -- | -------- |
| Клас фіч | [`клас210х210х4ц`](../../rules/01-data-and-db.md#клас210х210х4ц) |
| Форма події | `(4,)` — чотири числа, усі безрозмірні, `[-1, 1]` |
| Фічі | `balance`, `d_balance`, `vol_balance`, `price_balance` |
| Ціль | зігзаг **2.3%** нормований, **ема90** ціни, нога `розподіл` (minmax) |
| Згладжування | `сернога` між зонами півотів — центроване, вікно = довжина своєї ноги |
| Подій | 40 242, з ціллю 39 879 · 44 півоти, 43 ноги · 44 зони, 2 624 події |
| Джерело | `data/datasets/BTC_USDT/class210x210x4c/` |

Розділи 4–6 працюють на **шести парах** із заповненими пропусками часу —
`BTC`, `ETH`, `SOL`, `XRP`, `BNB`, `DOGE` (`*_USDT_fill`), разом 387 990
подій. Перелік і числа — розділ 6.

Ціль будується тим самим ланцюгом, що в дослідженнях 16 і 17:

```
ема90(ціна) -> півоти зігзага 2.3% -> нормована нога (розподіл)
            -> згладжування сернога між зонами півотів
```

Події класу і події цілі — той самий відбір, тому зіставляються за
`timestamp` подія в подію; розбіжність зупиняє прогін.

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

1. Зібрати події `клас210х210х4ц` одним рядом за часом (власний поділ
   датасету перемішаний).
2. Побудувати ціль і згладити її `ема20` між зонами півотів.
3. Порахувати кореляцію кожної з чотирьох фіч із сирою і згладженою
   ціллю — Пірсоном і Спірменом, на `train` розбиття по часу і на всіх
   подіях; поруч — розподіли фіч і обох цілей.
4. Порізати вихід на **28 клітинок** під перехресну ентропію: краї —
   зона півота, середина по квантилю цілі.
5. Описати мережу під цей вихід: вхід, фічі, шари, ваги, ручки.
6. Навчити її на **всіх монетах** — чотири довжини вікна, розбиття за
   датою `10.08` — і показати сигнали країв на невідомих даних.

## Розділ 1. Згладжений зігзаг · `підтверджено`

### Метод

[`scripts/correlate.py`](scripts/correlate.py). Ціль згладжується тими
самими правилами, що в дослідженні 16
([`smooth.py`](../16-zigzag23-wattn/scripts/smooth.py) → `smooth_leg`):
зони півотів недоторкані, нога в ногу не перетікає, ряд не виходить за
`[-1, 1]`. Спосіб задається іменем — `--way сернога`, `сер500`, `ема60`,
`мед41`: вид береться з префікса, період — з числа.

Спосіб тут **центрований** (`сер` — середнє по вікну
`[i − N/2, i + N/2]`), тому фазового зсуву немає. Причинна ЕМА його дає:
`(N−1)/2` подій. Центроване вікно заглядає вперед — для **цілі** це
припустимо, вона й так розмічається заднім числом (півот відомий лише
постфактум), а для фічі так робити не можна, це був би витік.

#### Вікно дорівнює довжині своєї ноги

Ноги зігзага 2.3 % дуже різні: медіана 802 події, середня 928,
найкоротша 227, найдовша 2 431. Одне число вікна на всіх означає різну
силу згладжування на кожній нозі — довгі лишаються смиканими, короткі
стають пласкими.

Дослідження 16 бере вікно як є. Тут вікно рахується **на кожну ногу
окремо** ([`correlate.apply_way`](scripts/correlate.py)):

```
вікно ноги = довжина ноги            # `сернога`, `меднога`
вікно ноги = min(N, довжина ноги)    # `сер500` і решта числових
```

Так усі ноги згладжуються однаково сильно у своїх одиницях. Вікно кожної
ноги йде у звіт (`leg_windows`).

Дві мірки:

```
Пірсон  = Σ(a − ā)(b − b̄) / √(Σ(a − ā)²·Σ(b − b̄)²)
Спірмен = той самий Пірсон на рангах
```

Два відрізки: `train` розбиття по часу (23 166 подій, 2026-02-14 …
05-12 — той самий, на якому вчились моделі 16 і 17) і **всі події**
(39 879, 2026-02-14 … 08-16). Сира ціль стоїть поруч зі згладженою: без
неї не видно, що дало саме згладжування.

## Розділ 2. Розподіли фіч і цілі · `підтверджено`

Гістограми всіх чотирьох фіч і обох цілей, 40 стовпців на діапазон
`[-1, 1]` — і фічі класу, і нормована нога живуть у ньому, тому шкала
спільна (усі події, 39 879):

| Ряд | Середнє | σ | NaN |
| --- | ------: | -: | --: |
| `balance` | 0.0259 | 0.3593 | 0 |
| `d_balance` | 0.0154 | 0.2824 | 0 |
| `vol_balance` | 0.0093 | 0.2407 | 0 |
| `price_balance` | −0.0065 | 0.2131 | 112 |
| ціль сира | 0.0212 | **0.5690** | — |
| ціль `сернога` | 0.0194 | **0.3846** | — |

Фічі стоять вузькою купою навколо нуля (σ 0.21 … 0.36), ціль розтягнута
майже на весь діапазон (σ 0.569): подія і ціль живуть у різних
масштабах. Згладжування по нозі стягує ціль до σ 0.385 — ближче до фіч,
але вершини `±1` при цьому лишаються на місці, бо зони не чіпаються.

## Розділ 3. Кореляція фіч із ціллю · `підтверджено`

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

**train**, 23 166 подій:

| Фіча | сира: Пірсон | сира: Спірмен | `сернога`: Пірсон | `сернога`: Спірмен | лишилось звʼязку |
| ---- | -----------: | ------------: | ----------------: | -----------------: | ---------------: |
| `balance` | 0.0590 | 0.0580 | 0.0534 | 0.0467 | 91 % |
| `d_balance` | 0.0499 | 0.0503 | 0.0485 | 0.0436 | 97 % |
| **`vol_balance`** | **−0.0921** | **−0.0900** | **−0.0957** | **−0.0864** | **104 %** |
| `price_balance` | −0.0365 | −0.0347 | −0.0308 | −0.0257 | 84 % |

**усі події**, 39 879:

| Фіча | сира: Пірсон | сира: Спірмен | `сернога`: Пірсон | `сернога`: Спірмен | лишилось звʼязку |
| ---- | -----------: | ------------: | ----------------: | -----------------: | ---------------: |
| `balance` | 0.0599 | 0.0593 | 0.0459 | 0.0400 | 77 % |
| `d_balance` | 0.0350 | 0.0357 | 0.0259 | 0.0223 | 74 % |
| **`vol_balance`** | **−0.0827** | **−0.0818** | **−0.0689** | **−0.0638** | 83 % |
| `price_balance` | −0.0258 | −0.0258 | −0.0169 | −0.0136 | 66 % |

Драбина вікон на тих самих даних, `vol_balance` (Пірсон на train / на
всіх подіях, у дужках — скільки лишилось від сирої цілі):

| Спосіб | Вікно, подій | Годин | Ніг з обрізаним вікном | train | усі події |
| ------ | -----------: | ----: | ---------------------: | ----: | --------: |
| `сер21` | 21 | 1.2 | 0 з 43 | −0.0900 (98 %) | −0.0808 (98 %) |
| `сер61` | 61 | 3.6 | 0 з 43 | −0.0870 (94 %) | −0.0775 (94 %) |
| `сер120` | 120 | 7.0 | 0 з 43 | −0.0845 (92 %) | −0.0739 (89 %) |
| `сер210` | 210 | 12.3 | 0 з 43 | −0.0827 (90 %) | −0.0699 (85 %) |
| `сер500` | 500 | 29.2 | 10 з 43 | −0.0851 (92 %) | −0.0674 (81 %) |
| **`сернога`** | **227 … 2 431** | **13 … 142** | **43 з 43** | **−0.0957 (104 %)** | **−0.0689 (83 %)** |

### Висновки

1. **Звʼязок сирої події з ціллю майже відсутній.** Найсильніша фіча —
   `vol_balance`, `|r| = 0.092` на train. Це втричі менше за
   `|r| = 0.2936`, який дає та сама фіча, згладжена ЕМА130
   (`клас210х4бема10х10х130`, етап 1 дослідження 16).
2. **Отже, звʼязок робить згладжування фіч, а не самі фічі.** Чотири
   числа події — це стан стакана за 210 секунд; ціль же стоїть на ема90
   ціни, тобто на 5.3 години. Без ЕМА-розкладки фіча і ціль просто на
   різних масштабах часу.
3. **Вікно по нозі — єдиний спосіб, який звʼязок не втратив, а додав.**
   На train `vol_balance` дає −0.0957 проти −0.0921 сирої цілі, тобто
   104 %; `d_balance` тримає 97 %. Жодне фіксоване вікно так не змогло:
   найкраще з них (`сер21`) лишало 98 %.
4. **Це вирівнювання, а не гасіння.** Абсолютна сила згладжування тут
   найбільша з усієї драбини — медіанне вікно 802 події проти 21 у
   `сер21`, — а звʼязок при цьому найвищий. Заважав не розмір вікна, а
   його однаковість: ноги різняться вдесятеро, і одне число вікна
   недозгладжує довгі й перезгладжує короткі.
5. **На всіх подіях приросту немає** — 83 % проти 98 % у `сер21`. Приріст
   видно тільки на train, тому це спостереження, а не готова ціль: воно
   тримається на 23 тисячах подій і не переноситься на решту ряду.
6. **Слабкі фічі витримують згладжування гірше за сильні.** На train
   `vol_balance` 104 % і `d_balance` 97 %, а `price_balance` — 84 %; на
   всіх подіях розрив ще більший: 83 % проти 66 %.
7. **Спірмен нижчий за Пірсона на довгих вікнах** (−0.0864 проти
   −0.0957): довге усереднення зближує сусідні значення, і ранги
   зливаються швидше, ніж самі числа.
8. **Знаки збігаються з дослідженням 16**: `balance` і `d_balance` — плюс,
   `vol_balance` і `price_balance` — мінус, на обох відрізках і на всіх
   способах згладжування.

## Розділ 4. Вихід у 28 клітинок · `підтверджено`

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

### Навіщо

Зараз вихід один, штраф — зважений MSE. Мінімум MSE при шумній цілі це
**умовне середнє**, тому мережа й видає числа біля нуля: сказати `−0.9`
і промазати коштує дорожче, ніж не сказати нічого. Розділ 6 показує це
на десяти довжинах вікна — «сказала: 0».

Замість «яке число» питаємо «в якій клітинці». Крайня клітинка має
власну ймовірність, і поріг ставиться на неї, а не на середнє.

### Клітинки

`[−1, 1]` ріжеться на **28 клітинок**. Дві крайні закріплені межею зони
(`band = 0.20`), решта 26 — по квантилю цілі, приблизно по 3.5 % подій
на клітинку. Так крайня клітинка **дослівно дорівнює зоні півота за
значенням**, а класи майже рівні за наповненням (перекіс 1.5 : 1 замість
6 : 1 при рівних клітинках по 0.2).

| № | Діапазон | Ширина | Подій | Частка |
| -: | -------- | -----: | ----: | -----: |
| **1** | `[-1.000  -0.800)` | 0.200 | 15 065 | 4.36 % |
| 2 | `[-0.800  -0.595)` | 0.205 | 12 125 | 3.51 % |
| 3 | `[-0.595  -0.508)` | 0.087 | 12 239 | 3.54 % |
| 4 | `[-0.508  -0.445)` | 0.063 | 12 069 | 3.49 % |
| 5 | `[-0.445  -0.391)` | 0.054 | 12 216 | 3.53 % |
| 6 | `[-0.391  -0.343)` | 0.048 | 12 248 | 3.54 % |
| 7 | `[-0.343  -0.304)` | 0.039 | 11 959 | 3.46 % |
| 8 | `[-0.304  -0.264)` | 0.040 | 12 279 | 3.55 % |
| 9 | `[-0.264  -0.228)` | 0.036 | 12 093 | 3.50 % |
| 10 | `[-0.228  -0.189)` | 0.039 | 12 218 | 3.54 % |
| 11 | `[-0.189  -0.154)` | 0.035 | 12 266 | 3.55 % |
| 12 | `[-0.154  -0.121)` | 0.033 | 12 161 | 3.52 % |
| 13 | `[-0.121  -0.091)` | 0.030 | 11 893 | 3.44 % |
| 14 | `[-0.091  -0.060)` | 0.031 | 12 426 | 3.60 % |
| 15 | `[-0.060  -0.033)` | 0.027 | 11 803 | 3.41 % |
| 16 | `[-0.033  -0.000)` | 0.033 | 12 490 | 3.61 % |
| 17 | `[-0.000  +0.034)` | 0.034 | 12 122 | 3.51 % |
| 18 | `[+0.034  +0.067)` | 0.033 | 12 166 | 3.52 % |
| 19 | `[+0.067  +0.100)` | 0.033 | 12 173 | 3.52 % |
| 20 | `[+0.100  +0.135)` | 0.035 | 11 841 | 3.43 % |
| 21 | `[+0.135  +0.173)` | 0.038 | 12 340 | 3.57 % |
| 22 | `[+0.173  +0.217)` | 0.044 | 12 278 | 3.55 % |
| 23 | `[+0.217  +0.263)` | 0.046 | 12 078 | 3.49 % |
| 24 | `[+0.263  +0.324)` | 0.061 | 12 187 | 3.53 % |
| 25 | `[+0.324  +0.403)` | 0.079 | 12 083 | 3.50 % |
| 26 | `[+0.403  +0.527)` | 0.124 | 12 181 | 3.52 % |
| 27 | `[+0.527  +0.800)` | 0.273 | 12 161 | 3.52 % |
| **28** | `[+0.800  +1.000]` | 0.200 | 14 516 | 4.20 % |

Межі напівінтервальні `[lo, hi)`; перша включає `−1.000`, остання
включає `+1.000`. Центр клітинки — середина її діапазону; для крайніх це
`−0.90` і `+0.90`.

**Межі не зашиваються числами в код.** Вони рахуються з цілі train одним
викликом і зберігаються у звіті: закріплені `±0.80` плюс
`np.quantile(y_train[|y| < 0.8], np.linspace(0, 1, 27))`. `y_train` — це
**всі шість монет, події до 10.08**: 346 528 подій, з ціллю 345 676.
Перекіс наповнення 1.28 : 1 (від 3.41 % до 4.36 % подій на клітинку).
Таблиця вище — те, що виходить; вона слугує перевіркою, а не входом.

### Мʼяка ціль

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

```
своя клітинка   0.70
сусідні (±1)    0.15 кожна
```

На краях ряду вага сусіда, якого немає, віддається своїй клітинці
(сума завжди 1.0). Приклад: `y = −0.93` → клітинка 1 → мітка
`[0.85, 0.15, 0, …, 0]`.

### Штраф

Перехресна ентропія по 28 клітинках із мʼякою міткою:

```
L = − Σ_i  q_i · log softmax(z)_i
```

`q` — мʼяка мітка, `z` — 28 логітів мережі. Ваги класів **не
додаються**: клітинки вже майже рівні за наповненням, у цьому й був сенс
квантиля. Ціна помилки в клітинках і штраф за розмах додаються до цього
виразу в розділі 6.

### Що читається з виходу

`p = softmax(z)`, `центр` — центри клітинок з таблиці.

| Потрібно | Формула |
| -------- | ------- |
| «зона нижнього півота» | `p₁ ≥ поріг` |
| «зона верхнього півота» | `p₂₈ ≥ поріг` |
| «підходимо до низу / верху» | `p₁ + p₂` / `p₂₇ + p₂₈` |
| число ноги назад у `[−1, 1]` | `ŷ = Σ pᵢ · центрᵢ` |
| впевненість | `1 − ентропія(p) / log 28` |

`ŷ` — це видача, яку видно на полотнах. Мірка розділу 6 інша й одна:
скільки сигналів клітинок 1 і 28 сказала модель і скільки з них
збіглося з ціллю.

## Розділ 5. Параметри трансформера · `підтверджено`

[`scripts/net_spec.py`](scripts/net_spec.py) →
[`results/net_2_3.json`](results/net_2_3.json). Ваги рахуються
**формулами**, а не збиранням моделі: розділ описує мережу, яку ще треба
побудувати, і числа мають перевірятись на папері.

### Фічі й вхід

| Що | Значення |
| -- | -------- |
| клас події | `клас210х210х4ц` |
| фіч на подію | **4** — `balance`, `d_balance`, `vol_balance`, `price_balance` |
| довжина події | 210 секунд |
| вхід моделі | вікно `(K, 4)` — K подій поспіль |
| ціль | ціль **останньої** події вікна |
| пари | усі шість `*_USDT_fill` — вікно не переходить з монети на монету |
| нормування | немає: чотири фічі вже безрозмірні в `[-1, 1]` |

Крок послідовності тут — **одна подія**, а не період ЕМА. Подія цього
класу і є чотири числа, середніх у ній немає, тому контекст збирає сама
увага, а не порахований наперед набір ЕМА.

### Шари

| Що | Значення |
| -- | -------- |
| перший шар | `Linear(4 → 64)` на кожну подію окремо, спільні ваги |
| позиція | вчений параметр `(1, K, 64)` |
| тіло | `TransformerEncoder`, **2 шари**, 4 голови, `dim_feedforward = 256`, `norm_first = True`, dropout 0.1 |
| зчитування | `flat`: `(K, 64) → (K · 64,)` |
| голова | `Linear(K · 64 → 28)` |
| вихід | 28 логітів (розділ 4) |

Шар за шаром для K = 128 — найкоротшого вікна розділу 6:

| Крок | Форма | Заходить | Виходить | Стиснення | Ваг |
| ---- | ----- | -------: | -------: | --------- | --: |
| вхід: вікно з K подій | `(128, 4)` | 512 | 512 | ні | — |
| `Linear(4 → 64)` на кожну подію | `(128, 64)` | 512 | 8 192 | ні | 320 |
| позиція події у вікні | `(128, 64)` | 8 192 | 8 192 | ні | 8 192 |
| шар уваги 1 з 2, матриця 128 × 128 | `(128, 64)` | 8 192 | 8 192 | ні | 49 984 |
| шар уваги 2 з 2, матриця 128 × 128 | `(128, 64)` | 8 192 | 8 192 | ні | 49 984 |
| зчитування `flat` | `(8192,)` | 8 192 | 8 192 | ні | — |
| голова `Linear(8192 → 28)` | `(28,)` | 8 192 | 28 | **так** | 229 404 |

Ваги шару енкодера — `4w² + 2wf + f + 9w`: увага `4w² + 4w`
(`in_proj` `3w × w + 3w`, `out_proj` `w × w + w`), два лінійних
`2wf + f + w`, дві норми `4w`. При `w = 64`, `f = 256` це **49 984** ваги
на шар.

### Ціна довжини вікна

| K | Контекст | Чисел на вході | Ваг уваги на шар | Чисел у зчитуванні | Параметрів |
| -: | -------- | -------------: | ---------------: | -----------------: | ---------: |
| 128 | 7 год 28 хв | 512 | 16 384 | 8 192 | 337 884 |
| 256 | 14 год 56 хв | 1 024 | 65 536 | 16 384 | 575 452 |
| 512 | 29 год 52 хв | 2 048 | 262 144 | 32 768 | 1 050 588 |
| 1024 | 59 год 44 хв | 4 096 | 1 048 576 | 65 536 | 2 000 860 |

Матриця уваги росте квадратом K, параметри — лінійно (через голову).

### Ручки навчання

| Ручка | Значення | Звідки |
| ----- | -------- | ------ |
| ширина | 64 | дослідження 17, `picked_2_3.json` |
| голови | 4 | там же |
| шари | 2 | там же |
| `dim_feedforward` | 256 (ширина × 4) | усталене для енкодера |
| dropout | 0.1 | `picked_2_3.json` |
| `norm_first` | `True` | усталене для енкодера |
| lr | 3e-4 | `picked_2_3.json` |
| оптимізатор | Adam | там же |
| батч | до 256, менший на довшому вікні | памʼять уваги: `батч × голови × K²` |
| seed | 0 | `train.SEED` |
| бюджет | 60 епох по 20 000 вікон **або** 40 хв | `train_all.EPOCHS`, `WINDOWS`, `MINUTES` |
| рання зупинка | немає: валідації немає | розбиття за датою |
| скейлер | немає: фічі вже безрозмірні `[-1, 1]` | правило невтрачання даних |
| K | перебір `128, 256, 512, 1024` | перебір |
| вибір K | найбільше збігів на краях на **test** | валідації немає |

### Дані

| Що | Значення |
| -- | -------- |
| фічі | `data/datasets/<PAIR>_fill/class210x210x4c/` — подія `(4,)` |
| ціна і сира ціль | `data/datasets/<PAIR>_fill/class210x4bema10x10x130/` |
| монет | 6 · подій 387 990, з ціллю 386 045 |
| ціль | зігзаг 2.3 % на `ема90` ціни, нога `розподіл`, згладжена `сернога` |
| зона півота | `\|y\| ≥ 0.80` і ±50 подій |
| розбиття | за датою: події до `10.08` — train, від `10.08` — test |

Ціль будується `correlate.data_of` — згладжування `сернога` (вікно
кожної ноги дорівнює її власній довжині, зони недоторкані), межі зон
`correlate.zone_mask(..., band=0.20, window=50)`.

### Правило невтрачання даних

**Жоден шар не може бути вужчим за те, що в нього заходить.** У таблиці
шарів це стовпець «стиснення»; у звіті — поле `keeps`, воно `false`
тільки там, де чисел на виході менше, ніж на вході.

Єдине допустиме стиснення — **остання голова**, бо відповідь скінченна.
Зчитування середнім (`mean`) втиснуло б вікно у 64 числа — при K = 128
це у 128 разів, тому воно не береться.

Правило перевіряється **конструктором**, а не коментарем: `width` менша
за кількість чисел кроку — помилка; зчитування, що стискає, за
замовчуванням не береться.

### Порядок робіт

1. Межі клітинок на цілі train — [`scripts/bins.py`](scripts/bins.py) →
   [`results/bins_2_3.json`](results/bins_2_3.json).
2. Мережа — [`scripts/net.py`](scripts/net.py): трансформер по осі
   подій, вікно `(K, 4)`, зчитування `flat`, голова
   `Linear(K · 64 → 28)`. Ваги збігаються з формулами цього розділу,
   це перевіряє тест.
3. Мʼяка розкладка цілі, ціна помилки й штраф —
   [`scripts/train_all.py`](scripts/train_all.py).
4. Перебір K, вибір кращого по збігах на краях на **test** — розділ 6.

## Розділ 6. Навчання на всіх монетах · `підтверджено`

[`scripts/train_all.py`](scripts/train_all.py) →
[`results/train_all_2_3.json`](results/train_all_2_3.json). Мережа
розділу 5 із виходом розділу 4, чотири довжини вікна, один прогін на
кожну.

### Дані

Усі шість пар із заповненими пропусками часу, той самий клас і та сама
ціль. Пари зшиваються в один ряд подія за подією; кожна подія памʼятає
свою монету, і вікно ніколи не переходить з монети на монету.

| Монета | Подій | train | test | Ніг | Зон | Подій у зонах |
| ------ | ----: | ----: | ---: | --: | --: | ------------: |
| BTC_USDT | 66 508 | 59 595 | 6 913 | 55 | 56 | 4 830 |
| ETH_USDT | 67 315 | 60 405 | 6 910 | 68 | 69 | 5 712 |
| SOL_USDT | 66 807 | 59 899 | 6 908 | 79 | 80 | 6 426 |
| XRP_USDT | 60 125 | 53 215 | 6 910 | 55 | 56 | 4 574 |
| BNB_USDT | 67 111 | 60 200 | 6 911 | 49 | 50 | 4 257 |
| DOGE_USDT | 60 124 | 53 214 | 6 910 | 63 | 64 | 5 118 |
| **разом** | **387 990** | **346 528** | **41 462** | **369** | **375** | **30 917** |

Період: `2026-02-14` … `2026-08-26` UTC для чотирьох пар, `2026-03-05` …
`2026-08-26` для `XRP` і `DOGE`.

### Пропуски

Дві речі, які раніше рвали видачу на клапті, тут не викидають нічого.

1. **Пропуск у фічі → нуль.** Подія, у якій фіча не порахувалась
   (ділення на нуль), заходить у мережу нулем: нуль тут і є «немає
   перекосу». Таких подій **49 038** з 387 990 — 12.6 %, і розподілені
   вони дуже нерівно:

   | Монета | Подій з пропуском | `price_balance` | `vol_balance` |
   | ------ | ----------------: | --------------: | ------------: |
   | BTC_USDT | 1 142 | 1 129 | 761 |
   | ETH_USDT | 3 133 | 3 114 | 886 |
   | BNB_USDT | 4 671 | 4 626 | 1 230 |
   | SOL_USDT | 8 040 | 8 017 | 1 283 |
   | XRP_USDT | 12 103 | 11 987 | 3 609 |
   | DOGE_USDT | 19 949 | 19 736 | 12 560 |

   Пропуск майже завжди в `price_balance`: у секунді не було угод, і
   ділити нема на що. Раніше одна така подія викидала **все вікно**, і
   на `BTC` при K = 128 з 34 444 вікон лишалось 14 139 — на `DOGE` не
   лишилось би майже нічого.
2. **Розрив у часі вікна не рве.** Вікно — це K подій **поспіль за
   номером**, а не за часом. Вимога рівних 210 с лишала найдовший
   суцільний відрізок у 411 подій (доба збору), тобто вікна 512 і 1024
   не існували б узагалі.

Разом це дає вікно на **кожній** події, у якої є ціль і K−1 попередніх
подій тієї самої монети по той самий бік межі.

### Розбиття

```
події < 10.08  ->  train  346 528        події >= 10.08  ->  test  41 462
```

Одна дата на всі монети, вікно цілком лежить по один її бік. Валідації
**немає**: навчання йде фіксований бюджет, ранньої зупинки нема, на
test модель не підглядає жодного разу. Через це і краще K вибирається
на test — міряти більше нема на чому, і це записано тут, а не сховано.

Бюджет однаковий **за часом**, а не за епохами: 60 епох по 20 000
випадкових вікон train, але не довше за 40 хвилин на одне K. Довше
вікно встигає менше епох — це і є його ціна.

### Штраф

До перехресної ентропії розділу 4 додається **ціна помилки в
клітинках** і **штраф за розмах** дослідження 12:

```
L = CE + Σ p·C[ціль] + WMSE + λ·(σ_цілі − σ_прогнозу)²

CE    = − Σ qᵢ · log softmax(z)ᵢ        мʼяка мітка розділу 4
C     = матриця цін, нижче
WMSE  = Σ w·(ŷ − y)² / Σ w,  w = 1 + α·|y|^β,  α = 3, β = 2
λ     = 1
```

`Σ p·C[ціль]` — **очікувана ціна**: ймовірність кожної клітинки
множиться на те, скільки коштує її сказати при цій цілі.

| Що сталося | Ціна |
| ---------- | ---: |
| сказала свою клітинку | 0 |
| звичайна помилка в середині | 1 |
| **прогавила край** — ціль 1 або 28, сказала середину | **3** |
| **хибний край** — ціль у середині, сказала 1 або 28 | **6** |
| **протилежний край** — сказала 1 замість 28 або навпаки | **12** |

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

### Метрики

Мірка одна, і тільки на краях. Сигнал моделі — клітинка з найбільшою
ймовірністю.

| Стовпець | Що це |
| -------- | ----- |
| у цілі | подій test, які справді в цій клітинці |
| сказала | скільки разів модель назвала цю клітинку |
| збіглось | і те, і те на одній події |
| промах | середня відстань у **подіях** від хибного сигналу до найближчого справжнього сигналу тієї самої клітинки в тій самій монеті |

На вкладці під видачею ŷ стоять ще два полотна — **смужки** `p₁` і
`p₂₈`, самі ймовірності країв на кожній події test. Сигнал береться
саме з них, тому видно не лише де модель сказала, а й наскільки
близько вона була сказати.

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

Чотири прогони, однаковий бюджет по годиннику (60 епох по 20 000 вікон
або 40 хв), однакові розбиття, штраф і seed. Різниця рівно одна —
довжина вікна.

| K | Ваг | Батч | Епох | Час | Вікон train | Вікон test |
| -: | -: | -: | -: | -: | -: | -: |
| **128** | 337 884 | 256 | 60 | 11.2 хв | 345 427 | 39 607 |
| 256 | 575 452 | 128 | 60 | 31.8 хв | 344 989 | 38 839 |
| 512 | 1 050 588 | 32 | 25 | 40.1 хв | 343 462 | 37 303 |
| 1024 | 2 000 860 | 16 | 7 | 41.7 хв | 340 390 | 34 231 |

Стелю часу першим дістав K = 512: 25 епох замість 60. K = 1024 встиг
7 — на батчі 16 епоха коштує близько 340 с.

Сигнали країв на test (41 462 події від 2026-08-10, шість монет):

| K | 1 · низ у цілі | сказала | збіглось | промах | 28 · верх у цілі | сказала | збіглось | промах |
| -: | -: | -: | -: | -: | -: | -: | -: | -: |
| **128** | 5 540 | 1 485 | **264** | 527.5 | 1 243 | 3 374 | **105** | 889.7 |
| 256 | 5 540 | 1 359 | 246 | 444.6 | 1 076 | 1 381 | 43 | 1 097.4 |
| 512 | 5 540 | 2 249 | 219 | 486.7 | 1 076 | 817 | 4 | 649.6 |
| 1024 | 5 221 | 100 | 0 | 709.5 | 874 | 56 | 0 | 823.3 |

«У цілі» меншає з довжиною вікна тому, що перші K − 1 подій кожної
монети вікна не мають: у K = 1024 з test випадає 7 231 вікно, разом з
ними — частина справжніх сигналів.

Краще K вибирається за сумою збігів на обох краях, при рівності — за
меншим промахом. Виграє **K = 128**: 264 + 105 = 369 збігів.

### Висновки

1. **Довше вікно не допомагає, а шкодить.** Збіги падають монотонно з
   довжиною: 369 → 289 → 223 → 0. Причина видна в стовпці «епох»:
   увага коштує `батч × голів × K²`, тому за ті самі 40 хв K = 1024
   бачить дані сім разів, а K = 128 — шістдесят. Довше вікно платить
   памʼяттю, а платити нема чим.
2. **Верхній край гірший за нижній на всіх K.** У цілі його втричі-вчетверо
   менше (1 076 проти 5 540), і збігів на ньому менше на порядок. При
   K = 512 їх лишається 4 — це вже нуль з точністю до випадковості.
3. **Модель не мовчить — вона промахується.** Сигналів вона дає стільки
   ж або більше, ніж їх є у цілі (K = 128 сказала «верх» 3 374 рази
   проти 1 243 справжніх). Ціна хибного краю 6 проти 3 за прогавлений
   край не змусила її мовчати, а перехресна ентропія з мʼякою міткою
   лишила краї живими — на відміну від виходу в одне число під MSE.
4. **Промах великий.** Найкращі 444 події — це близько 26 годин від
   хибного сигналу до найближчого справжнього сигналу тієї самої
   клітинки. На такій відстані сигнал непридатний для входу в позицію.
5. **Чотирьох фіч події замало.** Розділ 3 показав слабку кореляцію фіч
   із ціллю; навчання це підтверджує: 369 збігів на 6 783 справжні
   сигнали — 5.4 %. Довжина вікна цього не рятує, шукати треба в
   фічах, а не в K.

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

```bash
# розділи 1-3: згладжування, розподіли, кореляції
python3 research/18-smooth-zigzag-x4c/scripts/correlate.py --way сернога

# інший спосіб — тим самим прапорцем: сер500, сер210, сер120, ема60

# розділ 4: межі клітинок виходу з цілі train усіх монет
python3 research/18-smooth-zigzag-x4c/scripts/bins.py

# розділ 5: параметри мережі
python3 research/18-smooth-zigzag-x4c/scripts/net_spec.py

# розділ 6: навчання на всіх монетах, K = 128 … 1024 (близько 2 годин)
python3 research/18-smooth-zigzag-x4c/scripts/train_all.py

# інші довжини дописуються до готового звіту, без переучування решти
python3 research/18-smooth-zigzag-x4c/scripts/train_all.py --add --sizes 64

# інші ціни помилки — тими самими прапорцями
python3 research/18-smooth-zigzag-x4c/scripts/train_all.py \
    --miss 3 --false 6 --wrong 12

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

Потрібні готові `class210x210x4c/` і `class210x4bema10x10x130/` (звідти
беруться ціль і ціна) — для розділів 1–3 у `data/datasets/BTC_USDT/`, для
розділів 4–6 у `data/datasets/<PAIR>_fill/` **усіх шести пар** — як вони
будуються,
[`rules/01-data-and-db.md`](../../rules/01-data-and-db.md#базові-скрипти).
Нічого зовнішнього. Розділи 1–5 — близько 30 секунд, розділ 6 — близько
2 годин на `mps`.

## Файли

| Файл | Що |
| ---- | -- |
| [`scripts/correlate.py`](scripts/correlate.py) | збірка подій, згладжування цілі, зони, кореляції, розподіли |
| [`scripts/bins.py`](scripts/bins.py) | межі клітинок виходу і мʼяка мітка |
| [`scripts/net_spec.py`](scripts/net_spec.py) | параметри мережі: вхід, шари, ваги, правило невтрачання даних |
| [`scripts/net.py`](scripts/net.py) | сама мережа: трансформер по осі подій, вихід у клітинки |
| [`scripts/train_all.py`](scripts/train_all.py) | збірка монет, розбиття за датою, навчання чотирьох K, сигнали країв на test |
| [`results/correlation_2_3.json`](results/correlation_2_3.json) | кореляції, розподіли й ряди для вкладки |
| [`results/bins_2_3.json`](results/bins_2_3.json) | 28 клітинок: межі, центри, наповнення |
| [`results/net_2_3.json`](results/net_2_3.json) | мережа: шар за шаром, ваги, ручки |
| [`results/train_all_2_3.json`](results/train_all_2_3.json) | чотири прогони: сигнали країв і видача на test кожної монети |
| [`tests/test_smooth4c.py`](../../tests/test_smooth4c.py) | тести збірки, вирівнювання, кореляцій і клітинок |

## Вкладка

[Згладжений зігзаг і клас210х210х4ц](http://localhost:8080/smooth4c) —
шість розділів, ті самі, що в цьому файлі.

| Розділ вкладки | Що показує |
| -------------- | ---------- |
| 1. Згладжений зігзаг | ціна із зігзагом і **двома** зонами півотів — насиченою вузькою (`\|y\| ≥ 0.90`, ±40) і блідою ширшою (`\|y\| ≥ 0.80`, ±50); під нею сира ціль і згладжений `сернога` ряд на тій самій осі |
| 2. Розподіли фіч і цілі | пʼять гістограм: чотири фічі й ціль, сира заповненням і `сернога` обведенням |
| 3. Кореляція фіч із ціллю | дві таблиці (train і всі події), Пірсон і Спірмен, «лишилось звʼязку» |
| 4. Вихід у 28 клітинок | межі, центри й наповнення клітинок, мʼяка мітка, штраф і що читається з виходу |
| 5. Параметри трансформера | фічі й вхід, шар за шаром із стовпцем «стиснення», ціна довжини вікна, ручки навчання |
| 6. Навчання на всіх монетах | монети й розбиття за датою, ціни помилки, таблиця чотирьох прогонів із сигналами клітинок 1 і 28, перемикач K і на кожну монету пʼять полотен відрізка test — ціна із зігзагом, ціль, видача ŷ і дві смужкові — ймовірність клітинки 1 і клітинки 28 |

Дані вкладки: `/api/smooth4c`, `/api/smooth4c/bins`,
`/api/smooth4c/net` і `/api/smooth4c/train`
([`dashboard/app.py`](../../dashboard/app.py) → `SMOOTH4C_RESULT`,
`SMOOTH4C_BINS`, `SMOOTH4C_NET`, `SMOOTH4C_TRAIN`).
