Скачать MetaTrader 5

Рецепты MQL5 - Как не получить ошибку при установке/изменении торговых уровней?

2 апреля 2013, 14:45
Anatoli Kazharski
8
5 217

Введение

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

Очень часто на форуме-(ах) по программированию на MQL можно увидеть вопросы от новичков, касающиеся ошибок при установке/модификации торговых уровней (Stop Loss, Take Profit, отложенные ордера). Думаю, уже многие знакомы с сообщением в журнале, в котором в конце строки содержится [Invalid stops]. В этой статье мы создадим функции, в которых нормализуются и проверяются на корректность значения торговых уровней перед открытием/модификацией позиции.

На этот раз эксперт будет снабжен внешними параметрами, которые можно будет оптимизировать в тестере MetaTrader 5. Это уже будет немного похоже на простую торговую систему. До настоящей торговой системы, конечно же, пока еще довольно далеко, но не все сразу - нам предстоит еще много работы.

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


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

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

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

//--- Перечисление свойств позиции
enum ENUM_POSITION_PROPERTIES
  {
   P_SYMBOL        = 0,
   P_MAGIC         = 1,
   P_COMMENT       = 2,
   P_SWAP          = 3,
   P_COMMISSION    = 4,
   P_PRICE_OPEN    = 5,
   P_PRICE_CURRENT = 6,
   P_PROFIT        = 7,
   P_VOLUME        = 8,
   P_SL            = 9,
   P_TP            = 10,
   P_TIME          = 11,
   P_ID            = 12,
   P_TYPE          = 13,
   P_ALL           = 14
  };
//--- Перечисление свойств символа
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_ALL          = 13
  };

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

Далее идут внешние параметры эксперта:

//--- Внешние параметры эксперта
input int            NumberOfBars=2;     // Кол-во Бычьих/Медвежьих баров для покупки/продажи
input double         Lot         =0.1;   // Лот
input double         StopLoss    =50;    // Стоп Лосс
input double         TakeProfit  =100;   // Тейк Профит
input double         TrailingStop=10;    // Трейлинг Стоп
input bool           Reverse     =true;  // Разворот позиции

Рассмотрим подробнее внешние параметры:

  • NumberOfBars - в этом параметре задается количество однонаправленных баров для открытия позиции;
  • Lot - объем позиции;
  • TakeProfit - уровень фиксации прибыли в пунктах. Нулевое значение означает, что Take Profit устанавливать не нужно.
  • StopLoss - защитный уровень в пунктах. Нулевое значение означает, что Stop Loss устанавливать не нужно.
  • TrailingStop - шаг защитного уровня Трейлинг Стоп в пунктах. Для позиции BUY расчет производится от минимума бара (минимум минус количество пунктов из параметра StopLoss). Для позиции SELL расчет производится от максимума бара (максимум плюс количество пунктов из параметра StopLoss). Нулевое значение означает, что трейлинг отключен.
  • Reverse - включает/выключает переворот позиции.

Уточнения требует только параметр NumberOfBars. Не имеет смысла устанавливать в этом параметре значение, например, больше 5, так как это уже довольно редкое явление и открывать позицию уже поздно после такого движения. Поэтому понадобится переменная, с помощью которой можно будет корректировать значение этого параметра:

//--- Для проверки значения внешнего параметра NumberOfBars
int                  AllowedNumberOfBars=0;

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

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

//--- Свойства символа
int                  sym_digits=0;           // Количество знаков в цене после запятой
int                  sym_spread=0;           // Размер спреда в пунктах
int                  sym_stops_level=0;      // Ограничитель установки Stop ордеров
double               sym_point=0.0;          // Значение одного пункта
double               sym_ask=0.0;            // Цена ask
double               sym_bid=0.0;            // Цена bid
double               sym_volume_min=0.0;     // Минимальный объем для заключения сделки
double               sym_volume_max=0.0;     // Максимальный объем для заключения сделки
double               sym_volume_limit=0.0;   // Максимально допустимый объем для позиции и ордеров в одном направлении
double               sym_volume_step=0.0;    // Минимальный шаг изменения объема для заключения сделки
double               sym_offset=0.0;         // Отступ от максимально возможной цены для операции
double               sym_up_level=0.0;       // Цена верхнего уровня stop level
double               sym_down_level=0.0;     // Цена нижнего уровня stop level

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

