Глава 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 балансирует две цели:
- Член правдоподобия: Делать предсказания, соответствующие данным
- Член 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/23. Приближение среднего поля (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_var3. 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Ссылки
-
Variational Inference: A Review for Statisticians
- https://arxiv.org/abs/1601.00670
- Blei, Kucukelbir, McAuliffe (2017)
-
Auto-Encoding Variational Bayes
- https://arxiv.org/abs/1312.6114
- Kingma & Welling (2014)
-
beta-VAE: Learning Basic Visual Concepts with a Constrained Variational Framework
- https://openreview.net/forum?id=Sy2fzU9gl
- Higgins et al. (2017)
-
Stochastic Variational Inference
- https://arxiv.org/abs/1206.7051
- Hoffman et al. (2013)
-
Deep Variational Portfolio
- Применения VAE к оптимизации портфеля
Уровень сложности
Продвинутый - Требуется понимание:
- Теории вероятностей (KL-дивергенция, вариационное исчисление)
- Глубокого обучения (автоэнкодеры, обратное распространение)
- Концепций байесовского вывода
- PyTorch/тензорных операций
- Финансовых временных рядов
Дисклеймер
Эта глава предназначена только для образовательных целей. Торговля криптовалютами несёт существенный риск. Описанные здесь стратегии не были проверены в реальной торговле и должны быть тщательно протестированы перед любым применением в реальном мире. Прошлые результаты не гарантируют будущих результатов.