Рецепты MQL5 - Использование индикаторов для формирования условий торговли в эксперте

Anatoli Kazharski | 14 мая, 2013

Введение

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

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

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

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

 

Процесс разработки эксперта

Поместим исходный код эксперта (*.mq5) из предыдущей статьи в отдельную папку TestIndicatorConditions. В этой же папке создайте папку Include. Именно в ней нужно будет создать подключаемые файлы (*.mqh). Файлы можно сгенерировать с помощью Мастера MQL5 (Ctrl+N), либо создать вручную обычные текстовые файлы (*.txt) в нужной директории, а затем переименовать их расширение в *.mqh.

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

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

//--- Подключаем свои библиотеки
#include "Include\Enums.mqh"
#include "Include\InfoPanel.mqh"
#include "Include\Errors.mqh"
#include "Include\TradeSignals.mqh"
#include "Include\TradeFunctions.mqh"
#include "Include\ToString.mqh"
#include "Include\Auxiliary.mqh"

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

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

//--- Связь с основным файлом эксперта
#include "..\TestIndicatorConditions.mq5"
//--- Подключаем свои библиотеки
#include "Enums.mqh"
#include "InfoPanel.mqh"
#include "Errors.mqh"
#include "TradeSignals.mqh"
#include "ToString.mqh"
#include "Auxiliary.mqh"

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

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

//--- Индикаторы
enum ENUM_INDICATORS
  {
   MA       = 0, // Moving Average
   CCI      = 1, // CCI
   PCH      = 2  // Price Channel
  };

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

//--- Внешние параметры эксперта
sinput   long              MagicNumber=777;        // Магический номер
sinput   int               Deviation=10;           // Проскальзывание
input    ENUM_INDICATORS   Indicator=MA;           // Индикатор
input    int               IndicatorPeriod=5;      // Период индикатора
input    int               IndicatorSegments=2;    // Кол-во однонаправленных отрезков индикатора
input    double            Lot=0.1;                // Лот
input    double            VolumeIncrease=0.1;     // Приращение объема позиции
input    double            VolumeIncreaseStep=10;  // Шаг для приращения объема
input    double            StopLoss=50;            // Стоп Лосс
input    double            TakeProfit=100;         // Тейк Профит
input    double            TrailingStop=10;        // Трейлинг Стоп
input    bool              Reverse=true;           // Разворот позиции
sinput   bool              ShowInfoPanel=true;     // Показ информационной панели

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

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

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

Ранее корректировка значения переменной AllowedNumberOfBars (теперь AllowedNumberOfSegments) производилась в пользовательской функции GetBarsData(). Теперь вынесем ее в отдельную функцию и будем вызывать ее только при инициализации.

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

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

//+------------------------------------------------------------------+
//| Корректирует входные параметры                                   |
//+------------------------------------------------------------------+
void CorrectInputParameters()
  {
//--- Скорректируем значение количества баров для условия открытия позиции
   if(AllowedNumberOfSegments<=0)
     {
      if(IndicatorSegments<=1)
         AllowedNumberOfSegments=3;                     // Нужно не менее трех баров
      if(IndicatorSegments>=5)
         AllowedNumberOfSegments=5;                     // и не более 7
      else
         AllowedNumberOfSegments=IndicatorSegments+1;   // и всегда на два больше
     }
  }

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

