Используем нейронные сети в MetaTrader

Автор: lexy Вторник, Сентябрь 9th, 2014 Нет комментариев

Рубрика: Разное

Вероятно, многие из вас рассматривали возможность использования нейронных сетей в советниках.

Особенно эта тема стала актуальной после впечатляющей победы Better с его системой на основе нейронных сетей на Automated Trading Championship 2007 г. Многие форумы Интернета были заполнены темами, связанных с нейронными сетями и их применением в торговле на Forex.

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

В этой статье я покажу вам, каким образом вы сможете использовать свободно распространяемую библиотеку Fast Artificial Neural Network Library (FANN) в ваших программах на MQL4, избегая при этом некоторых подводных камней и ограничений.

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

Особенности библиотеки FANN

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

Рассмотрим ее:

FANN_EXTERNAL struct fann *FANN_API fann_create_standard(unsigned int num_layers, int lNnum, ... )

где num_layers - это общее число слоев в нейронной сети, включая входной и выходной слои. Переменная lNnum и переменные следующие далее — это количество нейронов в каждом слое, начиная с входного слоя и заканчивая выходным. Для создания сети с одним скрытым слоем с 5 нейронами, входным слоем с 10 входами и выходным слоем с одним нейроном, нужно вызвать ее следующим образом:

fann_create_standard(3,10,5,1);

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

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

FANN_EXTERNAL void FANN_API fann_train( struct fann * ann,
fann_type * input,
fann_type * desired_output )

 

Эта функция использует указатель на структуру struct fann (который был возвращен ранее функцией fann_create_standard()) а также входной и выходной векторы, которые имеют тип fann_type.

Тип данных определяется при компиляции библиотеки (может быть double or float). В данной реализации входные и выходные вектора являются массивами типа double.

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

FANN_EXTERNAL fann_type * FANN_API fann_run( struct fann * ann,
fann_type * input )

Эта функция использует указатель на структуру типа struct fann, представляющую собой уже созданную нейронную сеть и входной вектор input (массив типа double). Возвращаемое значение — выходной вектор в виде массива. Даже если выходной параметр один, мы всегда на выходе получаем массив (если он один, то одномерный), а не само значение.

Большинство функций библиотеки FANN используют указатель на структуру struct fan, а поскольку MQL4 не поддерживает структуры как типы данных, невозможно работать с ними напрямую. Для того, чтобы снять это ограничение, мы должны каким-то способом построить «обертку» (wrapper).

Самый простой метод — это создать массив из указателей на структуры типа struct fann, который содержит соответствующие значения и ссылаться на них при помощи целочисленного индекса типа int. Таким способом мы можем заменить неподдерживаемые типы переменных поддерживаемыми и создать «библиотеку-обертку», которая может бытьлегко интегрирована с MQL.

Строение обертки FANN (wrapping)

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

 

Финальная функция для «обертки» выглядит следующим образом.

/* Создает стандартную полносвязную нейронную сеть с обратным распространением ошибки (backpropagation neural network)
* num_layers - общее число слоев, включая входной и выходной слои.
* l1num - число нейронов в 1-м слое (входы)
* l2num, l3num, l4num - числа нейронов в скрытых и в выходном слое (в зависимости от числа num_layers).
* Возвращает:
* индекс хендла нейросети, -1 в случае ошибки
*/

int __stdcall f2M_create_standard(unsigned int num_layers, int l1num, int l2num, int l3num, int l4num);

Мы изменили fann_ на f2M_ (для FANN в MQL), здесь использовано фиксированное число аргументов (4 слоя) и возвращаемое значение — индекс внутреннего массива нейросети типа struct fann, который используется библиотекой FANN.

То же самое и для функции обучения:

/* Производит одну итерацию обучения используя заданные наборы входных и выходных данных.
* Обучение всегда инкрементное, поскольку представлен только один паттерн.
* ann - хендл нейросети, полученный ранее функцией f2M_create_*
* *input_vector - массив входных данных
* *output_vector - массив выходных данных
* Возвращает:
* 0 в случае успеха и -1 в случае ошибки
*/

