Использование библиотеки FANN2MQL в MetaTrader

Julien | 10 декабря, 2009

Для начала пожалуйста установите библиотеку Fann2MQL. Она нам понадобится в дальнейшем. Ее можно загрузить здесь.

Введение

До настоящего времени был опубликован только один пример использования библиотеки Fann2MQL (Используем нейронные сети в MetaTrader), которая позволяет трейдерам использовать библиотеку "FANN" (также доступную в виде исходных кодов) в программах на MQL. Но этот пример, предложенный создателем библиотеки Fann2MQL, не так просто понять новичкам.

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

В этом примере мы собираемся обучить простую нейронную сеть распознавать простейший паттерн.

Мы обучим сеть распознавать паттерн такого типа: он содержит 3 числа: a, b и с, значения выходного параметра output следующие:

если (a < b) и (b < c), то output = 1
если (a < b) и (b > c), то output = 0
если (a > b) и (b > c), то output = 0
если (a > b) и (b < c), то output = 1

Можно представить эти числа как координаты вектора, с которым можно ассоциировать направление движения рынка.

В данном случае, паттерн может быть интерпретирован как:

ВВЕРХ ВВЕРХ = ВВЕРХ

ВВЕРХ ВНИЗ = ВНИЗ

ВНИЗ ВНИЗ = ВНИЗ

ВНИЗ ВВЕРХ = ВВЕРХ

Для начала мы создадим нейронную сеть.

Затем мы покажем нейросети несколько примеров паттернов, чтобы она смогла обучиться и вывести правила.

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

Код с комментариями:

// Включаем библиотеку Fann2MQl
#include <Fann2MQL.mqh>

#property copyright "Copyright © 2009, Julien Loutre"
#property link      "http://www.forexcomm.com"

#property  indicator_separate_window
#property  indicator_buffers 0

// полное число слоев. в данном случае 1 входной слой, 2 скрытых слоя, 
// и один выходной слой - всего 4 слоя
int nn_layer = 4;
int nn_input = 3;   // число нейронов входного слоя. Наш паттерн состоит из 3 чисел
                    // это значит у нас 3 нейрона во входном слое
int nn_hidden1 = 8; // число нейронов в первом скрытом слое
int nn_hidden2 = 5; // число нейронов во втором скрытом слое
int nn_output = 1;  // число нейронов выходном слое

// массив trainingData[][] будет содержать примеры, которые
// мы собираемся использовать для обучения нейронной нейросети
double      trainingData[][4];  // ВАЖНО! size = nn_input + nn_output


int maxTraining = 500;    // максимальное число итераций 
                          // для обучения нейросети на примерах
double targetMSE = 0.002; // среднеквадратичная ошибка (Mean-Square Error) нейронов
                          // которую мы должны получить (далее станет понятно)

int ann; // эта переменная будет идентификатором нейронной сети

// когда индикатор снимается с графика, 
// мы удаляем все нейронные сети из памяти.
int deinit() {
   f2M_destroy_all_anns();
   return(0);
}

