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

Глава 344: Графовые Сети Внимания для Трейдинга

Графовые сети внимания (Graph Attention Networks, GAT) представляют собой прорыв в применении глубокого обучения к данным с графовой структурой. На финансовых рынках активы не существуют изолированно — они взаимосвязаны через секторальные отношения, цепочки поставок, корреляции и рыночную динамику. GAT используют эти связи через механизмы внимания, которые обучаются динамически взвешивать важность различных отношений.

Содержание

  1. Введение в графовые сети внимания
  2. Математические основы
  3. Архитектура GAT для трейдинга
  4. Детали реализации
  5. Применение в трейдинге
  6. Бэктестинг и оценка
  7. Ресурсы и ссылки

Введение в графовые сети внимания

Почему графы для финансов?

Финансовые рынки по своей природе являются реляционными. Рассмотрим экосистему криптовалют:

  • Движения 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 работает следующим образом:

  1. Линейное преобразование: Проецируем признаки узла в общее пространство

    z_i = W · h_i
  2. Оценки внимания: Вычисляем сырое внимание для каждого ребра

    e_ij = LeakyReLU(a^T · [z_i || z_j])
  3. Нормализация: Применяем softmax по соседям

    α_ij = softmax_j(e_ij) = exp(e_ij) / Σ_k exp(e_ik)
  4. Агрегация: Взвешенная сумма признаков соседей

    h'_i = σ(Σ α_ij · z_j)

Ключевой инсайт: веса внимания вычисляются динамически на основе текущих состояний узлов, что позволяет сети адаптироваться к изменяющимся рыночным условиям.


Математические основы

Представление графа

Финансовый граф G = (V, E, X) состоит из:

  • V: Множество из N узлов (активов)
  • E: Множество рёбер (отношений)
  • X ∈ R^(N×F): Матрица признаков узлов с F признаками на узел

Для криптовалютного трейдинга мы обычно строим рёбра на основе:

  1. На основе корреляции: Соединяем активы с |корреляция| > порог
  2. На основе сектора: Соединяем активы одной категории (DeFi, L1, мем-коины)
  3. k-NN граф: Соединяем каждый актив с k наиболее коррелированными
  4. Полносвязный: Все активы связаны (внимание обучает разреженность)

Коэффициенты внимания

Для узлов 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 к i
M_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. Скользящая корреляция: Пересчёт корреляций на скользящем окне
  2. Определение режима: Разные графы для разных состояний рынка
  3. Разрежение на основе внимания: Внимание обучается, какие рёбра важны

Признаки узлов для активов

Для каждого актива вычисляем векторы признаков, включающие:

Признаки на основе цены:

  • Доходность (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 предоставляет реляционный контекст для построения портфеля:

/// Оптимизация портфеля с использованием GAT
pub 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 + технические индикаторы

Результаты (симуляционный бэктест):

СтратегияШарпСортиноМакс. просадкаДоля выигр.
Держать BTC0.821.15-35.2%-
Одноактивный ML1.211.58-22.4%54.3%
GAT кросс-активный1.672.23-15.8%58.7%
GAT + режим внимания1.892.61-12.3%61.2%

Ключевые выводы:

  • GAT захватывает кросс-активную динамику, улучшая прогнозы
  • Веса внимания адаптируются к изменениям рыночного режима
  • Многоголовое внимание захватывает разные типы отношений
  • Динамическое построение графа превосходит статические графы

Ресурсы и ссылки

Ключевые статьи

  1. 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
  2. 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
    • Расширение графовых сетей на временные данные
  3. Graph Neural Networks for Financial Predictions

    • Различные применения в прогнозировании акций, выявлении мошенничества, оптимизации портфеля

Связанные главы

Библиотеки и инструменты

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 # Запуск бэктеста