Свёрточные нейросети умеют находить закономерности в данных, но на финансовых рынках легко принять особенности волатильности за сигнал и получить модель, которая хорошо работает только на исторических котировках. Разберём, как применять CNN к временным рядам и данным ордербука, избегать утечки будущего и проверять качество прогнозов, чтобы понять, когда свёрточные архитектуры действительно полезны в финансовом анализе. Изучить метод
Уровень сложностиСложный
Время на прочтение10 мин
Охват и читатели1.7K
Туториал
Привет, Хабр!
Меня зовут Андрей Бирюков. Я — независимый эксперт в области ИТ и ИБ, преподаю в учебных центрах и пишу статьи и книги.
Мы привыкли связывать сверточные нейросети (CNN) с задачами, относящимися к компьютерному зрению, например, к поиску лиц, классификации картинок или беспилотным авто.
Но сегодня те же архитектуры, которые когда‑то отличали кота от собаки на изображении, успешно предсказывают движение цен, находят устойчивые паттерны временных рядов и анализируют рыночные новости.
Умение «видеть» в хаосе котировок повторяющиеся структуры и извлекать эти полезные признаки в процессе обучения и есть суперсила сверточных сетей.
И сегодня мы поговорим об этом мощнейшем инструменте финансового анализа.
Псевдоизображения в финансахДля начала давайте рассмотрим классическое определение CNN. Свёрточные нейронные сети представляют собой класс нейронных сетей, который специализируется на обработке данных с пространственной структурой, прежде всего изображений и видео.
Их ключевая особенность — использование математической операции свёртки в одном или нескольких слоях, что позволяет эффективно извлекать визуальные признаки.

В целом свёрточные нейросети были спроектированы под три индуктивных смещения.
Локальность — важные признаки формируются в окрестности точки, а не по всему входу сразу.
Трансляционная инвариантность — один и тот же паттерн может появиться в любом месте входа.
Иерархия масштабов — простые признаки комбинируются в сложные по мере углубления.
Но в финансах эти смещения будут работать частично. Давайте разберём несколько конкретных примеров использования CNN. И начнём с рассмотрения временного ряда как «псевдоизображения» (1 D‑CNN).
Перед нами стоит задача классифицировать 60-минутные окна BTC‑USD, разбив их на три класса:
«Рост > +0.5%».
«Падение < −0.5%».
«Боковик».
В качестве горизонта мы будем рассматривать следующие 30 минут.
Здесь самое важное — определить, что нам нужно подать на вход. Использование сырых цен — это очень плохой вариант. Дело в том, что они нестационарны, то есть абсолютный уровень и волатильность дрейфуют во времени, и CNN выучивает не форму паттерна, а конкретные значения цены в тренировочном окне, из‑за чего трансляционная инвариантность свёртки превращается из полезного свойства в источник ложных срабатываний.
Вместо этого мы используем лог‑доходности и нормализацию:
import numpy as np
import pandas as pd
def build_features(df, window=60):
"""
df: DataFrame с колонками open, high, low, close, volume
Возвращает массив окон признаков и метки.
"""
# 1. Лог-доходности вместо цен (убираем нестационарность уровня)
df['ret'] = np.log(df['close'] / df['close'].shift(1))
df['hl_range'] = np.log(df['high'] / df['low'])
df['vol_z'] = (df['volume'] - df['volume'].rolling(200).mean()) / df['volume'].rolling(200).std()
# 2. Z-score по скользящему окну (адаптация к режиму волатильности)
for col in ['ret', 'hl_range', 'vol_z']:
roll = df[col].rolling(200)
df[f'{col}_z'] = (df[col] - roll.mean()) / (roll.std() + 1e-8)
features = df[['ret_z', 'hl_range_z', 'vol_z_z']].dropna().values
# 3. Метки: forward return через 30 шагов
fwd = np.log(df['close'].shift(-30) / df['close'])
labels = np.where(fwd > 0.005, 2, np.where(fwd < -0.005, 0, 1))
X, y = [], []
for i in range(window, len(features) - 30):
X.append(features[i - window:i].T) # (channels, time)
y.append(labels[i])
return np.array(X), np.array(y)АрхитектураПри реализации CNN у нас имеются некоторые подводные камни, на которые хотелось бы обратить особое внимание.
Так, нормализация считается на скользящем окне, а не по всему датасету, потому что иначе у нас будет утечка будущего (look‑ahead bias), то есть ситуация, когда модель при обучении или оценке использует информацию, которая фактически недоступна в момент реального принятия решения.
Также метки здесь считаются через shift(-30), то есть последние 30 строк должны быть отброшены, иначе тренируемся на будущем.
И в обязательном порядке должен использоваться Z‑score по волатильности, так как без него модель выучит «высокая волатильность = сигнал», а это режимный артефакт, а не паттерн.
import torch
import torch.nn as nn
class TemporalCNN(nn.Module):
def init(self, n_channels=3, n_classes=3):
super().__init__()
self.net = nn.Sequential(
nn.Conv1d(n_channels, 32, kernel_size=5, padding=2),
nn.BatchNorm1d(32),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Conv1d(32, 64, kernel_size=3, padding=1),
nn.BatchNorm1d(64),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Conv1d(64, 64, kernel_size=3, padding=1, dilation=2), # dilated — расширяем рецептивное поле
nn.ReLU(),
nn.AdaptiveAvgPool1d(1),
)
self.head = nn.Linear(64, n_classes)
def forward(self, x):
return self.head(self.net(x).squeeze(-1))В примере ниже функция строит схему кросс‑валидации для временных рядов, разбивая данные на последовательные блоки так, чтобы на каждом шаге обучение шло только на прошлом до train_end, а тест — на следующем блоке после небольшого зазора embargo.
Причём между концом обучения и началом теста не остаётся пересечений, так как этот зазор нужен, чтобы метки обучающих примеров (которые считаются по будущим значениям, например forward return через 30 шагов) не «подглядывали» в тестовый период, а двигающееся окно обучения эмулировало реальное переобучение модели по мере поступления новых данных.
def walk_forward_splits(n_samples, n_splits=5, embargo=30):
"""Purged walk-forward CV (López de Prado)."""
fold_size = n_samples // (n_splits + 1)
for k in range(n_splits):
train_end = fold_size * (k + 1)
test_start = train_end + embargo
test_end = min(test_start + fold_size, n_samples)
if test_end <= test_start:
break
yield np.arange(0, train_end), np.arange(test_start, test_end)2 D‑представления рядаГлавный недостаток подхода 1 D‑CNN на финансовых рядах — это ограниченное рецептивное поле и слабая работа с дальними зависимостями, так как свёртка видит только локальную окрестность. Поэтому «эхо» событий спустя сотни шагов, долгую память и режимные сдвиги она улавливает плохо.
В качестве альтернативы мы рассмотрим популярные способы превратить 1 D‑ряд в «картинку».