int __stdcall f2M_train(int ann, double *input_vector, double *output_vector);

и

/* Выполнить процедуру расчета выходных значений нейросети FANN
* ann - хендл нейросети, возвращенный функцией f2M_create_*
* *input_vector - массив входных данных
* Возвращает:
* 0 в случае успеха, отрицательное значение в случае ошибки
* Примечание:
* Для получения результата расчета выходного вектора используйте функцию f2M_get_output().
* При этом старые значения выходов будут перезаписаны
*/

int __stdcall f2M_run(int ann, double *input_vector);

И наконец, для уничтожения уже созданной нейросети нужно вызвать функцию:

/* Уничтожить нейросеть fann
* ann - хендл нейросети, возвращенный функцией f2M_create_*
* Возвращает:
* 0 в случае успеха, -1 в случае ошибки
* ПРЕДУПРЕЖДЕНИЕ: обработчики не могут быть испольтзованы снова, если ann!=(_ann-1)
* Другие обработчики могут быть снова использованы только после уничтожения последней нейросети.
*/

int __stdcall f2M_destroy(int ann);

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

Также можно использовать альтернативный способ — следующую функцию:

/* Удалить все нейросети, созданные библиотекой FANN
* Возвращает:
* 0 в случае успеха и -1 в случае ошибки
*/
int __stdcall f2M_destroy_all_anns();

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

/* Сохранить нейросеть полностью в конфигурационном файле
* ann - хендл нейросети, который был определен функцией f2M_create*
* Возвращает:
* 0 on success and -1 on failure
*/
int __stdcall f2M_save(int ann,char *path);

Сохраненная нейросеть затем может быть загружена (или вернее воссоздана) при помощи функции:

/* Загружает нейросеть из файла
* path - путь к фаулу с расширением".net"
* Возвращает:
* хендл нейросети, -1 в случае ошибки
*/
int __stdcall f2M_create_from_file(char *path);

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

Установка библиотеки Fann2MQL

Для удобства использования библиотеки я создал программу установки, которая содержит все исходные и скомпилированные библиотеки и файл Fann2MQL.mqh с описанием всех функций библиотеки. Процедура установки довольно проста. Сначала Вы увидите информацию об условиях использования библиотеки Fann2MQL в рамках GPL лицензии:


Установка библиотеки Fann2MQL, шаг 1

Затем выберите папку для установки библиотеки. Вы можете использовать по умолчанию путь Program Files\Fann2MQL\ или установить ее непосредственно в каталогMetaTrader\experts\. Все файлы будут скопированы в указанную папку, в случае необходимости, вы можете скопировать их вручную куда потребуется.

 


Установка библиотеки Fann2MQL, шаг 2

Программа установки разместит файлы в следующих папках:


Каталог include\ folder


Каталог libraries\ folder


Каталог src\ folder

Если вы выбрали для установки папку «Fann2MQL», то скопируйте ее содержимое (каталоги include and libraries) в каталог, где размещен MetaTrader. Программа установки также скопирует файлы библиотеки в каталог системных библиотек Windows (как правило, это Windows\system32).

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

Используем нейронные сети в советнике

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

Reinforcement learning), например Q-Learning или нечто подобное.

 

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

Здесь на примере я покажу как можно использовать нейросеть в качестве простейшего фильтра сигналов, создаваемых MACD. Не стоит воспринимать этот эксперт как законченное решение, это всего лишь простейший пример использования библиотеки Fann2MQL. На примере рассмотрения работы эксперта NeuroMACD.mq4 я покажу, как библиотека Fann2MQL может быть эффективно использована в MQL.

Для любого советника сначала объявляются глобальные переменные, определения и секция include. Начало эксперта NeuroMACD.mq4 выглядит следующим образом:

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

// Определение глобальных переменных
#define ANN_PATH "C:\\ANN\\"
// Имя советника
#define NAME "NeuroMACD"

