← К списку статей
August 6, 2026
5 мин. чтения

Гауссовы процессы для непараметрического моделирования цен

Гауссовы процессы для непараметрического моделирования цен
#bayesian
#gaussian-process
#kernel
#uncertainty
#non-parametric

Входит в серию «Классические базовые показатели машинного обучения».

Две вещи делают гауссовский процесс достойным отдельной статьи в этом блоге, и ни одна из них не означает, что «он дает вам неопределенность».

Первый — проектирование ядра. Все индуктивное смещение врача общей практики заключено в одной функции. k(x,x)k(x, x'), и эту функцию вы записываете намеренно: насколько неровным является путь, повторяется ли он, затухает ли повторение. Ничто другое в стандартном наборе инструментов не позволяет вам сформулировать структурную гипотезу о динамике рынка, которая бы в явном виде соответствовала ей. Второе — это «предельная вероятность» — цель обучения со штрафом за сложность, полученным из самой модели, а не из заранее заданного набора. Все остальные статьи в области переоснащения в этом блоге (анализ плато, PBO, deflated Sharpe) существуют потому, что регуляризация проверочного набора хрупка при поиске. Врач общей практики утверждает, что он не нужен. Это утверждение поддается проверке, и его проверка более интересна, чем еще одно руководство по определению неопределенности.

Что касается самой неопределенности: апостериорная дисперсия GP является структурной — она выпадает из того же вывода, который дает среднее значение, а не обертывается впоследствии подобранной моделью. Это настоящий контраст с конформным предсказанием, в котором уже рассказывается в этом блоге, почему неопределенность является правильным исходным фактором для определения размера позиции и что делать с интервалом, когда он у вас есть. В этой статье не обсуждается этот случай; он идет за моделью.

Далее следует ядро ​​и механизм вывода, реализация GPyTorch и — ясно сказано в конце — измерения, которых в этой статье еще нет.

Что такое гауссов процесс?

GP уже появляются в этом блоге как суррогат байесовской оптимизации в Optuna vs. координатный спуск с теми же обозначениями и тем же предостережением относительно низкой размерности. Здесь 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 нет фиксированного количества параметров — сложность модели растет вместе с данными. Имея 10 наблюдений, GP определяет 10-мерную гауссиану. Имея 10 000 наблюдений, он определяет 10 000-мерную гауссиану.

Это не означает, что у врачей общей практики нет гиперпараметров. Функция ядра имеет гиперпараметры (масштабы длин, амплитуды, периодичности), которые управляют свойствами функций, взятых из априора. Но сама функциональная форма никогда не бывает фиксированной. GP может представлять любую непрерывную функцию при наличии достаточного количества данных и правильного ядра. Вот что означает «непараметрическое» — модель не ограничена параметрическим семейством, таким как линейные функции или полиномы.

Для финансового моделирования это ценно. Рынки меняются. Отношения между характеристиками и доходностью нелинейны, нестационарны и зависят от режима. Параметрические модели навязывают структуру, которая может не соответствовать реальности. Врачи общей практики позволяют данным говорить.

Функции ядра: кодирование структуры рынка

Функция ядра k(x,x)k(x, x') является душой гауссовского процесса. Он определяет, какие функции априори вероятны, путем указания ковариации между значениями функции в любых двух входных точках. Различные ядра кодируют разные предположения о гладкости, периодичности и поведении на больших расстояниях.

Радиальная базисная функция (RBF)/квадратичная экспонента

Ядро RBF является наиболее распространенной отправной точкой:

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 это масштаб длины. Функции, полученные из GP с ядром RBF, бесконечно дифференцируемы — очень гладкие.

Торговая интерпретация: Шкала длины \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ядро Матерна сходится к RBF. Распространенный выбор:

  • ν=1/2\nu = 1/2: Эквивалент процесса Орнштейна-Уленбека. Функции непрерывны, но не дифференцируемы — грубы, как броуновское движение.
  • ν=3/2\nu = 3/2: Функции однажды дифференцируемы. Хороший баланс между плавностью и гибкостью.
  • ν=5/2\nu = 5/2: Функции дважды дифференцируемы. Гладче, чем 3/23/2 но менее жесткий, чем RBF.

Торговая интерпретация: Матерн-3/23/2 ядро, возможно, является лучшим значением по умолчанию для финансовых временных рядов. Это допускает ту шероховатость, которую демонстрируют реальные ценовые траектории, но при этом не будучи такими изрезанными, как ν=1/2\nu = 1/2. Это согласуется с литературой о том, что волатильность является грубой (Gatheral, Jaisson & Rosenbaum, 2018), которая эмпирически показывает, что траектория волатильности имеет показатели Херста около H0.1H \approx 0.1, гораздо более грубое, чем броуновское движение.

Основным опубликованным доказательством того, что ядра Матерна превосходят классические модели волатильности, являются Rizvi et al. (2017), которые сообщают, что MSE примерно на 20% лучше, чем случайное блуждание, и на 50% лучше, чем GARCH — на ежедневных данных о валютных парах за 2017 год, а не на криптовалютах и ​​не воспроизведенных здесь. Рассматривайте это как мотивацию для тестирования ядра, а не как эталон. Собственный GARCH(1,1) этого блога, соответствующий реальным ежедневным данным BTC, с диагностикой Ljung-Box и ARCH-LM, находится в Прогнозирование волатильности GARCH для криптовалюты; Честное сравнение было бы очным состязанием с Матерном 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_1 и k2k_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. Негладкая, апериодическая трендовая составляющая (Матерн-3/2).
  2. Периодическая составляющая, амплитуда которой затухает со временем (Периодическая ×\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), задняя часть ГП в контрольных точках 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

Первый термин представляет собой термин соответствия данных (наказывается прогнозами, далекими от наблюдений). Второй член — это штраф за сложность (наказывается за слишком гибкие модели, т. е. в которых матрица ядра имеет большой определитель). Третий член представляет собой константу нормализации.

Это автоматическая бритва Оккама, и это самая интересная вещь, которую врач общей практики привносит в торговый процесс. Сильная версия утверждения заключается в том, что для регуляризации не требуется отдельный набор проверок — штраф за сложность находится внутри цели, поэтому модель не может быть идеальной и гибкой бесплатно.

Это утверждение заслуживает скептицизма, особенно в этом блоге. Анализ плато показывает, что однобалльная оценка валидации является плохим критерием выбора и что устойчивость зависит от формы окрестности; PBO определяет, как часто победитель в выборке проигрывает вне выборки; deflated Sharpe цены в количестве испытаний. Предельная вероятность по-прежнему является внутривыборочной целью, максимизируемой в течение θ\theta — Фактор Оккама наказывает емкость модели, а не выбор из множества подобранных ядер. Если вы подберете двенадцать ядер-кандидатов и выберете одно с наибольшей предельной вероятностью, вы вернетесь на территорию множественного тестирования, и логика дефлятирования останется неизменной. Фальсифицируемая версия: приводит ли выбор предельного правдоподобия к меньшему разрыву между выборкой и вне выборки, чем выбор проверочного набора на тех же данных и том же семействе ядер? Это измеримо и не измеряется ниже.

Поверхность предельного правдоподобия также имеет локальные оптимумы. Многократные случайные перезапуски или тщательная инициализация имеют значение — инициализация шкалы длины медианным парным расстоянием обучающих входных данных и дисперсией шума выборочной дисперсией целей является разумной отправной точкой.

Вычислительные затраты и масштабируемость

Узкое место инвертируется (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\} являются псевдовходными данными, которые суммируют обучающие данные. Формулировка SVGP (стохастический вариационный GP), предложенная Hensman et al. (2013) позволяет проводить мини-пакетное обучение за небольшие деньги. O(nm2)O(nm^2) за итерацию. GPyTorch поддерживает это изначально.

  2. Интерполяция структурированного ядра (SKI/KISS-GP). Использует структуру Кронекера и Теплица в матрице ядра, когда входные данные лежат в сетке. Снижает стоимость до O(n+glogg)O(n + g \log g) где gg это размер сетки. Идеально подходит для временных рядов с регулярной выборкой (например, 1-минутные бары).

  3. Местные врачи общей практики в скользящих окнах. Обучайте отдельных врачей общей практики только на основе последних данных. Здесь вступает в силу ограничение, специфичное для GP: стандартные ядра стационарны (k(x,x)k(x,x') зависит только от xxx - x'), а рынки нет, поэтому обычный ответ — скользящее окно, но длина окна ограничена сверху O(n3)O(n^3), а не только по статистике. Поступательная оптимизация охватывает фиксированные и скользящие окна, продолжительность обучения/тестирования и частоту повторной оптимизации в целом; для врача общей практики размер окна — это не только статистическое, но и вычислительное решение, и фиксированное (постоянно растущее) окно просто недоступно после нескольких тысяч точек без аппроксимации. Это переосмысление является практическим следствием: с врачами общей практики вы не можете выбрать «использовать всю историю».

GP для прогнозирования доходности: практическая основа

Дизайн функций: почему 5–20 функций, а не 500

Общая таксономия функций ML для рыночных данных — дисбаланс в книге заказов, давление на книги, VPIN, лямбда Кайла, функции реализованной волатильности, циклическое кодирование времени, сигналы перекрестных активов и ставок финансирования — уже изложены в моделирование распространения с помощью машинного обучения; используйте этот список.

Что зависит от врача общей практики, так это размер списка. Методы ядра ухудшаются в больших размерностях: расстояния концентрируются, и стационарное ядро ​​теряет распознавание. Практический диапазон для финансового терапевта составляет 5-20 входных данных, а не сотни, которые с радостью съедает дерево с градиентным усилением. Механизм, который делает это жизнеспособным, — это ARD (автоматическое определение релевантности): присвойте каждому входному измерению свой собственный масштаб длины. d\ell_d, а обучение предельному правдоподобию подталкивает d\ell_d \to \infty для измерений, которые не несут никакого сигнала, поскольку бесконечная шкала длины означает, что ядро ​​игнорирует эту координату. Выбор функций становится побочным продуктом подгонки, а полученные знания d\ell_d читаются напрямую как рейтинг релевантности — что также делает его фальсифицируемым: сопоставьте составное ядро ​​с реальными столбцами, распечатайте масштабы длин и посмотрите, являются ли функции, в которые вы верите, теми, которые сохраняет модель.

Определение размера позиции и воздержание

Задний ГП дает σ(x)\sigma_*(x) напрямую, поэтому он попадает прямо в фильтр определения соотношения преимуществ и отсутствия торговли, разработанный в [Конформное прогнозирование для определения размера позиции с учетом риска] (/ru/blog/post/conformal-prediction-trading), с шириной интервала wt=2κσ(x)w_t = 2\kappa\sigma_*(x). Единственное отличие заключается в происхождении: GP определяет ширину на основе самой модели, а не на основе калибровочного набора, поэтому она зависит от того, где находится контрольная точка относительно обучающих данных, а не от глобального квантиля прошлых остатков.

Реализация с помощью GPyTorch

GPyTorch — это библиотека на основе PyTorch для масштабируемого вывода GP. Он использует ускорение графического процессора, автоматическое дифференцирование и современные методы линейной алгебры (сопряженные градиенты, разложение Ланцоша) для масштабирования 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(),
    }

The fast_pred_var() контекстный менеджер использует алгоритм LOVE (Lanczos Variance Estimates) для вычисления прогнозируемых отклонений в O(n)O(n) время вместо O(n2)O(n^2).

Сквозной торговый конвейер

Обратите внимание на построитель признаков ниже: каждая скользящая статистика должна быть строго обратной, а стандартизация входных данных должна применяться только к обучающему срезу. Этот второй момент не является общей гигиеной — это именно канал утечки нормализации, расчлененный и измеренный в таксономии прогнозного смещения, которая сообщает об инфляции Шарпа, которую производит каждый тип утечки. Врач общей практики стандартизирует свои входные данные по своей конструкции, так что это утечка, которой он наиболее подвержен.

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}) — примерно 4 миллиона операций на пакет. Это позволяет легко обрабатывать наборы данных из более чем 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 это нейронная сеть с параметрами ϕ\phi и kbasek_{\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 вновь вводит именно ту гибкость, которую должно было обеспечивать предельное правдоподобие: фактор Оккама наказывает ядро, но сеть перед ним имеет тысячи свободных параметров и такого штрафа нет.

Людковски и Риск (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}"
        )

