Выбор исходных данных

Мы определились с постановкой задачи, теперь перейдем к выбору исходных данных. Здесь есть свои особенности и подходы к решению данной задачи. На первый взгляд может показаться, что можно сгрузить всю доступную информацию в нейронную сеть и дать ей возможность самой выстроить правильные зависимости в процессе обучения. Такой подход непредсказуемо затянет процесс обучения без гарантии получения желаемого результата.

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

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

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

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

Еще одна проблема заключается в использовании сильно коррелируемых показателей. Наличие корреляции между показателями может свидетельствовать либо о причинно-следственной связи между ними, либо о том, что оба показателя зависимы от одного общего фактора. Следовательно, использование коррелируемых показателей объединяет две вышеуказанные проблемы и их следствия. Использование нескольких показателей, зависящих от одного фактора, завышает его влияние на общий результат. При этом излишние нейронные связи усложняют модель и затягивают процесс обучения.

Отбор показателей

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

Напомню, что согласно описанной ранее структуре каталогов, все скрипты сохраняются в папке terminal_dir\MQL5\Scripts. Для скриптов нашей книги мы создадим подкаталог NeuroNetworksBook, а для всех скриптов из данной главы — подкаталог initial_data. Таким образом, полный путь создаваемого файла примет вид:

  • terminal_dir\MQL5\Scripts\NeuroNetworksBook\initial_data\initial_data.mq5

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

#include <Math\Stat\Math.mqh>
//+------------------------------------------------------------------+
//| Параметры скрипта                                                |
//+------------------------------------------------------------------+
input datetime Start D'2015.01.01 00:00:00';   // Period Start
input datetime End  D'2020.12.31 23:59:00';    // Period End

При запуске скрипта MetaTrader 5 генерирует событие Start, которое обрабатывает функция OnStart в теле скрипта. В начале этой функции получим хендлы нескольких индикаторов для проведения дальнейшего анализа.

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

Параметры индикатора определяются пользователем на стадии постановки задачи. Я планирую производить обучение нейронной сети на данных таймфрейма М5, поэтому указал параметр Depth равным 48, что соответствует четырем часам. Таким образом, я ожидаю, что индикатор отобразит 4-часовые экстремумы.

Список и параметры индикаторов для анализа определяются архитектором нейронной сети исходя из своих соображений. Также возможен подбор параметров при оценке корреляции, что мы посмотрим немного позже. На данном этапе укажем индикаторы и их параметры из наших субъективных соображений.