int init() {
   int i;
   double MSE;
   
   Print("=================================== НАЧАЛО ВЫПОЛНЕНИЯ ================================");
   
   IndicatorBuffers(0);
   IndicatorDigits(6);
   
   // изменяем размер массива trainingData, чтобы использовать его далее.
   // мы собираемся изменить его размер один раз.
 
   ArrayResize(trainingData,1);
   
   Print("##### ИНИЦИАЛИЗАЦИЯ #####");
   
   // Cоздаем нейронную сеть
   ann = f2M_create_standard(nn_layer, nn_input, nn_hidden1, nn_hidden2, nn_output);
   
   // проверяем успешность ее создания: 0 = OK, -1 = ошибка
   debug("f2M_create_standard()",ann);
   
      // Задаем активационную функцию. 
        f2M_set_act_function_hidden (ann, FANN_SIGMOID_SYMMETRIC_STEPWISE);
        f2M_set_act_function_output (ann, FANN_SIGMOID_SYMMETRIC_STEPWISE);
        
     // Опыт показывает, что наилучшие результаты получаются, если для начальных весов 
     // используются случайные числа в этом диапазоне, однако вы можете его изменить
     // и посмотреть станут ли они лучше или хуже
        f2M_randomize_weights (ann, -0.77, 0.77);
        
   // Здесь я просто вывожу на консоль число входных и выходных нейронов
   // Это проверка для отладки.
   debug("f2M_get_num_input(ann)",f2M_get_num_input(ann));
   debug("f2M_get_num_output(ann)",f2M_get_num_output(ann));
        
   
   Print("##### ПОДГОТОВКА ДАННЫХ #####");

   // Теперь мы подготовим некоторые примеры данных (с известным результатом)
   // и добавим их в обучаемый набор

   // Поскольку мы добавили все примеры которые нужно,
   // мы собираемся предоставить этот обучаемый набор нейронам нейросети, чтобы они могли обучиться 

   // У функция prepareData() есть несколько аргументов:
   // - действие, которое нужно выполнить - обучение(train) или расчет(compute)
   // - данные (в нашем случае каждый набор содержит 3 числа)
   // - последний аргумент - это переменная для выходного значения нейросети.

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

   // См. комментарии к функции.
   //
   // В нашем случае паттерн который мы собираемся обучать следующий:
   // Есть 3 числа. Пусть это будут a, b и c.
   // Эти числа можно представить как координаты некоторого вектора,
   // с которым ассоциируется направление рынка
   // например (рыночное направление вверх или вниз)
   // если (a < b) и (b < c), то output = 1
   // если (a < b) и (b > c), то output = 0
   // если (a > b) и (b > c), то output = 0
   // если (a > b) и (b < c), то output = 1

   
   // ВВЕРХ ВВЕРХ = ВВЕРХ / если (a < b) и (b < c), то output = 1
   prepareData("train",1,2,3,1);
   prepareData("train",8,12,20,1);
   prepareData("train",4,6,8,1);
   prepareData("train",0,5,11,1);

   // ВВЕРХ ВНИЗ = ВНИЗ / если (a < b) и (b > c), то output = 0
   prepareData("train",1,2,1,0);
   prepareData("train",8,10,7,0);
   prepareData("train",7,10,7,0);
   prepareData("train",2,3,1,0);

   // ВНИЗ ВНИЗ = ВНИЗ / если (a > b) и (b > c), то output = 0
   prepareData("train",8,7,6,0);
   prepareData("train",20,10,1,0);
   prepareData("train",3,2,1,0);
   prepareData("train",9,4,3,0);
   prepareData("train",7,6,5,0);

   // ВНИЗ ВВЕРХ = ВВЕРХ / если (a > b) и (b < c), то output = 1
   prepareData("train",5,4,5,1);
   prepareData("train",2,1,6,1);
   prepareData("train",20,12,18,1);
   prepareData("train",8,2,10,1);
   
   // Теперь для проверки выведем на консоль все обучаемые примеры.
   // Это для отладки.

   printDataArray();
   
   
   Print("##### ОБУЧЕНИЕ #####");
   
   // Нам нужно обучить нейроны много раз так, чтобы результат был такой как требуется.

   // Здесь я буду обучать нейросеть с одними и теми же данными (нашими примерами) снова и снова, 
   // пока она полностью не научится правилам обучения либо
   // пока число обучений 'maxTraining' не превысит максимально заданное нами
   // (в нашем случае maxTraining = 500)

   // Чем лучше нейросеть обучена, тем меньше будет среднеквадратичная ошибка.
   // Функция teach() возвращает сренеквадратичную ошибку (MSE, Mean-Square Error)
   // 0.1 или меньше достаточно для простых правил
   // 0.02 или меньше лучше использовать для сложных правил вроде нашего примера 
   // мы пытаемся обучить нейросеть этим правилам (ведь распознавание паттернов непростая задача)

   for (i=0;i<maxTraining;i++) {
      MSE = teach(); // каждый раз в цикле вызываем функцию обучения
                     // детали см. комментарии к фунции teach().
      if (MSE < targetMSE) { 
   // если среднеквадратичное отклонение (MSE) меньше чем задано (было задано targetMSE = 0.02)
         debug("training finished. Trainings ",i+1); // то выведем на консоль сообщение 
                                                     // о количестве итераций за которое обучились
         i = maxTraining; // и выходим из цикла
      }
   }
   
   // выводим значение среднеквадратичного отклонения (MSE) после окончания обучения
   debug("MSE",f2M_get_MSE(ann));
   
   
   Print("##### ЗАПУСК #####");

   // А теперь мы можем попросить нейросеть проанализировать данные,
   // которые она никогда не видела. Распознает ли она их правильно?.

   // Как видно, здесь я использовал ту же функцию prepareData(), 
   // но с первым аргументом "compute".

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

   // Если хотите, вы можете напрямую вызывать функцию compute().
   // В нашем случае, она имеет вид: compute(inputVector[]);

   // Так что вместо вызова prepareData("compute",1,3,1,0); вы можете сделать следующее:
   // описываем новый массив
   //    double inputVector[];
   // изменяем размерность массива
   //    ArrayResize(inputVector,f2M_get_num_input(ann)); 
   // добавляем в массив данные
   //    inputVector[0] = 1;
   //    inputVector[1] = 3;
   //    inputVector[2] = 1;
   // вызываем функцию compute() с входными параметрами, которые в массиве InputVector
   //    result = compute(inputVector); 

   // Функция prepareData() вызывает функцию compute(), 
   // которая выводит результат на консоль, 
   // что дает возможность проследить работу.

   debug("1,3,1 = ВВЕРХ DOWN = DOWN. Правильный output 0.","");
   prepareData("compute",1,3,1,0);
   
   debug("1,2,3 = ВВЕРХ ВВЕРХ = ВВЕРХ. Правильный output 1.","");
   prepareData("compute",1,2,3,0);
   
   debug("3,2,1 = ВНИЗ ВНИЗ = ВНИЗ. Правильный output 0.","");
   prepareData("compute",3,2,1,0);
   
   debug("45,2,89 = ВНИЗ ВВЕРХ = ВВЕРХ. Правильный output 1.","");
   prepareData("compute",45,2,89,0);
   
   debug("1,3,23 = ВВЕРХ ВВЕРХ = ВВЕРХ. Правильный output 1.","");
   prepareData("compute",1,3,23,0);
   
   debug("7,5,6 = ВНИЗ ВВЕРХ = ВВЕРХ. Правильный output 1.","");
   prepareData("compute",7,5,6,0);
   
   debug("2,8,9 = ВВЕРХ ВВЕРХ = ВВЕРХ. Правильный output 1.","");
   prepareData("compute",2,8,9,0);
   
   Print("=================================== КОНЕЦ РАБОТЫ ================================");
   return(0);
}

