Использование индикаторов для формирования условий в эксперте

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

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

Также создадим функцию, в которой будет производиться проверка на невозможность совершить торговую операцию. Функцию для открытия позиции модифицируем так, чтобы эксперт определял режим торговли (Instant Execution и Market Execution).

Так как объём кода эксперта после всех дополнений уже достигнет приблизительно 2000 строк, пользоваться им будет довольно неудобно. Поэтому разделим его на несколько категорий и будем подключать, как отдельные файлы (библиотеки).

С целями определились. Начнём.

Сначала распределим все функции по разным файлам, но перед этим поместите исходный код эксперта (*.mq5) и его скомпилированную версию (*.ex5) в отдельную папку. В этой же папке создайте папку Include. Именно в ней нужно будет создать подключаемые файлы (*.mqh). Файлы можно создать используя Мастер (Ctrl+N) в редакторе MetaEditor, но у меня получается быстрее сделать это вручную. То есть, создаю обычный текстовый файл (*.txt) в нужной директории и переименовываю его в нужный формат, в данном случае *.mqh.

Ниже перечислены названия всех созданных подключаемых файлов и пояснения к ним:

  • - ENUMS.mqh - в этом файле будут расположены все перечисления (enum).
  • - CREATE_PANEL.mqh - здесь будут функции для установки информационной панели, создания графических объектов и их удаления.
  • - ERRORS.mqh - сюда перенесём все функции, которые возвращают коды ошибок и причины деинициализации.
  • - ARRAYS.mqh - все массивы поместим тоже в отдельный файл.
  • - TRADE_SIGNALS.mqh - здесь будут сосредоточены функции, которые заполняют массивы ценами, значениями индикаторов и блок сигналов.
  • - TRADE_FUNCTIONS.mqh - этот файл будет содержать в себе торговые функции.
  • - GET_STRING.mqh - здесь будут функции, которые конвертируют числовые значения в строковые.
  • - ADD_FUNCTIONS.mqh - сюда попадают функции, которые не определились ни в какую другую категорию.

Чтобы подключить эти библиотеки к основному файлу нужно использовать командную строку #include. Так как основной файл эксперта и папка подключаемых файлов (Include) находятся в одной  общей папке, то подключение файлов в коде будет выглядеть так, как показано ниже:

//+------------------------------------------------------------------+
//| СВОИ БИБЛИОТЕКИ                                                  |
//+------------------------------------------------------------------+
#include "Include/ENUMS.mqh"
#include "Include/CREATE_PANEL.mqh"
#include "Include/ERRORS.mqh"
#include "Include/ARRAYS.mqh"
#include "Include/TRADE_SIGNALS.mqh"
#include "Include/TRADE_FUNCTIONS.mqh"
#include "Include/GET_STRING.mqh"
#include "Include/ADD_FUNCTIONS.mqh"
//---

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

На рисунке ниже показано, как в итоге всё выглядит в редакторе MetaEditor:

Подключение файлов в проект


Далее в файле ENUMS.mqh добавим в код перечисление индикаторов. В качестве примера используем в этом эксперте два стандартных индикатора (Moving Average и Commodity Channel Index) и один пользовательский (MultiRange_PCH). Перечисление будет выглядеть так:

//+------------------------------------------------------------------+
//| ПЕРЕЧИСЛЕНИЕ ИНДИКАТОРОВ                                         |
//+------------------------------------------------------------------+
enum ENUM_INDICATORS
  {
   MA_IND  = 0, // Moving Average
   CCI_IND = 1, // CCI
   PCH_IND = 2  // Price Channel
  };
//---

Во внешние параметры внесём изменения, как это показано ниже (выделенные строки):