//+------------------------------------------------------------------+
//| Проверяет возможность вести торговлю                             |
//+------------------------------------------------------------------+
bool CheckTradingPermission()
  {
//--- Для режима реального времени
   if(IsRealtime())
     {
      //--- Проверка соединения с сервером
      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().

Реализуем функцию GetIndicatorHandle(), в которой в зависимости от того, какой индикатор был выбран во внешнем параметре Indicator, глобальной переменной indicator_handle будет присваиваться значение хэндла соответствующего индикатора. Код функции находится в нашей библиотеке функций торговых сигналов (файл \Include\TradeSignals.mqh), а переменная с хэндлом индикатора - в основном файле эксперта.

//+------------------------------------------------------------------+
//| Получает хэндл индикатора                                        |
//+------------------------------------------------------------------+
void GetIndicatorHandle()
  {
//--- Если выбран индикатор Moving Average
   if(Indicator==MA)
      indicator_handle=iMA(_Symbol,Period(),IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE);
//--- Если выбран индикатор CCI
   if(Indicator==CCI)
      indicator_handle=iCCI(_Symbol,Period(),IndicatorPeriod,PRICE_CLOSE);
//--- Если выбран индикатор MultiRange_PCH
   if(Indicator==PCH)
      indicator_handle=iCustom(_Symbol,Period(),"MultiRange_PCH",IndicatorPeriod);
//--- Если не удалось получить хэндл индикатора
   if(indicator_handle==INVALID_HANDLE)
      Print("Не удалось получить хэндл индикатора!");
  }

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

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

Предварительно в главном файле советника создадим динамические массивы для значений индикаторных буферов:

//--- Массивы для значений индикатора
double indicator_buffer1[];
double indicator_buffer2[];

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

//+------------------------------------------------------------------+
//| Получает значения индикаторов                                    |
//+------------------------------------------------------------------+
bool GetIndicatorsData()
  {
//--- Если хэндл индикатора был получен
   if(indicator_handle!=INVALID_HANDLE)
     {
      //--- Для индикаторов Moving Average или CCI
      if(Indicator==MA || Indicator==CCI)
        {
         //--- Установим обратный порядок индексации (... 3 2 1 0)
         ArraySetAsSeries(indicator_buffer1,true);
         //--- Получим значения индикатора
         if(CopyBuffer(indicator_handle,0,0,AllowedNumberOfSegments,indicator_buffer1)<AllowedNumberOfSegments)
           {
            Print("Не удалось скопировать значения ("+
                  _Symbol+"; "+TimeframeToString(Period())+") в массив indicator_buffer1! Ошибка ("+
                  IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError()));
            return(false);
           }
        }
      //--- Для индикатора MultiRange_PCH
      if(Indicator==PCH)
        {
         //--- Установим обратный порядок индексации (... 3 2 1 0)
         ArraySetAsSeries(indicator_buffer1,true);
         ArraySetAsSeries(indicator_buffer2,true);
         //--- Получим значения индикатора
         if(CopyBuffer(indicator_handle,0,0,AllowedNumberOfSegments,indicator_buffer1)<AllowedNumberOfSegments || 
            CopyBuffer(indicator_handle,1,0,AllowedNumberOfSegments,indicator_buffer2)<AllowedNumberOfSegments)
           {
            Print("Не удалось скопировать значения ("+
                  _Symbol+"; "+TimeframeToString(Period())+") в массивы indicator_buffer1 или indicator_buffer2! Ошибка ("+
                  IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError()));
            return(false);
           }
        }
      //---
      return(true);
     }
//--- Если хэндл индикатора не получен, попробуем получить его еще раз
   else
      GetIndicatorHandle();
//---
   return(false);
  }

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

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

