Как устанавливать/модифицировать торговые уровни и не получить ошибку?

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

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

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

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

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

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

//+------------------------------------------------------------------+
//| ПЕРЕЧИСЛЕНИЕ СВОЙСТВ ПОЗИЦИИ                                     |
//+------------------------------------------------------------------+
enum ENUM_PROP_POSITION
  {
   POS_COMMENT       = 0,
   POS_MAGIC         = 1,
   POS_PRICE_OPEN    = 2,
   POS_PRICE_CURRENT = 3,
   POS_SL            = 4,
   POS_TP            = 5,
   POS_SYMBOL        = 6,
   POS_TYPE          = 7,
   POS_VOLUME        = 8,
   POS_COMMISSION    = 9,
   POS_SWAP          = 10,
   POS_PROFIT        = 11,
   POS_TIME          = 12,
   POS_ID            = 13,
   POS_ALL           = 14
  };
//---
//+------------------------------------------------------------------+
//| ПЕРЕЧИСЛЕНИЕ СВОЙСТВ СИМВОЛА                                     |
//+------------------------------------------------------------------+
enum ENUM_PROP_SYMBOL
  {
   SYMB_DIGITS     = 0,
   SYMB_SPREAD     = 1,
   SYMB_STOPSLEVEL = 2,
   SYMB_POINTS     = 3,
   SYMB_ASK        = 4,
   SYMB_BID        = 5,
   SYMB_VOLMIN     = 6,
   SYMB_VOLMAX     = 7,
   SYMB_VOLLIMIT   = 8,
   SYMB_VOLSTEP    = 9,
   SYMB_FILTER     = 10,
   SYMB_UPLEVEL    = 11,
   SYMB_DWLEVEL    = 12,
   SYMB_ALL        = 13
  };
//---

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

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

//+------------------------------------------------------------------+
//| ВНЕШНИЕ ПАРАМЕТРЫ ЭКСПЕРТА                                       |
//+------------------------------------------------------------------+
input int    AmountBars = 2;    // Amount Bear/Bull Bars For Buy/Sell
input double Lot        = 0.1;  // Lot
input double StopLoss   = 50;   // Stop Loss (p)
input double TakeProfit = 100;  // Take Profit (p)
input double TrailingSL = 10;   // Trailing Stop (p)
input bool   Reverse    = true; // On/Off Reverse
//---

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

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

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

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

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

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

//---
// СВОЙСТВА СИМВОЛА
int dgt=0;            // Количество знаков в цене после запятой
int spread=0;         // Размер спреда в пунктах
int stops_level=0;    // Ограничитель установки Stop ордеров
double point=0.0;     // Значение одного пункта
double ask=0.0;       // Цена ask
double bid=0.0;       // Цена bid
double vol_min=0.0;   // Минимальный объем для заключения сделки
double vol_max=0.0;   // Максимальный объем для заключения сделки
double vol_lmt=0.0;   // Максимально допустимый объём для позиции и ордеров в одном направлении
double vol_step=0.0;  // Минимальный шаг изменения объема для заключения сделки
double pp_filter=0.0; // Отступ от максимально возможной цены для операции
double up_level=0.0;  // Цена верхнего уровня stop level
double dw_level=0.0;  // Цена нижнего уровня stop level
//---

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

//---
double prc_h[]; // High (цена максимума бара)
double prc_l[]; // Low (цена минимума бара)
//---

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

