Перейти к содержимому

Глава 328: Вариационный вывод для трейдинга

Обзор

Вариационный вывод (Variational Inference, VI) - это мощная техника для масштабируемого байесовского вывода, которая преобразует неразрешимые распределения вероятностей в задачи оптимизации. Вместо семплирования (как MCMC), VI находит наилучшее приближение к истинному апостериорному распределению из семейства более простых распределений. Это делает VI на порядки быстрее, сохраняя при этом оценку неопределённости - критическое преимущество для торговых приложений в реальном времени.

Почему вариационный вывод для трейдинга?

Проблема точечных оценок

Традиционные ML модели для трейдинга производят точечные оценки - одиночные предсказания без неопределённости:

  • Линейная регрессия: “Цена вырастет на 2%”
  • Нейронная сеть: “Вероятность роста 70%”
  • Случайный лес: “Сигнал на покупку”

Но рынки по своей природе неопределённы:

  • Что если модель уверена на 70% с высокой неопределённостью vs 70% с низкой неопределённостью?
  • Как должен меняться размер позиции в зависимости от неопределённости предсказания?
  • Когда следует воздержаться от торговли из-за неопределённости?

Решение вариационного вывода

VI предоставляет полные апостериорные распределения над предсказаниями:

Точечная оценка: E[r] = 2.5%
Вариационный вывод:
p(r|данные) ≈ N(μ=2.5%, σ=1.2%)
Это говорит нам:
- Ожидаемая доходность: 2.5%
- 68% доверительный интервал: [1.3%, 3.7%]
- 95% доверительный интервал: [0.1%, 4.9%]
- Вероятность положительной доходности: 98%

Теоретические основы

1. Нижняя граница правдоподобия (ELBO)

Ядро VI - максимизация ELBO (Evidence Lower Bound):

ELBO(q) = E_q[log p(x,z)] - E_q[log q(z)]
= E_q[log p(x|z)] - KL(q(z) || p(z))
где:
q(z) = приближённое апостериорное (то, что оптимизируем)
p(z) = априорное распределение
p(x|z) = правдоподобие
p(z|x) = истинное апостериорное (неразрешимо)

Интуиция: ELBO балансирует две цели:

  1. Член правдоподобия: Делать предсказания, соответствующие данным
  2. Член KL: Держать приближённое апостериорное близко к априорному (регуляризация)

2. KL-дивергенция

KL-дивергенция измеряет, насколько одно распределение отличается от другого:

KL(q || p) = E_q[log q(z) - log p(z)]
= ∫ q(z) log(q(z)/p(z)) dz
Свойства:
- KL(q || p) ≥ 0
- KL(q || p) = 0 тогда и только тогда, когда q = p
- НЕ симметрична: KL(q || p) ≠ KL(p || q)

Для гауссовских распределений:

KL(N(μ₁, σ₁²) || N(μ₂, σ₂²)) =
log(σ₂/σ₁) + (σ₁² + (μ₁-μ₂)²)/(2σ₂²) - 1/2

3. Приближение среднего поля (Mean-Field)

Простейший подход VI предполагает факторизованное апостериорное:

Истинное апостериорное: p(z₁, z₂, ..., zₙ | x) [сложные зависимости]
Среднее поле: q(z) = ∏ᵢ q(zᵢ) [независимые факторы]
Для трейдинга:
q(режим, доходности, волатильность) ≈ q(режим) × q(доходности) × q(волатильность)

4. Трюк репараметризации

Ключевая инновация для обучения VAE с градиентным спуском:

# Проблема: нельзя провести backprop через семплирование
z = sample(q(z|x)) # Недифференцируемо!
# Решение: репараметризация
ε ~ N(0, 1)
z = μ + σ × ε # Теперь дифференцируемо по μ, σ
# В коде:
class Reparameterize:
def forward(self, mu, log_var):
std = torch.exp(0.5 * log_var)
eps = torch.randn_like(std)
return mu + eps * std

Архитектура модели

Вариационный автоэнкодер для рыночных данных