//+------------------------------------------------------------------+
//| ВНЕШНИЕ ПАРАМЕТРЫ ЭКСПЕРТА                                       |
//+------------------------------------------------------------------+
sinput long            MagicNumber  = 777;     // Magic Number
sinput int             Deviation    = 10;      // Deviation (p)
input  ENUM_INDICATORS Indicator    = MA_IND;  // Indicator
input  int             PeriodInd    = 5;       // Period Indicator
input  int             AmountMove   = 2;       // Amount Up/Dw Move For Buy/Sell
input  double          TakeProfit   = 100;     // Take Profit (p)
input  double          StopLoss     = 50;      // Stop Loss (p)
input  double          TrailingSL   = 10;      // Step Trailing Stop (p)
input  bool            Reverse      = true;    // On/Off Reverse
input  double          Lot          = 0.1;     // Lot
input  double          Increase     = 0.1;     // Increase Volume
input  double          StepIncrease = 10;      // Step Increase (p)
sinput bool            InfoPanel    = true;    // Info Panel
//---

Как уже было написано выше, в выпадающем списке параметра Indicator можно будет выбрать один из трёх индикаторов.

Для всех индикаторов есть только один параметр, в котором задаётся период индикатора (PeriodInd). Параметр AmountBars из предыдущего эксперта переименован в AmountMove и теперь означает количество баров, в течении которых указанный индикатор (Moving Average или CCI) должен быть направленным вверх/вниз, чтобы условие на открытие позиции исполнилось. 

Добавлен ещё один внешний параметр (StepIncrease), с помощью которого можно указать шаг в пунктах для наращивания объёма позиции.

До этого корректировка значения переменной gAmountBars (теперь gAmountMove) производилась в пользовательской функции GetDataBars(). Теперь вынесем её в отдельную функцию и будем вызывать её только при инициализации. Так как теперь проверка условия на открытие позиции будет производиться  по значениям индикатора, то значение всегда будет присваиваться на два больше. То есть, если внешней переменной AmountMove присвоено значение 1, то переменной gAmountMove будет присвоено значение 3, так как, чтобы условие исполнилось нужно, например для BUY, чтобы значение индикатора на сформировавшемся баре было больше, чем на предыдущем, а для этого нужно получить три последних значения индикатора.

Ниже код функции CorrectingParameters():

//+------------------------------------------------------------------+
//| КОРРЕКТИРОВКА ПАРАМЕТРОВ                                         |
//+------------------------------------------------------------------+
void CorrectingParameters()
  {
// Скорректируем значение кол-ва баров для условия на открытие позиции
   if(gAmountMove<=0) // Если ноль или меньше, то инициализируем
     {
      if(AmountMove<=1) { gAmountMove=3; } // Нужно не менее трёх баров
      if(AmountMove>=5) { gAmountMove=7; } // и не более 7
      else { gAmountMove=AmountMove+2;   } // и всегда на два больше
     }
  }
//---

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

//+------------------------------------------------------------------+
//| ВЫЯСНЯЕТ ПРИЧИНУ НЕВОЗМОЖНОСТИ ТОРГОВАТЬ                         |
//+------------------------------------------------------------------+
int CheckReasonAllowedTrade()
  {
   if(NotTest()) // Если реал-тайм
     {
      // Проверка соединения с сервером
      if(!TerminalInfoInteger(TERMINAL_CONNECTED))     { return(1); }
      //---
      // Проверка запрета на торговлю
      //---
      // Разрешение на торговлю для данной запущенной программы
      if(!MQL5InfoInteger(MQL5_TRADE_ALLOWED))         { return(2); }
      //---
      // Разрешение на торговлю на уровне терминала
      if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { return(3); }
      //---
      // Разрешение торговли для текущего счета
      if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED))   { return(4); }
      //---
      // Разрешение торговли для эксперта
      if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT))    { return(5); }
     }
//---
   return(0);
  }
//---

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

Например, для индикатора Moving Average есть функция iMA(). Хэндлы всех стандартных индикаторов в терминале MetaTrader 5 можно получить с помощью таких функций. С полным списком можно ознакомиться в Справке по MQL5 в разделе Технические индикаторы.