void OnStart(void)
  {
   int h_ZZ=iCustom(_Symbol,PERIOD_M5,"Examples\\ZigZag.ex5",48,1,47);
   int h_CCI=iCCI(_Symbol,PERIOD_M5,12,PRICE_TYPICAL);
   int h_RSI=iRSI(_Symbol,PERIOD_M5,12,PRICE_TYPICAL);
   int h_Stoh=iStochastic(_Symbol,PERIOD_M5,12,8,3,MODE_LWMA,STO_LOWHIGH);
   int h_MACD=iMACD(_Symbol,PERIOD_M5,12,48,12,PRICE_TYPICAL);
   int h_ATR=iATR(_Symbol,PERIOD_M5,12);
   int h_BB=iBands(_Symbol,PERIOD_M5,48,0,3,PRICE_TYPICAL);
   int h_SAR=iSAR(_Symbol,PERIOD_M5,0.02,0.2);
   int h_MFI=iMFI(_Symbol,PERIOD_M5,12,VOLUME_TICK);

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

Получение котировок в MQL5 осуществляется функциями CopyOpen, CopyHigh, CopyLow и CopyClose. Функции созданы по одному шаблону, а из названия функции понятно, какие котировки она возвращает. За получение данных с индикаторных буферов отвечает функция CopyBuffer. Вызов функции похож на функции получения котировок, только название инструмента и таймфрейм заменяются хендлом индикатора и номером буфера. Напомню, что хендлы индикаторов мы получили немного выше.

   double close[], open[],high[],low[];
   if(CopyClose(_Symbol,PERIOD_M5,Start,End,close)<=0 ||
      CopyOpen(_Symbol,PERIOD_M5,Start,End,open)<=0   ||
      CopyHigh(_Symbol,PERIOD_M5,Start,End,high)<=0   ||
      CopyLow(_Symbol,PERIOD_M5,Start,End,low)<=0)
      return

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

   double zz[], cci[], macd_main[], macd_signal[],rsi[],atr[], bands_medium[];
   double bands_up[], bands_low[], sar[],stoch[],ssig[],mfi[];
   datetime end_zz=End+PeriodSeconds(PERIOD_M5)*(12*24*5);
   if(CopyBuffer(h_ZZ,0,Start,end_zz,zz)<=0 ||
      CopyBuffer(h_CCI,0,Start,End,cci)<=0  ||
      CopyBuffer(h_RSI,0,Start,End,rsi)<=0  ||
      CopyBuffer(h_MACD,MAIN_LINE,Start,End,macd_main)<=0     ||
      CopyBuffer(h_MACD,SIGNAL_LINE,Start,End,macd_signal)<=0 ||
      CopyBuffer(h_ATR,0,Start,End,atr)<=0  ||
      CopyBuffer(h_BB,BASE_LINE,Start,End,bands_medium)<=0 ||
      CopyBuffer(h_BB,UPPER_BAND,Start,End,bands_up)<=0    ||
      CopyBuffer(h_BB,LOWER_BAND,Start,End,bands_low)<=0   ||
      CopyBuffer(h_SAR,0,Start,End,sar)<=0  ||
      CopyBuffer(h_Stoh,MAIN_LINE,Start,End,stoch)<=0  ||
      CopyBuffer(h_Stoh,SIGNAL_LINE,Start,End,ssig)<=0 ||
      CopyBuffer(h_MFI,0,Start,End,mfi)<=0)
     {
      return;
     }

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

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

Так вместо цен открытия и закрытия свечи мы можем взять их разность (размер тела свечи) в качестве показателя интенсивности ценового движения. Свечные экстремумы High и Low мы также заменим на их отклонение от цены открытия или закрытия свечи. Аналогично поступим с индикаторами SAR и Bollinger Bands.

Вспомним о классических правилах торговли по индикатору MACD. В них помимо самих значений индикатора важно и положение сигнальной линии относительно гистограммы. Для проверки такой зависимости добавим в качестве еще одного показателя разность значений между линиями индикатора.

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

  • направление движения (вектор target1),
  • величина движения (вектор target2).

Решить эту задачу мы можем с помощью цикла. Запустим перебор значений индикатора ZigZag в обратном порядке (от последних значений к самым старым). Если индикатор нашел экстремум, сохраним его значение в локальную переменную extremum. При отсутствии экстремума используем последнее сохраненное значение.

Одновременно, в этом же цикле, будем рассчитывать и сохранять целевые значения для нашей выборки. Для этого будем от ценового значения последней вершины отнимать цену закрытия анализируемого бара. Таким образом мы получим величину движения до ближайшего будущего экстремума (вектор target2). Знак значения покажет направление движения (вектор target1).

   int total = ArraySize(close);
   double target1[], target2[], oc[], bmc[], buc[], blc[], macd_delta[];
   if(ArrayResize(target1total) <= 0 || ArrayResize(target2total) <= 0 ||
      ArrayResize(octotal) <= 0 || ArrayResize(bmctotal) <= 0   ||
      ArrayResize(buctotal) <= 0 || ArrayResize(blctotal)  <= 0 ||
      ArrayResize(macd_deltatotal) <= 0)
      return;

   double extremum = -1;
   for(int i = ArraySize(zz) - 2i >= 0i--)
     {
      if(zz[i + 1] > 0 && zz[i + 1] != EMPTY_VALUE)
         extremum = zz[i + 1];
      if(i >= total)
         continue;
      target2[i] = extremum - close[i];
      target1[i] = (target2[i] >= 0);
      oc[i] = close[i] - open[i];
      sar[i] -= close[i];
      bands_low[i] = close[i] - bands_low[i];
      bands_up[i] -= close[i];
      bands_medium[i] -= close[i];
      macd_delta[i] = macd_main[i] - macd_signal[i];
     }

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

   int handle = FileOpen("correlation.csv"FILE_WRITE | FILE_CSV | FILE_ANSI,
                                                                "\t"CP_UTF8);
   string message = "Indicator\tTarget 1\tTarget 2";
   if(handle != INVALID_HANDLE)
      return;
   FileWrite(handlemessage);
//---
   Correlation(target1target2oc"Close - Open"handle);
   Correlation(target1target2hc"High - Close %.5f"handle);
   Correlation(target1target2lc"Close - Low"handle);
   Correlation(target1target2cci"CCI %.5f"handle);
   Correlation(target1target2rsi"RSI"handle);
   Correlation(target1target2atr"ATR"handle);
   Correlation(target1target2sar"SAR"handle);
   Correlation(target1target2macd_main"MACD Main"handle);
   Correlation(target1target2macd_signal"MACD Signal"handle);
   Correlation(target1target2macd_delta"MACD Main-Signal"handle);

   Correlation(target1target2bands_medium, "BB Main", handle);
   Correlation(target1target2bands_low, "BB Low", handle);
   Correlation(target1target2bands_up, "BB Up", handle);
   Correlation(target1target2stoch, "Stochastic Main", handle);
   Correlation(target1target2ssig, "Stochastic Signal", handle);
   Correlation(target1target2mfi, "MFI", handle);
//---
   FileFlush(handle);
   FileClose(handle);
  }

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

  • индикатор и направление предстоящего движения,
  • индикатор и сила предстоящего движения.