Начнём с STFT, или спектрограммы.
Плюс такого подхода в том, что мы физически осмысленно можем ловить смену частот (ускорение/замедление трендов, кластеры волатильности).
Минус — необходимость вручную подбирать фиксированное разрешение по частоте и времени (принцип неопределённости). Соответственно, параметр nperseg подбираем под горизонт.
import scipy.signal as ss
def stft_image(series, nperseg=32, noverlap=24):
f, t, Z = ss.stft(series, nperseg=nperseg, noverlap=noverlap)
return np.abs(Z) # (freq_bins, time_bins)Multi‑asset correlation map хорошо ловит режимные сдвиги (risk‑on/risk‑off), что бывает реально полезно для портфельных задач и для BTC как рискового актива. При этом он не работает на одном активе, а данные нужно синхронизировать.
def corr_image(returns_df, window=60):
"""Матрица корреляций по окну — 'картинка' кросс-активных связей."""
return returns_df.iloc[-window:].corr().valuesТаким образом, выбор подхода зависит от задачи:
Для одного актива с коротким горизонтом используем 1 D‑CNN на нормализованных доходностях.
В случае сильной волатильности применяем STFT + CNN.
Для портфельных и кросс‑активных сигналов нужны correlation maps + CNN или GNN.
Использование превращения 1 D‑ряда в «картинку» плохо тем, что оно чаще всего приносит сложность без добавления информации. Например, STFT навязывает фиксированное разрешение по времени и частоте.
В качестве альтернативы можно рассмотреть метод GAF (Gramian Angular Field), в котором мы будем кодировать одномерный временной ряд в двумерное изображение. Идея в том, что исходный ряд сначала нормализуется, затем переводится в полярные координаты (угол кодирует значение, радиус — время), после чего попарные отношения между точками ряда формируют матрицу‑«картинку».

