English 中文 Español Deutsch 日本語 Português
preview
Возможности Мастера MQL5, которые вам нужно знать (Часть 33): Ядра гауссовского процесса

Возможности Мастера MQL5, которые вам нужно знать (Часть 33): Ядра гауссовского процесса

MetaTrader 5Индикаторы |
565 0
Stephen Njuki
Stephen Njuki

Введение

Ядра гауссовского процесса (Gaussian Process Kernels) - это ковариационные функции, используемые в гауссовских процессах для измерения взаимосвязей между точками данных, например, во временных рядах. Эти ядра генерируют матрицы, которые фиксируют взаимосвязь внутри данных, позволяя гауссовскому процессу делать прогнозы или предсказания, исходя из того, что данные подчиняются нормальному распределению. Поскольку эти серии направлены на изучение новых идей, а также на изучение того, как эти идеи могут быть использованы, ядра гауссовского процесса (GP) могут быть использованы при создании пользовательского сигнала.

За последние пять статей мы рассмотрели много тем, связанных с машинным обучением, поэтому в этой статье мы сделаем перерыв и обратимся к старой доброй статистике. Статистика и машинное обучение зачастую взаимосвязаны, однако при разработке этого конкретного пользовательского сигнала мы не будем рассматривать алгоритмы машинного обучения. Ядра GP заслуживают внимания благодаря своей гибкости.

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

Кроме того, они хорошо справляются с обработкой зашумленных данных, поскольку позволяют увеличивать значение шума до созданной матрицы K (см. ниже). Также в них можно включать предыдущие знания, плюс они очень масштабируемы. Имеется довольно много различных ядер на выбор. Список включает в себя (но не ограничивается): квадратичное экспоненциальное ядро (RBF), линейное ядро, периодическое ядро, рациональное квадратичное ядро, ядро Матерна, экспоненциальное ядро, полиномиальное ядро, ядро белого шума, ядро скалярного произведения, ядро спектральной смеси, постоянное ядро, косинусное ядро, ядро нейронной сети (арккосинуса) и ядра произведения и суммы.

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

где

  • x и x′: - входные векторы или точки во входном пространстве.
  • σ f 2 - параметр дисперсии ядра, который служит предопределенным или оптимизируемым параметром.
  • - шкала длины. Контролирует гладкость результирующей функции. Меньшее значение l (или более короткий масштаб длины) приводит к быстро меняющейся функции, тогда как большее значение l (больший масштаб длины) приводит к более плавной функции.
  • exp - экспоненциальная функция.

Реализация ядра в MQL5 проста:

//+------------------------------------------------------------------+
// RBF Kernel Function
//+------------------------------------------------------------------+
matrix CSignalGauss::RBF_Kernel(vector &Rows, vector &Cols)
{  matrix _rbf;
   _rbf.Init(Rows.Size(), Cols.Size());
   for(int i = 0; i < int(Rows.Size()); i++)
   {  for(int ii = 0; ii < int(Cols.Size()); ii++)
      {  _rbf[i][ii] = m_variance * exp(-0.5 * pow(Rows[i] - Cols[ii], 2.0) / pow(m_next, 2.0));
      }
   }
   return(_rbf);
}


Гауссовские процессы в финансовых временных рядах

Гауссовские процессы представляют собой вероятностную структуру, в которой прогнозы делаются на основе распределений, а не фиксированных значений. Вот почему его также называют непараметризованным фреймворком. Выходные прогнозы включают в себя среднее значение прогноза и дисперсию (или неопределенность) вокруг этого прогноза. Дисперсия GP отражает неопределенность или уверенность в сделанном прогнозе. Эта неопределенность в принципе случайна, поскольку она основана на нормальном распределении GP. Однако, поскольку она подразумевает интервал и уровень уверенности (обычно основанный на 95%), не всем ее прогнозам обязательно нужно следовать. По сравнению с другими методами статистического прогнозирования, такими как ARIMA, GP отличается высокой гибкостью и способен моделировать сложные нелинейные зависимости с количественной оценкой неопределенности, в то время как ARIMA лучше всего работает со стационарными временными рядами с заданными структурами. Главным недостатком GP является то, что он требует больших вычислительных затрат, а такие методы, как ARIMA, - нет.