Из результатов анализа формируется текстовое сообщение и записывается в локальный файл.

void Correlation(double &target1[], double &target2[],
                          double &indicator[], string name,
                          int handle)
  { 
//---
   double correlation=0;
   string message="";
   if(MathCorrelationPearson(target1,indicator,correlation))
      message=StringFormat("%s\t%.5f",name,correlation);
   if(MathCorrelationPearson(target2,indicator,correlation))
      message=StringFormat("%s\t%.5f",message,correlation);
   if(handle!=INVALID_HANDLE)
      FileWrite(handle,message);
  }

Результаты проведенного мной анализа представлены на графике ниже. Полученные данные свидетельствуют об отсутствии корреляции между нашими целевыми данными и значениями индикатора ATR. Отклонение от экстремумов свечи до цены ее закрытия (High — Close, Close — Low) также демонстрируют низкую корреляцию с ожидаемым ценовым движением. Следовательно, мы можем смело исключить данные показатели из нашего дальнейшего анализа.

Корреляция показателей индикаторов к ожидаемому ценовому движению

Корреляция показателей индикаторов к ожидаемому ценовому движению

В целом же, проведенный анализ показывает, что определить направление предстоящего движения гораздо проще, чем предсказать его силу. Все индикаторы показали большую корреляцию с направлением, нежели с величиной предстоящего движения. В то же время корреляция со всеми показателями остается довольно низкая. Наибольшую корреляцию показал индикатор RSI с показателями 0,40 к направлению и 0,22 к величине движения.

Напомню, что коэффициент корреляции принимает значения от −1 (обратная зависимость) до 1 (прямая зависимость). При 0 полностью отсутствует зависимость между случайными величинами.

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

Следующим этапом проверим корреляцию между данными различных индикаторов. Чтобы не сравнивать каждый индикатор со всеми остальными, проведем анализ корреляции индикаторов с RSI (победителем предыдущего этапа). Работу выполним с помощью ранее созданного скрипта с небольшими изменениями. Новый скрипт сохраним в файле initial_data_rsi.mq5 нашего подкаталога.

Корреляция показателей индикаторов к RSI

Корреляция показателей индикаторов к RSI

Проведенный анализ продемонстрировал сильную корреляцию RSI с целым рядом индикаторов. Stochastic, CCI и MFI имеют коэффициент корреляции с RSI более 0,70, а основная линия Bollinger Bands показала обратную корреляцию с RSI с показателем −0,76. Это свидетельствует о том, что показатели вышеуказанных индикаторов лишь будут дублировать сигналы. Включение их для анализа в нашу нейронную сеть лишь усложнит ее архитектуру и обслуживание. При этом ожидаемый эффект от их использования будет минимален. Поэтому мы исключаем вышеуказанные индикаторы из дальнейшей проработки.

Минимальную корреляцию с RSI демонстрируют 2 показателя отклонений:

  • сигнальная линия MACD (0,40);
  • между ценами открытия и закрытия (0,23).

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

Обновленный скрипт сохраним в файле initial_data_macd.mq5 подкаталога.

Корреляция показателей индикаторов к MACD Main-Signal

Корреляция показателей индикаторов к MACD Main-Signal

