← 返回文章列表
August 6, 2026
5 分钟阅读

非参数价格建模的高斯过程

非参数价格建模的高斯过程
#bayesian
#gaussian-process
#kernel
#uncertainty
#non-parametric

“经典 ML 基线”系列的一部分。

有两件事使得高斯过程值得在这个博客上单独写一篇文章,而且这两件事都不是“它给你带来了不确定性”。

首先是内核设计。 GP 的整个归纳偏差存在于一个函数中 k(x,x)k(x, x'),而这个函数是你故意写下来的:路径有多崎岖,是否重复,重复是否衰减。标准工具包中没有任何其他内容可以让您明确地陈述有关市场动态的结构性假设,然后与之相适应。第二个是边际可能性——一个训练目标,其复杂性惩罚源自模型本身,而不是来自保留的集合。本博客上过拟合弧中的所有其他文章(plateauanalysisPBOdeflated Sharpe)都存在,因为验证集正则化在搜索下很脆弱。一位全科医生声称不需要。该声明是可测试的,并且测试它比另一个不确定性大小教程更有趣。

就不确定性本身而言:GP 后验方差是“结构性的”——它脱离了产生均值的相同推论,而不是事后包裹在拟合模型中。这与共形预测形成了真正的对比,该预测已经在本博客中介绍了为什么不确定性是头寸调整的正确输入,以及一旦有了间隔就如何处理间隔。本文并没有重新论证这个案例;它追随模型。

接下来是内核和推理机制、GPyTorch 实现,以及(在最后明确说明的)本文尚未提供的测量结果。

什么是高斯过程?

GP 已经作为贝叶斯优化代理出现在本博客中的 Optuna 与坐标下降 中,具有相同的符号和相同的低维警告。这里的 GP 是模型本身,适合市场数据而不是超参数搜索表面,因此处理更深入。

高斯过程是随机变量的集合,其中任何有限数量的随机变量都具有联合高斯分布。它是函数的分布,而不是参数的分布。

形式上,一个函数 f:XRf: \mathcal{X} \to \mathbb{R} 如果对于任何有限的输入集,则从 GP 中得出 {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)] 是均值函数并且 k(x,x)=Cov(f(x),f(x))k(x, x') = \text{Cov}(f(x), f(x')) 是协方差(核)函数。我们将其简洁地写为:

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

平均函数编码了关于平均行为的先验信念 ff。在交易中,我们通常设置 m(x)=0m(x) = 0,编码我们对回报没有先验方向偏差的假设。所有结构都进入内核。

为什么是非参数?

具有 5 个特征的线性回归模型有 6 个参数。具有两个 64 个单元的隐藏层的神经网络有数千个。 GP 没有固定数量的参数——模型的复杂性随着数据的增加而增长。 With 10 observations, the GP defines a 10-dimensional Gaussian.通过 10,000 个观测值,它定义了 10,000 维高斯。

这并不意味着 GP 没有超参数。核函数具有超参数(长度尺度、幅度、周期性),用于控制从先验中得出的函数的属性。但函数形式本身从来都不是固定的。只要有足够的数据和正确的核,GP 就可以表示任何连续函数。这就是“非参数”的含义——模型不受线性函数或多项式等参数族的限制。

对于财务建模来说,这是很有价值的。市场发生变化。特征和回报之间的关系是非线性、非平稳且依赖于状态的。参数模型强加的结构可能与现实不符。全科医生让数据说话。

内核功能:编码市场结构