Ожидаемое превышение составляет 0,317/0,046/0,003 при 1/2/3 сигме. Число, которое имеет значение, — это то, что это печатает при реальном прогоне по крипто-барам, и этой таблицы еще нет в этой статье. Предварительное ожидание состоит в том, что GP слишком самоуверен — гауссова вероятность нестационарных доходностей с толстыми хвостами должна плохо скрываться при 3 сигмах — но «должен» не является показателем.

Оставшиеся подводные камни

  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 и нейронных сетей, обратитесь к опубликованным сравнениям, а не к свежему утверждению здесь: моделирование распространения с помощью машинного обучения содержит таблицу повышения градиента и глубокого обучения для финансовых табличных данных (пороги размера данных, интерпретируемость, адаптация режима, задержка) и временное слияние Transformers имеет TFT, LSTM и ванильный Transformer.

Воспользуйтесь услугами врачей общей практики, если:

  • У вас есть наборы данных малого и среднего размера (менее 10 000 наблюдений за окно обучения).
  • Вам нужна явная, проверяемая структурная гипотеза о сигнале (тренд + сезонность + шум).
  • Неопределенность должна меняться в зависимости от расстояния от обучающих данных, а не только от глобального остаточного квантиля.

Не пользуйтесь услугами врачей общей практики, если:

  • Вам нужен субмиллисекундный вывод на основе миллионов наблюдений
  • Входная размерность превышает ~50
  • Сигнал существует в сложных взаимодействиях функций высокого порядка, которые не могут быть представлены стационарными ядрами (помогает DKL за счет свойства Оккама)