Здесь довольно интересные данные показывает индикатор SAR. При средних показателях обратной корреляции с целевыми данными он демонстрирует довольно высокую обратную корреляцию с обоими отобранными индикаторами. Коэффициент корреляции с MACD составил −0,66, а для RSI — −0,62. Это дает нам основание для исключения индикатора SAR из корзины анализируемых показателей.

Аналогичная ситуация по всем трем линиям индикатора Bollinger Bands.

Таким образом, на текущий момент мы отобрали два индикатора в нашу корзину показателей для последующего обучения нейронной сети.

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

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

В скрипте initial_data_rsi_pow.mq5 проведем анализ изменения корреляции с ожидаемым ценовым движением при возведении в различную степень значений индикатора RSI. Новый скрипт разместим в соответствующем подкаталоге.

Динамика изменения корреляции значений RSI к ожидаемому движению при возведении показателя в степень

Динамика изменения корреляции значений RSI к ожидаемому движению при возведении показателя в степень

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

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

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

Этот эффект хорошо заметен при анализе изменения корреляции при возведении в степень разницы между линиями индикатора MACD, который мы провели в скрипте initial_data_macd_pow.mq5 нашего подкаталога.

Динамика изменения корреляции значений MACD к ожидаемому движению при возведении показателя в степень

Динамика изменения корреляции значений MACD к ожидаемому движению при возведении показателя в степень

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

 

Влияние временного сдвига на коэффициент корреляции

После выбора показателей для анализа вспомним, что мы имеем дело с временными рядами. Одной из особенностей временных рядов является их «историческая память». Каждое последующее значение является зависимым не только от одного предыдущего значения, но и от некой глубины исторических значений.

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

Для решения данной задачи немного изменим скрипт и заменим в нем функцию Correlation на ShiftCorrelation. Новая функция является полным наследником функции Correlation и построена по тому же алгоритму.

В параметрах функции добавим новую переменную max_shift, в которую передадим максимальный сдвиг для анализа.

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

Остается только вызвать функцию расчета коэффициента корреляции и записать полученные данные в файл.

Для анализа изменения корреляции с ростом смещения во времени обернем все операции в цикл. Количество циклических итераций соответствует параметру max_shift.

void ShiftCorrelation(double &targ1[], double &targ2[],
                      double &signal[], string name,
                      int max_shift, int handle)
  {
   int total = ArraySize(targ1);
   if(max_shift > total)
      max_shift = total - 10;
   if(max_shift < 10)
      return;
   double correlation = 0;
   for(int i = 0i < max_shifti++)
     {
      double t1[], t2[], s[];
      if(ArrayCopy(t1targ10itotal - i) <= 0 ||
         ArrayCopy(t2targ20itotal - i) <= 0 ||
         ArrayCopy(ssignal00total - i) <= 0)
        {
         continue;
        }
      //---

      string message;
      if(MathCorrelationPearson(st1correlation))
         message = StringFormat("%d\t%.5f", icorrelation);
      if(MathCorrelationPearson(st2correlation))
         message = StringFormat("%s\t%.5f", messagecorrelation);
      if(handle != INVALID_HANDLE)
         FileWrite(handlemessage);
     }
//---
   return;
  }

Динамика изменения корреляции значений RSI к ожидаемому движению при сдвиге во времени

Динамика изменения корреляции значений RSI к ожидаемому движению при сдвиге во времени

Чтобы проанализировать влияние смещения значений индикатора RSI во времени на корреляцию с целевыми данными, создадим новый скрипт в файле initial_data_rsi_shift.mq5 подкаталога.

Результаты проведенного анализа показывают стремительное снижение корреляции до 30-го бара. Затем наблюдается небольшая обратная корреляция с пиковым коэффициентом −0,042 в районе 60-го бара и последующим плавным приближением к 0. В такой ситуации наибольшую эффективность даст нам использование первых 30 баров. Дальнейшее расширение глубины анализа может привести к снижению эффективности использования вычислительных ресурсов. Ценность такого решения можно проверить на практике.

Аналогичный анализ данных индикатора MACD в скрипте initial_data_macd_shift.mq5 показал схожую динамику с небольшим смещение зоны перехода из прямой в обратную корреляцию в район 40-го бара.

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

Динамика изменения корреляции значений MACD к ожидаемому движению при сдвиге во времени

Динамика изменения корреляции значений MACD к ожидаемому движению при сдвиге во времени