核函数 k(x,x)k(x, x') 是高斯过程的灵魂。 It defines which functions are probable a priori by specifying the covariance between function values at any two input points. Different kernels encode different assumptions about smoothness, periodicity, and long-range behavior.

Radial Basis Function (RBF) / Squared Exponential

The RBF kernel is the most common starting point:

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 是信号方差(输出尺度)并且 \ell 是长度尺度。从带有 RBF 核的 GP 中得出的函数是无限可微的——非常平滑。

交易解读: 长度尺度 \ell 控制两个数据点可以相距多远并且仍然相关。较短的长度尺度意味着模型会对局部模式做出反应;长尺度意味着它可以捕捉广泛的趋势。信号方差 σ2\sigma^2 控制函数的幅度——预测收益有多大。

金融问题: 无限平滑是不现实的。财务回报有跳跃、政权更迭和间断。 RBF 内核可以过度平滑这些特征,从而在结构断裂附近产生过于保守的预测。

母亲内核

Matern 类通过引入平滑参数来推广 RBF ν\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} 是第二类修正贝塞尔函数。作为 ν\nu \to \infty,Matern 核收敛到 RBF。常见选择:

  • ν=1/2\nu = 1/2:相当于 Ornstein-Uhlenbeck 过程。函数是连续的,但不可微分——粗糙,就像布朗运动一样。
  • ν=3/2\nu = 3/2:函数一旦可微。平滑度和灵活性之间的良好平衡。
  • ν=5/2\nu = 5/2:函数是二次可微的。比平滑 3/23/2 但刚性不如 RBF。

交易解读: The Matern-3/23/2 内核可以说是金融时间序列的最佳默认值。它允许真实价格路径表现出的粗糙度,而不会像 ν=1/2\nu = 1/2。这与“波动性很粗糙”文献(Gatheral、Jaisson 和 Rosenbaum,2018 年)一致,该文献根据经验表明,波动性路径的赫斯特指数约为 H0.1H \approx 0.1,比布朗运动粗糙得多。

Rizvi 等人已发表的证明 Matern 内核击败经典波动率模型的主要证据。 (2017),他们报告 MSE 比随机游走好大约 20%,比 GARCH 好 50% — 基于 2017 年每日货币对数据,而不是加密货币,并且不在此处复制。 将其视为尝试内核的动机,而不是基准。该博客自己的 GARCH(1,1) 适合真实的 BTC 每日数据,具有 Ljung-Box 和 ARCH-LM 诊断,位于 GARCH 加密货币波动率预测;在同一样本上与 Matern GP 进行面对面的比较才是诚实的比较,而且还没有进行过比较。

周期性内核

金融市场具有周期性模式:日内交易量曲线、周内效应、每月再平衡流量、季度收益季节。周期性内核捕获这些:

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 是时期。从该内核中提取的函数以句点重复 pp,由长度尺度调制 \ell 它控制一段时间内相关性衰减的速度。

**交易解读:**设定 p=24p = 24 (小时)捕捉日内模式,或 p=5p = 5 (交易日)每周季节性。与傅里叶特征不同,周期核不假设固定数量的谐波——GP 从数据中学习周期的形状。

组合内核:加法和乘法组合

GP 内核的真正威力在于组合。如果 k1k_1k2k_2 是有效的内核,因此:

  • 总和: k1+k2k_1 + k_2 — 函数是独立分量的总和(加法分解)
  • 产品: k1×k2k_1 \times k_2 - 组件之间的相互作用(例如,局部周期性行为)

一个有用的财务回报复合内核:

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')

这将信号分解为:

  1. 非平滑、非周期性趋势成分 (Matern-3/2)
  2. 振幅随时间衰减的周期性分量(Periodic ×\times 径向基函数)

产品 kPeriodickRBFk_{\text{Periodic}} \cdot k_{\text{RBF}} 创建一个局部周期性内核:模式重复,但远处重复的影响小于附近重复的影响。这对于金融季节性来说是完全正确的,随着市场微观结构的发展,金融季节性会随着时间的推移而变化。

光谱混合内核

为了获得最大的灵活性,光谱混合 (SM) 内核(Wilson & Adams,2013)将内核的谱密度参数化为高斯混合:

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 是混合物重量, vqv_q 是光谱方差,并且 μq\mu_q 是频谱平均值(频率)。根据博赫纳定理,任何平稳核都可以用这种方式表示。 SM 内核可以同时从数据中发现周期性成分、长期趋势和短期相关性。

