← Alle Insights

Graph Neural Networks für Finanzmärkte.

Aktien bewegen sich nicht isoliert. Sie hängen an Sektoren, Lieferketten, gemeinsamen Faktor-Exposures. Klassische ML-Modelle behandeln jede Aktie als unabhängige Beobachtung — und werfen damit einen Großteil der vorhandenen Information weg. Graph Neural Networks codieren diese Beziehungen direkt in die Architektur.

Was ist eigentlich der Graph?

Der Markt-Graph hat Aktien als Knoten und Beziehungen als Kanten. Welche Beziehungen sinnvoll sind, hängt vom Problem ab:

Warum GNN statt einfacher Cross-Sectional-Features?

Man könnte Sektor-Returns einfach als Features pro Aktie hinzufügen — das ist der pragmatische Shortcut, und er funktioniert oft erstaunlich gut. GNNs liefern zwei Vorteile darüber hinaus:

Architektur: GAT auf Marktdaten.

Ein praktikabler Aufbau: pro Tag eine Knoten-Feature-Matrix (Returns, Volumen, Volatilität, Faktor-Loadings pro Aktie) und eine Adjazenz-Matrix (Sektor + rollierende Korrelation). Zwei GAT-Layer, ein linearer Kopf für die Vorhersage.

import torch
import torch.nn as nn
import torch.nn.functional as F

class GATLayer(nn.Module):
    def __init__(self, in_dim, out_dim, heads=4, dropout=0.2):
        super().__init__()
        self.heads = heads
        self.out_dim = out_dim
        self.W = nn.Linear(in_dim, heads * out_dim, bias=False)
        self.a_src = nn.Parameter(torch.empty(1, heads, out_dim))
        self.a_dst = nn.Parameter(torch.empty(1, heads, out_dim))
        nn.init.xavier_uniform_(self.a_src)
        nn.init.xavier_uniform_(self.a_dst)
        self.dropout = nn.Dropout(dropout)

    def forward(self, h, adj):
        # h: [N, F], adj: [N, N] mit 0/1
        N = h.size(0)
        h = self.W(h).view(N, self.heads, self.out_dim)
        e_src = (h * self.a_src).sum(-1)  # [N, heads]
        e_dst = (h * self.a_dst).sum(-1)  # [N, heads]
        e = e_src.unsqueeze(1) + e_dst.unsqueeze(0)  # [N, N, heads]
        e = F.leaky_relu(e, 0.2)
        mask = (adj > 0).unsqueeze(-1)
        e = e.masked_fill(~mask, float("-inf"))
        alpha = F.softmax(e, dim=1)
        alpha = self.dropout(alpha)
        out = torch.einsum("ijh,jhd->ihd", alpha, h)
        return out.reshape(N, self.heads * self.out_dim)
class MarketGAT(nn.Module):
    def __init__(self, n_features, hidden=32, heads=4):
        super().__init__()
        self.gat1 = GATLayer(n_features, hidden, heads=heads)
        self.gat2 = GATLayer(hidden * heads, hidden, heads=1)
        self.head = nn.Sequential(
            nn.LayerNorm(hidden),
            nn.GELU(),
            nn.Linear(hidden, 1),
        )

    def forward(self, node_feats, adj):
        h = F.elu(self.gat1(node_feats, adj))
        h = F.elu(self.gat2(h, adj))
        return self.head(h).squeeze(-1)

Daten-Engineering: der eigentliche Aufwand.

Wie immer bei Modellen mit alternativen Strukturen: die Daten-Pipeline ist der Engpass. Konkret zu lösen:

Trainings-Setup.

Die Loss-Funktion kann punktweise sein (Vorhersage der nächsten Tages-Rendite pro Aktie) oder ranking-basiert (Spearman-Loss, der Top-N gegen Bottom-N optimiert). Für Long-Short-Strategien ist Ranking meist robuster — der absolute Wert der Prognose interessiert weniger als die relative Sortierung.

Walk-Forward gilt auch hier. Bei GNNs ist der Aufwand pro Trainings-Schritt spürbar höher als bei einem klassischen Tabellen-Modell. Eine Iteration über fünf Jahre Tagesdaten mit 500 Aktien ist mit moderner GPU in wenigen Stunden machbar.

Wo der Mehrwert tatsächlich entsteht.

Aus Erfahrung: Der größte Beitrag eines GNN entsteht in Phasen erhöhter Sektorrotation oder Stress, wenn klassische Faktor-Modelle ihre Stabilität verlieren. In ruhigen Trendphasen liefert ein gut getuntes Lightgbm-Modell auf ähnlichen Features oft vergleichbare Ergebnisse mit deutlich weniger Komplexität.

Ein GNN ist daher nicht der erste Schritt im ML-Trading-Stack. Es ist die Erweiterung, wenn die einfacheren Modelle ihr Plateau erreicht haben und Sie zusätzliche Information aus der Marktstruktur extrahieren wollen.

Risiken und Grenzen.

Die Definition des Graphen ist selbst ein Hyperparameter — und einer mit hohem Overfitting-Potenzial. Wer den Graphen auf Basis von gleichzeitigen Korrelationen baut, riskiert versteckten Look-Ahead. Wer ihn rein statisch hält, verpasst Regime-Wechsel. Eine Mischung aus statischer Sektor-Struktur und langsam rollierenden Korrelations-Kanten ist meist der pragmatische Kompromiss.

Sie haben Sektor-Daten, Korrelationsmatrizen oder Lieferketten-Daten und wollen daraus ein GNN-Modell bauen? Erstgespräch buchen — wir prüfen, ob der Aufwand sich gegenüber Ihrer bestehenden Pipeline lohnt.