//+------------------------------------------------------------------+
//| ПОЛУЧАЕТ ЗНАЧЕНИЯ БАРОВ                                          |
//+------------------------------------------------------------------+
void GetDataBars()
  {
// Скорректируем значение кол-ва баров для условия на открытие позиции
   if(gAmountBars<=0) // Если ноль или меньше, то инициализируем
     {
      if(AmountBars<=1) { gAmountBars=2; } // Нужно не менее двух баров
      if(AmountBars>=5) { gAmountBars=6; } // и не более 5
      else { gAmountBars=AmountBars+1; }   // и всегда на один больше
     }
//---
// Установим обратный порядок индексации (... 3 2 1 0)
   ArraySetAsSeries(prc_c,true);
   ArraySetAsSeries(prc_o,true);
   ArraySetAsSeries(prc_h,true);
   ArraySetAsSeries(prc_l,true);
//---
// ЗАПОЛНЕНИЕ ЦЕНОВЫХ МАССИВОВ ДАННЫМИ
//---
// Получим цену закрытия бара
// Если полученных значений меньше, чем запрошено
// вывести сообщение об этом
   if(CopyClose(_Symbol,_Period,0,gAmountBars,prc_c)<gAmountBars)
     {
      Print("Не удалось скопировать значения ("
            +_Symbol+"; "+TFtoS(_Period)+") в массив цен Close! "
            "Ошибка ("+IS(GetLastError())+"): "+ErrorDesc(GetLastError())+"");
     }
//---
// Получим цену открытия бара
// Если полученных значений меньше, чем запрошено
// вывести сообщение об этом
   if(CopyOpen(_Symbol,_Period,0,gAmountBars,prc_o)<gAmountBars)
     {
      Print("Не удалось скопировать значения ("
            +_Symbol+"; "+TFtoS(_Period)+") в массив цен Open! "
            "Ошибка ("+IS(GetLastError())+"): "+ErrorDesc(GetLastError())+"");
     }
//---
// Получим цену максимума бара
// Если полученных значений меньше, чем запрошено
// вывести сообщение об этом
   if(CopyHigh(_Symbol,_Period,0,gAmountBars,prc_h)<gAmountBars)
     {
      Print("Не удалось скопировать значения ("
            +_Symbol+"; "+TFtoS(_Period)+") в массив цен High! "
            "Ошибка ("+IS(GetLastError())+"): "+ErrorDesc(GetLastError())+"");
     }
//---
// Получим цену минимума бара
// Если полученных значений меньше, чем запрошено
// вывести сообщение об этом
   if(CopyLow(_Symbol,_Period,0,gAmountBars,prc_l)<gAmountBars)
     {
      Print("Не удалось скопировать значения ("
            +_Symbol+"; "+TFtoS(_Period)+") в массив цен Low! "
            "Ошибка ("+IS(GetLastError())+"): "+ErrorDesc(GetLastError())+"");
     }
  }
//---

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

Функция GetTradingSignal() теперь немного усложнилась, так как в зависимости от того, сколько баров указано в параметре AmountBars, условие будет формироваться иначе (см. код ниже):

//+------------------------------------------------------------------+
//| ОПРЕДЕЛЯЕТ ТОРГОВЫЕ СИГНАЛЫ                                      |
//+------------------------------------------------------------------+
int GetTradingSignal()
  {
//________________________
// Сигнал на продажу (1) :
   if(gAmountBars==2 && prc_c[1]<prc_o[1]) { return(1); }
//---
   if(gAmountBars==3 && prc_c[1]<prc_o[1] && prc_c[2]<prc_o[2]) { return(1); }
//---
   if(gAmountBars==4 && prc_c[1]<prc_o[1] && 
      prc_c[2]<prc_o[2] && prc_c[3]<prc_o[3]) { return(1); }
//---
   if(gAmountBars==5 && prc_c[1]<prc_o[1] && 
      prc_c[2]<prc_o[2] && prc_c[3]<prc_o[3] && prc_c[4]<prc_o[4]) { return(1); }
//---
   if(gAmountBars>=6 && prc_c[1]<prc_o[1] && prc_c[2]<prc_o[2] && 
      prc_c[3]<prc_o[3] && prc_c[4]<prc_o[4] && prc_c[5]<prc_o[5]) { return(1); }
//---
//________________________
// Сигнал на покупку (0) :
   if(gAmountBars==2 && prc_c[1]>prc_o[1]) { return(0); }
//---
   if(gAmountBars==3 && prc_c[1]>prc_o[1] && prc_c[2]>prc_o[2]) { return(0); }
//---
   if(gAmountBars==4 && prc_c[1]>prc_o[1] && 
      prc_c[2]>prc_o[2] && prc_c[3]>prc_o[3]) { return(0); }
//---
   if(gAmountBars==5 && prc_c[1]>prc_o[1] && 
      prc_c[2]>prc_o[2] && prc_c[3]>prc_o[3] && prc_c[4]>prc_o[4]) { return(0); }
//---
   if(gAmountBars>=6 && prc_c[1]>prc_o[1] && prc_c[2]>prc_o[2] && 
      prc_c[3]>prc_o[3] && prc_c[4]>prc_o[4] && prc_c[5]>prc_o[5]) { return(0); }
//---
// Отсутствие сигнала (3)
   return(3);
  }
