Использование библиотеки 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.
Надеюсь что этот пример будет полезным для вас, и позволит сэкономить много времени.
Если у вас есть вопросы, пожалуйста задавайте и я отвечу.