Ядро RBF

Вычисление ядра GP в основном включает 6 матриц и 2 вектора. Все эти матрицы и векторы используются в нашей функции GetOutput, код которой вкратце приведен ниже:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CSignalGauss::GetOutput(double BasisMean, vector &Output)
{  
   ...

   matrix _k = RBF_Kernel(_past_time, _past_time);
   matrix _noise;
   _noise.Init(_k.Rows(), _k.Cols());
   _noise.Fill(fabs(0.05 * _k.Min()));
   _k += _noise;
   matrix _norm;
   _norm.Init(_k.Rows(), _k.Cols());
   _norm.Identity();
   _norm *= 0.0005;
   _k += _norm;
   vector _next_time;
   _next_time.Init(m_next);
   for(int i = 0; i < m_next; i++)
   {  _next_time[i] = _past_time[_past_time.Size() - 1] + i + 1;
   }
   matrix _k_s = RBF_Kernel(_next_time, _past_time);
   matrix _k_ss = RBF_Kernel(_next_time, _next_time);
// Compute K^-1 * y
   matrix _k_inv = _k.Inv();
   if(_k_inv.Rows() > 0 && _k_inv.Cols() > 0)
   {  vector _alpha = _k_inv.MatMul(_past);
      // Compute mean predictions: mu_* = K_s * alpha
      vector _mu_star = _k_s.MatMul(_alpha);
      vector _mean;
      _mean.Init(_mu_star.Size());
      _mean.Fill(BasisMean);
      _mu_star += _mean;
   // Compute covariance: Sigma_* = K_ss - K_s * K_inv * K_s^T
      matrix _v = _k_s.MatMul(_k_inv);
      matrix _sigma_star = _k_ss - (_v.MatMul(_k_s.Transpose()));
      vector _variances = _sigma_star.Diag();
      //Print(" sigma star: ",_sigma_star);
      Print(" pre variances: ",_variances);
      Output = _mu_star;
      SetOutput(Output);
      SetOutput(_variances);
      Print(" variances: ",_variances);
   }
}

Перечень матриц и векторов:

  • _k
  • _k_s
  • _k_ss
  • _k_inv
  • _v
  • и_sigma_star

_k - якорь и основная ковариационная матрица. Она фиксирует взаимосвязи между парой входных векторов и используемой функцией ядра. Мы используем ядро радиальной базисной функции (RBF), исходный код которого уже был представлен выше. Поскольку мы делаем прогноз для временного ряда, входными данными для нашего ядра RBF будут два схожих временных вектора, которые регистрируют временные индексы ряда данных, который мы хотим спрогнозировать. Кодирование сходств между зарегистрированными временными точками служит основой для определения общей структуры. Имейте в виду, что существует множество других форматов ядра, как видно из четырнадцати, перечисленных выше, и в будущих статьях мы могли бы рассмотреть эти альтернативы в качестве пользовательского класса сигналов.

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

Эту матрицу можно определить как:

где

  • k(x i , y i ) - радиальная базисная функция между двумя значениями в векторах x и y индекса i. В нашем случае x и y — один и тот же входной вектор времени, поэтому оба вектора обозначены как x. x — это обычно обучающие данные, а y — заполнитель для тестовых данных.
  • k s — ковариационный вектор (или матрица в четной шкале длины, или число проекций превышает 1) между обучающими данными и тестовой точкой x∗.
  • x 1 , x 2 ,…, xn, - входные точки обучающих данных.
  • x∗ - точка входных тестовых данных, в которой необходимо сделать прогнозы.

То есть матрица _k_ss по сути является зеркальным отражением матрицы _k. Ключевым отличием которой является то, что она представляет собой ковариацию индексов времени прогноза. Поэтому ее размеры соответствуют входному параметру шкалы длины. Она играет важную роль в вычислении оценки неопределенности прогноза. Матрица измеряет дисперсию индексов времени прогнозирования до включения данных. В формуле это можно выразить так:

где

  • k ss - ковариационная матрица точек тестовых данных.
  • x∗, x∗′ - входные точки тестовых данных, по которым должны быть сделаны прогнозы.
  • k(x∗,x∗′) - функция ядра, примененная к паре тестовых точек 𝑥∗ и 𝑥∗′.

_k_inv является матрицей, обратной матрице k. Инверсия матриц выполняется встроенными функциями, однако и здесь возможны ошибки, поскольку часто инверсия невозможна. Вот почему в нашем коде мы проверяем, есть ли в перевернутой матрице какие-либо строки или столбцы. Если ничего не обнаружено, это означает, что инверсия не удалась. Инверсия важна для вычисления весовых коэффициентов, применяемых к обучающим данным во время прогнозирования. Можно рассмотреть альтернативные пользовательские методы выполнения инверсии, такие как метод Холецкого (при условии, что это не встроенный подход), однако их кодирование в качестве опций остается на усмотрение читателя.

Вектор веса используется для объединения результатов обучения на основе их ковариационной структуры. _alpha определяет, насколько сильно новые точки данных влияют на прогноз относительно точек теста/шкалы длины. Мы получаем альфу из матричного произведения инвертированной матрицы k и необработанных старых данных, для которых мы ищем прогноз. Это подводит нас к нашей цели - вектору _mu_star. Фиксируются прогнозы по входному периоду шкалы длины на основе предыдущих данных, размер которых определяется входным параметром m_past в пользовательском классе сигнала. Таким образом, число прогнозируемых средних значений определяется шкалой длины, которую мы в классе называем m_next. Поскольку векторы в MQL5 не сортируются как ряды (при копировании и работе с коэффициентами), прогнозируется, что наивысший индекс этого прогнозируемого вектора появится последним, а нулевой индекс должен появиться в ближайшее время. Это означает, что помимо оценки того, какие значения появятся следующими в ряду, мы можем также спрогнозировать тенденцию, которая последует, в зависимости от величины параметра шкалы длины. Эти прогнозируемые значения также называются контрольными точками.

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

где

  • k ss - ковариационная матрица новых точек данных (_k_ss).
  • k s - ковариационная матрица между новыми точками данных и обучающими данными (_k_s).
  • K - ковариационная матрица обучающих данных, первая матрица, которую мы определили.

Из нашего уравнения выше следует, что матрица _v указывается как произведение _k_s и обратного K-1. Мы проводим тестирование на 95% уверенности, и благодаря таблицам нормального распределения это означает, что для каждой контрольной точки значение верхней границы равно прогнозируемому среднему значению плюс 1,96, умноженному на соответствующее значение дисперсии в матрице. Наоборот, нижнее граничное значение равно прогнозируемому среднему значению за вычетом 1.96, умноженному на значение дисперсии из нашей матрицы _sigma_star. Эта дисперсия помогает количественно оценить уверенность в средних прогнозах, причем, что важно, большие значения указывают на большую неопределенность. Таким образом, чем шире диапазон уверенности (от верхнего до нижнего значения), тем менее уверенным следует быть!

Вычисления ядра GP включают в себя поиск инверсии матрицы, и, как уже упоминалось выше, читатель может провести дополнительную настройку, рассмотрев такие подходы, как разложение Холецкого, чтобы минимизировать ошибки и нечисловые значения (NaN). Помимо этого, общая методика нормализации, применяемая к матрице _k на ранней стадии ядра GP представляет собой добавление небольшого ненулевого значения по ее диагонали, чтобы предотвратить появление отрицательных значений матрицы _sigma_star. Напомним, что эта матрица имеет квадраты значений стандартного отклонения или значения дисперсии, поэтому все ее значения должны быть положительными. И как ни странно, пропуск этого небольшого прибавления по диагонали матрицы _k может привести к тому, что эта матрица будет иметь отрицательные значения! Поэтому это важный шаг, и он указан в листинге функции получения выходных данных выше.

Итак, наши прогнозы дают нам две вещи: прогноз и то, насколько мы должны быть уверены в этом прогнозе. В этой статье, как можно увидеть ниже, мы проводим тесты, полагаясь исключительно на примерную проекцию, не принимая во внимание уровни уверенности, подразумеваемые матрицей _sigma_star. Во многих отношениях это является существенным недооцениванием ядер GP. Так как же мы можем учесть подразумеваемую уверенность прогноза в нашем пользовательском сигнале? Существует множество способов достижения этой цели, но даже до их рассмотрения быстрое решение обычно выходит за рамки класса пользовательских сигналов и заключается в использовании величины достоверности для определения размера позиции.