Если же нужно получить хэндл пользовательского индикатора, то нужно использовать функцию iCustom().

Функцию GetHandlesIndicators(), в которой в зависимости от того, какой индикатор был выбран во внешнем параметре Indicator, реализуем таким образом, чтобы если по какой-то причине хэндл не был получен, будет возвращаться false. Если же хэндл есть, то будет возвращаться true.

Ниже можно ознакомиться с кодом функции GetHandlesIndicators(). Она находится в файле TRADE_SIGNALS.mqh. Переменная hdlInd, которой присваивается хэндл индикатора находится на глобальном уровне программы в главном файле.

//+------------------------------------------------------------------+
//| ПОЛУЧАЕТ ХЭНДЛЫ ИНДИКАТОРОВ                                      |
//+------------------------------------------------------------------+
void GetHandlesIndicators()
  {
// Если нужен индикатор Moving Average
   if(Indicator==MA_IND)
     { hdlInd=iMA(_Symbol,_Period,PeriodInd,0,MODE_SMA,PRICE_CLOSE); }
//---
// Если нужен индикатор CCI
   if(Indicator==CCI_IND)
     { hdlInd=iCCI(_Symbol,_Period,PeriodInd,PRICE_CLOSE); }
//---
// Если нужен индикатор MultiRange_PCH
   if(Indicator==PCH_IND)
     { hdlInd=iCustom(_Symbol,_Period,"MultiRange_PCH",PeriodInd); }
//---
// Если неудалось получить хендл индикатора
   if(hdlInd==INVALID_HANDLE)
     { Print("Не удалось получить хэндл индикатора!"); }
  }
//---

Итак. Далее создадим функцию GetDataIndicators(), в которой с помощью полученных хэндлов индикаторов можно получить их значения. Это аналогично тому, когда нужно получить значения баров с помощью таких функций, как: CopyTime(), CopyClose(), CopyOpen(), CopyHigh() и CopyLow(), которые рассматривались в статье "Изучение свойств позиции в тестере MetaTrader 5". Только вместо этих функций нужно использовать функцию CopyBuffer().

Так как у индикатора может быть множество буферов (рядов значений), то в качестве второго параметра в функцию CopyBuffer() нужно передать номер буфера. Номера буферов для стандартных индикаторов можно узнать в Справке. В пользовательских индикаторах, если есть исходный код, номера буферов можно посмотреть в коде. Если кода нет, то придётся подобрать номера экспериментальным путём ("методом тыка") наблюдая, как исполняются условия в тестере стратегий в режиме визуализации.

Перед тем, как написать функцию нужно создать динамические массивы в файле ARRAYS.mqh:

//---
// Массив для значений индикатора
double bfr_ind01[],bfr_ind02[];
//---

Код функции GetDataIndicators() представлен ниже:

//+------------------------------------------------------------------+
//| ПОЛУЧАЕТ ЗНАЧЕНИЯ ИНДИКАТОРОВ                                    |
//+------------------------------------------------------------------+
bool GetDataIndicators()
  {
// Если хэндл индикатора был получен
   if(hdlInd!=INVALID_HANDLE)
     {
      // Если выбран индикатор Moving Average или CCI
      if(Indicator==MA_IND || Indicator==CCI_IND)
        {
         // Установим обратный порядок индексации (... 3 2 1 0)
         ArraySetAsSeries(bfr_ind01,true);
         //---
         // Получим значения индикатора
         if(CopyBuffer(hdlInd,0,0,gAmountMove,bfr_ind01)<gAmountMove)
           {
            Print("Не удалось скопировать значения ("+
                  _Symbol+"; "+TFtoS(_Period)+") в массив bfr_ind01! Ошибка ("+
                  IS(GetLastError())+"): "+ErrorDesc(GetLastError())+"");
            //---
            return(false);
           }
        }
      //---
      // Если выбран индикатор MultiRange_PCH
      if(Indicator==PCH_IND)
        {
         // Установим обратный порядок индексации (... 3 2 1 0)
         ArraySetAsSeries(bfr_ind01,true);
         ArraySetAsSeries(bfr_ind02,true);
         //---
         // Получим значения индикатора
         if(CopyBuffer(hdlInd,0,0,gAmountMove,bfr_ind01)<gAmountMove || 
            CopyBuffer(hdlInd,1,0,gAmountMove,bfr_ind02)<gAmountMove)
           {
            Print("Не удалось скопировать значения ("+
                  _Symbol+"; "+TFtoS(_Period)+") в массивы bfr_ind01 или bfr_ind02! Ошибка ("+
                  IS(GetLastError())+"): "+ErrorDesc(GetLastError())+"");
            //---
            return(false);
           }
        }
      //---
      return(true);
     }
   else // Если хэндл индикатора не получен, то...
     {
      // ...попробуем получить его ещё раз
      GetHandlesIndicators();
     }
//---
   return(false);
  }