Что еще не измеряется в этой статье

Все вышеперечисленное является модельной техникой. Ничто из этого не является доказательством того, что врач общей практики зарабатывает деньги на криптовалюте, и стандартом этого блога является то, что в статье приводятся собственные цифры. Открытые элементы в порядке их запуска:

  1. Длина ARD масштабируется на реальных 1-метровых барах BTCUSDT. Установите составное ядро, распечатайте d\ell_d за функцию. Это напрямую проверяет утверждение «ARD — это выбор встроенных функций» и дает фальсифицируемый рейтинг релевантности функций.
  2. Таблица калибровки на основе прогнозного анализа. Ожидаемое и эмпирическое превышение при 1/2/3 сигма, изложенное ясно, включая случай, когда врач общей практики оказывается слишком самоуверенным.
  3. Стратегия, основанная на доверии, продвижение вперед по тем же пяти основным направлениям, что и честный отрицательный, сброшенная для подсчета проб. Если она терпит неудачу, она попадает в эту серию как еще один отрицательный результат.
  4. Настенные часы O(n3)O(n^3) кривая для n=250/500/1000/2000/5000n = 250 / 500 / 1000 / 2000 / 5000, точно по сравнению с SVGP, поэтому раздел масштабируемости основан на диаграмме, а не на утверждении.