//---- входные параметры
extern double Lots=0.1;
extern double StopLoss=180.0;
extern double TakeProfit=270.0;
extern int FastMA=18;
extern int SlowMA=36;
extern int SignalMA=21;
extern double Delta=-0.6;
extern int AnnsNumber=16;
extern int AnnInputs=30;
extern bool NeuroFilter=true;
extern bool SaveAnn=false;
extern int DebugLevel=2;
extern double MinimalBalance=100;
extern bool Parallel=true;

// Глобальные переменные

// Путь к каталогу c нейросетями
string AnnPath;

// Magic number для торговли
int MagicNumber=65536;

// AnnsArray[ann#] - массив нейросетей
int AnnsArray[];

// флаг статуса загрузки всех нейросетей
bool AnnsLoaded=true;

// AnnOutputs[ann#] - массив выходов нейросети
double AnnOutputs[];

// InputVector[] - массив входных данных нейросети
double InputVector[];

// Номер тикета для длинной позиции
int LongTicket=-1;

// Номер тикета для короткой позиции
int ShortTicket=-1;

// Сохраненные значения входов для длинной и короткой позиций.
double LongInput[];
double ShortInput[];

 

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

Константа ANN_PATH задает путь, в котором хранятся файлы с обученными нейросетями. В данном случае мы должны создать эту папку в корневом каталоге на диске C, т.е C:\ANN.

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

Точка входа любого советника — это его функция init():

int init()
  {
   int i,ann;

   if(!is_ok_period(PERIOD_M5))
     {
      debug(0,"Wrong period!");
      return(-1);
     }

   AnnInputs=(AnnInputs/3)*3; // Должно быть целым числом,
                              // делящимся на 3 без остатка

   if(AnnInputs<3)
     {
      debug(0,"Слишком мало входных данных!");
     }
// Вычисляем MagicNumber и путь AnnPath
   MagicNumber+=(SlowMA+256*FastMA+65536*SignalMA);
   AnnPath=StringConcatenate(ANN_PATH,NAME,"-",MagicNumber);

// Инициализиуем нейросети
   ArrayResize(AnnsArray,AnnsNumber);
   for(i=0;i<AnnsNumber;i++)
     {
      if(i%2==0)
        {
         ann=ann_load(AnnPath+"."+i+"-long.net");
           } else {
         ann=ann_load(AnnPath+"."+i+"-short.net");
        }
      if(ann<0)
         AnnsLoaded=false;
      AnnsArray[i]=ann;
     }
   ArrayResize(AnnOutputs,AnnsNumber);
   ArrayResize(InputVector,AnnInputs);
   ArrayResize(LongInput,AnnInputs);
   ArrayResize(ShortInput,AnnInputs);

// Инициализируем потоки (Intel TBB threads)
   f2M_parallel_init();

   return(0);
  }

 

Сначала проверяется правильность таймфрейма, на котором работает эксперт (PERIOD_M5).

Переменная AnnInputs содержит число входов нейросети. Поскольку мы будем использовать 3 набора с различными аргументами, оно должно делиться на 3 без остатка. AnnPathзадается таким образом, чтобы в ней содержалась информация об имени советника NAME и MagicNumber, который вычисляются как функция входных параметров SlowMA,FastMA и SignalMA, которые в дальнейшем используются при вычислении индикатора MACD.

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

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

Я не уверен в том, что это крайне необходимо, но на всякий случай, для полного использования потенциала библиотеки Fann2MQL, которая параллельно обрабатывает несколько нейросетей, добавлена возможность использования нескольких ядер процессора. Эта возможность реализована с использованием технологии Intel® Threading Building Blocks. Для инициализации данного интерфейса используется функция f2M_parallel_init().

Вот способ, который я использую для инициализации нейросетей:

int ann_load(string path)
  {
   int ann=-1;

   /* Загрузить нейросеть */
   ann=f2M_create_from_file(path);
   if(ann!=-1)
     {
      debug(1,"Нейросеть: '"+path+"' успешно загружена. Ее хендл: "+ann);
     }
   if(ann==-1)
     {

      /* Создание нейросети */
      ann=
          f2M_create_standard(4,AnnInputs,AnnInputs,AnnInputs/2+1,1);
      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.4,0.4);
      debug(1,"Нейросеть: '"+path+"' успешно создана. Ее хендл: "+ann);
     }
   if(ann==-1)
     {
      debug(0,"ИНИЦИАЛИЗАЦИЯ НЕЙРОСЕТИ!");
     }
   return(ann);
  }

 

Как видно, в случае, если вызов f2M_create_from_file() закончился неудачно, о чем свидетельствует отрицательное значение функции, при помощи функцииf2M_create_standard() создается нейросеть с аргументами, такими что созданная нейросеть должна иметь 4 слоя (включая входной и выходной слои). AnnInput — это число нейронов во входном слое, AnnInput — это число нейронов в первом скрытом слое, AnnInput/2+1 — число нейронов во втором скрытом слое и 1 нейрон в выходном слое.

Функция f2M_set_act_function_hidden() используется для установки активационной функции нейронов скрытого слоя как SIGMOID_SYMMETRIC_STEPWISE (см. типы активационных функций в документации библиотеки FANN), та же самая активационная функция задается и для нейронов выходного слоя.

Далее идет вызов функции f2m_randomize_weights(), которая используется для инициализации весов связей нейронов внутри сети. Здесь использован диапазон <-0.4; 0.4>, но вы можете использовать любой другой, в зависимости от вашей задачи.

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

void debug(int level,string text)
  {
   if(DebugLevel>=level)
     {
      if(level==0)
         text="ОШИБКА: "+text;
      Print(text);
     }
  }

 

Если первый аргумент level (уровень отладки) в функции debug() выше чем DebugLevel, то функция ничего не выводит. Если же он меньше или равен DebugLevel, то выводится строка text. Если DebugLevel=0, то к выводимой строке добавляется строка «ОШИБКА: «. Таким образом вы можете разделить отладочные сообщения на различные уровни.

Самые важные и вероятные сообщения связаны с ошибками, поэтому им присвоен уровень 0. Они будут выводится пока DebugLevel неотрицательный, мы не рекомендуем устанавливать его отрицательным. При уровне 1 выводится некоторая важная информация, например подтверждения об успешной загрузке нейросети или ее создании. На уровне 2 и выше значимость выводимой информации постепенно уменьшается.

Перед подробным объяснением содержимого функции start(), которая является довольно длинной, я должен показать вам некоторые функции, которые нужны для того, чтобы подготовить входы нейросети и произвести ее запуск:

void ann_prepare_input()
  {
   int i;

   for(i=0;i<=AnnInputs-1;i=i+3)
     {
      InputVector[i]=
         10*iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                  MODE_MAIN,i*3);
      InputVector[i+1]=
         10*iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                  MODE_SIGNAL,i*3);
      InputVector[i+2]=InputVector[i-2]-InputVector[i-1];
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double ann_run(int ann,double &vector[])
  {
   int ret;
   double out;
   ret=f2M_run(ann,vector);
   if(ret<0)
     {
      debug(0,"ОШИБКА запуска нейросети!!! ann="+ann);
      return(FANN_DOUBLE_ERROR);
     }
   out=f2M_get_output(ann,0);
   debug(3,"f2M_get_output("+ann+") результат: "+out);
   return(out);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int anns_run_parallel(int anns_count,int &anns[],double &input_vector[])
  {
   int ret;

   ret=f2M_run_parallel(anns_count,anns,input_vector);

   if(ret<0)
     {
      debug(0,"f2M_run_parallel("+anns_count+") результат: "+ret);
     }
   return(ret);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void run_anns()
  {
   int i;

   if(Parallel)
     {
      anns_run_parallel(AnnsNumber,AnnsArray,InputVector);
     }

   for(i=0;i<AnnsNumber;i++)
     {
      if(Parallel)
        {
         AnnOutputs[i]=f2M_get_output(AnnsArray[i],0);
           } else {
         AnnOutputs[i]=ann_run(AnnsArray[i],InputVector);
        }
     }
  }
//+------------------------------------------------------------------+

 

Функция ann_prepare_input() используется для подготовки входных значений сетей. Хотя ее цель простая, следует отметить, что входные данные должны быть нормализованы надлежащим образом. В данном случае ничего изощренного нет, я просто использовал MACD и значения сигналов, которые никогда не выходят за требуемый диапазон данных. Этот аспект следует принимать во внимание при программировании реальных задач. В реальном примере следует уделять больше внимания этому вопросу.

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

Библиотека Fann2MQL имеет возможность расширения обычной функциональности MetaTrader’а при помощи параллельной обработки нейронных сетей. За это отвечает глобальная переменная Parallel. Функция run_anns() запускает все инициализированные нейросети, снимает с них вызодные параметры и помещает их в массив n AnnOutput[]. Функцияanns_run_parallel способна выполнять эту работу в многопотоковом режиме. Она вызвает f2m_run_parallel(), в которой первый аргумент — это число нейросетей для обработки, второй аргумент — это массив, содержащий хендлы всех нейросетей которые вы хотите запустить и третий аргумент — это вектор входных данных.

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

Теперь рассмотрим функцию start():

int
start()
  {
   int i;
   bool BuySignal=false;
   bool SellSignal=false;

   double train_output[1];

   /* Is trade allowed? */
   if(!trade_allowed())
     {
      return(-1);
     }

   /* Подготавливаем и запускаем нейросети */
   ann_prepare_input();
   run_anns();

/* Вычисляем последние и предыдущие значения MACD.
* Текущий незавершенный бар мы не используем
*/
   double MacdLast=iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                         MODE_MAIN,1);
   double MacdPrev=iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                         MODE_MAIN,2);

   double SignalLast=iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                           MODE_SIGNAL,
                           1);
   double SignalPrev=iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                           MODE_SIGNAL,
                           2);

   /* сигнал BUY-покупка */
   if(MacdLast>SignalLast && MacdPrev<SignalPrev)
     {
      BuySignal=true;
     }
   /* сигнал SELL-продажа */
   if(MacdLast<SignalLast && MacdPrev>SignalPrev)
     {
      SellSignal=true;
     }

   /* Длинных позиций нет */
   if(LongTicket==-1)
     {
      /* сигнал BUY */
      if(BuySignal)
        {
         /* если установлен NeuroFilter,
            используем мудрость нейросети для принятия решения:) */
         if(!NeuroFilter || ann_wise_long()>Delta)
           {
            LongTicket=
          OrderSend(Symbol(),OP_BUY,Lots,Ask,3,
                    Bid-StopLoss*Point,
                    Ask+TakeProfit*Point,
                    NAME+"-"+"L ",MagicNumber,0,Blue);
           }
         /* Запоминаем входы нейросети */
         for(i=0;i<AnnInputs;i++)
           {
            LongInput[i]=InputVector[i];
           }
        }
        } else {
      /* Обработка длиинных позиций*/
      OrderSelect(LongTicket,SELECT_BY_TICKET);
      if(OrderCloseTime()==0)
        {
         // Order is opened
         if(SellSignal && OrderProfit()>0)
           {
            OrderClose(LongTicket,Lots,Bid,3);
           }
        }
      if(OrderCloseTime()!=0)
        {
         // Order is closed
         LongTicket=-1;
         if(OrderProfit()>=0)
           {
            train_output[0]=1;
              } else {
            train_output[0]=-1;
           }
         for(i=0;i<AnnsNumber;i+=2)
           {
            ann_train(AnnsArray[i],LongInput,train_output);
           }
        }
     }

   /* Коротких позиций нет */
   if(ShortTicket==-1)
     {
      if(SellSignal)
        {
         /* если установлен NeuroFilter,
            используем мудрость нейросети для принятия решения:) */
         if(!NeuroFilter || ann_wise_short()>Delta)
           {
            ShortTicket=
          OrderSend(Symbol(),OP_SELL,Lots,Bid,3,
                    Ask+StopLoss*Point,
                    Bid-TakeProfit*Point,NAME+"-"+"S ",
                    MagicNumber,0,Red);
           }
         /* Remember network input */
         for(i=0;i<AnnInputs;i++)
           {
            ShortInput[i]=InputVector[i];
           }
        }
        } else {
      /* Работа с короткими позициями */
      OrderSelect(ShortTicket,SELECT_BY_TICKET);
      if(OrderCloseTime()==0)
        {
         // Order is opened
         if(BuySignal && OrderProfit()>0)
           {
            OrderClose(LongTicket,Lots,Bid,3);
           }
        }
      if(OrderCloseTime()!=0)
        {
         // Order is closed
         ShortTicket=-1;
         if(OrderProfit()>=0)
           {
            train_output[0]=1;
              } else {
            train_output[0]=-1;
           }
         for(i=1;i<AnnsNumber;i+=2)
           {
            ann_train(AnnsArray[i],ShortInput,train_output);
           }
        }
     }

   return(0);
  }