//---

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

//+------------------------------------------------------------------+
//| ПОЛУЧАЕТ СВОЙСТВА ПОЗИЦИИ                                        |
//+------------------------------------------------------------------+
void GetPropPosition(int pos_prop)
  {
// Узнаем, есть ли позиция
   isPos=PositionSelect(_Symbol);
//---
   if(isPos) // Если позиция есть, то...
     {
      // ...получим её свойства
      switch(pos_prop)
        {
         case POS_COMMENT       : pos_comment=PositionGetString(POSITION_COMMENT);                break;
         case POS_MAGIC         : pos_magic=PositionGetInteger(POSITION_MAGIC);                   break;
         case POS_PRICE_OPEN    : pos_price=PositionGetDouble(POSITION_PRICE_OPEN);               break;
         case POS_PRICE_CURRENT : pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT);           break;
         case POS_SL            : pos_sl=PositionGetDouble(POSITION_SL);                          break;
         case POS_TP            : pos_tp=PositionGetDouble(POSITION_TP);                          break;
         case POS_SYMBOL        : pos_symbol=PositionGetString(POSITION_SYMBOL);                  break;
         case POS_TYPE          : pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); break;
         case POS_VOLUME        : pos_volume=PositionGetDouble(POSITION_VOLUME);                  break;
         case POS_COMMISSION    : pos_commission=PositionGetDouble(POSITION_COMMISSION);          break;
         case POS_SWAP          : pos_swap=PositionGetDouble(POSITION_SWAP);                      break;
         case POS_PROFIT        : pos_profit=PositionGetDouble(POSITION_PROFIT);                  break;
         case POS_TIME          : pos_time=(datetime)PositionGetInteger(POSITION_TIME);           break;
         case POS_ID            : pos_id=PositionGetInteger(POSITION_IDENTIFIER);                 break;
         //---
         case POS_ALL :
            pos_comment=PositionGetString(POSITION_COMMENT);
            pos_magic=PositionGetInteger(POSITION_MAGIC);
            pos_price=PositionGetDouble(POSITION_PRICE_OPEN);
            pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT);
            pos_sl=PositionGetDouble(POSITION_SL);
            pos_tp=PositionGetDouble(POSITION_TP);
            pos_symbol=PositionGetString(POSITION_SYMBOL);
            pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
            pos_volume=PositionGetDouble(POSITION_VOLUME);
            pos_commission=PositionGetDouble(POSITION_COMMISSION);
            pos_swap=PositionGetDouble(POSITION_SWAP);
            pos_profit=PositionGetDouble(POSITION_PROFIT);
            pos_time=(datetime)PositionGetInteger(POSITION_TIME);
            pos_id=PositionGetInteger(POSITION_IDENTIFIER);
            //---
            break;
            //---
         default: Print("Переданное свойство позиции не учтено в перечислении!"); return;
        }
     }
   else // Если позиции нет, то...
     { ZeroPropPosition(); } // ...обнулим переменные свойств позиции
  }
//---

По такой же схеме реализуем функцию GetPropSymbol() для получения свойств символа (см. код ниже).