//--- Массивы ценовых данных
double               close_price[]; // Close (цены закрытия бара)
double               open_price[];  // Open (цены открытия бара)
double               high_price[];  // High (цены максимума бара)
double               low_price[];   // Low (цены минимума бара)

Теперь приступим к модификации и созданию функций. У нас уже есть функция GetBarsData, в которой в ценовые массивы копируются цены открытия и закрытия баров. Теперь нам нужны еще максимумы и минимумы. Кроме того, нужно проводить корректировку значения из параметра NumberOfBars. Вот как выглядит эта функция после модификации:

//+------------------------------------------------------------------+
//| Получает значения баров                                          |
//+------------------------------------------------------------------+
void GetBarsData()
  {
//--- Скорректируем значение количества баров для условия открытия позиции
   if(NumberOfBars<=1)
      AllowedNumberOfBars=2;              // Нужно не менее двух баров
   if(NumberOfBars>=5)
      AllowedNumberOfBars=5;              // и не более 5
   else
      AllowedNumberOfBars=NumberOfBars+1; // и всегда на один больше
//--- Установим обратный порядок индексации (... 3 2 1 0)
   ArraySetAsSeries(close_price,true);
   ArraySetAsSeries(open_price,true);
   ArraySetAsSeries(high_price,true);
   ArraySetAsSeries(low_price,true);
//--- Получим цену закрытия бара
//    Если полученных значений меньше, чем запрошено, вывести сообщение об этом
   if(CopyClose(_Symbol,Period(),0,AllowedNumberOfBars,close_price)<AllowedNumberOfBars)
     {
      Print("Не удалось скопировать значения ("
            +_Symbol+", "+TimeframeToString(Period())+") в массив цен Close! "
            "Ошибка "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
//--- Получим цену открытия бара
//    Если полученных значений меньше, чем запрошено, вывести сообщение об этом
   if(CopyOpen(_Symbol,Period(),0,AllowedNumberOfBars,open_price)<AllowedNumberOfBars)
     {
      Print("Не удалось скопировать значения ("
            +_Symbol+", "+TimeframeToString(Period())+") в массив цен Open! "
            "Ошибка "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
//--- Получим цену максимума бара
//    Если полученных значений меньше, чем запрошено, вывести сообщение об этом
   if(CopyHigh(_Symbol,Period(),0,AllowedNumberOfBars,high_price)<AllowedNumberOfBars)
     {
      Print("Не удалось скопировать значения ("
            +_Symbol+", "+TimeframeToString(Period())+") в массив цен High! "
            "Ошибка "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
//--- Получим цену минимума бара
//    Если полученных значений меньше, чем запрошено, вывести сообщение об этом
   if(CopyLow(_Symbol,Period(),0,AllowedNumberOfBars,low_price)<AllowedNumberOfBars)
     {
      Print("Не удалось скопировать значения ("
            +_Symbol+", "+TimeframeToString(Period())+") в массив цен Low! "
            "Ошибка "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
  }

Не менее двух баров и всегда на один больше нужно потому, что ориентироваться будем только на сформировавшиеся бары, которые начинаются с индекса [1]. Корректировку в этом случае на самом деле можно и не делать, ведь данные баров можно начать копировать с указанного индекса в третьем параметре функций CopyOpen, CopyClose, CopyHigh и CopyLow. Ограничение в пять сформировавшихся баров тоже можно изменить (больше/меньше) на ваше усмотрение.

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

//+------------------------------------------------------------------+
//| Определяет торговые сигналы                                      |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE GetTradingSignal()
  {
//--- Сигнал на покупку (ORDER_TYPE_BUY) :
   if(AllowedNumberOfBars==2 && 
      close_price[1]>open_price[1])
      return(ORDER_TYPE_BUY);
   if(AllowedNumberOfBars==3 && 
      close_price[1]>open_price[1] && 
      close_price[2]>open_price[2])
      return(ORDER_TYPE_BUY);
   if(AllowedNumberOfBars==4 && 
      close_price[1]>open_price[1] && 
      close_price[2]>open_price[2] && 
      close_price[3]>open_price[3])
      return(ORDER_TYPE_BUY);
   if(AllowedNumberOfBars==5 && 
      close_price[1]>open_price[1] && 
      close_price[2]>open_price[2] && 
      close_price[3]>open_price[3] && 
      close_price[4]>open_price[4])
      return(ORDER_TYPE_BUY);
   if(AllowedNumberOfBars>=6 && 
      close_price[1]>open_price[1] && 
      close_price[2]>open_price[2] && 
      close_price[3]>open_price[3] && 
      close_price[4]>open_price[4] && 
      close_price[5]>open_price[5])
      return(ORDER_TYPE_BUY);
//--- Сигнал на продажу (ORDER_TYPE_SELL) :
   if(AllowedNumberOfBars==2 && 
      close_price[1]<open_price[1])
      return(ORDER_TYPE_SELL);
   if(AllowedNumberOfBars==3 && 
      close_price[1]<open_price[1] && 
      close_price[2]<open_price[2])
      return(ORDER_TYPE_SELL);
   if(AllowedNumberOfBars==4 && 
      close_price[1]<open_price[1] && 
      close_price[2]<open_price[2] && 
      close_price[3]<open_price[3])
      return(ORDER_TYPE_SELL);
   if(AllowedNumberOfBars==5 && 
      close_price[1]<open_price[1] && 
      close_price[2]<open_price[2] && 
      close_price[3]<open_price[3] && 
      close_price[4]<open_price[4])
      return(ORDER_TYPE_SELL);
   if(AllowedNumberOfBars>=6 && 
      close_price[1]<open_price[1] && 
      close_price[2]<open_price[2] && 
      close_price[3]<open_price[3] && 
      close_price[4]<open_price[4] && 
      close_price[5]<open_price[5])
      return(ORDER_TYPE_SELL);
//--- Отсутствие сигнала (WRONG_VALUE):
   return(WRONG_VALUE);
  }

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

//+------------------------------------------------------------------+
//| Получает свойства позиции                                        |
//+------------------------------------------------------------------+
void GetPositionProperties(ENUM_POSITION_PROPERTIES position_property)
  {
//--- Узнаем, есть ли позиция
   pos_open=PositionSelect(_Symbol);
//--- Если позиция есть, получим её свойства
   if(pos_open)
     {
      switch(position_property)
        {
         case P_SYMBOL        : pos_symbol=PositionGetString(POSITION_SYMBOL);                  break;
         case P_MAGIC         : pos_magic=PositionGetInteger(POSITION_MAGIC);                   break;
         case P_COMMENT       : pos_comment=PositionGetString(POSITION_COMMENT);                break;
         case P_SWAP          : pos_swap=PositionGetDouble(POSITION_SWAP);                      break;
         case P_COMMISSION    : pos_commission=PositionGetDouble(POSITION_COMMISSION);          break;
         case P_PRICE_OPEN    : pos_price=PositionGetDouble(POSITION_PRICE_OPEN);               break;
         case P_PRICE_CURRENT : pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT);           break;
         case P_PROFIT        : pos_profit=PositionGetDouble(POSITION_PROFIT);                  break;
         case P_VOLUME        : pos_volume=PositionGetDouble(POSITION_VOLUME);                  break;
         case P_SL            : pos_sl=PositionGetDouble(POSITION_SL);                          break;
         case P_TP            : pos_tp=PositionGetDouble(POSITION_TP);                          break;
         case P_TIME          : pos_time=(datetime)PositionGetInteger(POSITION_TIME);           break;
         case P_ID            : pos_id=PositionGetInteger(POSITION_IDENTIFIER);                 break;
         case P_TYPE          : pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); break;
         case P_ALL           :
            pos_symbol=PositionGetString(POSITION_SYMBOL);
            pos_magic=PositionGetInteger(POSITION_MAGIC);
            pos_comment=PositionGetString(POSITION_COMMENT);
            pos_swap=PositionGetDouble(POSITION_SWAP);
            pos_commission=PositionGetDouble(POSITION_COMMISSION);
            pos_price=PositionGetDouble(POSITION_PRICE_OPEN);
            pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT);
            pos_profit=PositionGetDouble(POSITION_PROFIT);
            pos_volume=PositionGetDouble(POSITION_VOLUME);
            pos_sl=PositionGetDouble(POSITION_SL);
            pos_tp=PositionGetDouble(POSITION_TP);
            pos_time=(datetime)PositionGetInteger(POSITION_TIME);
            pos_id=PositionGetInteger(POSITION_IDENTIFIER);
            pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);                     break;
         default: Print("Переданное свойство позиции не учтено в перечислении!");               return;
        }
     }
//--- Если позиции нет, обнулим переменные свойств позиции
   else
      ZeroPositionProperties();
  }

Аналогичным образом реализуем функцию GetSymbolProperties для получения свойств символа:

//+------------------------------------------------------------------+
//| Получает свойства символа                                        |
//+------------------------------------------------------------------+
void GetSymbolProperties(ENUM_SYMBOL_PROPERTIES symbol_property)
  {
   int lot_offset=1; // Количество пунктов для отступа от уровней stops level
//---
   switch(symbol_property)
     {
      case S_DIGITS        : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);                   break;
      case S_SPREAD        : sym_spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD);                   break;
      case S_STOPSLEVEL    : sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);   break;
      case S_POINT         : sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);                           break;
      //---
      case S_ASK           :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits);                       break;
      case S_BID           :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits);                       break;
         //---
      case S_VOLUME_MIN    : sym_volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);                 break;
      case S_VOLUME_MAX    : sym_volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);                 break;
      case S_VOLUME_LIMIT  : sym_volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT);             break;
      case S_VOLUME_STEP   : sym_volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);               break;
      //---
      case S_FILTER        :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         sym_offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*sym_point),sym_digits);        break;
         //---
      case S_UP_LEVEL      :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits);
         sym_up_level=NormalizeDouble(sym_ask+sym_stops_level*sym_point,sym_digits);                     break;
         //---
      case S_DOWN_LEVEL    :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits);
         sym_down_level=NormalizeDouble(sym_bid-sym_stops_level*sym_point,sym_digits);                   break;
         //---
      case S_ALL           :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD);
         sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits);
         sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits);
         sym_volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
         sym_volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
         sym_volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT);
         sym_volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
         sym_offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*sym_point),sym_digits);
         sym_up_level=NormalizeDouble(sym_ask+sym_stops_level*sym_point,sym_digits);
         sym_down_level=NormalizeDouble(sym_bid-sym_stops_level*sym_point,sym_digits);                   break;
         //---
      default: Print("Переданное свойство символа не учтено в перечислении!"); return;
     }
  }

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

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

