← Retour aux articles
August 6, 2026
5 min de lecture

Processus gaussiens pour la modélisation des prix non paramétriques

Processus gaussiens pour la modélisation des prix non paramétriques
#bayesian
#gaussian-process
#kernel
#uncertainty
#non-parametric

Fait partie de la série « Lignes de base Classic ML ».

Deux choses font qu'un processus gaussien mérite un article séparé sur ce blog, et aucune d'elles n'est "cela vous donne de l'incertitude".

Le premier est la conception du noyau. L’ensemble du biais inductif d’un médecin généraliste réside dans une seule fonction k(x,x)k(x, x'), et cette fonction est quelque chose que vous écrivez délibérément : à quel point le chemin est difficile, s'il se répète, si la répétition décroît. Rien d'autre dans la boîte à outils standard ne vous permet d'énoncer une hypothèse structurelle sur la dynamique du marché qui s'y conforme explicitement. La seconde est la vraisemblance marginale — un objectif de formation avec une pénalité de complexité dérivée du modèle lui-même, et non d'un ensemble retenu. Tous les autres articles de l'arc de surajustement sur ce blog (analyse de plateau, PBO, deflated Sharpe) existent parce que la régularisation de l'ensemble de validation est fragile lors de la recherche. Un médecin généraliste prétend ne pas en avoir besoin. Cette affirmation est testable, et la tester est plus intéressante qu’un autre didacticiel sur la taille de l’incertitude.

Sur l'incertitude elle-même : la variance postérieure du GP est structurelle – elle découle de la même inférence qui produit la moyenne, plutôt que d'être enroulée autour d'un modèle ajusté par la suite. C'est le vrai contraste avec la prédiction conforme, qui explique déjà sur ce blog pourquoi l'incertitude est la bonne entrée pour dimensionner une position et que faire avec un intervalle une fois que vous en avez un. Cet article ne réaffirme pas ce cas ; ça va après le modèle.

Ce qui suit est le noyau et la machinerie d'inférence, une implémentation de GPyTorch et - clairement indiqué à la fin - les mesures que cet article n'a pas encore.

Qu'est-ce qu'un processus gaussien ?

Les médecins généralistes apparaissent déjà sur ce blog en tant que substitut de l'optimisation bayésienne dans Optuna vs. coordinate descente, avec la même notation et la même mise en garde concernant la faible dimensionnalité. Ici, le GP est le modèle lui-même, ajusté aux données du marché plutôt qu'à une surface de recherche hyperparamétrique, le traitement va donc plus en profondeur.

Un processus gaussien est un ensemble de variables aléatoires dont un nombre fini a une distribution gaussienne conjointe. Il s'agit d'une distribution sur des fonctions, pas d'une distribution sur des paramètres.

Formellement, une fonction f:XRf: \mathcal{X} \to \mathbb{R} est tiré d'un GP si pour tout ensemble fini d'entrées {x1,x2,,xn}X\{x_1, x_2, \ldots, x_n\} \subset \mathcal{X}:

(f(x1)f(x2)f(xn))N((m(x1)m(x2)m(xn)),(k(x1,x1)k(x1,xn)k(xn,x1)k(xn,xn)))\begin{pmatrix} f(x_1) \\ f(x_2) \\ \vdots \\ f(x_n) \end{pmatrix} \sim \mathcal{N}\left(\begin{pmatrix} m(x_1) \\ m(x_2) \\ \vdots \\ m(x_n) \end{pmatrix}, \begin{pmatrix} k(x_1, x_1) & \cdots & k(x_1, x_n) \\ \vdots & \ddots & \vdots \\ k(x_n, x_1) & \cdots & k(x_n, x_n) \end{pmatrix}\right)

m(x)=E[f(x)]m(x) = \mathbb{E}[f(x)] est la fonction moyenne et k(x,x)=Cov(f(x),f(x))k(x, x') = \text{Cov}(f(x), f(x')) est la fonction de covariance (noyau). Nous écrivons cela de manière compacte sous la forme :

fGP(m(),k(,))f \sim \mathcal{GP}(m(\cdot), k(\cdot, \cdot))

La fonction moyenne code une croyance antérieure sur le comportement moyen de ff. En trading, nous définissons généralement m(x)=0m(x) = 0, codant l’hypothèse selon laquelle nous n’avons aucun biais directionnel préalable sur les rendements. Toute la structure va dans le noyau.

Pourquoi non paramétrique ?

Un modèle de régression linéaire avec 5 caractéristiques comporte 6 paramètres. Un réseau de neurones avec deux couches cachées de 64 unités en compte des milliers. Un GP n'a pas de nombre fixe de paramètres : la complexité du modèle augmente avec les données. Avec 10 observations, le GP définit une gaussienne à 10 dimensions. Avec 10 000 observations, il définit une gaussienne à 10 000 dimensions.

Cela ne veut pas dire que les généralistes n’ont pas d’hyperparamètres. La fonction noyau possède des hyperparamètres (échelles de longueur, amplitudes, périodicités) qui contrôlent les propriétés des fonctions tirées du prior. Mais la forme fonctionnelle elle-même n’est jamais figée. Le GP peut représenter n'importe quelle fonction continue, avec suffisamment de données et le bon noyau. C'est ce que signifie « non paramétrique » : le modèle n'est pas limité à une famille paramétrique comme les fonctions linéaires ou les polynômes.