Заключение

Случай с гауссовскими процессами в трейдинге заключается не в том, что «они дают вам планки ошибок» — конформное предсказание дает вам планки ошибок с меньшим количеством допущений о распределении и гарантией покрытия, которой нет у GP. Дело в том, что врач общей практики заставляет вас записать вашу структурную гипотезу в виде ядра, сопоставляет ее с целью, которая оценивает ее собственную сложность, а затем сообщает вам, какие из ваших функций он фактически использовал, через шкалы длины ARD. Это необычайно разборчивая модель.

Затраты одинаково конкретны: кубическое масштабирование, которое ограничивает ваше окно обучения, предположения о стационарности, которые скользящее окно восстанавливает лишь частично, гауссова вероятность того, что доходность с толстым хвостом будет нарушена, и — как только вы достигнете глубокого обучения ядра — тихая потеря того самого свойства Оккама, которое в первую очередь мотивировало предельную вероятность. Пригодно ли то, что осталось, для торговли – это эмпирический вопрос, и четыре измерения, перечисленные выше, помогут ответить на него.

Ссылки

  • Расмуссен, CE, и Уильямс, CKI (2006). Гауссовы процессы для машинного обучения. МТИ Пресс.
  • Уилсон А. и Адамс Р. (2013). Ядра гауссовского процесса для обнаружения и экстраполяции закономерностей. ИКМЛ.
  • Хенсман Дж., Фузи Н. и Лоуренс Н. Д. (2013). Гауссовы процессы для больших данных. УАИ.
  • Гатерал Дж., Джейссон Т. и Розенбаум М. (2018). Волатильность грубая. Количественные финансы, 18(6).
  • Ризви, С.А.А., Робертс, С.Дж., Осборн, Массачусетс, и Никоса, Ф. (2017). Новый подход к прогнозированию финансовой волатильности с помощью гауссовских конвертов процессов. arXiv:1705.00891.
  • Людковски М. и Риск Дж. (2025). Модели гауссовских процессов для количественных финансов. Спрингер.
  • Гарднер Дж. Р., Плейсс Г., Биндел Д., Вайнбергер К. К. и Уилсон А. Г. (2018). GPyTorch: матрица черного ящика-матрица гауссовского процесса с ускорением графического процессора. НейриПС.
Дисклеймер: Информация в этой статье предоставлена исключительно в образовательных и ознакомительных целях и не является финансовым, инвестиционным или торговым советом. Торговля криптовалютами сопряжена с высоким риском убытков.

Авторы

Eugen Soloviov
Eugen Soloviov

Инженер торговых систем

Разработка торговых ботов с 2017 года: межбиржевой арбитраж (подключал до 30 бирж), парный арбитраж на коинтеграции между спотом и фьючерсами, скальпинг, фронтраннинг, торговля по новостям, сентиментный анализ, трендовые алгоритмы, а также алгоритмы управления и балансировки портфелей. Делает выставление ордеров до 1 мс, warehouse для big data, бэктестинг-движки, AI-агентов и интерфейсы для ботов (в т.ч. open-source profitmaker.cc). Стек: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, архитектура.

Newsletter

Будьте в курсе событий

Подпишитесь на нашу рассылку, чтобы получать эксклюзивную аналитику по AI-трейдингу и обновления платформы.

Мы уважаем вашу конфиденциальность. Отписаться можно в любой момент.