//+------------------------------------------------------------------+
//| ПОЛУЧАЕТ СВОЙСТВА СИМВОЛА                                        |
//+------------------------------------------------------------------+
void GetPropSymbol(int symb_prop)
  {
   int flt=1; // Количество пунктов для отступа от уровней stops level
//---   
   switch(symb_prop)
     {
      case SYMB_DIGITS     : dgt=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);                    break;
      case SYMB_SPREAD     : spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD);                 break;
      case SYMB_STOPSLEVEL : stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); break;
      case SYMB_POINTS     : point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);                         break;
      //---
      case SYMB_ASK        :
         dgt=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         ask=ND(SymbolInfoDouble(_Symbol,SYMBOL_ASK),dgt);                                         break;
         //---
      case SYMB_BID        :
         dgt=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         bid=ND(SymbolInfoDouble(_Symbol,SYMBOL_BID),dgt);                                         break;
         //---
      case SYMB_VOLMIN     : vol_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);                  break;
      case SYMB_VOLMAX     : vol_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);                  break;
      case SYMB_VOLLIMIT   : vol_lmt=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT);                break;
      case SYMB_VOLSTEP    : vol_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);                break;
      //---
      case SYMB_FILTER     :
         dgt=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         pp_filter=ND(DgtMlt(flt*point),dgt);                                                      break;
         //---
      case SYMB_UPLEVEL    :
         dgt=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         ask=ND(SymbolInfoDouble(_Symbol,SYMBOL_ASK),dgt);
         up_level=ND(ask+stops_level*point,dgt);                                                   break;
         //---
      case SYMB_DWLEVEL    :
         dgt=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         ask=ND(SymbolInfoDouble(_Symbol,SYMBOL_ASK),dgt);
         dw_level=ND(bid-stops_level*point,dgt);                                                   break;
         //---
      case POS_ALL :
         dgt=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD);
         stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         ask=ND(SymbolInfoDouble(_Symbol,SYMBOL_ASK),dgt);
         bid=ND(SymbolInfoDouble(_Symbol,SYMBOL_BID),dgt);
         vol_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
         vol_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
         vol_lmt=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT);
         vol_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
         pp_filter=ND(DgtMlt(flt*point),dgt);
         up_level=ND(ask+stops_level*point,dgt);
         dw_level=ND(bid-stops_level*point,dgt);                                                   break;
         //---
      default: Print("Переданное свойство символа не учтено в перечислении!"); return;
     }
  }
//---

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

//+------------------------------------------------------------------+
//| КОРРЕКЦИЯ ЗНАЧЕНИЯ ПО КОЛИЧЕСТВУ ЗНАКОВ В ЦЕНЕ (int)             |
//+------------------------------------------------------------------+
int DgtMlt(int value)
  {
   if(dgt==3 || dgt==5) { return(value*=10); } else { return(value); }
  }
//+------------------------------------------------------------------+
//| КОРРЕКЦИЯ ЗНАЧЕНИЯ ПО КОЛИЧЕСТВУ ЗНАКОВ В ЦЕНЕ (double)          |
//+------------------------------------------------------------------+
double DgtMlt(double value)
  {
   if(dgt==3 || dgt==5) { return(value*=10); } else { return(value); }
  }
//---

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

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ ОБЪЁМ ДЛЯ ПОЗИЦИИ                                     |
//+------------------------------------------------------------------+
double GetLot(double lot)
  {
   double lots=0.0; // Для корректировки с учётом шага
   //---
   GetPropSymbol(SYMB_VOLMIN); // Получим минимально возможный лот
   GetPropSymbol(SYMB_VOLMAX); // Получим максимально возможный лот
   GetPropSymbol(SYMB_VOLSTEP); // Получим шаг увеличения/уменьшения лота
//---
// Скорректируем с учётом шага лота
   lots=MathRound(lot/vol_step)*vol_step;
//---
// Если меньше минимального, то вернём минимальный
   if(lots<vol_min) { return(ND(vol_min,2)); }
//---
// Если больше максимального, то вернём максимальный
   if(lots>vol_max) { return(ND(vol_max,2)); }
//---
   return(ND(lots,2));
  }
//---

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

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

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ УРОВЕНЬ TAKEPROFIT                                    |
//+------------------------------------------------------------------+
double GetLevelTakeProfit(int type_pos)
  {
// Если Take Profit нужен
   if(TakeProfit>0)
     {
      double tp=0.0; // Для рассчитанного значения Take Profit
      //---
      // Если нужно рассчитать значение для позиции SELL
      if(type_pos==POSITION_TYPE_SELL)
        {
         // Рассчитаем уровень
         tp=ND(bid-DgtMlt(TakeProfit*point),dgt);
         //---
         // Если рассчитанное значение ниже нижней границы stops level
         if(tp<dw_level) { return(tp); }
         else // Если значение выше или равно, то скорректируем его
           { tp=dw_level-pp_filter; return(tp); }
        }
      //---
      // Если нужно рассчитать значение для позиции BUY
      if(type_pos==POSITION_TYPE_BUY)
        {
         // Рассчитаем уровень
         tp=ND(ask+DgtMlt(TakeProfit*point),dgt);
         //---
         // Если рассчитанное значение выше верхней границы stops level
         if(tp>up_level) { return(tp); }
         else // Если значение ниже или равно, то скорректируем его
           { tp=up_level+pp_filter; return(tp); }
        }
     }
//---
   return(0.0);
  }