//---

Функция GetTradingSignal() очень заметно изменилась. В момент отсутствия позиции и в момент, когда позиция есть условия отличаются. Для индикаторов Moving Average и CCI условия одинаковые. А для MultiRange_PCH сделан отдельный блок. Также для исключения повторов и тем самым, чтобы сделать код более читаемым, написана вспомогательная функция GetSignal(), которая возвращает сигнал на открытие позиции или на её разворот, если она есть и это указано во внешних параметрах.

Ниже представлен код функции GetSignal():

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ СИГНАЛ                                                |
//+------------------------------------------------------------------+
int GetSignal()
  {
// Блок для проверки условий для индикаторов Moving Average и CCI
   if(Indicator==MA_IND || Indicator==CCI_IND)
     {
      // Сигнал на продажу (1) :
      if(gAmountMove==3 && bfr_ind01[1]<bfr_ind01[2]) { return(1); }
      //---
      if(gAmountMove==4 && 
         bfr_ind01[1]<bfr_ind01[2] && bfr_ind01[2]<bfr_ind01[3]) { return(1); }
      //---
      if(gAmountMove==5 && bfr_ind01[1]<bfr_ind01[2] && 
         bfr_ind01[2]<bfr_ind01[3] && bfr_ind01[3]<bfr_ind01[4]) { return(1); }
      //---
      if(gAmountMove==6 && bfr_ind01[1]<bfr_ind01[2] && 
         bfr_ind01[2]<bfr_ind01[3] && bfr_ind01[3]<bfr_ind01[4] && bfr_ind01[4]<bfr_ind01[5]) { return(1); }
      //---
      if(gAmountMove>=7 && bfr_ind01[1]<bfr_ind01[2] && bfr_ind01[2]<bfr_ind01[3] && 
         bfr_ind01[3]<bfr_ind01[4] && bfr_ind01[4]<bfr_ind01[5] && bfr_ind01[5]<bfr_ind01[6]) { return(1); }

      //---

      // Сигнал на покупку (0) :
      if(gAmountMove==3 && bfr_ind01[1]>bfr_ind01[2]) { return(0); }
      //---
      if(gAmountMove==4 && 
         bfr_ind01[1]>bfr_ind01[2] && bfr_ind01[2]>bfr_ind01[3]) { return(0); }
      //---
      if(gAmountMove==5 && bfr_ind01[1]>bfr_ind01[2] && 
         bfr_ind01[2]>bfr_ind01[3] && bfr_ind01[3]>bfr_ind01[4]) { return(0); }
      //---
      if(gAmountMove==6 && bfr_ind01[1]>bfr_ind01[2] && 
         bfr_ind01[2]>bfr_ind01[3] && bfr_ind01[3]>bfr_ind01[4] && bfr_ind01[4]>bfr_ind01[5]) { return(0); }
      //---
      if(gAmountMove>=7 && bfr_ind01[1]>bfr_ind01[2] && bfr_ind01[2]>bfr_ind01[3] && 
         bfr_ind01[3]>bfr_ind01[4] && bfr_ind01[4]>bfr_ind01[5] && bfr_ind01[5]>bfr_ind01[6]) { return(0); }
     }
//---
// Блок для проверки условий для индикатора MultiRange_PCH
   if(Indicator==PCH_IND)
     {
      // Сигнал на продажу (1) :
      if(prc_c[1]<bfr_ind02[1] && prc_o[1]>bfr_ind02[1]) { return(1); }
      //---
      // Сигнал на покупку (0) :
      if(prc_c[1]>bfr_ind01[1] && prc_o[1]<bfr_ind01[1]) { return(0); }
     }
//---
   return(-1);
  }