//+------------------------------------------------------------------+
//| Проверяет условие и возвращает сигнал                            |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE GetSignal()
  {
//--- Проверка условий для индикаторов Moving Average и CCI
   if(Indicator==MA || Indicator==CCI)
     {
      //--- Сигнал на продажу
      if(AllowedNumberOfSegments==3 && 
         indicator_buffer1[1]<indicator_buffer1[2])
         return(ORDER_TYPE_SELL);
      //---
      if(AllowedNumberOfSegments==4 && 
         indicator_buffer1[1]<indicator_buffer1[2] && 
         indicator_buffer1[2]<indicator_buffer1[3])
         return(ORDER_TYPE_SELL);
      //---
      if(AllowedNumberOfSegments==5 && 
         indicator_buffer1[1]<indicator_buffer1[2] && 
         indicator_buffer1[2]<indicator_buffer1[3] && 
         indicator_buffer1[3]<indicator_buffer1[4])
         return(ORDER_TYPE_SELL);
      //---
      if(AllowedNumberOfSegments==6 && 
         indicator_buffer1[1]<indicator_buffer1[2] && 
         indicator_buffer1[2]<indicator_buffer1[3] && 
         indicator_buffer1[3]<indicator_buffer1[4] && 
         indicator_buffer1[4]<indicator_buffer1[5])
         return(ORDER_TYPE_SELL);
      //---
      if(AllowedNumberOfSegments>=7 && 
         indicator_buffer1[1]<indicator_buffer1[2] && 
         indicator_buffer1[2]<indicator_buffer1[3] && 
         indicator_buffer1[3]<indicator_buffer1[4] && 
         indicator_buffer1[4]<indicator_buffer1[5] && 
         indicator_buffer1[5]<indicator_buffer1[6])
         return(ORDER_TYPE_SELL);

      //--- Сигнал на покупку
      if(AllowedNumberOfSegments==3 && 
         indicator_buffer1[1]>indicator_buffer1[2])
         return(ORDER_TYPE_BUY);
      //---
      if(AllowedNumberOfSegments==4 && 
         indicator_buffer1[1]>indicator_buffer1[2] && 
         indicator_buffer1[2]>indicator_buffer1[3])
         return(ORDER_TYPE_BUY);
      //---
      if(AllowedNumberOfSegments==5 && 
         indicator_buffer1[1]>indicator_buffer1[2] && 
         indicator_buffer1[2]>indicator_buffer1[3] && 
         indicator_buffer1[3]>indicator_buffer1[4])
         return(ORDER_TYPE_BUY);
      //---
      if(AllowedNumberOfSegments==6 && 
         indicator_buffer1[1]>indicator_buffer1[2] && 
         indicator_buffer1[2]>indicator_buffer1[3] && 
         indicator_buffer1[3]>indicator_buffer1[4] && 
         indicator_buffer1[4]>indicator_buffer1[5])
         return(ORDER_TYPE_BUY);
      //---
      if(AllowedNumberOfSegments>=7 && 
         indicator_buffer1[1]>indicator_buffer1[2] && 
         indicator_buffer1[2]>indicator_buffer1[3] && 
         indicator_buffer1[3]>indicator_buffer1[4] && 
         indicator_buffer1[4]>indicator_buffer1[5] && 
         indicator_buffer1[5]>indicator_buffer1[6])
         return(ORDER_TYPE_BUY);
     }
//--- Блок для проверки условий для индикатора MultiRange_PCH
   if(Indicator==PCH)
     {
      //--- Сигнал на продажу
      if(close_price[1]<indicator_buffer2[1] && 
         open_price[1]>indicator_buffer2[1])
         return(ORDER_TYPE_SELL);
      //--- Сигнал на покупку
      if(close_price[1]>indicator_buffer1[1] && 
         open_price[1]<indicator_buffer1[1])
         return(ORDER_TYPE_BUY);
     }
//--- Отсутствие сигнала
   return(WRONG_VALUE);
  }

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