//---

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

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ УРОВЕНЬ STOPLOSS                                      |
//+------------------------------------------------------------------+
double GetLevelStopLoss(int type_pos)
  {
// Если Stop Loss нужен
   if(StopLoss>0)
     {
      double sl=0.0; // Для рассчитанного значения Stop Loss
      //---
      // Если нужно рассчитать значение для позиции SELL
      if(type_pos==POSITION_TYPE_SELL)
        {
         // Рассчитаем уровень
         sl=ND(bid+DgtMlt(StopLoss*point),dgt);
         //---
         // Если рассчитанное значение выше верхней границы stops level
         if(sl>up_level) { return(sl); }
         else // Если значение ниже или равно, то скорректируем его
           { sl=up_level+pp_filter; return(sl); }
        }
      //---
      // Если нужно рассчитать значение для позиции BUY
      if(type_pos==POSITION_TYPE_BUY)
        {
         // Рассчитаем уровень
         sl=ND(ask-DgtMlt(StopLoss*point),dgt);
         //---
         // Если рассчитанное значение ниже нижней границы stops level
         if(sl<dw_level) { return(sl); }
         else // Если значение выше или равно, то скорректируем его
           { sl=dw_level-pp_filter; return(sl); }
        }
     }
//---
   return(0.0);
  }
//---

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

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ УРОВЕНЬ TRAILING STOPLOSS                             |
//+------------------------------------------------------------------+
double GetLevelTrailStopLoss(int type)
  {
// Переменные для расчётов
   double level=0.0;
   double bpoint=0.0,spoint=0.0;
//---
   bpoint=prc_l[1]; // Значение Low для Buy
   spoint=prc_h[1]; // Значение High для Sell
//---
// Рассчитаем уровень для позиции BUY
   if(type==POSITION_TYPE_BUY)
     {
      // Минимум бара минус указанное кол-во пунктов
      level=ND(bpoint-DgtMlt(StopLoss*point),dgt);
      //---
      // Если рассчитанный уровень ниже, ...
      // ...чем нижний уровень ограничения (stops level), то расчёт закончен, ...
      if(level<dw_level) { return(level); } // ...вернём уровень
      else
        {// Если же не ниже, то попробуем рассчитать от цены bid
         level=ND(bid-DgtMlt(StopLoss*point),dgt);
         //---
         // Если рассчитанный уровень ниже ограничителя, то...
         if(level<dw_level) { return(level); } // ...вернём уровень
         else // Иначе установим максимально возможный близкий
           { return(dw_level-pp_filter); }
        }
     }
//---
// Рассчитаем уровень для позиции SELL
   if(type==POSITION_TYPE_SELL)
     {
      // Максимум бара плюс указанное кол-во пунктов
      level=ND(spoint+DgtMlt(StopLoss*point),dgt);
      //---
      // Если рассчитанный уровень выше, ...
      // ...чем верхний уровень ограничения (stops level), то...
      if(level>up_level) { return(level); } // ...вернём уровень
      else
        {// Если же не выше, то попробуем рассчитать от цены ask
         level=ND(ask+DgtMlt(StopLoss*point),dgt);
         //---
         // Если рассчитанный уровень ниже ограничителя, то...
         if(level>up_level) { return(level); } // ...вернём уровень
         else // Иначе установим максимально возможный близкий
           { return(up_level+pp_filter); }
        }
     }
//---
   return(0.0);
  }
//---

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

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