//---

Код функции GetTradingSignal() выглядит теперь так, как показано ниже:

//+------------------------------------------------------------------+
//| ОПРЕДЕЛЯЕТ ТОРГОВЫЕ СИГНАЛЫ                                      |
//+------------------------------------------------------------------+
int GetTradingSignal()
  {
   if(!pos.exist) // Если позиции нет
     {
      // Сигнал на продажу (1) :
      if(GetSignal()==1) { return(1); }
      //---
      // Сигнал на покупку (0) :
      if(GetSignal()==0) { return(0); }
     }
//---
   if(pos.exist) // Если позиция есть
     {
      GetPropPosition(POS_TYPE); // Получим тип позиции
      GetPropPosition(POS_PRICE_LDEAL); // Получим цену последней позиции
      //---
      // Блок для проверки условий для индикаторов Moving Average и CCI
      if(Indicator==MA_IND || Indicator==CCI_IND)
        {
         // Сигнал на продажу (1) :
         if(pos.type==POSITION_TYPE_BUY && GetSignal()==1) { return(1); }
         //---
         if(pos.type==POSITION_TYPE_SELL && GetSignal()==1 &&
            prc_c[1]<pos.price_ldeal-DgtMlt(StepIncrease*_Point)) { return(1); }
         //---
         // Сигнал на покупку (0) :
         if(pos.type==POSITION_TYPE_SELL && GetSignal()==0) { return(0); }
         //---
         if(pos.type==POSITION_TYPE_BUY && GetSignal()==0 &&
            prc_c[1]>pos.price_ldeal+DgtMlt(StepIncrease*_Point)) { return(0); }
        }
      //---
      // Блок для проверки условий для индикатора MultiRange_PCH
      if(Indicator==PCH_IND)
        {
         // Сигнал на продажу (1) :
         if(pos.type==POSITION_TYPE_BUY && 
            prc_c[1]<bfr_ind02[1] && prc_o[1]>bfr_ind02[1]) { return(1); }
         //---
         if(pos.type==POSITION_TYPE_SELL && 
            prc_c[1]<pos.price_ldeal-DgtMlt(StepIncrease*_Point)) { return(1); }
         //---
         // Сигнал на покупку (0) :
         if(pos.type==POSITION_TYPE_SELL && 
            prc_c[1]>bfr_ind01[1] && prc_o[1]<bfr_ind01[1]) { return(0); }
         //---
         if(pos.type==POSITION_TYPE_BUY && 
            prc_c[1]>pos.price_ldeal+DgtMlt(StepIncrease*_Point)) { return(0); }
        }
     }
//---
// Отсутствие сигнала (3)
   return(3);
  }
//---

Осталось разобраться с режимами Instant Execution и Market Execution. Это относится к свойствам символа и в Справке можно прочитать такое объяснение.

  • - Instant Execution - торговля по потоковым ценам.
  • - Market Execution - исполнение ордеров по рынку.

На самом деле, прочитав такое объяснение не сразу можно понять, что из этого следует. А дело в том, что, если текущий режим Market Execution, то открыть позицию сразу с установленными уровнями Stop Loss и Take Profit не получится. То есть, нужно сначала открыть позицию, а потом уже модифицировать её установив эти уровни.