Это самый недооценённый сценарий, так как вместо того чтобы предсказывать цену CNN‑ом, мы используем его как энкодер признаков, а решение принимает градиентный бустинг.
import lightgbm as lgb
from sklearn.metrics import roc_auc_score
# Обучаем CNN на вспомогательной задаче (например, предсказание знака forward return)
cnn = TemporalCNN().cuda()
# ... обучение ...
# Извлекаем эмбеддинги
cnn.eval()
embeddings = []
with torch.no_grad():
for batch in loader:
emb = cnn.net(batch.cuda()).squeeze(-1).cpu().numpy()
embeddings.append(emb)
X_emb = np.vstack(embeddings) # (n, 64)Дальше мы можем использовать стандартный пайплайн: LightGBM + walk‑forward + калибровка вероятностей.
Этот алгоритм работает потому, что CNN выучивает локальные паттерны, которые трудно захардкодить. Также бустинг устойчив к шуму, не переобучается на 64 признаках так, как CNN на сыром ряде.
На практике такая гибридная схема на BTC‑данных стабильно даёт Sharpe выше, чем end‑to‑end CNN, и, что важнее, устойчивее к смене режима.
Детектор аномалий / микроструктурных сигналовА вот здесь CNN будет ближе всего к «честному» компьютерному зрению. Здесь наша задача заключается в том, чтобы найти на «карте» ордербука локальные структуры, такие как стенки, iceberg‑паттерны или спуфинг.

Для практической реализации нам потребуется следующий код:
class OrderBookCNN(nn.Module):
"""Вход: (batch, 1, price_levels, time_steps) — 2D-снимок стакана."""
def init(self):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(1, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
nn.Conv2d(16, 32, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
nn.AdaptiveAvgPool2d(1),
)
self.head = nn.Linear(32, 2) # аномалия / норма
def forward(self, x):
return self.head(self.features(x).flatten(1))Здесь аналогия с CV работает почти буквально, так как есть:
Локальность по уровням цены и по времени.
Трансляционная инвариантность по уровням (стенка на 5-м уровне = стенка на 15-м).
Иерархия «тик → паттерн → режим».
Для построения пайплайна с использованием CNN в финансовом анализе необходимо начать с baseline без CNN, то есть использовать логистическую регрессию или LightGBM на инженерных признаках (доходности, волатильность, RSI, режимные индикаторы).
Затем добавьте 1 D‑CNN на нормализованных доходностях как отдельную модель и сравните с baseline на purge/embargo CV.
Если CNN выигрывает, то используйте её как энкодер признаков, а финальное решение отдайте бустингу. Ну а 2 D‑представления (STFT, correlation maps) добавляйте только в том случае, если у вас есть гипотеза, что именно мультимасштабная или кросс‑активная структура несёт сигнал. Иначе это будет усложнением без выигрыша.
Заключительный тезис заключается в том, что сила CNN в финансах — не в «предсказании курса биткоина», а в извлечении локальных мультимасштабных признаков, которые потом используются в более устойчивых моделях.
Свёрточное зрение здесь действительно работает, но как компонент пайплайна, а не как волшебная кнопка.

Когда анализируешь финансовые рынки с помощью CNN, важно не просто найти закономерности в котировках, но и понять, как превратить их в торговые сигналы и проверить их практическую ценность.
На бесплатных вебинарах от преподавателей Otus вы проследите путь от распознавания паттернов в данных до принятия торговых решений с помощью нейросетей — чтобы использовать машинное обучение для решения реальных задач финансового анализа.
27 октября, 20:00. «Свёрточные сети: от распознавания изображений до прогнозирования курса биткоина». Записаться
18 ноября, 20:00. «Нейросети в алгоритмической торговле — учим модель принимать торговые решения». Записаться
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | Свёрточные сети в финансовом анализе: от компьютерного зрения к прогнозированию рынков | 0 | 9.06 | 23-10-2026 |
| 2 | Ваш бэктест нейронки врёт на ±10 процентных пунктов | 0 | 8.99 | 12-08-2026 |
| 3 | Тернарная нейронка на C# | 0 | 9 | 13-09-2026 |
| 4 | Почему нейросеть ломает перспективу: FLUX 2 024 года обошёл Nano Banana 2.1 и GPT Image 2.5 | 0 | 17.05 | 21-10-2026 |
| 5 | Торговля по паттернам с точки зрения алгоритмов | 0 | 8.21 | 01-08-2026 |
| 6 | Нейросеть-автопилот вместо 400 Playwright-тестов | 0 | 7 | 07-07-2026 |
| 7 | Разработка метода прогнозирования стоимости бизнеса публичных компаний в рамках сравнительного подхода с использованием искусственного интеллекта | 0 | 4.34 | 01-01-1970 |
| 8 | Делаем сайт из картинки в нейронке | 0 | 10.77 | 28-05-2026 |
| 9 | Книга: «Анализ данных с LLM. Текст, таблицы, изображения и аудио» | 0 | 7.03 | 23-06-2026 |
| 10 | Как я первый в Казахстане измерил ИИ видимость банков (и что из этого вышло) | 0 | 8.08 | 12-08-2026 |