//+------------------------------------------------------------------+
//| TRAILING STOPLOSS                                                |
//+------------------------------------------------------------------+
void TrailingStopLoss()
  {
// Если включен трейлинг и StopLoss
   if(TrailingSL>0 && StopLoss>0)
     {
      double new_sl=0.0; // Для расчёта нового уровня Stop loss
      bool condition=false; // Для проверки условия на модификацию
      //---
      isPos=PositionSelect(_Symbol); // Получим флаг наличия/отсутствия позиции
      //---
      if(isPos) // Если есть позиция
        {
         GetPropSymbol(SYMB_ALL); // Получим свойства символа
         GetPropPosition(POS_ALL); // Получим свойства позиции
         //---
         // Получим уровень для Stop Loss
         new_sl=GetLevelTrailStopLoss(pos_type);
         //---
         // В зависимости от типа позиции проверим соответствующее условие
         switch(pos_type)
           {
            case POSITION_TYPE_BUY  :
               // Если новое значение для Stop Loss выше,
               // чем текущее значение плюс установленный шаг
               condition=pos_sl+DgtMlt(TrailingSL*point)<new_sl; break;
               //---
            case POSITION_TYPE_SELL :
               // Если новое значение для Stop Loss ниже,
               // чем текущее значение минус установленный шаг
               condition=pos_sl-DgtMlt(TrailingSL*point)>new_sl; break;
           }
         //---
         if(pos_sl>0) // Если Stop Loss есть, то сравним значения перед модификацией
           {
            // Если выполняется условие на модификацию ордера
            // То есть, новое значение ниже/выше, чем текущее...
            if(condition)
              {
               // ...модифицируем защитный уровень позиции
               if(!trd.PositionModify(_Symbol,new_sl,pos_tp))
                 { Print("Ошибка при модификации позиции: ",GetLastError()," - ",ErrorDesc(GetLastError())); }
              }
           }
         //---
         if(pos_sl==0) // Если Stop Loss нет, то просто установим его
           {
            if(!trd.PositionModify(_Symbol,new_sl,pos_tp))
              { Print("Ошибка при модификации позиции: ",GetLastError()," - ",ErrorDesc(GetLastError())); }
           }
        }
     }
  }
//---

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

//+------------------------------------------------------------------+
//| ТОРГОВЫЙ БЛОК                                                    |
//+------------------------------------------------------------------+
void TradingBlock()
  {
   int signal=-1; // Переменная для приёма сигнала
   string comment="hello :)"; // Комментарий для позиции
   double
   tp=0.0,  // Take Profit
   sl=0.0,  // Stop Loss
   lot=0.0, // Для расчёта позиции в случае переворота позиции
   oprice=0.0; // Цена открытия позиции
   int
   type_ord=-1, // Тип позиции для открытия
   opptype_pos=-1; // Противоложный тип позиции
//---
   signal=GetTradingSignal(); // Получим сигнал
//---
// Если сигнала нет (3), выходим
   if((signal=GetTradingSignal())==3) { return; }
//---
// Узнаем, есть ли позиция
   isPos=PositionSelect(_Symbol);
//---
// Получим свойства символа
   GetPropSymbol(SYMB_ALL);
//---
// Определим значения торговым переменным
   switch(signal)
     {
      // Присвоим переменным значения для BUY
      case ORDER_TYPE_BUY  :
         oprice=ask;
         type_ord=ORDER_TYPE_BUY;
         opptype_pos=POSITION_TYPE_SELL; break;
         //---
         // Присвоим переменным значения для SELL
      case ORDER_TYPE_SELL :
         oprice=bid;
         type_ord=ORDER_TYPE_SELL;
         opptype_pos=POSITION_TYPE_BUY;  break;
     }
//---
// Получим уровни Take Profit и Stop Loss
   sl=GetLevelStopLoss(type_ord);
   tp=GetLevelTakeProfit(type_ord);
//---
// Если позиции нет
   if(!isPos)
     {
      lot=GetLot(Lot); // Скорректируем объём
      //---
      // Откроем позицию
      // Если позиция не открылась, вывести сообщение об этом
      if(!trd.PositionOpen(_Symbol,(ENUM_ORDER_TYPE)type_ord,lot,oprice,sl,tp,comment))
        { Print("Ошибка при открытии позиции: ",GetLastError()," - ",ErrorDesc(GetLastError())); }
     }
   else // Если позиция есть
     {
      // Получим тип позиции
      GetPropPosition(POS_TYPE);
      //---
      // Если позиция противоположна сигналу и включен переворот позиции
      if(pos_type==opptype_pos && Reverse)
        {
         // Получим объём позиции
         GetPropPosition(POS_VOLUME);
         //---
         // Скорректируем объём
         lot=pos_volume+GetLot(Lot);
         //---
         // Откроем позицию
         // Если позиция не открылась, вывести сообщение об этом
         if(!trd.PositionOpen(_Symbol,(ENUM_ORDER_TYPE)type_ord,lot,oprice,sl,tp,comment))
           { Print("Ошибка при открытии позиции: ",GetLastError()," - ",ErrorDesc(GetLastError())); }
        }
     }
//---
   return;
  }