В итоге код пользовательской функции OpenPosition() теперь выглядит вот так:

//+------------------------------------------------------------------+
//| ОТКРЫТИЕ ПОЗИЦИИ                                                 |
//+------------------------------------------------------------------+
void OpenPosition(double lot,
                  ENUM_ORDER_TYPE type_ord,
                  double oprice,
                  double sl,
                  double tp,
                  string comment)
  {
   trd.SetExpertMagicNumber(MagicNumber); // Установим номер мэджика в торговую структуру
   trd.SetDeviationInPoints(DgtMlt(Deviation)); // Установим размер проскальзывания в пунктах
//---
// В режиме Instant Execution позицию можно открыть
// сразу с установленными уровнями Stop Loss и Take Profit
   if(smb.trade_exec==SYMBOL_TRADE_EXECUTION_INSTANT)
     {
      // Если позиция не открылась, вывести сообщение об этом
      if(!trd.PositionOpen(_Symbol,type_ord,lot,oprice,sl,tp,comment))
        { Print("Ошибка при открытии позиции: ",GetLastError()," - ",ErrorDesc(GetLastError())); }
     }
//---
// В режиме Market Execution сначала нужно открыть позицию и
// только после этого можно установить уровни Stop Loss и Take Profit
   if(smb.trade_exec==SYMBOL_TRADE_EXECUTION_MARKET)
     {
      // Если позиции нет, то сначала откроем позицию
      // а затем установим Stop Loss и Take Profit
      if(!pos.exist)
        {
         // Если позиция не открылась, вывести сообщение об этом
         if(!trd.PositionOpen(_Symbol,type_ord,lot,oprice,0,0,comment))
           { Print("Ошибка при открытии позиции: ",GetLastError()," - ",ErrorDesc(GetLastError())); }
         else
           {// Если позиция открылась, то сначала выберем её и...
            if((pos.exist=PositionSelect(_Symbol))) // ...если позиция есть, то...
              {
               // ...установим Stop Loss и Take Profit
               if(!trd.PositionModify(_Symbol,sl,tp))
                 { Print("Ошибка при модификации позиции: ",GetLastError()," - ",ErrorDesc(GetLastError())); }
              }
           }
        }
      else
        {// Если позиция есть, то увеличим её объём и 
         // оставим Stop Loss и Take Profit на их прежнем уровне
         // Если позиция не открылась, вывести сообщение об этом
         if(!trd.PositionOpen(_Symbol,type_ord,lot,oprice,sl,tp,comment))
           { Print("Ошибка при открытии позиции: ",GetLastError()," - ",ErrorDesc(GetLastError())); }
        }
     }
  }
//---

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

Настройки тестера нужно установить так, как показано на рисунке ниже:

Настройки тестера

Далее показано, как настроены параметры эксперта для оптимизации:

Настройки эксперта

На двухъядерном процессоре оптимизация длилась приблизительно 40 минут. На графике оптимизации можно частично оценить качество торговой системы по количеству результатов в прибыльной зоне:

График оптимизации

Результат по максимальному значению фактора восстановления получился вот таким:

Результат теста по максимальному значению фактора восстановления

В конце статьи можно скачать исходный код эксперта в архиве. Архив нужно распаковать и поместить папку (expIndCond) с файлами в директорию Metatrader 5\MQL5\Experts. Также для использования эксперта нужно скачать индикатор MultiRange_PCH. Его нужно поместить в директорию Metatrader 5\MQL5\Indicators.

Множество других примеров в виде готовых экспертов и результаты тестов можно посмотреть в разделе Торговые системы.

На этом всё. Успехов!




Скачать архив с файлами эксперта expIndCond.zip
Скачать индикатор MultiRange_PCH.mq5
Скачать сет с настройками эксперта


Комментариев нет :

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