交易解读: 当您不知道数据中存在什么模式时,SM 内核非常有用。它可以识别回报系列中隐藏的周期性(例如,由自动重新平衡驱动的加密货币市场中微妙的 4 小时周期)。缺点是超参数较多,并且存在小数据集过度拟合的风险。

后验推理:从先验到预测

给定训练数据 D={(xi,yi)}i=1n\mathcal{D} = \{(x_i, y_i)\}_{i=1}^n 在哪里 yi=f(xi)+ϵiy_i = f(x_i) + \epsilon_iϵiN(0,σn2)\epsilon_i \sim \mathcal{N}(0, \sigma_n^2), 测试点处的 GP 后验 XX_* 有一个封闭式解。这是 GP 相对于大多数贝叶斯模型的关键计算优势。

后验方程

K=k(X,X)K = k(X, X) 成为 n×nn \times n 训练协方差矩阵, K=k(X,X)K_* = k(X_*, X) 成为 m×nm \times n 互协方差矩阵,以及 K=k(X,X)K_{**} = k(X_*, X_*) 成为 m×mm \times m 检验协方差矩阵。后验是:

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

在哪里:

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

后验均值 fˉ\bar{f}_* 是训练目标的线性组合,由测试点和训练点之间的核相似度加权。后验协方差 Cov(f)\text{Cov}(f_*) 从先前的协方差开始 KK_{**} 并减去从训练数据中获得的信息。当训练数据密集时,后验方差很小。当训练数据稀疏时,后验方差会恢复为先验方差。

边际可能性和值得测试的声明

内核超参数 θ\theta (长度尺度、方差、噪声水平)是通过最大化对数边际似然来学习的:

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

第一项是数据拟合项(惩罚远离观察的预测)。第二项是复杂性惩罚(惩罚过于灵活的模型 - 即内核矩阵具有较大行列式的模型)。第三项是归一化常数。

这就是自动奥卡姆剃刀,也是 GP 给交易管道带来的最有趣的东西。 该主张的强有力版本是正则化不需要单独的验证集 - 复杂性惩罚在目标之内,因此模型不能免费购买灵活性。

这种说法在这个博客上特别值得怀疑。 高原分析表明单点验证分数是一个糟糕的选择标准,并且鲁棒性取决于邻域的形状; PBO 量化样本内获胜者输掉样本外的频率; deflated Sharpe 试验次数的价格。边际可能性仍然是样本内目标被最大化 θ\theta — 奥卡姆因子惩罚模型容量,而不是对许多拟合核的选择。如果您适合 12 个候选内核并选择具有最佳边际可能性的一个,那么您将回到多重测试领域,并且通货紧缩逻辑将保持不变。可证伪的版本:在相同数据和相同内核系列上,边际似然选择是否比验证集选择产生更小的样本内/样本外差距?这是可以测量的,下面不测量。

边际似然面也具有局部最优。多次随机重新启动或仔细的初始化很重要——将长度尺度初始化为训练输入的中值成对距离,将噪声方差初始化为目标的样本方差是一个合理的起点。

计算成本和可扩展性

瓶颈在于反转 (K+σn2I)(K + \sigma_n^2 I),这需要花费 O(n3)O(n^3) 及时和 O(n2)O(n^2) 记忆中。立方墙已经在这个博客上从另一个方向进行了争论:搜索方法与评估成本当目标很便宜时,彻底取消基于 GP 的贝叶斯优化的资格,因为替代成本超过了它节省的评估成本。这里的算术是相同的;应用程序不同。作为适合市场数据的“模型”,三次项并不是取消资格,而是预算:它为训练窗口设置了硬性上限。

