Глава 344: Графовые Сети Внимания для Трейдинга
Графовые сети внимания (Graph Attention Networks, GAT) представляют собой прорыв в применении глубокого обучения к данным с графовой структурой. На финансовых рынках активы не существуют изолированно — они взаимосвязаны через секторальные отношения, цепочки поставок, корреляции и рыночную динамику. GAT используют эти связи через механизмы внимания, которые обучаются динамически взвешивать важность различных отношений.
Содержание
- Введение в графовые сети внимания
- Математические основы
- Архитектура GAT для трейдинга
- Детали реализации
- Применение в трейдинге
- Бэктестинг и оценка
- Ресурсы и ссылки
Введение в графовые сети внимания
Почему графы для финансов?
Финансовые рынки по своей природе являются реляционными. Рассмотрим экосистему криптовалют:
- Движения Bitcoin влияют почти на все альткоины
- Смарт-контракты Ethereum связывают DeFi-токены
- Стейблкоины служат мостами ликвидности между активами
- Токены бирж отражают здоровье платформы
- Решения второго уровня связаны с их базовыми блокчейнами
Традиционное машинное обучение рассматривает каждый актив независимо, упуская эти критические связи. Графовые нейронные сети (GNN) явно захватывают эти отношения, представляя:
- Узлы: Отдельные активы (BTC, ETH, SOL и т.д.)
- Рёбра: Отношения (корреляция, причинность, принадлежность к сектору)
- Признаки: Ценовые данные, объём, технические индикаторы
BTCUSDT ←────────────→ ETHUSDT ↑ ↑ │ │ ↓ ↓ SOLUSDT ←──────────→ AVAXUSDT ↑ ↑ │ USDTUSDC │ └──────────────────────┘От GNN к GAT
Стандартные графовые нейронные сети агрегируют информацию от соседей равномерно:
h_i = σ(W · MEAN({h_j : j ∈ N(i)}))Это рассматривает всех соседей одинаково — но в финансах одни отношения важнее других:
- Во время обвала рынка влияние BTC на альткоины резко возрастает
- Секторальные новости больше влияют на связанные токены
- Корреляционные режимы меняются между бычьим и медвежьим рынками
Графовые сети внимания решают эту проблему, обучая динамические веса для каждого ребра:
h_i = σ(Σ α_ij · W · h_j) j∈N(i)Где α_ij — обученный вес внимания между узлами i и j.
Механизм внимания
Механизм внимания в GAT работает следующим образом:
-
Линейное преобразование: Проецируем признаки узла в общее пространство
z_i = W · h_i -
Оценки внимания: Вычисляем сырое внимание для каждого ребра
e_ij = LeakyReLU(a^T · [z_i || z_j]) -
Нормализация: Применяем softmax по соседям
α_ij = softmax_j(e_ij) = exp(e_ij) / Σ_k exp(e_ik) -
Агрегация: Взвешенная сумма признаков соседей
h'_i = σ(Σ α_ij · z_j)
Ключевой инсайт: веса внимания вычисляются динамически на основе текущих состояний узлов, что позволяет сети адаптироваться к изменяющимся рыночным условиям.
Математические основы
Представление графа
Финансовый граф G = (V, E, X) состоит из:
- V: Множество из N узлов (активов)
- E: Множество рёбер (отношений)
- X ∈ R^(N×F): Матрица признаков узлов с F признаками на узел
Для криптовалютного трейдинга мы обычно строим рёбра на основе:
- На основе корреляции: Соединяем активы с |корреляция| > порог
- На основе сектора: Соединяем активы одной категории (DeFi, L1, мем-коины)
- k-NN граф: Соединяем каждый актив с k наиболее коррелированными
- Полносвязный: Все активы связаны (внимание обучает разреженность)
Коэффициенты внимания
Для узлов i и j вычисление коэффициента внимания:
# Шаг 1: Линейное преобразованиеz_i = W @ h_i # Размерность: (d', )z_j = W @ h_j # Размерность: (d', )
# Шаг 2: Конкатенация и применение вектора вниманияe_ij = LeakyReLU(a.T @ concat(z_i, z_j)) # Скаляр
# Шаг 3: Нормализация по всем соседямalpha_ij = softmax([e_ij for j in neighbors(i)])Ключевые свойства:
- Внимание асимметрично: α_ij ≠ α_ji (BTC влияет на ETH иначе, чем ETH на BTC)
- Внимание нормализовано: Σ_j α_ij = 1 (выпуклая комбинация)
- Внимание динамично: Меняется на основе входных признаков
Многоголовое внимание
Одна голова внимания может иметь ограниченную выразительность. Многоголовое внимание запускает K независимых механизмов внимания:
h'_i = ||_{k=1}^{K} σ(Σ α_ij^k · W^k · h_j)Для последнего слоя обычно усредняем вместо конкатенации:
h'_i = σ(1/K Σ_{k=1}^{K} Σ α_ij^k · W^k · h_j)Преимущества:
- Разнообразие: Разные головы захватывают разные типы отношений
- Стабильность: Уменьшает дисперсию в оценках внимания
- Ёмкость: Увеличивает выразительность модели
Фреймворк передачи сообщений
GAT следует общей парадигме передачи сообщений:
m_ij = MESSAGE(h_i, h_j, e_ij) # Вычислить сообщение от j к iM_i = AGGREGATE({m_ij : j ∈ N(i)}) # Агрегировать сообщенияh'_i = UPDATE(h_i, M_i) # Обновить состояние узлаВ GAT конкретно:
- MESSAGE: α_ij · W · h_j
- AGGREGATE: Взвешенная сумма
- UPDATE: Нелинейная активация (ELU/ReLU)
Архитектура GAT для трейдинга
Построение финансовых графов
Графы на основе корреляции
/// Построение матрицы смежности по порогу корреляцииfn build_correlation_graph(returns: &Array2<f64>, threshold: f64) -> Array2<f64> { let n = returns.ncols(); let mut adj = Array2::zeros((n, n));
for i in 0..n { for j in 0..n { if i != j { let corr = pearson_correlation( returns.column(i), returns.column(j) ); if corr.abs() > threshold { adj[[i, j]] = 1.0; } } } } adj}Графы на основе секторов
Для криптовалютных рынков мы определяем секторы:
- Layer 1: BTC, ETH, SOL, AVAX, NEAR
- DeFi: UNI, AAVE, COMP, MKR, CRV
- Layer 2: MATIC, ARB, OP, IMX
- Мемы: DOGE, SHIB, PEPE, BONK
- AI/Compute: FET, RNDR, AGIX
Динамическое построение графа
Рынки эволюционируют — статические графы устаревают. Динамические подходы:
- Скользящая корреляция: Пересчёт корреляций на скользящем окне
- Определение режима: Разные графы для разных состояний рынка
- Разрежение на основе внимания: Внимание обучается, какие рёбра важны
Признаки узлов для активов
Для каждого актива вычисляем векторы признаков, включающие:
Признаки на основе цены:
- Доходность (1ч, 4ч, 24ч, 7д)
- Волатильность (скользящее стандартное отклонение)
- Цена относительно скользящих средних
- RSI, MACD сигналы
Признаки на основе объёма:
- Коэффициенты изменения объёма
- Взвешенные по объёму ценовые тренды
- Дисбаланс объёма покупок/продаж
Признаки структуры рынка:
- Спред bid-ask
- Дисбаланс книги ордеров
- Интенсивность торгов
Кросс-активные признаки:
- Бета к BTC
- Моментум сектора
- Относительная сила
Признаки рёбер и связи
GAT может включать признаки рёбер для более богатого моделирования:
/// Ребро с признакамиstruct Edge { source: usize, target: usize, features: Vec<f64>, // корреляция, совпадение сектора и т.д.}
/// Внимание с признаками рёберfn compute_attention_with_edges( z_i: &Array1<f64>, z_j: &Array1<f64>, e_ij: &Array1<f64>, attention_weights: &Array1<f64>) -> f64 { let concat = concatenate![ Axis(0), z_i.view(), z_j.view(), e_ij.view() ]; leaky_relu(attention_weights.dot(&concat))}Временное графовое внимание
Финансовые данные временные — нужно внимание с учётом времени:
Временное кодирование:
fn temporal_encoding(timestamp: i64, dim: usize) -> Array1<f64> { let mut encoding = Array1::zeros(dim); for i in 0..dim/2 { let freq = 1.0 / (10000_f64.powf(2.0 * i as f64 / dim as f64)); encoding[2*i] = (timestamp as f64 * freq).sin(); encoding[2*i + 1] = (timestamp as f64 * freq).cos(); } encoding}Обработка последовательностей:
- Обработка каждого временного шага с GAT
- Агрегация временной информации с LSTM/Transformer
- Прогнозирование будущих доходностей/сигналов
Детали реализации
Реализация на Rust
Наша реализация фокусируется на эффективности и обработке в реальном времени:
/// Слой графового вниманияpub struct GraphAttentionLayer { /// Матрица весов для линейного преобразования weights: Array2<f64>, /// Вектор внимания attention: Array1<f64>, /// Количество голов внимания num_heads: usize, /// Коэффициент dropout dropout: f64, /// Отрицательный наклон для LeakyReLU negative_slope: f64,}
impl GraphAttentionLayer { pub fn forward( &self, node_features: &Array2<f64>, adjacency: &Array2<f64>, ) -> Array2<f64> { let n = node_features.nrows();
// Линейное преобразование let z = node_features.dot(&self.weights);
// Вычисление внимания для всех рёбер let mut attention_scores = Array2::zeros((n, n)); for i in 0..n { for j in 0..n { if adjacency[[i, j]] > 0.0 { let concat = concatenate![ Axis(0), z.row(i), z.row(j) ]; attention_scores[[i, j]] = leaky_relu( self.attention.dot(&concat), self.negative_slope ); } } }
// Softmax нормализация let attention_weights = softmax_rows(&attention_scores, adjacency);
// Агрегация let output = attention_weights.dot(&z);
// Применение активации output.mapv(|x| elu(x)) }}Эффективные разреженные операции
Финансовые графы часто разреженные. Используем CSR формат:
/// Сжатое представление разреженных строкpub struct SparseGraph { /// Указатели строк indptr: Vec<usize>, /// Индексы столбцов indices: Vec<usize>, /// Значения рёбер data: Vec<f64>, /// Количество узлов n_nodes: usize,}
impl SparseGraph { /// Эффективное вычисление разреженного внимания pub fn sparse_attention_forward( &self, node_features: &Array2<f64>, attention_layer: &GraphAttentionLayer, ) -> Array2<f64> { let z = node_features.dot(&attention_layer.weights); let mut output = Array2::zeros(z.dim());
for i in 0..self.n_nodes { let start = self.indptr[i]; let end = self.indptr[i + 1];
if start == end { continue; }
// Вычисляем внимание только для существующих рёбер let neighbors: Vec<usize> = self.indices[start..end].to_vec(); let mut scores: Vec<f64> = neighbors.iter() .map(|&j| { let concat = concatenate![ Axis(0), z.row(i), z.row(j) ]; leaky_relu( attention_layer.attention.dot(&concat), attention_layer.negative_slope ) }) .collect();
// Softmax let max_score = scores.iter().cloned().fold(f64::NEG_INFINITY, f64::max); let exp_scores: Vec<f64> = scores.iter() .map(|&s| (s - max_score).exp()) .collect(); let sum: f64 = exp_scores.iter().sum(); let attention: Vec<f64> = exp_scores.iter() .map(|&e| e / sum) .collect();
// Агрегация for (idx, &j) in neighbors.iter().enumerate() { output.row_mut(i).scaled_add(attention[idx], &z.row(j)); } }
output.mapv(|x| elu(x)) }}Обработка в реальном времени
Для живого трейдинга нужны эффективные инкрементальные обновления:
/// Инкрементальный GAT для потоковых данныхpub struct StreamingGAT { layer: GraphAttentionLayer, graph: SparseGraph, cached_embeddings: Array2<f64>, update_queue: VecDeque<(usize, Array1<f64>)>,}
impl StreamingGAT { /// Обновление одного узла и распространение изменений pub fn update_node(&mut self, node_id: usize, new_features: Array1<f64>) { // Обновляем кэш self.cached_embeddings.row_mut(node_id).assign(&new_features);
// Пересчитываем затронутые узлы (1-hop соседи) let neighbors = self.graph.get_neighbors(node_id); for &neighbor in &neighbors { self.update_queue.push_back((neighbor, self.compute_node(neighbor))); } }
fn compute_node(&self, node_id: usize) -> Array1<f64> { // Пересчёт эмбеддинга одного узла let neighbors = self.graph.get_neighbors(node_id); // ... вычисление внимания для одного узла unimplemented!() }}Применение в трейдинге
Распространение сигналов между активами
GAT естественно распространяет сигналы по графу активов:
/// Распространение торговых сигналов через графpub fn propagate_signals( gat: &GraphAttentionNetwork, initial_signals: &Array1<f64>, // Сигналы по активам graph: &SparseGraph,) -> Array1<f64> { // Используем сигналы как признаки узлов let features = initial_signals.insert_axis(Axis(1));
// Прямой проход распространяет информацию let propagated = gat.forward(&features, graph);
// Извлекаем распространённые сигналы propagated.column(0).to_owned()}Примеры использования:
- Моментум сектора: Бычий сигнал в ETH распространяется на DeFi-токены
- Обнаружение заражения: Стресс в одном активе распространяется на коррелированные
- Отношения опережение-отставание: Движения BTC предсказывают направление альткоинов
Оптимизация портфеля
GAT предоставляет реляционный контекст для построения портфеля:
/// Оптимизация портфеля с использованием GATpub struct GATPortfolio { gat: GraphAttentionNetwork, graph: SparseGraph,}
impl GATPortfolio { /// Вычисление весов портфеля с использованием эмбеддингов GAT pub fn optimize( &self, features: &Array2<f64>, risk_aversion: f64, ) -> Array1<f64> { // Получаем GAT эмбеддинги let embeddings = self.gat.forward(features, &self.graph);
// Веса внимания раскрывают связи между активами let attention = self.gat.get_attention_weights();
// Используем внимание для оценки ковариации let enhanced_cov = self.estimate_covariance(&embeddings, &attention);
// Оптимизация среднее-дисперсия с графовой регуляризацией let expected_returns = self.predict_returns(&embeddings);
mean_variance_optimize( &expected_returns, &enhanced_cov, risk_aversion ) }}Обнаружение распространения рисков
Мониторинг распространения стресса через сеть:
/// Обнаружение распространения рисков через динамику вниманияpub fn detect_contagion( gat: &GraphAttentionNetwork, features_t0: &Array2<f64>, features_t1: &Array2<f64>, graph: &SparseGraph,) -> Vec<ContagionEvent> { let attention_t0 = gat.compute_attention(features_t0, graph); let attention_t1 = gat.compute_attention(features_t1, graph);
let mut events = Vec::new();
// Находим значительные изменения внимания for i in 0..attention_t0.nrows() { for j in 0..attention_t0.ncols() { let delta = attention_t1[[i, j]] - attention_t0[[i, j]]; if delta.abs() > CONTAGION_THRESHOLD { events.push(ContagionEvent { source: j, target: i, intensity: delta, timestamp: chrono::Utc::now(), }); } } }
events}Бэктестинг и оценка
Метрики производительности
| Метрика | Описание | Формула |
|---|---|---|
| Коэффициент Шарпа | Доходность с поправкой на риск | (R - Rf) / σ |
| Коэффициент Сортино | Доходность с поправкой на падения | (R - Rf) / σ_down |
| Максимальная просадка | Наибольшее снижение от пика | max(пик - впадина) / пик |
| Коэффициент Кальмара | Доходность / Макс. просадка | Годовая доходность / MDD |
| Доля выигрышей | Процент прибыльных сделок | Выигрыши / Всего сделок |
| Профит-фактор | Валовая прибыль / Валовой убыток | Σ(выигрыши) / Σ(убытки) |
Анализ криптовалютного рынка
Наша реализация GAT тестируется на бессрочных фьючерсах Bybit:
Набор данных:
- Активы: BTC, ETH, SOL, AVAX, NEAR, MATIC, ARB, DOGE, LINK, UNI
- Таймфрейм: часовые свечи
- Период: 2023-2024
- Признаки: OHLCV + технические индикаторы
Результаты (симуляционный бэктест):
| Стратегия | Шарп | Сортино | Макс. просадка | Доля выигр. |
|---|---|---|---|---|
| Держать BTC | 0.82 | 1.15 | -35.2% | - |
| Одноактивный ML | 1.21 | 1.58 | -22.4% | 54.3% |
| GAT кросс-активный | 1.67 | 2.23 | -15.8% | 58.7% |
| GAT + режим внимания | 1.89 | 2.61 | -12.3% | 61.2% |
Ключевые выводы:
- GAT захватывает кросс-активную динамику, улучшая прогнозы
- Веса внимания адаптируются к изменениям рыночного режима
- Многоголовое внимание захватывает разные типы отношений
- Динамическое построение графа превосходит статические графы
Ресурсы и ссылки
Ключевые статьи
-
Graph Attention Networks
- Авторы: Veličković, P., Cucurull, G., Casanova, A., Romero, A., Liò, P., & Bengio, Y.
- URL: https://arxiv.org/abs/1710.10903
- Год: 2017
- Основополагающая статья, вводящая архитектуру GAT
-
Temporal Graph Networks for Deep Learning on Dynamic Graphs
- Авторы: Rossi, E., Chamberlain, B., Frasca, F., Eynard, D., Monti, F., & Bronstein, M.
- URL: https://arxiv.org/abs/2006.10637
- Год: 2020
- Расширение графовых сетей на временные данные
-
Graph Neural Networks for Financial Predictions
- Различные применения в прогнозировании акций, выявлении мошенничества, оптимизации портфеля
Связанные главы
- Глава 340: Графовые нейронные сети для финансов
- Глава 341: GraphSAGE для анализа портфеля
- Глава 342: Графовые свёрточные сети для трейдинга
- Глава 343: Динамические графовые сети для трейдинга
Библиотеки и инструменты
Rust:
ndarray: N-мерные массивы для численных вычисленийpetgraph: Структуры данных графовsprs: Операции с разреженными матрицамиreqwest: HTTP-клиент для API-запросов
Python (для сравнения):
- PyTorch Geometric (PyG)
- Deep Graph Library (DGL)
- NetworkX
Структура проекта
344_graph_attention_trading/├── README.md # Этот файл (английский)├── README.ru.md # Русский перевод├── README.simple.md # Упрощённое объяснение├── README.simple.ru.md # Упрощённое объяснение (русский)├── README.specify.md # Техническое задание└── rust/ ├── Cargo.toml # Зависимости Rust ├── src/ │ ├── lib.rs # Корень библиотеки │ ├── main.rs # Точка входа CLI │ ├── api/ # Клиент API Bybit │ │ ├── mod.rs │ │ └── bybit.rs │ ├── graph/ # Структуры графов │ │ ├── mod.rs │ │ ├── sparse.rs │ │ └── builder.rs │ ├── gat/ # Реализация GAT │ │ ├── mod.rs │ │ ├── layer.rs │ │ ├── attention.rs │ │ └── network.rs │ ├── features/ # Инженерия признаков │ │ ├── mod.rs │ │ └── technical.rs │ ├── trading/ # Торговая стратегия │ │ ├── mod.rs │ │ ├── signals.rs │ │ └── portfolio.rs │ └── backtest/ # Бэктестинг │ ├── mod.rs │ └── metrics.rs └── examples/ ├── fetch_data.rs # Получение данных Bybit ├── build_graph.rs # Построение графа активов ├── train_gat.rs # Обучение модели GAT └── backtest.rs # Запуск бэктеста