Это означало бы, что в дополнение к ядру GP в пользовательском классе сигналов у нас также будет еще одно ядро GP в пользовательском классе управления капиталом. И в этой ситуации как класс сигнала, так и классы управления капиталом должны были бы использовать схожие наборы входных данных и дисперсию, а также параметры шкалы длины. Однако в классе пользовательского сигнала простым решением было бы создание вектора дисперсий, который копирует диагональ матрицы _sigma_star, нормализует ее с помощью нашей функции SetOutput, а затем определяет индекс с наименьшим значением. Этот индекс затем будет использоваться в векторе _mu_star для определения состояния пользовательского сигнала. Так как _mu_star нормализуется с помощью той же функции, любые значения ниже 0,5 будут соответствовать отрицательным изменениям, которые будут медвежьими, тогда как любые значения выше 0,5 будут бычьими.

Другой метод использования количественной неопределенности прогноза в пределах класса пользовательских сигналов может заключаться в том, чтобы взять средневзвешенное значение всех прогнозов или проекций по всем контрольным точкам, но использовать нормализованное значение дисперсии в качестве обратного веса. Это будет обратное значение, то есть мы вычитаем его из единицы или инвертируем, прибавляя небольшое значение к знаменателю. Эта инверсия важна, поскольку, как подчеркивалось выше, большая дисперсия подразумевает большую неопределенность. Эти подходы к использованию неопределенности здесь только упоминаются, но не реализованы ни в коде, ни в нашем тестовом прогоне. Читатель может провести собственное тестирование.


Предварительная обработка и нормализация данных

Ядра GP можно использовать для различных финансовых временных рядов данных. В широком смысле эти данные могут быть в абсолютном формате, например, абсолютные цены, или в инкрементном формате, например, изменения цен. В этой статье мы собираемся протестировать ядро RBF с инкрементным форматом данных. Для этого, как видно из листинга GetOutput выше, мы начинаем с заполнения вектора _past разницей двух других векторов, которые копируют цены закрытия из разных точек, с разницей в 1 бар.

В дополнение к этому, мы могли бы нормализовать эти изменения цен, преобразовав их из исходных точек и расположив их в диапазоне от -1,0 до +1,0. Функция SetOutput, которая используется для постнормализации или другого пользовательского метода, также может использоваться для предварительной обработки данных. Однако для нашего тестирования мы используем необработанные изменения цен в формате данных float (double) и применяем нормализацию только к прогнозным значениям (то, что мы выше называли контрольными точками). Код функции SetOutput приведен ниже:

//+------------------------------------------------------------------+
//|
//+------------------------------------------------------------------+
void CSignalGauss::SetOutput(vector &Output)
{  vector _copy;
   _copy.Copy(Output);
   if(Output.HasNan() == 0 && _copy.Max() - _copy.Min() > 0.0)
   {  for (int i = 0; i < int(Output.Size()); i++)
      {  if(_copy[i] >= 0.0)
         {  Output[i] = 0.5 + (0.5 * ((_copy[i] - _copy.Min()) / (_copy.Max() - _copy.Min())));
         }
         else if(_copy[i] < 0.0)
         {  Output[i] = (0.5 * ((_copy[i] - _copy.Min()) / (_copy.Max() - _copy.Min())));
         }
      }
   }
   else
   {  Output.Fill(0.5);
   }
}

Мы использовали эту нормализацию в предыдущих статьях серии. Все, что мы делаем, — это перемасштабируем прогнозируемые векторные значения так, чтобы они находились в диапазоне от 0,0 до +1,0 с важной оговоркой: все, что меньше 0,5, должно быть отрицательным в денормализованном ряду, а все, что больше 0,5, должно быть положительным.


Тестирование и оценка