//---

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

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ ФЛАГ ТЕСТИРОВАНИЯ                                     |
//+------------------------------------------------------------------+
bool Testing()
  {
   return(MQL5InfoInteger(MQL5_TESTING));
  }
//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ ФЛАГ ОПТИМИЗАЦИИ                                      |
//+------------------------------------------------------------------+
bool Optimization()
  {
   return(MQL5InfoInteger(MQL5_OPTIMIZATION));
  }
//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ ФЛАГ ВИЗУАЛЬНОГО РЕЖИМА ТЕСТИРОВАНИЯ                  |
//+------------------------------------------------------------------+
bool VisualMode()
  {
   return(MQL5InfoInteger(MQL5_VISUAL_MODE));
  }
//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ ФЛАГ РЕЖИМА РЕАЛЬНОГО ВРЕМЕНИ ВНЕ ТЕСТЕРА             |
//| ЕСЛИ ВСЕ УСЛОВИЯ ВЫПОЛНЯЮТСЯ                                     |
//+------------------------------------------------------------------+
bool NotTest()
  {
   if(!Testing() && !Optimization() && !VisualMode()) { return(true); } else { return(false); }
  }
//---

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

//+------------------------------------------------------------------+
//| УСТАНОВКА ИНФОРМАЦИОННОЙ ПАНЕЛИ                                  |
//|------------------------------------------------------------------+
void SetInfoPanel()
  {
   // Если сейчас тест в режиме визуализации или реал-тайм
   if(VisualMode() || NotTest())
     {
      // Остальной код функции SetInfoPanel()
      // ...
     }
  }
//---

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

//+------------------------------------------------------------------+
//| ИНИЦИАЛИЗАЦИЯ                                                    |
//+------------------------------------------------------------------+
void OnInit()
  {
   NewBar(); // Инициализируем новый бар
   GetPropPosition(POS_ALL); // Получим свойства позиции и
   SetInfoPanel(); // установим информационную панель
  }
//+------------------------------------------------------------------+
//| ДЕИНИЦИАЛИЗАЦИЯ                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
// Вывести в журнал причину деинициализации
   Print(GetTextReason(reason));
//---
// При удалении с графика
   if(reason==REASON_REMOVE)
     {
      // Удалить все объекты с графика,
      // которые относятся к информационной панели
      DeleteInfoPanel();
     }
  }
//+------------------------------------------------------------------+
//| СОБЫТИЕ ТИК ТЕКУЩЕГО ИНСТРУМЕНТА                                 |
//+------------------------------------------------------------------+
void OnTick()
  {
// Если бар не новый, выходим
   if(!NewBar()) { return; }
   else // Если есть новый бар
     {
      GetDataBars(); // Получим данные баров
      TradingBlock(); // Проверим условия и торгуем
      TrailingStopLoss(); // Трейлинг
     }
//---
   GetPropPosition(POS_ALL); // Получим свойства позиции и
   SetInfoPanel(); // установим/обновим информационную панель
  }
//+------------------------------------------------------------------+
//| ТОРГОВОЕ СОБЫТИЕ                                                 |
//+------------------------------------------------------------------+
void OnTrade()
  {
   GetPropPosition(POS_ALL); // Получим свойства позиции и
   SetInfoPanel(); // установим/обновим информационную панель
  }
//---

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

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

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

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

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

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


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

Успехов!




Скачать эксперт expGPPplus.mq5



2 комментария :

  1. Файл в конце статьи обновлён. Не хватало стандартного свойства позиции с идентификатором POSITION_PRICE_CURRENT.

    ОтветитьУдалить
  2. Обновлена функция GetLot(). В старой версии не учитывался шаг лота при корректировке. Файл в конце статьи обновлён.

    ОтветитьУдалить