┌─────────────────────────────────────────────────────────────────┐
│ АРХИТЕКТУРА РЫНОЧНОГО VAE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ВХОДНОЙ СЛОЙ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Рыночные признаки (на временной шаг): │ │
│ │ - OHLCV данные (нормализованные) │ │
│ │ - Технические индикаторы (RSI, MACD, BB) │ │
│ │ - Профиль объёма │ │
│ │ - Метрики потока ордеров │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ СЕТЬ ЭНКОДЕРА │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ LSTM/Transformer слои │ │
│ │ - Захватывает временные зависимости │ │
│ │ - Выходы: μ_z, log(σ²_z) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ЛАТЕНТНОЕ ПРОСТРАНСТВО (z) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ z = μ + σ × ε (трюк репараметризации) │ │
│ │ │ │
│ │ Латентные измерения представляют: │ │
│ │ - Режим рынка (бычий/медвежий/боковой) │ │
│ │ - Режим волатильности (низкий/средний/высокий) │ │
│ │ - Силу тренда │ │
│ │ - Склонность к возврату к среднему │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ СЕТЬ ДЕКОДЕРА │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Восстанавливает рыночные признаки из латентного кода │ │
│ │ - Предсказывает доходности следующего шага │ │
│ │ - Предсказывает волатильность │ │
│ │ - Выдаёт оценки неопределённости │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ВЫХОДНЫЕ ГОЛОВЫ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Реконструкция: p(x|z) - восстановленные признаки │ │
│ │ Доходности: μ_r, σ_r - распределение предсказанных │ │
│ │ Режим: p(режим|z) - вероятности режима рынка │ │
│ │ Уверенность: оценка неопределённости модели │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

Определение режима через латентное пространство

VAE учится организовывать латентное пространство по режимам рынка:

Визуализация латентного пространства:
Бычий рынок
* * *
* * * *
* * * * * * * *
* * * * * *
Боковой ← * * * * * * → Трендовый
* * * * * *
* * * * * * * *
* * * *
* * *
Медвежий рынок
Каждая точка представляет состояние рынка
Кластеризация возникает автоматически при обучении

Торговая стратегия

Генерация сигналов с неопределённостью

def generate_signals(vae_model, market_data):
# Кодируем текущее состояние рынка
mu, log_var = vae_model.encode(market_data)
# Семплируем несколько раз для оценки неопределённости
n_samples = 100
predictions = []
for _ in range(n_samples):
z = reparameterize(mu, log_var)
pred = vae_model.predict_returns(z)
predictions.append(pred)
# Агрегируем предсказания
mean_return = np.mean(predictions)
std_return = np.std(predictions)
# Вероятность положительной доходности
prob_positive = np.mean([p > 0 for p in predictions])
# Генерируем сигнал с уверенностью
if prob_positive > 0.7 and std_return < threshold:
return Signal("LONG", confidence=prob_positive, uncertainty=std_return)
elif prob_positive < 0.3 and std_return < threshold:
return Signal("SHORT", confidence=1-prob_positive, uncertainty=std_return)
else:
return Signal("HOLD", confidence=0.5, uncertainty=std_return)

Вариационная байесовская оптимизация портфеля

def variational_portfolio(expected_returns, uncertainties, risk_aversion=1.0):
"""
Оптимизация портфеля с учётом неопределённости параметров
Вместо: max w'μ - λ/2 w'Σw (стандартная mean-variance)
Используем: max E[w'r] - λ/2 Var[w'r] - κ * штраф_неопределённости
где штраф_неопределённости учитывает неопределённость модели
"""
n_assets = len(expected_returns)
# Строим ковариационную матрицу с инфляцией неопределённости
cov_matrix = estimate_covariance(returns)
# Раздуваем ковариацию на основе неопределённости предсказаний
uncertainty_matrix = np.diag(uncertainties ** 2)
adjusted_cov = cov_matrix + uncertainty_matrix
# Решаем оптимизацию портфеля
def objective(w):
portfolio_return = w @ expected_returns
portfolio_var = w @ adjusted_cov @ w
return -(portfolio_return - risk_aversion * portfolio_var)
# Ограничения: сумма весов = 1, без коротких продаж
constraints = [
{'type': 'eq', 'fun': lambda w: np.sum(w) - 1},
]
bounds = [(0, 1) for _ in range(n_assets)]
result = minimize(objective, x0=np.ones(n_assets)/n_assets,
constraints=constraints, bounds=bounds)
return result.x

Ключевые компоненты

1. Стохастический вариационный вывод