//+------------------------------------------------------------------+
//| Коррекция значения по количеству знаков в цене (int)             |
//+------------------------------------------------------------------+
int CorrectValueBySymbolDigits(int value)
  {
   return (sym_digits==3 || sym_digits==5) ? value*=10 : value;
  }
//+------------------------------------------------------------------+
//| Коррекция значения по количеству знаков в цене (double)          |
//+------------------------------------------------------------------+
double CorrectValueBySymbolDigits(double value)
  {
   return (sym_digits==3 || sym_digits==5) ? value*=10 : value;
  }

В нашем эксперте будет внешний параметр для указания объема (Lot) открываемой позиции. Сделаем функцию CalculateLot, которая будет корректировать объем в соответствии со спецификацией символа:

//+------------------------------------------------------------------+
//| Рассчитывает объем для позиции                                   |
//+------------------------------------------------------------------+
double CalculateLot(double lot)
  {
//--- Для корректировки с учетом шага
   double corrected_lot=0.0;
//---
   GetSymbolProperties(S_VOLUME_MIN);  // Получим минимально возможный лот
   GetSymbolProperties(S_VOLUME_MAX);  // Получим максимально возможный лот
   GetSymbolProperties(S_VOLUME_STEP); // Получим шаг увеличения/уменьшения лота
//--- Скорректируем с учетом шага лота
   corrected_lot=MathRound(lot/sym_volume_step)*sym_volume_step;
//--- Если меньше минимального, вернем минимальный
   if(corrected_lot<sym_volume_min)
      return(NormalizeDouble(sym_volume_min,2));
//--- Если больше максимального, вернем максимальный
   if(corrected_lot>sym_volume_max)
      return(NormalizeDouble(sym_volume_max,2));
//---
   return(NormalizeDouble(corrected_lot,2));
  }

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