int start() {
   return(0);
}

/*************************
** Функция printDataArray()
** Выводит данные, используемые для обучения нейронов
** Она не используется, сделана для отладки.
*************************/
void printDataArray() {
   int i,j;
   int bufferSize = ArraySize(trainingData)/(f2M_get_num_input(ann)+f2M_get_num_output(ann))-1;
   string lineBuffer = "";
   for (i=0;i<bufferSize;i++) {
      for (j=0;j<(f2M_get_num_input(ann)+f2M_get_num_output(ann));j++) {
         lineBuffer = StringConcatenate(lineBuffer, trainingData[i][j], ",");
      }
      debug("DataArray["+i+"]", lineBuffer);
      lineBuffer = "";
   }
}


/*************************
** Функция prepareData()
** Подготавливает данные для обучения либо для вычисления.
** Берет данные и заносит их в массив, 
** затем отдает их нейросети для обучения или для вычисления
** Мы можете модифицировать ее под вашу задачу.
*************************/
void prepareData(string action, double a, double b, double c, double output) {
   double inputVector[];
   double outputVector[];
   // we resize the arrays to the right size
   ArrayResize(inputVector,f2M_get_num_input(ann));
   ArrayResize(outputVector,f2M_get_num_output(ann));
   
   inputVector[0] = a;
   inputVector[1] = b;
   inputVector[2] = c;
   outputVector[0] = output;
   if (action == "train") {
      addTrainingData(inputVector,outputVector);
   }
   if (action == "compute") {
      compute(inputVector);
   }
   // если в вашей задаче входных данных больше чем 3,
   // просто измените струкутру этой функции
}