class StochasticVI:
"""
Мини-батч VI для больших датасетов
Вместо вычисления ELBO на всём датасете:
ELBO = Σᵢ E_q[log p(xᵢ|z)] - KL(q(z) || p(z))
Используем стохастические оценки:
ELBO ≈ (N/M) Σⱼ∈batch E_q[log p(xⱼ|z)] - KL(q(z) || p(z))
"""
def __init__(self, model, learning_rate=0.001):
self.model = model
self.optimizer = Adam(model.parameters(), lr=learning_rate)
def compute_elbo(self, x_batch, beta=1.0):
# Кодируем
mu, log_var = self.model.encode(x_batch)
# Репараметризуем
z = self.reparameterize(mu, log_var)
# Декодируем
x_recon = self.model.decode(z)
# Ошибка реконструкции (отрицательное log-правдоподобие)
recon_loss = F.mse_loss(x_recon, x_batch, reduction='sum')
# KL-дивергенция (аналитическая для гауссианов)
kl_loss = -0.5 * torch.sum(1 + log_var - mu.pow(2) - log_var.exp())
# ELBO = -recon_loss - beta * kl_loss
elbo = -recon_loss - beta * kl_loss
return elbo, recon_loss, kl_loss
def train_step(self, x_batch, beta=1.0):
self.optimizer.zero_grad()
elbo, recon_loss, kl_loss = self.compute_elbo(x_batch, beta)
loss = -elbo # Минимизируем отрицательный ELBO
loss.backward()
self.optimizer.step()
return loss.item()

2. Амортизированный вывод

class AmortizedEncoder(nn.Module):
"""
Вместо оптимизации q(z) для каждой точки данных отдельно,
обучаем единую сеть энкодера, которая отображает x → q(z|x)
Это амортизирует стоимость вывода:
- Обучение: O(N) для обучения энкодера
- Вывод: O(1) на новую точку данных
"""
def __init__(self, input_dim, latent_dim, hidden_dims=[256, 128]):
super().__init__()
layers = []
prev_dim = input_dim
for h_dim in hidden_dims:
layers.extend([
nn.Linear(prev_dim, h_dim),
nn.BatchNorm1d(h_dim),
nn.LeakyReLU(),
])
prev_dim = h_dim
self.encoder = nn.Sequential(*layers)
self.mu_layer = nn.Linear(prev_dim, latent_dim)
self.logvar_layer = nn.Linear(prev_dim, latent_dim)
def forward(self, x):
h = self.encoder(x)
mu = self.mu_layer(h)
log_var = self.logvar_layer(h)
return mu, log_var

3. Beta-VAE для разделённых представлений

class BetaVAE(nn.Module):
"""
Beta-VAE добавляет коэффициент β к члену KL:
L = E_q[log p(x|z)] - β * KL(q(z|x) || p(z))
β > 1: Более разделённые латентные факторы
β < 1: Лучшая реконструкция
Для трейдинга:
- Выше β: Более чёткое разделение режимов
- Ниже β: Более точная реконструкция цен
"""
def __init__(self, input_dim, latent_dim, beta=4.0):
super().__init__()
self.beta = beta
self.encoder = AmortizedEncoder(input_dim, latent_dim)
self.decoder = Decoder(latent_dim, input_dim)
def loss_function(self, x, x_recon, mu, log_var):
recon_loss = F.mse_loss(x_recon, x, reduction='sum')
kl_loss = -0.5 * torch.sum(1 + log_var - mu.pow(2) - log_var.exp())
return recon_loss + self.beta * kl_loss

Детали реализации

Требования к данным

Данные криптовалютного рынка:
├── OHLCV данные (минимум 1-минутное разрешение)
│ └── Множество активов (BTC, ETH, SOL, ...)
├── Технические индикаторы
│ ├── RSI, MACD, полосы Боллинджера
│ ├── Скользящие средние (SMA, EMA)
│ └── Метрики волатильности (ATR, историческая vol)
├── Признаки объёма
│ ├── Профиль объёма
│ ├── Соотношение buy/sell объёма
│ └── Отклонение от VWAP
└── Поток ордеров (опционально)
├── Дисбаланс order book
└── Метрики потока сделок
Предобработка:
├── Нормализация (z-score или min-max)
├── Стационарность (дифференцирование для цен)
├── Формирование последовательностей (скользящие окна)
└── Разбиение train/validation/test (временное)

Конфигурация обучения

model:
input_dim: 64 # Количество входных признаков
latent_dim: 16 # Размерность латентного пространства
hidden_dims: [256, 128, 64]
beta: 4.0 # Вес KL для разделения
dropout: 0.1
training:
batch_size: 128
learning_rate: 0.001
weight_decay: 0.0001
max_epochs: 200
early_stopping_patience: 20
# Отжиг beta (начинаем с реконструкции, постепенно добавляем KL)
beta_start: 0.0
beta_end: 4.0
beta_warmup_epochs: 50
data:
sequence_length: 60 # 1 час 1-минутных данных
prediction_horizon: 5 # На 5 минут вперёд
train_ratio: 0.7
val_ratio: 0.15
test_ratio: 0.15