Функция CalculateTakeProfit предназначена для расчета значения уровня фиксации прибыли:

//+------------------------------------------------------------------+
//| Рассчитывает уровень Take Profit                                 |
//+------------------------------------------------------------------+
double CalculateTakeProfit(ENUM_ORDER_TYPE order_type)
  {
//--- Если Take Profit нужен
   if(TakeProfit>0)
     {
      //--- Для рассчитанного значения Take Profit
      double tp=0.0;
      //--- Если нужно рассчитать значение для позиции SELL
      if(order_type==ORDER_TYPE_SELL)
        {
         //--- Рассчитаем уровень
         tp=NormalizeDouble(sym_bid-CorrectValueBySymbolDigits(TakeProfit*sym_point),sym_digits);
         //--- Вернем рассчитанное значение, если оно ниже нижней границы stops level
         //    Если значение выше или равно, вернем скорректированное значение
         return(tp<sym_down_level ? tp : sym_down_level-sym_offset);
        }
      //--- Если нужно рассчитать значение для позиции BUY
      if(order_type==ORDER_TYPE_BUY)
        {
         //--- Рассчитаем уровень
         tp=NormalizeDouble(sym_ask+CorrectValueBySymbolDigits(TakeProfit*sym_point),sym_digits);
         //--- Вернем рассчитанное значение, если оно выше верхней границы stops level
         //    Если значение ниже или равно, вернем скорректированное значение
         return(tp>sym_up_level ? tp : sym_up_level+sym_offset);
        }
     }
//---
   return(0.0);
  }