//+------------------------------------------------------------------+

 

Поскольку код содержит понятные комментарии, я вкратце объясню его работу.

Функция trade_allowed() проверяет, может ли советник торговать. Значение переменной AnnsLoaded указывает, все ли нейросети были инициализованы правильно, затем проверяется таймфрейм и минимальный баланс, а в самом конце проверка условия, заключаещегося в том, что торговля производится только на первом тике нового бара.

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

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

Переменная LongTicket содержит номер тикета текущей открытой длинной позиции. Если он равен -1, то длинных позиций нет, поэтому если устанавлен BuySignal, то это может означать хорошую возможность открытия длинной позиции. Если переменная NeuroFilter не установлена, то просто посылается ордер для покупки и открывается длинная позиция без фильтрации сделок с использованием нейронной сети.

В этом случае переменная LongInput нужна для того чтобы для использования в дальнейшем запомнить входной вектор InputVector, подготовленный при помощиann_prepare_input().

Если переменная LongTicket содержит правильный номер тикета, то эксперт проверяет открыта ли в данный момент позиция или она была закрыта при достижении уровней Stop Loss или Take Profit.

Если ордер не закрыт, то ничего не происходит, тем не менее если ордер закрыт, вектор train_output[], имеющий только одно выходное значение, устанавливается в -1 если ордер был закрыт по Stop Loss или 1, если ордер был закрыт с прибылью по Take Profit.

Это значение затем передается функции ann_train(), и все нейросети способны обрабатывать длинные позиции и обучаться на результатах. Поскольку в качестве входного вектора используется массив LongInput, в момент открытия позиции, он содержит значения массива InputVector. Таким образом нейросетью берется сигнал, приводящий к прибыли или убыткам. Когда нейросеть обучена, устанавка NeuroFilter в true приводит к фильтрациям сделок при помощи нейросетиФункция ann_wise_long(), используется нейросетью для вычисления средних значений от результатов всех нейросетей, обрабатывающих длинные позиции. Параметр Delta используется в качестве порогового значения для принятия решения о правильности фильтрованного сигналаКак и многие другие значения, оно было получено путем оптимизации.

Теперь когда мы знаем как все это работает, я покажу как это может быть использовано. Тестовая пара EURUSD. Я брал исторические данные с одного из брокеров, сконвертированные в таймфрейм M5.

Для обучения и оптимизации использовался период 2007.12.31-2009.01.01(обучаемый набор), тестирование проводилось на периоде 2009.01.01-2009.03.22(тестовый набор). В самом первом запуске я попытался найти наиболее прибыльные значения для аргументов StopLoss, TakeProfit, SlowMA, FastMA и SignalMA, которые я затем реализовал в файле NeuroMACD.mq4.