/*************************
** Функция addTrainingData()
** Добавляет образец для обучения в обучаемый набор
** (пример данных + правильный ответ) в глобальный набор обучаемого множества
*************************/
void addTrainingData(double inputArray[], double outputArray[]) {
   int j;
   int bufferSize = ArraySize(trainingData)/(f2M_get_num_input(ann)+f2M_get_num_output(ann))-1;
   
   // записываем входные данные 
   for (j=0;j<f2M_get_num_input(ann);j++) {
      trainingData[bufferSize][j] = inputArray[j];
   }
   // записываем выходные данные 
   for (j=0;j<f2M_get_num_output(ann);j++) {
      trainingData[bufferSize][f2M_get_num_input(ann)+j] = outputArray[j];
   }
   
   ArrayResize(trainingData,bufferSize+2);
}


/*************************
** Функция teach()
** Берет все обучаемые данные и использует их для однократного обучения
** Для того, чтобы обучить нейроны как полагается, вы должны запускать
** эту функцию много раз, пока значение среднеквадратичной ошибки (MSE)
** уменьшится до заданного значения.
*************************/
double teach() {
   int i,j;
   double MSE;
   double inputVector[];
   double outputVector[];
   ArrayResize(inputVector,f2M_get_num_input(ann));
   ArrayResize(outputVector,f2M_get_num_output(ann));
   int call;
   int bufferSize = ArraySize(trainingData)/(f2M_get_num_input(ann)+f2M_get_num_output(ann))-1;
   for (i=0;i<bufferSize;i++) {
      for (j=0;j<f2M_get_num_input(ann);j++) {
         inputVector[j] = trainingData[i][j];
      }
      outputVector[0] = trainingData[i][3];
      //f2M_train() однократно показывает нейросети один пример.
      call = f2M_train(ann, inputVector, outputVector);
   }
   // Мы показали нейросети пример, 
   // то, как хорошо она обучилась можно проверить при помощи MSE (среднеквадратичная ошибка). 
   // Если она небольшая, значит сеть неплохо обучилась!
   MSE = f2M_get_MSE(ann);
   return(MSE);
}


/*************************
** Функция compute()
** Выдает результат вычисления нейросети
** на основе предоставленных входных данных (InputVector)
*************************/
double compute(double inputVector[]) {
   int j;
   int out;
   double output;
   ArrayResize(inputVector,f2M_get_num_input(ann));
   
   // Даем нейросети новые входные данные
   out = f2M_run(ann, inputVector);
   // и спрашиваем нейросеть о результате используя f2M_get_output().
   output = f2M_get_output(ann, 0);
   debug("Computing()",MathRound(output));
   return(output);
}


/*************************
** Функция debug()
** Печатает данные
*************************/
void debug(string a, string b) {
   Print(a+" ==> "+b);
}

Результат


Рис 1. Результат работы программы.

Выводы

Также можно почитать статью "Используем нейронные сети в MetaTrader", написанную Mariusz Woloszyn, автором библиотеки Fann2MQL.

Мне понадобилось 4 дня на то, чтобы разобраться, как использовать библиотеку Fann в MetaTrader, при этом я использовал информацию отсюда и результаты поиска Google.

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

Если у вас есть вопросы, пожалуйста задавайте и я отвечу.