Pour la modélisation financière, cela est précieux. Les marchés changent. La relation entre les caractéristiques et les rendements est non linéaire, non stationnaire et dépend du régime. Les modèles paramétriques imposent une structure qui peut ne pas correspondre à la réalité. Les médecins généralistes laissent parler les données.

Fonctions du noyau : encodage de la structure du marché

La fonction noyau k(x,x)k(x, x') est l'âme d'un processus gaussien. Il définit quelles fonctions sont probables a priori en spécifiant la covariance entre les valeurs de fonction à deux points d'entrée quelconques. Différents noyaux codent différentes hypothèses sur la régularité, la périodicité et le comportement à long terme.

Fonction de base radiale (RBF) / Exponentielle au carré

Le noyau RBF est le point de départ le plus courant :

kRBF(x,x)=σ2exp(xx222)k_{\text{RBF}}(x, x') = \sigma^2 \exp\left(-\frac{\|x - x'\|^2}{2\ell^2}\right)

σ2\sigma^2 est la variance du signal (échelle de sortie) et \ell est l'échelle de longueur. Les fonctions tirées d'un GP avec un noyau RBF sont infiniment différentiables – très fluides.

Interprétation du trading : L'échelle de longueur \ell contrôle la distance entre deux points de données qui peuvent être tout en étant corrélés. Une échelle courte signifie que le modèle réagit aux modèles locaux ; une échelle longue signifie qu’elle capture les grandes tendances. La variance du signal σ2\sigma^2 contrôle l’amplitude de la fonction – quelle peut être l’ampleur des rendements prédits.

Problème pour la finance : Une douceur infinie est irréaliste. Les rendements financiers connaissent des sauts, des changements de régime et des discontinuités. Le noyau RBF peut surlisser ces caractéristiques, produisant des prédictions trop conservatrices à proximité de ruptures structurelles.

Noyau maternel

La classe Matern généralise le RBF en introduisant un paramètre de douceur ν\nu:

kMatern(x,x)=σ221νΓ(ν)(2νxx)νKν(2νxx)k_{\text{Matern}}(x, x') = \sigma^2 \frac{2^{1-\nu}}{\Gamma(\nu)} \left(\frac{\sqrt{2\nu}\|x - x'\|}{\ell}\right)^{\nu} K_{\nu}\left(\frac{\sqrt{2\nu}\|x - x'\|}{\ell}\right)

KνK_{\nu} est la fonction de Bessel modifiée du deuxième type. Comme ν\nu \to \infty, le noyau Matern converge vers le RBF. Choix courants :

  • ν=1/2\nu = 1/2: Equivalent au procédé Ornstein-Uhlenbeck. Les fonctions sont continues mais non différentiables – rugueuses, comme le mouvement brownien.
  • ν=3/2\nu = 3/2: Les fonctions sont une fois différentiables. Un bon équilibre entre douceur et souplesse.
  • ν=5/2\nu = 5/2: Les fonctions sont deux fois différenciables. Plus lisse que 3/23/2 mais moins rigide que RBF.

Interprétation commerciale : La Matern-3/23/2 Le noyau est sans doute la meilleure valeur par défaut pour les séries temporelles financières. Il permet le genre de rugosité que présentent les trajectoires des prix réels sans être aussi irrégulier que ν=1/2\nu = 1/2. Cela concorde avec la littérature sur « la volatilité est approximative » (Gatheral, Jaisson et Rosenbaum, 2018), qui montre empiriquement que les trajectoires de volatilité ont des exposants de Hurst autour de H0.1H \approx 0.1, bien plus grossier que le mouvement brownien.

La principale preuve publiée démontrant que les noyaux de Matern battent les modèles de volatilité classiques est celle de Rizvi et al. (2017), qui rapportent un MSE environ 20 % meilleur qu'une marche aléatoire et 50 % meilleur que GARCH — sur les données quotidiennes de 2017 sur les paires de devises, pas sur la cryptographie, et non reproduites ici. Traitez-le comme une motivation pour essayer le noyau, pas comme une référence. L'ajustement GARCH(1,1) de ce blog sur les données quotidiennes réelles BTC, avec les diagnostics Ljung-Box et ARCH-LM, se trouve dans Prévision de volatilité GARCH pour crypto ; un face-à-face avec un Matern GP sur le même échantillon serait une comparaison honnête, et elle n'a pas été réalisée.

Noyau périodique

Les marchés financiers ont des schémas cycliques : courbes de volumes intrajournalières, effets jours de la semaine, flux de rééquilibrage mensuels, saisons de résultats trimestriels. Le noyau périodique capture les éléments suivants :

kPeriodic(x,x)=σ2exp(2sin2(πxx/p)2)k_{\text{Periodic}}(x, x') = \sigma^2 \exp\left(-\frac{2\sin^2\left(\pi|x - x'|/p\right)}{\ell^2}\right)

pp est la période. Les fonctions tirées de ce noyau se répètent avec un point pp, modulé par l'échelle de longueur \ell qui contrôle la rapidité avec laquelle la corrélation décroît au cours d'une période.

Interprétation commerciale : Définir p=24p = 24 (heures) pour capturer les tendances intrajournalières, ou p=5p = 5 (jours de bourse) pour la saisonnalité hebdomadaire. Contrairement aux fonctionnalités de Fourier, le noyau périodique ne suppose pas un nombre fixe d'harmoniques : le GP apprend la forme du cycle à partir des données.

Combinaison de noyaux : composition additive et multiplicative