Функция CalculateStopLoss предназначена для расчета значения защитного уровня:

//+------------------------------------------------------------------+
//| Рассчитывает уровень Stop Loss                                   |
//+------------------------------------------------------------------+
double CalculateStopLoss(ENUM_ORDER_TYPE order_type)
  {
//--- Если Stop Loss нужен
   if(StopLoss>0)
     {
      //--- Для рассчитанного значения Stop Loss
      double sl=0.0;
      //--- Если нужно рассчитать значение для позиции BUY
      if(order_type==ORDER_TYPE_BUY)
        {
         // Рассчитаем уровень
         sl=NormalizeDouble(sym_ask-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
         //--- Вернем рассчитанное значение, если оно ниже нижней границы stops level
         //    Если значение выше или равно, вернем скорректированное значение
         return(sl<sym_down_level ? sl : sym_down_level-sym_offset);
        }
      //--- Если нужно рассчитать значение для позиции SELL
      if(order_type==ORDER_TYPE_SELL)
        {
         //--- Рассчитаем уровень
         sl=NormalizeDouble(sym_bid+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
         //--- Вернем рассчитанное значение, если оно выше верхней границы stops level
         //    Если значение ниже или равно, вернем скорректированное значение
         return(sl>sym_up_level ? sl : sym_up_level+sym_offset);
        }
     }
//---
   return(0.0);
  }

Функция CalculateTrailingStop предназначена для расчета значения трейлинга защитного уровня:

//+------------------------------------------------------------------+
//| Рассчитывает уровень Trailing Stop                               |
//+------------------------------------------------------------------+
double CalculateTrailingStop(ENUM_POSITION_TYPE position_type)
  {
//--- Переменные для расчётов
   double            level       =0.0;
   double            buy_point   =low_price[1];    // Значение Low для Buy
   double            sell_point  =high_price[1];   // Значение High для Sell
//--- Рассчитаем уровень для позиции BUY
   if(position_type==POSITION_TYPE_BUY)
     {
      //--- Минимум бара минус указанное количество пунктов
      level=NormalizeDouble(buy_point-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
      //--- Если рассчитанный уровень ниже, чем нижний уровень ограничения (stops level), 
      //    то расчет закончен, вернем текущее значение уровня
      if(level<sym_down_level)
         return(level);
      //--- Если же не ниже, то попробуем рассчитать от цены bid
      else
        {
         level=NormalizeDouble(sym_bid-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
         //--- Если рассчитанный уровень ниже ограничителя, вернем текущее значение уровня
         //    Иначе установим максимально возможный близкий
         return(level<sym_down_level ? level : sym_down_level-sym_offset);
        }
     }
//--- Рассчитаем уровень для позиции SELL
   if(position_type==POSITION_TYPE_SELL)
     {
      // Максимум бара плюс указанное кол-во пунктов
      level=NormalizeDouble(sell_point+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
      //--- Если рассчитанный уровень выше, чем верхний уровень ограничения (stops level), 
      //    то расчёт закончен, вернем текущее значение уровня
      if(level>sym_up_level)
         return(level);
      //--- Если же не выше, то попробуем рассчитать от цены ask
      else
        {
         level=NormalizeDouble(sym_ask+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
         //--- Если рассчитанный уровень выше ограничителя, вернем текущее значение уровня
         //    Иначе установим максимально возможный близкий
         return(level>sym_up_level ? level : sym_up_level+sym_offset);
        }
     }
//---
   return(0.0);
  }

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

Обратите внимание, как используются все те функции, которые были созданы/модифицированы выше. С помощью переключателя switch в зависимости от того, какой тип у текущей позиции, определяется соответствующее условие, результат которого сохраняется в переменной condition. Для модификации позиции используется метод PositionModify из торгового класса CTrade стандартной библиотеки.

//+------------------------------------------------------------------+
//| Изменяет уровень Trailing Stop                                   |
//+------------------------------------------------------------------+
void ModifyTrailingStop()
  {
//--- Если включен трейлинг и StopLoss
   if(TrailingStop>0 && StopLoss>0)
     {
      double         new_sl=0.0;       // Для расчета нового уровня Stop loss
      bool           condition=false;  // Для проверки условия на модификацию
      //--- Получим флаг наличия/отсутствия позиции
      pos_open=PositionSelect(_Symbol);
      //--- Если есть позиция
      if(pos_open)
        {
         //--- Получим свойства символа
         GetSymbolProperties(S_ALL);
         //--- Получим свойства позиции
         GetPositionProperties(P_ALL);
         //--- Получим уровень для Stop Loss
         new_sl=CalculateTrailingStop(pos_type);
         //--- В зависимости от типа позиции проверим соответствующее условие на модификацию Trailing Stop
         switch(pos_type)
           {
            case POSITION_TYPE_BUY  :
               //--- Если новое значение для Stop Loss выше,
               //    чем текущее значение плюс установленный шаг
               condition=new_sl>pos_sl+CorrectValueBySymbolDigits(TrailingStop*sym_point);
               break;
            case POSITION_TYPE_SELL :
               //--- Если новое значение для Stop Loss ниже,
               //    чем текущее значение минус установленный шаг
               condition=new_sl<pos_sl-CorrectValueBySymbolDigits(TrailingStop*sym_point);
               break;
           }
         //--- Если Stop Loss есть, то сравним значения перед модификацией
         if(pos_sl>0)
           {
            //--- Если выполняется условие на модификацию ордера, т.е. новое значение ниже/выше, 
            //    чем текущее, модифицируем защитный уровень позиции
            if(condition)
              {
               if(!trade.PositionModify(_Symbol,new_sl,pos_tp))
                  Print("Ошибка при модификации позиции: ",GetLastError()," - ",ErrorDescription(GetLastError()));
              }
           }
         //--- Если Stop Loss нет, то просто установим его
         if(pos_sl==0)
           {
            if(!trade.PositionModify(_Symbol,new_sl,pos_tp))
               Print("Ошибка при модификации позиции: ",GetLastError()," - ",ErrorDescription(GetLastError()));
           }
        }
     }
  }

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

//+------------------------------------------------------------------+
//| Торговый блок                                                    |
//+------------------------------------------------------------------+
void TradingBlock()
  {
   ENUM_ORDER_TYPE      signal=WRONG_VALUE;                 // Переменная для приема сигнала
   string               comment="hello :)";                 // Комментарий для позиции
   double               tp=0.0;                             // Take Profit
   double               sl=0.0;                             // Stop Loss
   double               lot=0.0;                            // Объем для расчета позиции в случае переворота позиции
   double               position_open_price=0.0;            // Цена открытия позиции
   ENUM_ORDER_TYPE      order_type=WRONG_VALUE;             // Тип ордера для открытия позиции
   ENUM_POSITION_TYPE   opposite_position_type=WRONG_VALUE; // Противоположный тип позиции
//--- Получим сигнал
   signal=GetTradingSignal();
//--- Если сигнала нет, выходим
   if(signal==WRONG_VALUE)
      return;
//--- Узнаем, есть ли позиция
   pos_open=PositionSelect(_Symbol);
//--- Получим все свойства символа
   GetSymbolProperties(S_ALL);
//--- Определим значения торговым переменным
   switch(signal)
     {
      //--- Присвоим переменным значения для BUY
      case ORDER_TYPE_BUY  :
         position_open_price=sym_ask;
         order_type=ORDER_TYPE_BUY;
         opposite_position_type=POSITION_TYPE_SELL;
         break;
         //--- Присвоим переменным значения для SELL
      case ORDER_TYPE_SELL :
         position_open_price=sym_bid;
         order_type=ORDER_TYPE_SELL;
         opposite_position_type=POSITION_TYPE_BUY;
         break;
     }
//--- Рассчитаем уровни Take Profit и Stop Loss
   sl=CalculateStopLoss(order_type);
   tp=CalculateTakeProfit(order_type);
//--- Если позиции нет
   if(!pos_open)
     {
      //--- Скорректируем объём
      lot=CalculateLot(Lot);
      //--- Откроем позицию
      //    Если позиция не открылась, вывести сообщение об этом
      if(!trade.PositionOpen(_Symbol,order_type,lot,position_open_price,sl,tp,comment))
        {
         Print("Ошибка при открытии позиции: ",GetLastError()," - ",ErrorDescription(GetLastError()));
        }
     }
//--- Если позиция есть
   else
     {
      //--- Получим тип позиции
      GetPositionProperties(P_TYPE);
      //--- Если позиция противоположна сигналу и включен переворот позиции
      if(pos_type==opposite_position_type && Reverse)
        {
         //--- Получим объём позиции
         GetPositionProperties(P_VOLUME);
         //--- Скорректируем объём
         lot=pos_volume+CalculateLot(Lot);
         //--- Откроем позицию. Если позиция не открылась, вывести сообщение об этом
         if(!trade.PositionOpen(_Symbol,order_type,lot,position_open_price,sl,tp,comment))
           {
            Print("Ошибка при открытии позиции: ",GetLastError()," - ",ErrorDescription(GetLastError()));
           }
        }
     }
//---
   return;
  }

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

//+------------------------------------------------------------------+
//| Возвращает флаг тестирования                                     |
//+------------------------------------------------------------------+
bool IsTester()
  {
   return(MQL5InfoInteger(MQL5_TESTER));
  }
//+------------------------------------------------------------------+
//| Возвращает флаг оптимизации                                      |
//+------------------------------------------------------------------+
bool IsOptimization()
  {
   return(MQL5InfoInteger(MQL5_OPTIMIZATION));
  }
//+------------------------------------------------------------------+
//| Возвращает флаг визуального режима тестирования                  |
//+------------------------------------------------------------------+
bool IsVisualMode()
  {
   return(MQL5InfoInteger(MQL5_VISUAL_MODE));
  }
//+------------------------------------------------------------------+
//| Возвращает флаг режима реального времени вне тестера,            |
//| если все условия выполняются                                     |
//+------------------------------------------------------------------+
bool IsRealtime()
  {
   if(!IsTester() && !IsOptimization() && !IsVisualMode())
      return(true);
   else
      return(false); 
  }

В функции SetInfoPanel теперь нужно всего лишь добавить условие, которое будет говорить программе, что информационную панель нужно выводить только в режимах визуализации и реального времени. Если этого не сделать, то время тестирования будет возрастет примерно в 4-5 раз. Это особенно актуально, когда проводится оптимизация параметров.

//+------------------------------------------------------------------+
//| Устанавливает информационную панель                              |
//|------------------------------------------------------------------+
void SetInfoPanel()
  {
//--- Режим визуализации или реального времени
   if(IsVisualMode() || IsRealtime())
     {
     // Остальной код функции SetInfoPanel()
     // ...
     }
  }

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Инициализируем новый бар
   CheckNewBar();
//--- Получим свойства и установим панель
   GetPositionProperties(P_ALL);
//--- Установим информационную панель
   SetInfoPanel();
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Если бар не новый, выходим
   if(!CheckNewBar())
      return;
//--- Если есть новый бар
   else
     {
      GetBarsData();          // Получим данные баров
      TradingBlock();         // Проверим условия и торгуем
      ModifyTrailingStop();   // Изменим уровень Trailing Stop
     }
//--- Получим свойства и обновим значения на панели
   GetPositionProperties(P_ALL);
//--- Обновим информационную панель
   SetInfoPanel();
  }
//+------------------------------------------------------------------+
//| Торговое событие                                                 |
//+------------------------------------------------------------------+
void OnTrade()
  {
//--- Получить свойства позиции и обновить значения на панели
   GetPositionProperties(P_ALL);
//--- Обновим информационную панель
   SetInfoPanel();
  }


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

Теперь произведем оптимизацию параметров. Настройки тестера установим, например так, как на рисунке ниже:

Рис. 1. Настройки тестера для оптимизации параметров.

Рис. 1. Настройки тестера для оптимизации параметров.

Настройки эксперта установим с широкими диапазонами параметров:

Рис. 2. Настройки эксперта для оптимизации параметров.

Рис. 2. Настройки эксперта для оптимизации параметров.

На двухъядерном процессоре (Intel Core2 Duo P7350 @ 2.00GHz) время оптимизации заняло около 7 минут. Результат по максимальному фактору восстановления получился вот таким:

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

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


Заключение

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

Прикрепленные файлы |
Yury Kulikov
Yury Kulikov | 2 апр 2013 в 18:52
Чтобы не получить ошибку при установке/изменении торговых уровней необходимо еще приводить эти уровни к кратности минимального изменения цены (SYMBOL_TRADE_TICK_SIZE). Минимальное изменение цены не всегда совпадает со значением пункта.
Anatoli Kazharski
Anatoli Kazharski | 2 апр 2013 в 19:17
TheXpert:

Ну обычные флаги

0х1 0х2 0х4 0х8 .... затем смотреть наличие и подгружать что надо.

Да, точно. Не сразу понял, но так было бы ещё удобнее. Я пока не совсем разобрался с побитовыми операциями, попробую поэкспериментировать. В Справке что-то сложно-усваиваемый материал по этой теме. Не понимаю на все 100%, а хочется понимать, что делаешь. )) Может быть кто-нибудь напишет статью на эту тему.

Anatoli Kazharski
Anatoli Kazharski | 2 апр 2013 в 19:21
Yurich:
Чтобы не получить ошибку при установке/изменении торговых уровней необходимо еще приводить эти уровни к кратности минимального изменения цены (SYMBOL_TRADE_TICK_SIZE). Минимальное изменение цены не всегда совпадает со значением пункта.
Спасибо. Так будет точнее.
Nauris Zukas
Nauris Zukas | 22 июл 2015 в 17:59
А как же закрыть позицию? Если в эксперте есть и условия (сигнал) на закрытие позиций, как это сделать, где можно посмотреть такие же  примеры?
Anatoli Kazharski
Anatoli Kazharski | 22 июл 2015 в 18:04
Nauris:
А как же закрыть позицию? Если в эксперте есть и условия (сигнал) на закрытие позиций, как это сделать, где можно посмотреть такие же примеры?
В статье используется класс CTrade стандартной библиотеки. В нём есть метод PositionClose. Можете воспользоваться им. 
Итоги MQL5 Маркета за 1 квартал 2013 года Итоги MQL5 Маркета за 1 квартал 2013 года

С момента своего основания MQL5 Маркет - магазин торговых роботов и технических индикаторов - уже привлек в свои ряды более 250 разработчиков, которые опубликовали 580 продуктов. Итоги первого квартала 2013 года показывают, что некоторые продавцы довольно успешны в MQL5 Маркете и уже получили с продаж солидную прибыль.

Индикатор для построения графика "Крестики - Нолики" Индикатор для построения графика "Крестики - Нолики"

Существует множество типов графиков, которые представляют информацию о текущей ситуации на рынке. Многие из них пришли к нам из далёкого прошлого, и как раз одним из таких является график "Крестики-нолики". В статье описан пример построения графика "Крестики - Нолики" в виде индикатора в реальном времени.

MQL5 Cloud Network: Вы все еще считаете? MQL5 Cloud Network: Вы все еще считаете?

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

Индикатор "ЗигЗаг": новый взгляд и новые решения Индикатор "ЗигЗаг": новый взгляд и новые решения

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