Переменная NeuroFIlter устанавливалась в false, также как и SaveAnn, переменная AnnsNumber была равна 0 для блокировки работы нейросети.

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

Отчет по данным обучения после оптимизации параметров

 

Как видно, этот советник был запущен на мини-счете с размером лота 0.01 и начальным балансом в 200. Тем не менее вы можете изменить эти параметры в соответствии с настройками счета или собственными предпочтениями.

Теперь у нас есть прибыльные и убыточные сделки, так что можно установить SaveAnn в true, а AnnsNumber задать равным 30. После этого я запустил тестер еще раз. Результаты получились точно такими же, за исключением того, что весь процесс был гораздо более медленный (теперь использовались нейросети), а каталог C:\ANN наполнился обученными нейросетями как показано на рисунке ниже:

Однако перед запуском сначала нужно убедиться в том, что каталог C:\ANN существует.


Содержимое папки C:\\ANN\\.

 

Мы обучили нейрости и теперь самое время их проверить. Сначала проверим их работу на обучаемом наборе. Изменим NeuroFilter в true и SaveAnn в false и запустим тестер стартегий.

Результаты, которые я получил, приведены ниже. Отметим, что они могут несколько отличаться от тех, которые получите вы, из-за того, что связи между нейронами при инициализации нейросети заполняются случайными значениями (в данном примере я использовал вызов функции f2M_randomize_weights() в функции ann_load()).


Отчет полученный на обучаемом наборе с использованием фильтра сделок на основе нейросети.

Теперь полная прибыль (net profit) стала немного больше (20.03 вместо 16.92), однако profit factor значительно выше (1.25 вместо 1.1). Число сделок гораздо меньше () и среднее число последовательных убыточных сделок сократилось с 7 до 2.

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

Результат, полученный теперь уже на тестовой выборке (период 2009.01.01 — 2009.30.28) приведен ниже:

 


Результат, полученный на тестовом наборе с использованием нейросетевой фильтрации сделок.

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

Реальный эффект применения нейросетей в данном случае виден только по сравнению с результатами работы советника на тестовых данных, с использованием (NeuroFilter=true) и без использования (NeuroFilter=false) нейросетевого фильтра.


Результат на тестовом наборе без нейросетевой фильтрации сделок.

Различия очевидны. Как видите, нейросетевая фильтрация превратила убыточного советника в прибыльного!

 

Выводы

Я надеюсь что эта статья научила вас как можно использовать нейронные сети в терминале MetaTrader.

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

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

Бывало, процесс оптимизации обучения c подкреплением (Reinforcement Learning) сокращался с примерно 4 дней «всего лишь» до 28 часов на 4-х ядерном процессоре Intel.

В процессе написания данной статьи я решил выложить Fann2MQL на сайте http://fann2mql.wordpress.com/. Там вы можете найти последнюю версию библиотеки Fann2MQL, а также возможно и все ее будущие версии, а также документацию ко всем функциям.

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

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

Есть очень много информации про FANN на странице Fast Artificial Neural Network Library: http://leenissen.dk/fann/!

Post Scriptum

После написания этой статьи я обнаружил существенную ошибку в NeuroMACD.mq4. При использовании функции OrderClose () для короткой позиции указывался тикет длинной позиции.

Это привело к перекосу стратегии, заключающемуся в том, что преимущественно удерживались короткие позиции и заключались длинные.

/* Maintain short position */
OrderSelect(ShortTicket,SELECT_BY_TICKET);
if(OrderCloseTime()==0)
  {
// Order is opened
   if(BuySignal && OrderProfit()>0)
     {
      OrderClose(LongTicket,Lots,Bid,3);
     }
  }

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

Прикрепленные файлы:
 NeuroMACD-fixed.mq4 (10.1 Kb)
 NeuroMACD.mq4 (10.1 Kb)

Источник: mql4.com

Оставить комментарий

Чтобы оставлять комментарии Вы должны быть авторизованы.

Похожие посты