Le véritable pouvoir des noyaux GP réside dans leur composition. Si k1k_1 et k2k_2 sont des noyaux valides, tout comme :

  • Somme : k1+k2k_1 + k_2 — la fonction est la somme de composants indépendants (décomposition additive)
  • Produit : k1×k2k_1 \times k_2 — interactions entre composants (par exemple, comportement localement périodique)

Un noyau composite utile pour les rendements financiers :

k(x,x)=kMatern-3/2(x,x)+kPeriodic(x,x)kRBF(x,x)k(x, x') = k_{\text{Matern-3/2}}(x, x') + k_{\text{Periodic}}(x, x') \cdot k_{\text{RBF}}(x, x')

Cela décompose le signal en :

  1. Une composante de tendance apériodique et non lisse (Matern-3/2)
  2. Une composante périodique dont l'amplitude décroît avec le temps (Periodic ×\times RBF)

Le produit kPeriodickRBFk_{\text{Periodic}} \cdot k_{\text{RBF}} crée un noyau localement périodique : le motif se répète, mais les répétitions distantes ont moins d'influence que les répétitions proches. Cela s’applique parfaitement à la saisonnalité financière, qui évolue au fil du temps à mesure que la microstructure du marché évolue.

Noyaux de mélange spectral

Pour une flexibilité maximale, le noyau de mélange spectral (SM) (Wilson & Adams, 2013) paramétrise la densité spectrale du noyau comme un mélange de gaussiennes :