交易应用程序的可扩展性策略:

  1. 稀疏 GP(诱导点)。 替换 n×nn \times n 矩阵有一个 m×mm \times m 矩阵其中 mnm \ll n。诱发点 Z={z1,,zm}Z = \{z_1, \ldots, z_m\} 是总结训练数据的伪输入。 Hensman 等人提出的 SVGP(随机变分 GP)公式。 (2013) 允许有成本的小批量训练 O(nm2)O(nm^2) 每次迭代。 GPyTorch 本身就支持这一点。

  2. 结构化核插值 (SKI/KISS-GP)。 当输入位于网格上时,利用核矩阵中的 Kronecker 和 Toeplitz 结构。降低成本 O(n+glogg)O(n + g \log g) 在哪里 gg 是网格大小。非常适合定期采样的时间序列(例如 1 分钟柱)。

  3. 滑动窗口上的本地 GP。 仅根据最近的数据训练单独的 GP。这就是 GP 特定约束的作用所在:标准内核是静止的(k(x,x)k(x,x') 仅取决于 xxx - x')而市场则不然,所以通常的答案是滚动窗口——但窗口长度的上限为 O(n3)O(n^3),而不仅仅是统计数据。 前向优化 一般涵盖锚定窗口与滚动窗口、训练/测试长度以及重新优化频率;对于 GP 来说,窗口大小调整既是一种计算决策,也是一种统计决策,并且锚定(不断增长)的窗口在没有近似的情况下根本无法超过几千个点。这种重构是实际的结果:对于全科医生来说,你不能选择“使用所有历史记录”。

回报预测 GP:实用框架

功能设计:为什么是 5-20 个功能,而不是 500 个

市场数据机器学习的一般特征分类——订单不平衡、账面压力、VPIN、Kyle lambda、已实现波动率特征、循环时间编码、跨资产和融资率信号——已经在机器学习的传播建模中列出;使用该列表。

GP 特定的是列表的大小。核方法在高维度上会退化:距离集中,固定核失去辨别能力。金融 GP 的实际输入范围是 5-20 个输入,而不是梯度提升树愉快地吃掉的数百个输入。使其生存的机制是ARD(自动相关性确定):为每个输入维度提供自己的长度尺度 d\ell_d和边际似然训练推动 d\ell_d \to \infty 对于不携带信号的维度,因为无限的长度尺度意味着内核忽略该坐标。特征选择成为拟合的副产品,并且学到的 d\ell_d 作为相关性排名可以直接读取——这也是它可证伪的原因:将复合内核拟合到真实的条上,打印长度尺度,并查看您相信的特征是否是模型保留的特征。

头寸规模和弃权

GP 后验给出 σ(x)\sigma_*(x) 直接,因此它直接进入风险感知头寸调整的保形预测中开发的边缘比调整和无交易过滤器,间隔宽度 wt=2κσ(x)w_t = 2\kappa\sigma_*(x)。唯一的区别是来源:GP 从模型本身而不是从校准集生成宽度,因此它随着测试点相对于训练数据的位置而变化,而不是随着过去残差的全局分位数而变化。

使用 GPyTorch 实现

GPyTorch 是一个基于 PyTorch 的库,用于可扩展的 GP 推理。它利用 GPU 加速、自动微分和现代线性代数技术(共轭梯度、Lanczos 分解)来扩展 GP 超越简单的范围 O(n3)O(n^3) 限制。

用于回报预测的基本精确 GP

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 是实现上面讨论的每维长度尺度的原因。训练结束后, model.covar_matern.base_kernel.lengthscale 是要读出的向量。

训练循环

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

具有不确定性的预测

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(),
    }

fast_pred_var() 上下文管理器使用 LOVE(Lanczos 方差估计)算法来计算预测方差 O(n)O(n) 时间而不是 O(n2)O(n^2)

端到端交易管道