Ключевые метрики

Производительность модели

  • ELBO: Нижняя граница правдоподобия (чем выше, тем лучше)
  • MSE реконструкции: Насколько хорошо модель восстанавливает входы
  • KL-дивергенция: Расстояние апостериорного от априорного
  • Качество обхода латентного пространства: Визуальный осмотр обученных факторов

Торговые показатели

  • Sharpe Ratio: Доходность с поправкой на риск (цель > 2.0)
  • Sortino Ratio: Доходность с поправкой на downside риск
  • Maximum Drawdown: Максимальная просадка
  • Win Rate: % прибыльных сделок
  • Калибровка: Соответствуют ли предсказанные вероятности реальности?

Преимущества вариационного вывода

АспектТочечные оценкиВариационный вывод
СкоростьБыстроБыстро (vs MCMC)
НеопределённостьНетПолное апостериорное
Размер позицииФиксированныйС учётом неопределённости
Определение режимаОтдельная модельВстроено (латентное пространство)
ПереобучениеВысокий рискРегуляризация априорным
Out-of-distributionНет детекцииДетектируется через KL

Сравнение с другими подходами

vs. MCMC (Марковские цепи Монте-Карло)

  • MCMC: Точное апостериорное, но медленно (часы для сложных моделей)
  • VI: Приближённое апостериорное, но быстро (минуты/секунды)

vs. Dropout неопределённость

  • Dropout: Легко реализовать, но ad-hoc неопределённость
  • VI: Принципиальная неопределённость с теоретическими гарантиями

vs. Ансамблевые методы

  • Ансамбль: Несколько моделей, высокие требования к памяти/вычислениям
  • VI: Одна модель, эффективный вывод

vs. Гауссовские процессы

  • GP: Точная неопределённость, но O(N³) сложность
  • VI: Масштабируется до миллионов точек данных

Продакшн соображения

Pipeline вывода:
├── Сбор данных (Bybit WebSocket)
│ └── Real-time OHLCV + индикаторы
├── Feature engineering
│ └── Вычисление входных признаков (нормализованных)
├── Инференс VAE
│ └── μ, σ = encoder(признаки)
│ └── Семплирование z несколько раз
├── Агрегация предсказаний
│ └── Среднее, std, доверительные интервалы
├── Генерация сигналов
│ └── Применение торговых правил с неопределённостью
└── Риск-менеджмент
└── Размер позиции на основе неопределённости
Бюджет задержки:
├── Сбор данных: ~10мс
├── Вычисление признаков: ~5мс
├── Forward pass VAE: ~2мс
├── Семплирование (100 сэмплов): ~10мс
├── Генерация сигналов: ~1мс
└── Итого: ~30мс

Структура директории

328_variational_inference_trading/
├── README.md # Английская версия
├── README.ru.md # Этот файл
├── readme.simple.md # Объяснение для начинающих
├── readme.simple.ru.md # Русская версия для начинающих
├── python/ # Реализация на Python
│ ├── requirements.txt
│ ├── config.py
│ ├── data_fetcher.py # Получение данных Bybit через CCXT
│ ├── vae_model.py # Реализация VAE
│ ├── trainer.py # Цикл обучения
│ ├── strategy.py # Торговая стратегия
│ ├── backtest.py # Движок бэктестинга
│ └── main.py # Главная точка входа
└── rust_variational/ # Реализация на Rust
├── Cargo.toml
├── src/
│ ├── lib.rs # Точка входа библиотеки
│ ├── api/ # Клиент Bybit API
│ ├── variational/ # Реализации VI
│ ├── features/ # Feature engineering
│ ├── strategy/ # Торговая стратегия
│ └── backtest/ # Движок бэктестинга
└── examples/
├── fetch_data.rs
├── train_vae.rs
└── live_signals.rs

Ссылки

  1. Variational Inference: A Review for Statisticians

  2. Auto-Encoding Variational Bayes

  3. beta-VAE: Learning Basic Visual Concepts with a Constrained Variational Framework

  4. Stochastic Variational Inference

  5. Deep Variational Portfolio

    • Применения VAE к оптимизации портфеля

Уровень сложности

Продвинутый - Требуется понимание:

  • Теории вероятностей (KL-дивергенция, вариационное исчисление)
  • Глубокого обучения (автоэнкодеры, обратное распространение)
  • Концепций байесовского вывода
  • PyTorch/тензорных операций
  • Финансовых временных рядов

Дисклеймер

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