kSM(x,x)=q=1Qwqexp(2π2xx2vq)cos(2πxxμq)k_{\text{SM}}(x, x') = \sum_{q=1}^{Q} w_q \exp\left(-2\pi^2 \|x - x'\|^2 v_q\right) \cos\left(2\pi \|x - x'\| \mu_q\right)

wqw_q sont des poids de mélange, vqv_q sont des variances spectrales, et μq\mu_q sont des moyennes spectrales (fréquences). Par le théorème de Bochner, tout noyau stationnaire peut être représenté de cette façon. Le noyau SM peut découvrir simultanément des composantes périodiques, des tendances à long terme et des corrélations à court terme, le tout à partir de données.

Interprétation du trading : Le noyau SM est utile lorsque vous ne savez pas quels modèles existent dans les données. Il peut identifier des périodicités cachées dans les séries de rendements (par exemple, des cycles subtils de 4 heures sur les marchés de cryptographie pilotés par un rééquilibrage automatisé). L'inconvénient est davantage d'hyperparamètres et le risque de surapprentissage avec de petits ensembles de données.

Inférence postérieure : d'avant à la prédiction

Compte tenu des données de formation D={(xi,yi)}i=1n\mathcal{D} = \{(x_i, y_i)\}_{i=1}^nyi=f(xi)+ϵiy_i = f(x_i) + \epsilon_i et ϵiN(0,σn2)\epsilon_i \sim \mathcal{N}(0, \sigma_n^2), le GP postérieur aux points de test XX_* a une solution de forme fermée. C’est le principal avantage informatique des généralistes par rapport à la plupart des modèles bayésiens.

Les équations postérieures

Laissez K=k(X,X)K = k(X, X) être le n×nn \times n matrice de covariance de formation, K=k(X,X)K_* = k(X_*, X) être le m×nm \times n matrice de covariance croisée, et K=k(X,X)K_{**} = k(X_*, X_*) être le m×mm \times m tester la matrice de covariance. Le postérieur est :

fX,y,XN(fˉ,Cov(f))f_* | X, y, X_* \sim \mathcal{N}(\bar{f}_*, \text{Cov}(f_*))

où:

fˉ=K(K+σn2I)1y\bar{f}_* = K_* (K + \sigma_n^2 I)^{-1} y

Cov(f)=KK(K+σn2I)1KT\text{Cov}(f_*) = K_{**} - K_* (K + \sigma_n^2 I)^{-1} K_*^T

La moyenne postérieure fˉ\bar{f}_* est une combinaison linéaire des objectifs d'entraînement, pondérée par la similarité du noyau entre les points de test et d'entraînement. La covariance postérieure Cov(f)\text{Cov}(f_*) commence à partir de la covariance antérieure KK_{**} et soustrait les informations obtenues des données d'entraînement. Lorsque les données d'entraînement sont denses, la variance a posteriori est faible. Lorsque les données d'entraînement sont rares, la variance postérieure revient à la variance antérieure.

La vraisemblance marginale et l'affirmation qui mérite d'être testée

Les hyperparamètres du noyau θ\theta (échelles de longueur, variances, niveau de bruit) sont apprises en maximisant la vraisemblance marginale log :

logp(yX,θ)=12yT(K+σn2I)1y12logK+σn2In2log2π\log p(y | X, \theta) = -\frac{1}{2} y^T (K + \sigma_n^2 I)^{-1} y - \frac{1}{2} \log |K + \sigma_n^2 I| - \frac{n}{2} \log 2\pi

Le premier terme est un terme d’ajustement des données (pénalise les prédictions éloignées des observations). Le deuxième terme est une pénalité de complexité (pénalise les modèles trop flexibles, c'est-à-dire où la matrice du noyau a un déterminant important). Le troisième terme est une constante de normalisation.

C'est le rasoir automatique d'Occam, et c'est la chose la plus intéressante qu'un GP apporte à un pipeline commercial. La version forte de l'affirmation est qu'aucun ensemble de validation séparé n'est nécessaire pour la régularisation — la pénalité de complexité est à l'intérieur de l'objectif, de sorte que le modèle ne peut pas acheter gratuitement un ajustement avec flexibilité.

Cette affirmation mérite le scepticisme sur ce blog en particulier. Plateau Analysis montre qu'un score de validation sur un seul point est un mauvais critère de sélection et que la robustesse réside dans la forme du quartier ; PBO quantifie la fréquence à laquelle le gagnant de l'échantillon perd hors de l'échantillon ; deflated Sharpe prix en nombre d'essais. Une probabilité marginale est toujours un objectif dans l'échantillon maximisé sur θ\theta — le facteur Occam pénalise la capacité du modèle, et non la sélection sur de nombreux noyaux ajustés. Si vous adaptez douze noyaux candidats et choisissez celui avec la meilleure probabilité marginale, vous êtes de retour dans le territoire des tests multiples et la logique de déflation s'applique inchangée. La version falsifiable : la sélection par vraisemblance marginale produit-elle un écart plus petit dans l'échantillon/hors échantillon que la sélection par ensemble de validation sur les mêmes données et la même famille de noyau ? Cela est mesurable et n’est pas mesuré ci-dessous.

La surface de vraisemblance marginale a également des optima locaux. Plusieurs redémarrages aléatoires ou une initialisation minutieuse sont importants - l'initialisation de l'échelle de longueur à la distance médiane par paire des entrées d'entraînement et de la variance du bruit à la variance de l'échantillon des cibles est un point de départ raisonnable.

Coût de calcul et évolutivité

Le goulot d’étranglement s’inverse (K+σn2I)(K + \sigma_n^2 I), ce qui coûte O(n3)O(n^3) dans le temps et O(n2)O(n^2) en mémoire. Le mur cubique est déjà discuté sur ce blog dans l'autre sens : méthode de recherche vs coût d'évaluation disqualifie purement et simplement l'optimisation bayésienne basée sur GP lorsque l'objectif est bon marché, car le substitut coûte plus cher que les évaluations qu'il économise. L'arithmétique est la même ici ; l'application est différente. En tant que modèle adapté aux données du marché, le terme cubique n'est pas une disqualification mais un budget : il fixe un plafond strict à la fenêtre de formation.

Stratégies d'évolutivité pour les applications de trading :

  1. GPs clairsemés (induisant des points). Remplacez le n×nn \times n matrice avec un m×mm \times m matrice où mnm \ll n. Les points inducteurs Z={z1,,zm}Z = \{z_1, \ldots, z_m\} sont des pseudo-entrées qui résument les données d’entraînement. La formulation SVGP (Stochastic Variational GP) de Hensman et al. (2013) permet une formation en mini-lots avec un coût O(nm2)O(nm^2) par itération. GPyTorch le prend en charge nativement.

  2. Interpolation structurée du noyau (SKI/KISS-GP). Exploite la structure de Kronecker et Toeplitz dans la matrice du noyau lorsque les entrées se trouvent sur une grille. Réduit le coût de O(n+glogg)O(n + g \log g)gg est la taille de la grille. Idéal pour les séries temporelles régulièrement échantillonnées (par exemple, barres d'une minute).

  3. Généristes locaux sur fenêtres coulissantes. Former des médecins généralistes distincts sur les données récentes uniquement. C'est là que la contrainte spécifique au GP intervient : les noyaux standards sont stationnaires (k(x,x)k(x,x') ne dépend que de xxx - x') et les marchés ne le sont pas, donc la réponse habituelle est une fenêtre glissante — mais la longueur de la fenêtre est délimitée au-dessus par O(n3)O(n^3), pas seulement par des statistiques. Walk-forward optimisation couvre les fenêtres ancrées et mobiles, les longueurs de train/test et la fréquence de réoptimisation en général ; pour un médecin généraliste, le dimensionnement de la fenêtre est une décision informatique autant que statistique, et une fenêtre ancrée (en constante croissance) n'est tout simplement pas disponible au-delà de quelques milliers de points sans approximation. Ce recadrage est la conséquence pratique : avec les médecins généralistes, vous n’avez pas le choix « d’utiliser tout l’historique ».

GP pour la prévision du retour : un cadre pratique

Conception des fonctionnalités : pourquoi 5 à 20 fonctionnalités, pas 500

La taxonomie générale des caractéristiques du ML des données de marché — déséquilibre du carnet d'ordres, pression comptable, VPIN, lambda de Kyle, caractéristiques de volatilité réalisée, codage temporel cyclique, signaux d'actifs croisés et de taux de financement — est déjà présentée dans [modélisation de propagation avec apprentissage automatique] (/fr/blog/post/spread-modeling-machine-learning) ; use that list.

Ce qui est spécifique au GP, c'est la taille de la liste. Les méthodes à noyau se dégradent dans les grandes dimensions : les distances se concentrent et un noyau stationnaire perd sa discrimination. La plage pratique pour un GP financier est de 5 à 20 entrées, et non les centaines qu'un arbre boosté par gradient mange volontiers. Le mécanisme qui permet de survivre est ARD (Automatic Relevance Determination) : donne à chaque dimension d'entrée sa propre échelle de longueur d\ell_d, et la formation de vraisemblance marginale pousse d\ell_d \to \infty pour les dimensions qui ne transportent aucun signal, car une échelle de longueur infinie signifie que le noyau ignore cette coordonnée. La sélection des fonctionnalités devient un sous-produit de l'ajustement, et les connaissances acquises d\ell_d sont directement lisibles comme un classement de pertinence - ce qui le rend aussi falsifiable : ajustez le noyau composite sur de vraies barres, imprimez les échelles de longueur et voyez si les caractéristiques auxquelles vous croyez sont celles que le modèle conserve.

Dimensionnement des postes et abstention

The GP posterior gives σ(x)\sigma_*(x) directement, il tombe donc directement dans le filtre de dimensionnement du ratio de bord et de non-échange développé dans Conformal Prediction for Risk-Aware Position Sizing, avec la largeur de l'intervalle wt=2κσ(x)w_t = 2\kappa\sigma_*(x). La seule différence est la provenance : le GP produit la largeur à partir du modèle lui-même plutôt qu'à partir d'un ensemble d'étalonnage, elle varie donc en fonction de l'emplacement du point de test par rapport aux données d'entraînement plutôt qu'en fonction d'un quantile global de résidus passés.

Implémentation avec GPyTorch

GPyTorch est une bibliothèque basée sur PyTorch pour l'inférence GP évolutive. Il exploite l'accélération GPU, la différenciation automatique et les techniques modernes d'algèbre linéaire (gradients conjugués, décomposition de Lanczos) pour faire évoluer les GP au-delà du naïf. O(n3)O(n^3) limite.

GP exact de base pour la prédiction de retour

import torch
import gpytorch
import numpy as np
from torch.utils.data import TensorDataset, DataLoader


class ExactGPModel(gpytorch.models.ExactGP):
    """Exact GP with a composite kernel for financial returns."""

    def __init__(self, train_x, train_y, likelihood):
        super().__init__(train_x, train_y, likelihood)
        self.mean_module = gpytorch.means.ZeroMean()

        self.covar_matern = gpytorch.kernels.ScaleKernel(
            gpytorch.kernels.MaternKernel(nu=1.5, ard_num_dims=train_x.shape[1])
        )
        self.covar_periodic = gpytorch.kernels.ScaleKernel(
            gpytorch.kernels.PeriodicKernel()
        )
        self.covar_rbf_decay = gpytorch.kernels.ScaleKernel(
            gpytorch.kernels.RBFKernel()
        )

    def forward(self, x):
        mean = self.mean_module(x)
        covar = self.covar_matern(x) + self.covar_periodic(x) * self.covar_rbf_decay(x)
        return gpytorch.distributions.MultivariateNormal(mean, covar)

ard_num_dims c'est ce qui permet les échelles de longueur par dimension discutées ci-dessus. Après la formation, model.covar_matern.base_kernel.lengthscale est le vecteur à lire.

Boucle d'entraînement

def train_gp(train_x, train_y, n_epochs=200, lr=0.05, device=None):
    """Train the GP by maximizing the marginal log-likelihood.

    Returns the device alongside the model so that callers move test
    tensors to the same place -- otherwise prediction crashes on GPU.
    """
    if device is None:
        device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    train_x = train_x.to(device)
    train_y = train_y.to(device)

    likelihood = gpytorch.likelihoods.GaussianLikelihood().to(device)
    model = ExactGPModel(train_x, train_y, likelihood).to(device)

    model.train()
    likelihood.train()

    optimizer = torch.optim.Adam(model.parameters(), lr=lr)
    mll = gpytorch.mlls.ExactMarginalLogLikelihood(likelihood, model)

    losses = []
    for epoch in range(n_epochs):
        optimizer.zero_grad()
        output = model(train_x)
        loss = -mll(output, train_y)
        loss.backward()
        optimizer.step()
        losses.append(loss.item())

        if (epoch + 1) % 50 == 0:
            noise = likelihood.noise.item()
            print(
                f"Epoch {epoch+1}/{n_epochs} | "
                f"Loss: {loss.item():.4f} | "
                f"Noise: {noise:.6f}"
            )

    return model, likelihood, device, losses

Prédiction avec incertitude

def predict_with_uncertainty(model, likelihood, test_x, device):
    """Generate predictions with uncertainty estimates."""
    model.eval()
    likelihood.eval()

    test_x = test_x.to(device)

    with torch.no_grad(), gpytorch.settings.fast_pred_var():
        posterior = likelihood(model(test_x))

        mean = posterior.mean
        variance = posterior.variance
        lower, upper = posterior.confidence_region()  # 2-sigma bounds

    return {
        "mean": mean.cpu().numpy(),
        "std": variance.sqrt().cpu().numpy(),
        "lower_2sigma": lower.cpu().numpy(),
        "upper_2sigma": upper.cpu().numpy(),
    }

Le fast_pred_var() Le gestionnaire de contexte utilise l'algorithme LOVE (Lanczos Variance Estimates) pour calculer les variances prédictives dans O(n)O(n) temps au lieu de O(n2)O(n^2).

Pipeline de trading de bout en bout

Remarque sur le générateur de fonctionnalités ci-dessous : chaque statistique glissante doit être strictement rétrospective et la standardisation des entrées doit être adaptée uniquement à la tranche d'entraînement. Ce deuxième point n'est pas une hygiène générique - c'est précisément le canal de fuite de normalisation disséqué et mesuré dans la taxonomie des biais d'anticipation, qui rapporte l'inflation de Sharpe produite par chaque type de fuite. Un généraliste standardise ses apports par construction, c'est donc la fuite à laquelle il est le plus exposé.

import pandas as pd


def build_features(df: pd.DataFrame, lookback: int = 10) -> pd.DataFrame:
    """Build features for GP-based return prediction."""
    features = pd.DataFrame(index=df.index)

    for lag in range(1, lookback + 1):
        features[f"ret_lag_{lag}"] = df["close"].pct_change().shift(lag)

    ret = df["close"].pct_change()
    features["vol_ratio"] = (
        ret.rolling(10).std() / ret.rolling(50).std()
    )

    features["vol_zscore"] = (
        (df["volume"] - df["volume"].rolling(50).mean())
        / df["volume"].rolling(50).std()
    )

    if "bid_vol" in df.columns and "ask_vol" in df.columns:
        features["obi"] = (
            (df["bid_vol"] - df["ask_vol"])
            / (df["bid_vol"] + df["ask_vol"])
        )

    if hasattr(df.index, "hour"):
        hours = df.index.hour + df.index.minute / 60.0
        features["time_sin"] = np.sin(2 * np.pi * hours / 24)
        features["time_cos"] = np.cos(2 * np.pi * hours / 24)

    features.dropna(inplace=True)
    return features


def run_gp_strategy(
    df: pd.DataFrame,
    train_window: int = 500,
    retrain_every: int = 50,
    confidence_threshold: float = 1.0,
    risk_fraction: float = 0.02,
    max_leverage: float = 1.0,
):
    """Walk-forward GP trading strategy with uncertainty-based sizing."""
    features = build_features(df)
    returns = df["close"].pct_change().reindex(features.index)
    target = returns.shift(-1)  # predict next-bar return

    mask = features.notna().all(axis=1) & target.notna()
    features = features[mask]
    target = target[mask]

    positions = pd.Series(0.0, index=features.index)
    predictions = pd.DataFrame(
        index=features.index, columns=["mean", "std"], dtype=float
    )

    model = likelihood = device = None
    x_mean = x_std = y_mean = y_std = None

    for i in range(train_window, len(features)):
        if model is None or (i - train_window) % retrain_every == 0:
            train_x = torch.tensor(
                features.iloc[i - train_window : i].values,
                dtype=torch.float32,
            )
            train_y = torch.tensor(
                target.iloc[i - train_window : i].values,
                dtype=torch.float32,
            )

            x_mean, x_std = train_x.mean(0), train_x.std(0) + 1e-8
            y_mean, y_std = train_y.mean(), train_y.std() + 1e-8
            train_x_norm = (train_x - x_mean) / x_std
            train_y_norm = (train_y - y_mean) / y_std

            model, likelihood, device, _ = train_gp(
                train_x_norm, train_y_norm, n_epochs=100
            )

        test_x = torch.tensor(
            features.iloc[i : i + 1].values, dtype=torch.float32
        )
        test_x_norm = (test_x - x_mean) / x_std

        pred = predict_with_uncertainty(model, likelihood, test_x_norm, device)

        pred_mean = pred["mean"][0] * y_std.item() + y_mean.item()
        pred_std = pred["std"][0] * y_std.item()

        predictions.iloc[i] = [pred_mean, pred_std]

        z_score = abs(pred_mean) / (pred_std + 1e-8)
        if z_score > confidence_threshold:
            size = min(z_score * risk_fraction, max_leverage)
            positions.iloc[i] = np.sign(pred_mean) * size

    strategy_returns = positions.shift(1) * returns
    return strategy_returns, predictions, positions

Mise à l'échelle vers des ensembles de données plus grands avec des GP clairsemés

Lorsque la fenêtre d’entraînement dépasse quelques milliers de points, l’inférence exacte du GP devient lente. Passez à un GP clairsemé variationnel :

class SparseGPModel(gpytorch.models.ApproximateGP):
    """Sparse variational GP for large-scale return prediction."""

    def __init__(self, inducing_points):
        variational_distribution = (
            gpytorch.variational.CholeskyVariationalDistribution(
                inducing_points.size(0)
            )
        )
        variational_strategy = (
            gpytorch.variational.VariationalStrategy(
                self,
                inducing_points,
                variational_distribution,
                learn_inducing_locations=True,
            )
        )
        super().__init__(variational_strategy)
        self.mean_module = gpytorch.means.ZeroMean()
        self.covar_module = gpytorch.kernels.ScaleKernel(
            gpytorch.kernels.MaternKernel(nu=1.5)
        )

    def forward(self, x):
        mean = self.mean_module(x)
        covar = self.covar_module(x)
        return gpytorch.distributions.MultivariateNormal(mean, covar)


def train_sparse_gp(train_x, train_y, n_inducing=128, n_epochs=50, batch_size=256):
    """Train sparse GP with mini-batch stochastic variational inference."""
    indices = torch.randperm(train_x.size(0))[:n_inducing]
    inducing_points = train_x[indices]

    model = SparseGPModel(inducing_points)
    likelihood = gpytorch.likelihoods.GaussianLikelihood()

    model.train()
    likelihood.train()

    optimizer = torch.optim.Adam(
        [{"params": model.parameters()}, {"params": likelihood.parameters()}],
        lr=0.01,
    )
    mll = gpytorch.mlls.VariationalELBO(
        likelihood, model, num_data=train_y.size(0)
    )

    dataset = TensorDataset(train_x, train_y)
    loader = DataLoader(dataset, batch_size=batch_size, shuffle=True)

    for epoch in range(n_epochs):
        for x_batch, y_batch in loader:
            optimizer.zero_grad()
            output = model(x_batch)
            loss = -mll(output, y_batch)
            loss.backward()
            optimizer.step()

    return model, likelihood

Avec 128 points inducteurs, le coût par lot est O(1282×batch_size)O(128^2 \times \text{batch\_size}) — environ 4 millions d'opérations par lot. Cela gère facilement des ensembles de données de plus de 100 000 observations sur un seul GPU.

Deep Kernel Learning : le GP rencontre les réseaux de neurones

Lorsque l'espace d'entrée est de grande dimension ou que la relation entre les caractéristiques et les rendements est hautement non linéaire, un noyau simple peut avoir des difficultés. L'apprentissage profond du noyau (DKL) transmet les entrées via un réseau neuronal avant d'appliquer le noyau GP :

kDKL(x,x)=kbase(gϕ(x),gϕ(x))k_{\text{DKL}}(x, x') = k_{\text{base}}(g_\phi(x), g_\phi(x'))

gϕg_\phi est un réseau de neurones avec des paramètres ϕ\phi et kbasek_{\text{base}} est un noyau standard (par exemple, Matern-3/2). Le réseau apprend une représentation de fonctionnalités où le noyau GP est le plus efficace. L'ensemble du modèle (paramètres de réseau et hyperparamètres du noyau) est formé de bout en bout en maximisant la vraisemblance marginale.

class DeepKernelGP(gpytorch.models.ExactGP):
    """GP with a neural network feature extractor."""

    def __init__(self, train_x, train_y, likelihood, input_dim):
        super().__init__(train_x, train_y, likelihood)
        self.mean_module = gpytorch.means.ZeroMean()

        self.feature_extractor = torch.nn.Sequential(
            torch.nn.Linear(input_dim, 8),
            torch.nn.ReLU(),
            torch.nn.Linear(8, 4),
            torch.nn.ReLU(),
            torch.nn.Linear(4, 2),
        )

        self.covar_module = gpytorch.kernels.ScaleKernel(
            gpytorch.kernels.MaternKernel(nu=1.5, ard_num_dims=2)
        )

    def forward(self, x):
        features = self.feature_extractor(x)
        mean = self.mean_module(features)
        covar = self.covar_module(features)
        return gpytorch.distributions.MultivariateNormal(mean, covar)

DKL combine l'apprentissage de la représentation des réseaux de neurones avec la quantification de l'incertitude des généralistes. La couche GP garantit que les prédictions éloignées des données d’entraînement présentent une grande incertitude – ce que les réseaux neuronaux standards ne parviennent pas à fournir. Notez également que DKL réintroduit exactement la flexibilité que la vraisemblance marginale était censée contrôler : le facteur Occam pénalise le noyau, mais le réseau devant lui a des milliers de paramètres libres et aucune pénalité de ce type.

Ludkovski et Risk (2025), Gaussian Process Models for Quantitative Finance, étudient DKL dans un contexte de finance quantitative plus large, y compris la tarification des options et l'optimisation du portefeuille ; ces résultats sont les leurs, sur leurs problèmes, et ne constituent pas une preuve de la prédiction du retour cryptographique.

Diagnostics et pièges

Calibrage

Un médecin généraliste bien calibré dispose d’intervalles prédictifs qui correspondent à la couverture empirique. La vérification est la même que celle utilisée dans prédiction conforme — qui couvre également la théorie selon laquelle la couverture marginale n'est pas une couverture conditionnelle, une limitation qui s'applique tout autant aux intervalles GP :

from scipy.stats import norm

def calibration_report(predictions, actuals):
    """Check if GP uncertainty is well-calibrated."""
    z_scores = (actuals - predictions["mean"]) / (predictions["std"] + 1e-8)

    for sigma in [1, 2, 3]:
        expected_outside = 2 * (1 - norm.cdf(sigma))
        actual_outside = (np.abs(z_scores) > sigma).mean()
        print(
            f"{sigma}-sigma | Expected outside: {expected_outside:.3f} | "
            f"Actual outside: {actual_outside:.3f}"
        )

Le dépassement attendu est de 0,317/0,046/0,003 à 1/2/3 sigma. Le nombre qui compte est ce que cela imprime sur une véritable marche en avant sur les barres cryptographiques, et ce tableau ne figure pas encore dans cet article. L'attente initiale est que le médecin généraliste est trop confiant - une probabilité gaussienne sur des rendements non stationnaires à queue large devrait mal se cacher à 3 sigma - mais "devrait" n'est pas une mesure.

Pièges restants

  1. Mise à l'échelle d'entrée. Les échelles de longueur sont relatives à l'échelle d'entrée, donc une fonctionnalité s'étend [0,10000][0, 10000] et un à distance [0,1][0, 1] ne peut pas partager un message significatif \ell; ARD est ce qui sauve les plages hétérogènes, et la standardisation est ce qui rend l'initialisation d'ARD saine.

  2. Surajustement de la vraisemblance marginale. Avec de nombreux hyperparamètres du noyau (en particulier les noyaux composites ou de mélange spectral), la vraisemblance marginale peut encore être surajustée. Utilisez des priors : un prior log-normal sur les échelles de longueur centrées sur la distance médiane par paire, un prior semi-normal sur les variances.

  3. Conditionnement matriciel de covariance. (K+σn2I)(K + \sigma_n^2 I) peut devenir numériquement singulier lorsque le bruit σn2\sigma_n^2 est trop petit ou lorsque les points d'entraînement sont presque dupliqués. GPyTorch ajoute 10610^{-6} gigue en diagonale ; les données financières mal conditionnées nécessitent souvent davantage.

  4. Biais d'anticipation. Couvert ci-dessus et, de manière détaillée, dans la taxonomie des biais d'anticipation.

Quand utiliser les généralistes par rapport à d'autres modèles

Critère Médecin généraliste XGBoost Réseau neuronal
Incertitude intégrée Oui (structurel) Non (nécessite une conformité/bootstrap) Non (nécessite un abandon/ensemble MC)
Efficacité des données Excellent (<1 000 échantillons) * *
Évolutivité Mauvais exact, bon clairsemé * *
Non-linéarité Dépend du noyau * *
Interprétabilité Décomposition du noyau + échelles de longueur ARD * *
Non-stationnarité Nécessite une fenêtre coulissante ou DKL * *

* Pour les colonnes XGBoost et neural-network, reportez-vous aux comparaisons publiées plutôt qu'à une nouvelle affirmation ici : spread modeling with machine learning a le tableau gradient-boosting-vs-deep-learning pour les données tabulaires financières (seuils de taille des données, interprétabilité, adaptation du régime, latence) et fusion temporelle transformers a TFT contre LSTM contre vanilla Transformer.

Utilisez des médecins généralistes lorsque :

  • Vous disposez d'ensembles de données petits à moyens (moins de ~ 10 000 observations par fenêtre de formation)
  • Vous souhaitez une hypothèse structurelle explicite et inspectable sur le signal (tendance + saisonnalité + bruit)
  • L'incertitude doit varier avec la distance par rapport aux données d'entraînement, pas seulement avec un quantile résiduel global

N'utilisez pas de médecins généralistes lorsque :

  • Vous avez besoin d'une inférence inférieure à la milliseconde sur des millions d'observations
  • La dimensionnalité d'entrée dépasse ~ 50
  • Le signal vit dans des interactions complexes de fonctionnalités d'ordre élevé que les noyaux stationnaires ne peuvent pas représenter (DKL aide, au détriment de la propriété Occam)

Ce que cet article ne mesure pas encore

Tout ce qui précède est un modèle de machinerie. Rien de tout cela ne prouve qu’un médecin généraliste gagne de l’argent grâce à la cryptographie, et la norme de ce blog est que l’article comporte ses propres chiffres. Les échéances, dans l'ordre d'exécution :

  1. Échelles de longueur ARD sur de vraies barres BTCUSDT de 1 m. Ajustez le noyau composite, imprimez d\ell_d par fonctionnalité. Cela teste directement l'affirmation « ARD est une sélection de fonctionnalités intégrée » et produit un classement falsifiable par pertinence des fonctionnalités.
  2. Le tableau d'étalonnage d'une analyse progressive. Dépassement attendu par rapport au dépassement empirique à 1/2/3 sigma, énoncé clairement, y compris le cas où le médecin généraliste s'avère trop confiant.
  3. La stratégie basée sur la confiance, marche en avant, sur les cinq mêmes majors que le négatif honnête, dégonflée pour le nombre d'essais. Si elle échoue, elle s'inscrit dans cette série comme un autre résultat négatif.
  4. L'horloge murale O(n3)O(n^3) courbe pour n=250/500/1000/2000/5000n = 250 / 500 / 1000 / 2000 / 5000, exact par rapport à SVGP, de sorte que la section d'évolutivité repose sur un graphique au lieu d'une assertion.

Conclusion

L'argument en faveur des processus gaussiens dans le trading n'est pas « ils vous donnent des barres d'erreur » : la prédiction conforme vous donne des barres d'erreur avec moins d'hypothèses de distribution et une garantie de couverture que le GP n'a pas. Le cas est qu'un GP vous fait écrire votre hypothèse structurelle en tant que noyau, la compare à un objectif qui évalue sa propre complexité, puis vous indique laquelle de vos fonctionnalités il a réellement utilisée via les échelles de longueur ARD. C'est un modèle inhabituellement lisible.

Les coûts sont tout aussi concrets : une mise à l'échelle cubique qui plafonne votre fenêtre de formation, des hypothèses de stationnarité qu'une fenêtre déroulante ne répare que partiellement, une probabilité gaussienne que les retours à queue large seront violés et - une fois que vous aurez atteint l'apprentissage approfondi du noyau - la perte silencieuse de la propriété Occam qui a motivé la probabilité marginale en premier lieu. La question de savoir si ce qui reste est échangeable est une question empirique, et les quatre mesures énumérées ci-dessus permettraient d'y répondre.

Références

  • Rasmussen, CE et Williams, CKI (2006). Processus gaussiens pour l'apprentissage automatique. Presse du MIT.
  • Wilson, A. et Adams, R. (2013). Noyaux de processus gaussiens pour la découverte de modèles et l'extrapolation. ICML.
  • Hensman, J., Fusi, N. et Lawrence, ND (2013). Processus gaussiens pour le Big Data. AUI.
  • Gatheral, J., Jaisson, T. et Rosenbaum, M. (2018). La volatilité est rude. Finance quantitative, 18(6).
  • Rizvi, SA, Roberts, SJ, Osborne, MA et Nyikosa, F. (2017). Une nouvelle approche pour prévoir la volatilité financière avec les enveloppes de processus gaussiennes. arXiv :1705.00891.
  • Ludkovski, M., & Risk, J. (2025). Modèles de processus gaussiens pour la finance quantitative. Springer.
  • Gardner, J.R., Pleiss, G., Bindel, D., Weinberger, K.Q. et Wilson, A.G. (2018). GPyTorch : Inférence de processus gaussien Blackbox Matrix-Matrix avec accélération GPU. NeurIPS.
blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Gardez une longueur d'avance sur le marché

Abonnez-vous à notre newsletter pour des insights exclusifs sur le trading IA, des analyses de marché et des mises à jour de la plateforme.

Nous respectons votre vie privée. Désabonnement possible à tout moment.