请注意下面的特征构建器:每个滚动统计数据都必须严格向后看,并且输入标准化必须仅适合训练切片。第二点不是一般卫生——它恰恰是在前瞻偏差分类法中剖析和测量的“标准化泄漏”通道,它报告了每种泄漏类型产生的夏普膨胀。 GP 通过构建标准化其输入,因此这是它最容易遭受的泄漏。

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

使用稀疏 GP 扩展到更大的数据集

当训练窗口超过几千点时,精确的 GP 推理会变得很慢。切换到变分稀疏 GP:

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

有 128 个诱导点,每批次成本为 O(1282×batch_size)O(128^2 \times \text{batch\_size}) — 每批大约 400 万次操作。这可以在单个 GPU 上轻松处理超过 100,000 个观测值的数据集。

深度内核学习:GP 遇上神经网络

当输入空间是高维的或者特征和回报之间的关系是高度非线性的时,普通的内核可能会遇到困难。深度内核学习 (DKL) 在应用 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 是一个带有参数的神经网络 ϕ\phikbasek_{\text{base}} 是一个标准内核(例如,Matern-3/2)。网络学习 GP 内核最有效的特征表示。整个模型(网络参数和内核超参数)通过最大化边际似然进行端到端训练。

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 将神经网络的表示学习与 GP 的不确定性量化相结合。 GP 层确保远离训练数据的预测具有高度不确定性——众所周知,标准神经网络无法提供这一点。还要注意,DKL 准确地重新引入了边际似然应该监管的灵活性:奥卡姆因子惩罚内核,但它前面的网络有数千个自由参数,并且没有这样的惩罚。

Ludkovski 和 Risk (2025),定量金融的高斯过程模型,在更广泛的定量金融背景下对 DKL 进行了调查,包括期权定价和投资组合优化;这些结果是他们的,针对他们的问题,并不是加密货币回报预测的证据。

诊断和陷阱

校准

经过良好校准的全科医生具有与经验覆盖范围相匹配的预测区间。该检查与共形预测中使用的检查相同——它还涵盖了为什么边际覆盖率不是条件覆盖率的理论,这一限制同样适用于 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}"
        )

1/2/3 西格玛时的预期超出值为 0.317 / 0.046 / 0.003。重要的数字是在加密货币条上真正向前运行时打印的数字,而该表尚未出现在本文中。先前的预期是 GP 过于自信——肥尾、非平稳回报的高斯似然值应该在 3 sigma 下表现得很差——但“应该”并不是一个衡量标准。

剩余的陷阱

  1. 输入缩放。 长度比例与输入比例相关,因此特征范围 [0,10000][0, 10000] 和一个远程 [0,1][0, 1] 无法分享有意义的 \ell; ARD 可以拯救异构范围,而标准化则可以使 ARD 的初始化更加理智。

  2. 过度拟合边际似然。 对于许多核超参数(尤其是复合或谱混合核),边际似然仍然可能过度拟合。使用先验:以中值成对距离为中心的长度尺度上的对数正态先验,方差上的半正态先验。

  3. 协方差矩阵调节。 (K+σn2I)(K + \sigma_n^2 I) 当噪声可以在数值上变得奇异 σn2\sigma_n^2 太小或者训练点几乎重复时。 GPyTorch 添加 10610^{-6} 对角线抖动;条件较差的财务数据往往需要更多。

  4. 前瞻偏差。 上文已介绍,并在前瞻偏差分类法 中详细介绍。

何时使用 GP 与其他模型

标准 全科医生 XGBoost 神经网络
内在的不确定性 是(结构性) 否(需要保形/引导) 否(需要 MC 退出/合奏)
数据效率 优秀(<1000 个样本) * *
可扩展性 差精确,好稀疏 * *
非线性 依赖于内核 * *
可解释性 核分解 + ARD 长度尺度 * *
非平稳性 需要推拉窗或DKL * *

* 对于 XGBoost 和神经网络专栏,请遵循已发布的比较,而不是此处的新断言:使用机器学习进行传播建模 具有金融表格数据的梯度提升与深度学习表(数据大小阈值、可解释性、机制适应、延迟)和时间融合Transformer 有 TFT、LSTM 和普通 Transformer。