//+------------------------------------------------------------------+
//| Определяет торговые сигналы                                      |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE GetTradingSignal()
  {
//--- Если позиции нет
   if(!pos.exists)
     {
      //--- Сигнал на продажу
      if(GetSignal()==ORDER_TYPE_SELL)
         return(ORDER_TYPE_SELL);
      //--- Сигнал на покупку
      if(GetSignal()==ORDER_TYPE_BUY)
         return(ORDER_TYPE_BUY);
     }
//--- Если позиция есть
   if(pos.exists)
     {
      //--- Получим тип позиции
      GetPositionProperties(P_TYPE);
      //--- Получим цену последней сделки
      GetPositionProperties(P_PRICE_LAST_DEAL);
      //--- Блок для проверки условий для индикаторов Moving Average и CCI
      if(Indicator==MA || Indicator==CCI)
        {
         //--- Сигнал на продажу
         if(pos.type==POSITION_TYPE_BUY && 
            GetSignal()==ORDER_TYPE_SELL)
            return(ORDER_TYPE_SELL);
         //---
         if(pos.type==POSITION_TYPE_SELL && 
            GetSignal()==ORDER_TYPE_SELL && 
            close_price[1]<pos.last_deal_price-CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point))
            return(ORDER_TYPE_SELL);
         //--- Сигнал на покупку
         if(pos.type==POSITION_TYPE_SELL && 
            GetSignal()==ORDER_TYPE_BUY)
            return(ORDER_TYPE_BUY);
         //---
         if(pos.type==POSITION_TYPE_BUY && 
            GetSignal()==ORDER_TYPE_BUY && 
            close_price[1]>pos.last_deal_price+CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point))
            return(ORDER_TYPE_BUY);
        }
      //--- Блок для проверки условий для индикатора MultiRange_PCH
      if(Indicator==PCH)
        {
         //--- Сигнал на продажу
         if(pos.type==POSITION_TYPE_BUY && 
            close_price[1]<indicator_buffer2[1] && 
            open_price[1]>indicator_buffer2[1])
            return(ORDER_TYPE_SELL);
         //---
         if(pos.type==POSITION_TYPE_SELL && 
            close_price[1]<pos.last_deal_price-CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point))
            return(ORDER_TYPE_SELL);
         //--- Сигнал на покупку
         if(pos.type==POSITION_TYPE_SELL && 
            close_price[1]>indicator_buffer1[1] && 
            open_price[1]<indicator_buffer1[1])
            return(ORDER_TYPE_BUY);
         //---
         if(pos.type==POSITION_TYPE_BUY && 
            close_price[1]>pos.last_deal_price+CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point))
            return(ORDER_TYPE_BUY);
        }
     }
//--- Отсутствие сигнала
   return(WRONG_VALUE);
  }

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

Напомню, для режима исполнения Market Execution открыть позицию сразу с установленными уровнями Stop Loss и Take Profit не получится: нужно сначала открыть позицию, а потом уже модифицировать ее, установив эти уровни.

Начиная с 803 билда, для типов исполнения Market Execution и Exchange Execution при открытии позиции можно устанавливать уровни Stop Loss и Take Profit.

В структуру свойств символа добавим режим заключения сделок:

//--- Свойства символа
struct symbol_properties
  {
   int               digits;           // Количество знаков в цене после запятой
   int               spread;           // Размер спреда в пунктах
   int               stops_level;      // Ограничитель установки Stop ордеров
   double            point;            // Значение одного пункта
   double            ask;              // Цена ask
   double            bid;              // Цена bid
   double            volume_min;       // Минимальный объем для заключения сделки
   double            volume_max;       // Максимальный объем для заключения сделки
   double            volume_limit;     // Максимально допустимый объем для позиции и ордеров в одном направлении
   double            volume_step;      // Минимальный шаг изменения объема для заключения сделки
   double            offset;           // Отступ от максимально возможной цены для операции
   double            up_level;         // Цена верхнего уровня stop level
   double            down_level;       // Цена нижнего уровня stop level
   ENUM_SYMBOL_TRADE_EXECUTION execution_mode; // Режим заключения сделок
  };

Соответствующим образом необходимо дополнить перечисление ENUM_SYMBOL_PROPERTIES

//--- Перечисление свойств символа
enum ENUM_SYMBOL_PROPERTIES
  {
   S_DIGITS          = 0,
   S_SPREAD          = 1,
   S_STOPSLEVEL      = 2,
   S_POINT           = 3,
   S_ASK             = 4,
   S_BID             = 5,
   S_VOLUME_MIN      = 6,
   S_VOLUME_MAX      = 7,
   S_VOLUME_LIMIT    = 8,
   S_VOLUME_STEP     = 9,
   S_FILTER          = 10,
   S_UP_LEVEL        = 11,
   S_DOWN_LEVEL      = 12,
   S_EXECUTION_MODE  = 13,
   S_ALL             = 14
  };