Для наших тестовых прогонов на ядре RBF мы тестируем пару EURUSD за 2023 год на дневном таймфрейме. Как это часто бывает, настройки получены в результате очень короткого периода оптимизации и не проверяются с помощью форвард-тестов. Применение нашего советника, собранного с помощью Мастера, к реальным рыночным условиям потребует от читателя большей самостоятельности и усердия, чтобы гарантировать проведение обширного тестирования на большей истории и с использованием прямых проверок (перекрестной проверки). Результаты тестового прогона приведены ниже:

r1

c1


Заключение

Мы рассмотрели ядра гауссовского процесса как потенциальный сигнал торговой системы. "Гауссовость" часто подразумевает случайность или исходит из того, что используемая система предполагает, что ее среда случайна, и поэтому она делает ставку с шансом 50 на 50. Однако сторонники идеи утверждают, что предоставленная выборка данных используется для прогнозирования на заранее определенном периоде с вероятностной основой. Утверждается, что этот метод обеспечивает большую гибкость, поскольку не делается никаких предположений о том, чему соответствуют изучаемые данные (то есть он является непараметрическим). Единственное общее предположение заключается в том, что данные подчиняются гауссовскому распределению. Таким образом, хотя неопределенность и включена в процесс и результаты, утверждается, что процесс не основан на случайности.

Кроме того, при кодировании и тестировании ядер гауссовского процесса мы не использовали количественную оценку неопределенности, которая, как можно было бы утверждать, отличает его от других методов прогнозирования. При этом использование и опора на нормальные распределения поднимают вопрос о том, следует ли использовать ядра гауссовского процесса независимо или они лучше всего работают в паре с другим сигналом. Мастер MQL5, основную информацию по которому можно найти здесь и здесь, позволяет легко тестировать более одного сигнала в одном советнике, поскольку каждый выбранный сигнал может быть оптимизирован до подходящего веса. Какой альтернативный сигнал стоит сочетать с этими ядрами? Лучший ответ на этот вопрос может дать только читатель на основе собственного тестирования, однако, на мой взгляд, это осцилляторы, такие как RSI, или стохастик, или балансовый объем (on-balance volume), и даже некоторые новостные индикаторы. Такие индикаторы, как простые скользящие средние, пересечения скользящих средних, ценовой канал и другие индикаторы со значительным запаздыванием, могут работать неэффективно в сочетании с ядрами гауссовского процесса.

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

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/15615

Прикрепленные файлы |
SignalWZ_33.mqh (9.03 KB)
wz_33.mq5 (6.5 KB)
Объединение стратегий фундаментального и технического анализа на языке MQL5 для начинающих Объединение стратегий фундаментального и технического анализа на языке MQL5 для начинающих
В этой статье обсудим, как эффективно интегрировать следование тренду и фундаментальные принципы в один советник для создания более надежной стратегии. Статья продемонстрирует, насколько просто любой желающий может приступить к созданию собственных торговых алгоритмов с помощью языка MQL5.
Разрабатываем мультивалютный советник (Часть 23): Приводим в порядок конвейер этапов автоматической оптимизации проектов (II) Разрабатываем мультивалютный советник (Часть 23): Приводим в порядок конвейер этапов автоматической оптимизации проектов (II)
Мы стремимся создать систему автоматической периодической оптимизации торговых стратегий, используемых в одном итоговом советнике. С развитием система становится всё более сложной, поэтому время от времени надо смотреть на неё в целом с целью выявления узких мест и неоптимальных решений.
От начального до среднего уровня: Массивы и строки (III) От начального до среднего уровня: Массивы и строки (III)
Эта статья посвящена рассмотрению двух аспектов. Во-первых, того, как стандартная библиотека может преобразовывать бинарные значения в другие формы представления, такие как восьмеричная, десятичная и шестнадцатеричная. А во-вторых, мы поговорим о том, как можно определить ширину нашего пароля на основе секретной фразы, используя уже полученные знания.
Торговая стратегия SP500 на языке MQL5 для начинающих Торговая стратегия SP500 на языке MQL5 для начинающих
Узнайте, как использовать язык MQL5 для точного прогнозирования индекса S&P 500, добавляя классический технический анализ для обеспечения стабильности и объединяя алгоритмы с проверенными временем принципы для получения надежной информации о рынке.