在以下情况下使用全科医生:

  • 您拥有中小型数据集(每个训练窗口约 10,000 个观察值)
  • 您需要关于信号的明确的、可检查的结构假设(趋势+季节性+噪声)
  • 不确定性必须随与训练数据的距离而变化,而不仅仅是随全局残差分位数而变化

在以下情况下不要使用全科医生:

  • 您需要对数百万个观察结果进行亚毫秒级推理
  • 输入维度超过~50
  • 信号存在于固定核无法表示的复杂高阶特征交互中(DKL 有帮助,但以奥卡姆性质为代价)

本文尚未衡量的内容

以上所有都是模型机械。这些都不是 GP 通过加密货币赚钱的证据,而该博客的标准是文章带有自己的数字。未清项目按其运行顺序排列:

  1. 真实 BTCUSDT 1m 条上的 ARD 长度刻度。 适合复合内核,打印 d\ell_d 每个功能。这直接测试了“ARD 是内置特征选择”的说法,并产生了可伪造的特征相关性排名。
  2. 前向运行的校准表。 1/2/3 sigma 处的预期超出与经验超出,明确说明,包括 GP 证明过于自信的情况。
  3. **信心门控策略,向前迈进,在与诚实的负面相同的五个专业上,对试验计数进行缩减。**如果失败,它将作为另一个负面结果插入该系列。
  4. 挂钟 O(n3)O(n^3) 曲线n=250/500/1000/2000/5000n = 250 / 500 / 1000 / 2000 / 5000,精确与 SVGP,因此可扩展性部分依赖于图表而不是断言。

结论

高斯过程在交易中的情况并不是“它们给你误差线”——保形预测给你误差线,具有较少的分布假设和 GP 没有的覆盖保证。情况是,GP 让你将结构假设写成内核,将其与以其自身复杂性定价的目标进行拟合,然后通过 ARD 长度尺度告诉你它实际使用了哪些特征。这是一个异常清晰的模型。

成本同样具体:三次缩放限制了你的训练窗口,滚动窗口只能部分修复的平稳性假设,肥尾回报将违反的高斯可能性,以及——一旦你进行深度核学习——最初激发边际可能性的奥卡姆性质的悄然损失。剩下的东西是否可以交易是一个经验问题,上面列出的四个衡量标准可以回答这个问题。

参考文献

  • Rasmussen, C. E. 和 Williams, C. K. I. (2006)。 机器学习的高斯过程。麻省理工学院出版社。
  • 威尔逊,A. 和亚当斯,R. (2013)。用于模式发现和外推的高斯过程内核。 ICML
  • Hensman, J.、Fusi, N. 和 Lawrence, N.D. (2013)。大数据的高斯过程。 UAI
  • Gatheral, J.、Jaisson, T. 和 Rosenbaum, M. (2018)。波动性很大。 定量金融,18(6)。
  • Rizvi, S.A.A.、Roberts, S.J.、Osborne, M.A. 和 Nyikosa, F. (2017)。用高斯过程包络线预测金融波动的新方法。 arXiv:1705.00891
  • Ludkovski, M. 和 Risk, J. (2025)。 定量金融的高斯过程模型。施普林格。
  • Gardner, J. R.、Pleiss, G.、Bindel, D.、Weinberger, K. Q. 和 Wilson, A. G. (2018)。 GPyTorch:具有 GPU 加速的黑盒矩阵-矩阵高斯过程推理。 NeurIPS
免责声明:本文提供的信息仅用于教育和参考目的,不构成财务、投资或交易建议。加密货币交易涉及重大损失风险。

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

紧跟市场步伐

订阅我们的时事通讯,获取独家 AI 交易见解、市场分析和平台更新。

我们尊重您的隐私。您可以随时退订。