и функцию GetSymbolProperties():

case S_EXECUTION_MODE: symb.execution_mode=(ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE);   break;
      //---
      case S_ALL           :
         symb.digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         symb.spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD);
         symb.stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         symb.point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         symb.ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),symb.digits);
         symb.bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),symb.digits);
         symb.volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
         symb.volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
         symb.volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT);
         symb.volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
         symb.offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*symb.point),symb.digits);
         symb.up_level=NormalizeDouble(symb.ask+symb.stops_level*symb.point,symb.digits);
         symb.down_level=NormalizeDouble(symb.bid-symb.stops_level*symb.point,symb.digits);
         symb.execution_mode=(ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE);                       break;
         //---

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

//+------------------------------------------------------------------+
//| Открывает позицию                                                |
//+------------------------------------------------------------------+
void OpenPosition(double lot,
                  ENUM_ORDER_TYPE order_type,
                  double price,
                  double sl,
                  double tp,
                  string comment)
  {
//--- Установим номер мэджика в торговую структуру
   trade.SetExpertMagicNumber(MagicNumber);
//--- Установим размер проскальзывания в пунктах
   trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation));
//--- Режим Instant Execution
//    Позицию можно открыть сразу с установленными Stop Loss и Take Profit
   if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_INSTANT)
     {
      //--- Если позиция не открылась, вывести сообщение об этом
      if(!trade.PositionOpen(_Symbol,order_type,lot,price,sl,tp,comment))
         Print("Ошибка при открытии позиции: ",GetLastError()," - ",ErrorDescription(GetLastError()));
     }
//--- Режим Market Execution 
//    Сначала нужно открыть позицию и только после этого можно установить Stop Loss и Take Profit
//    *** Начиная с 803 билда, уровни Stop Loss и Take Profit можно устанавливать при открытии позиции ***
   if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_MARKET)
     {
      //--- Если позиции нет, то сначала откроем позицию, а затем установим Stop Loss и Take Profit
      if(!pos.exists)
        {
         //--- Если позиция не открылась, вывести сообщение об этом
         if(!trade.PositionOpen(_Symbol,order_type,lot,price,0,0,comment))
            Print("Ошибка при открытии позиции: ",GetLastError()," - ",ErrorDescription(GetLastError()));
         //--- Получим флаг наличия/отсутствия позиции
         pos.exists=PositionSelect(_Symbol);
         //--- Если есть позиция
         if(pos.exists)
           {
            //--- Установим Stop Loss и Take Profit
            if(!trade.PositionModify(_Symbol,sl,tp))
               Print("Ошибка при модификации позиции: ",GetLastError()," - ",ErrorDescription(GetLastError()));
           }
        }
      //--- Если позиция есть, то увеличим ее объем и оставим прежнее значение уровней Stop Loss и Take Profit
      else
        {
         //--- Если позиция не открылась, вывести сообщение об этом
         if(!trade.PositionOpen(_Symbol,order_type,lot,price,sl,tp,comment))
            Print("Ошибка при открытии позиции: ",GetLastError()," - ",ErrorDescription(GetLastError()));
        }
     }
  }

Нам осталось внести финальные и очень важные штрихи в функции обработки событий:

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

 

Оптимизация параметров и тестирование эксперта.

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

Рис. 1. Настройки тестера

Рис. 1. Настройки тестера.

Далее настроим параметры эксперта для оптимизации (см. также *.set файл с настройками в приложении к статье):

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

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

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

Рис. 3. График оптимизации

Рис. 3. График оптимизации.

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

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

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

 

Заключение

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