Мультивалютный советник и работа с отложенными ордерами на MQL5

Мультивалютный советник и работа с отложенными ордерами на MQL5
На этот раз рассмотрим создание мультивалютного советника, торговый алгоритм которого строится на работе с отложенными ордерами Buy Stop и Sell Stop. Схему будем строить для внутридневной торговли/тестов. Разберём такие вопросы, как:

- Торговля в указанном временном диапазоне. Сделаем так, чтобы можно было указать время начала и окончания торговли. Например, это может быть временной диапазон во время европейской торговой сессии или американской торговой сессии. Конечно же будет возможность подобрать наилучший временной диапазон во время оптимизации параметров эксперта.
- Установка/модификация/удаление отложенных ордеров.
- Обработка торговых событий: определение закрыта ли последняя позиция по Take Profit или Stop Loss, контроль истории сделок на каждом символе.

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

В качестве шаблона возьмём код из статьи Мультивалютный советник на MQL5. Пример простой, точной и быстрой схемы. Изменения будут существенные, но структура схемы в принципе останется такой же. Выше уже упоминалось о том, что схему будем создавать для внутридневной торговли, но также будет возможность отключать этот режим. Отложенные ордера тогда будут всегда устанавливаться сразу же (по событию "новый бар"), если позиция была закрыта.

Начнём с внешних параметров торгового эксперта. Сначала создадим новое перечисление ENUM_HOURS в подключаемом файле Enums.mqh. Количество идентификаторов в этом перечислении равно количеству часов в сутках (см. код ниже).

//--- Перечисление часов
enum ENUM_HOURS
  {
   h00 = 0,  // 00 : 00
   h01 = 1,  // 01 : 00
   h02 = 2,  // 02 : 00
   h03 = 3,  // 03 : 00
   h04 = 4,  // 04 : 00
   h05 = 5,  // 05 : 00
   h06 = 6,  // 06 : 00
   h07 = 7,  // 07 : 00
   h08 = 8,  // 08 : 00
   h09 = 9,  // 09 : 00
   h10 = 10, // 10 : 00
   h11 = 11, // 11 : 00
   h12 = 12, // 12 : 00
   h13 = 13, // 13 : 00
   h14 = 14, // 14 : 00
   h15 = 15, // 15 : 00
   h16 = 16, // 16 : 00
   h17 = 17, // 17 : 00
   h18 = 18, // 18 : 00
   h19 = 19, // 19 : 00
   h20 = 20, // 20 : 00
   h21 = 21, // 21 : 00
   h22 = 22, // 22 : 00
   h23 = 23  // 23 : 00
  };
//---

В списке внешних параметров создадим четыре параметра, которые относятся к торговле во временном диапазоне:

- TimeRangeTrade - включение/отключение режима. Как уже упоминалось выше, сделаем возможность работы торгового советника не только во временном диапазоне, но и в круглосуточном (беспрерывном) режиме.
- StartTrade - час начала торговли. Как только время сервера станет равно указанному значению, эксперт выставит отложенные ордера при условии, что режим включен.
- StopEntryTrade - час окончания установки ордеров. Как только время сервера станет равным этому значению, эксперт больше не будет устанавливать отложенные ордера, если позиция закроется.
- EndTrade - час окончания торговли. Как только время сервера станет равным этому значению, эксперт прекращает торговлю. Открытая позиция на указанном символе будет закрыта, а отложенные ордера будут удалены.

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

//--- Внешние параметры эксперта
sinput long       MagicNumber       = 777;      // Магический номер
sinput int        Deviation         = 10;       // Проскальзывание
//---
sinput string delimeter_00=""; // --------------------------------
sinput string     Symbol_01         = "EURUSD"; // Символ 1
input  bool       TimeRangeTrade_01 = true;     // |     Торговый временной диапазон
input  ENUM_HOURS StartTrade_01     = h10;      // |     Час начала торговли
input  ENUM_HOURS StopOpenOrders_01 = h17;      // |     Час окончания установки ордеров
input  ENUM_HOURS EndTrade_01       = h22;      // |     Час окончания торговли
input  double     PendingOrder_01   = 50;       // |     Отложенный ордер
input  double     TakeProfit_01     = 100;      // |     Тейк Профит
input  double     StopLoss_01       = 50;       // |     Стоп Лосс
input  double     TrailingStop_01   = 10;       // |     Трейлинг Стоп/Ордер
input  bool       Reverse_01        = true;     // |     Разворот позиции
input  double     Lot_01            = 0.1;      // |     Лот
//---
sinput string delimeter_01=""; // --------------------------------
sinput string     Symbol_02         = "AUDUSD"; // Символ 2
input  bool       TimeRangeTrade_02 = true;     // |     Торговый временной диапазон
input  ENUM_HOURS StartTrade_02     = h10;      // |     Час начала торговли
input  ENUM_HOURS StopOpenOrders_02 = h17;      // |     Час окончания установки ордеров
input  ENUM_HOURS EndTrade_02       = h22;      // |     Час окончания торговли
input  double     PendingOrder_02   = 50;       // |     Отложенный ордер
input  double     TakeProfit_02     = 100;      // |     Тейк Профит
input  double     StopLoss_02       = 50;       // |     Стоп Лосс
input  double     TrailingStop_02   = 10;       // |     Трейлинг Стоп/Ордер
input  bool       Reverse_02        = true;     // |     Разворот позиции
input  double     Lot_02            = 0.1;      // |     Лот
//---

Соответственные изменения нужно произвести и в списке массивов, которые заполняются значениями внешних параметров:

//--- Массивы для хранения внешних параметров
string     Symbols[NUMBER_OF_SYMBOLS];        // Символ
bool       TimeRangeTrade[NUMBER_OF_SYMBOLS]; // Торговый временной диапазон
ENUM_HOURS StartTrade[NUMBER_OF_SYMBOLS];     // Час начала торговли
ENUM_HOURS StopEntryTrade[NUMBER_OF_SYMBOLS]; // Час окончания входов
ENUM_HOURS EndTrade[NUMBER_OF_SYMBOLS];       // Час окончания торговли
double     PendingOrder[NUMBER_OF_SYMBOLS];   // Отложенный ордер
double     TakeProfit[NUMBER_OF_SYMBOLS];     // Тейк Профит
double     StopLoss[NUMBER_OF_SYMBOLS];       // Стоп Лосс
double     TrailingStop[NUMBER_OF_SYMBOLS];   // Трейлинг Стоп
bool       Reverse[NUMBER_OF_SYMBOLS];        // Разворот позиции
double     Lot[NUMBER_OF_SYMBOLS];            // Лот
//---

Сделаем так, чтобы в режиме переворота позиции (параметр Reverse в положении true) при срабатывании одного из отложенных ордеров, противоположный отложенный ордер переустанавливался. У нас нет возможности изменить объём отложенного ордера, как в случае изменения его ценовых уровней (цена ордера, уровень Take Profit/Stop Loss), поэтому его сначала нужно удалить и затем установить новый отложенный ордер с нужным объёмом.

Также, если включен режим переворота позиции и одновременно с этим режим Trailing Stop, то вслед за ценой будет перемещаться отложенный ордер. Если при этом будет ещё включен и Stop Loss, то он будет рассчитываться и устанавливаться от отложенного ордера. Фактически его наличие при таком сочетании параметров будет на всякий случай, как "подушка безопасности".

На глобальном уровне программы создадим две строковые переменные для комментариев отложенных ордеров:

//--- Комментарии отложенных ордеров
string comment_top_order    ="top_order";
string comment_bottom_order ="bottom_order";
//---

Во время загрузки эксперта в момент инициализации в функции OnInit() будем проверять внешние параметры на корректность. Проверка будет заключаться в том, что при включенном режиме торговли во временном диапазоне, час начала торговли не должен быть меньше одного часа до часа окончания установки отложенных ордеров, а час окончания установки отложенных ордеров должен быть не меньше одного часа до часа окончания торговли. Напишем функцию CheckInputParameters(), которая будет производить такую проверку (см. код ниже).

//+------------------------------------------------------------------+
//| Проверяет внешние параметры                                      |
//+------------------------------------------------------------------+
bool CheckInputParameters()
  {
//--- Пройдёмся по всем указанным символам
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- Если такого символа нет или
      //    торговля во временном диапазоне отключена, переходим к следующему символу
      if(Symbols[s]=="" || !TimeRangeTrade[s])
         continue;
      //--- Если начало торговли позже либо равно окончанию, то сообщим об этом
      if(StartTrade[s]>=EndTrade[s])
        {
         Print(Symbols[s],
               ": Час начала торговли ("+IntegerToString(StartTrade[s])+") "
               "должен быть раньше, чем час окончания ("+IntegerToString(EndTrade[s])+")!");
         return(false);
        }
      //--- Если час окончания установки ордеров позже либо равен окончанию, то сообщим об этом
      if(StopEntryTrade[s]>=EndTrade[s] ||
         StopEntryTrade[s]<=StartTrade[s])
        {
         Print(Symbols[s],
               ": Час окончания входов в позицию ("+IntegerToString(StopEntryTrade[s])+") "
               "должен быть раньше, чем час окончания ("+IntegerToString(EndTrade[s])+") и "
               "позже, чем час начала торговли  ("+IntegerToString(StartTrade[s])+")!");
         return(false);
        }
     }
//--- Параметры корректны
   return(true);
  }
//---

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

//+------------------------------------------------------------------+
//| Проверяет находимся ли в торговом временном диапазоне            |
//+------------------------------------------------------------------+
bool CheckTimeRange(int symbol_number)
  {
//--- Если включена торговля во временном диапазоне
   if(TimeRangeTrade[symbol_number])
     {
      MqlDateTime last_date; // Структура даты и времени
      //--- Получим последние данные даты и времени
      TimeTradeServer(last_date);
      //--- Если вне временного диапазона
      if(last_date.hour<StartTrade[symbol_number] ||
         last_date.hour>=EndTrade[symbol_number])
         return(false);
     }
//--- Если во временном диапазоне
   return(true);
  }
//+------------------------------------------------------------------+
//| Проверяет находимся ли во временном диапазоне установки ордеров  |
//+------------------------------------------------------------------+
bool CheckTimeOpenOrders(int symbol_number)
  {
//--- Если включена торговля во временном диапазоне
   if(TimeRangeTrade[symbol_number])
     {
      MqlDateTime last_date; // Структура даты и времени
      //--- Получим последние данные даты и времени
      TimeTradeServer(last_date);
      //--- Если вне временного диапазона
      if(last_date.hour<StartTrade[symbol_number] ||
         last_date.hour>=StopEntryTrade[symbol_number])
         return(false);
     }
//--- Если во временном диапазоне
   return(true);
  }
//---

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

//--- Перечисление свойств отложенного ордера
enum ENUM_ORDER_PROPERTIES
  {
   O_SYMBOL          = 0,
   O_MAGIC           = 1,
   O_COMMENT         = 2,
   O_PRICE_OPEN      = 3,
   O_PRICE_CURRENT   = 4,
   O_PRICE_STOPLIMIT = 5,
   O_VOLUME_INITIAL  = 6,
   O_VOLUME_CURRENT  = 7,
   O_SL              = 8,
   O_TP              = 9,
   O_TIME_SETUP      = 10,
   O_TIME_EXPIRATION = 11,
   O_TIME_SETUP_MSC  = 12,
   O_TYPE_TIME       = 13,
   O_TYPE            = 14,
   O_ALL             = 15
  };
//---

Затем в подключаемом файле TradeFunctions.mqh нужно создать структуру и переменную свойств отложенного ордера (см. код ниже):

//--- Свойства отложенного ордера
struct pending_order_properties
  {
   string            symbol;          // Символ
   long              magic;           // Магический номер
   string            comment;         // Комментарий
   double            price_open;      // Цена, указанная в ордере
   double            price_current;   // Текущая цена по символу ордера
   double            price_stoplimit; // Цена постановки Limit ордера при срабатывании StopLimit ордера
   double            volume_initial;  // Первоначальный объем при постановке ордера
   double            volume_current;  // Невыполненный объём
   double            sl;              // Уровень Stop Loss
   double            tp;              // Уровень Take Profit
   datetime          time_setup;      // Время постановки ордера
   datetime          time_expiration; // Время истечения ордера
   datetime          time_setup_msc;  // Время установки ордера на исполнение в миллисекундах с 01.01.1970
   datetime          type_time;       // Время жизни ордера
   ENUM_ORDER_TYPE   type;            // Tип позиции
  };
//--- переменная свойств ордера
pending_order_properties ord;
//---

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

Код функции GetOrderProperties():

//+------------------------------------------------------------------+
//| Получает свойства предварительно выбранного ордера               |
//+------------------------------------------------------------------+
void GetOrderProperties(ENUM_ORDER_PROPERTIES order_property)
  {
   switch(order_property)
     {
      case O_SYMBOL          : ord.symbol=OrderGetString(ORDER_SYMBOL);                              break;
      case O_MAGIC           : ord.magic=OrderGetInteger(ORDER_MAGIC);                               break;
      case O_COMMENT         : ord.comment=OrderGetString(ORDER_COMMENT);                            break;
      case O_PRICE_OPEN      : ord.price_open=OrderGetDouble(ORDER_PRICE_OPEN);                      break;
      case O_PRICE_CURRENT   : ord.price_current=OrderGetDouble(ORDER_PRICE_CURRENT);                break;
      case O_PRICE_STOPLIMIT : ord.price_stoplimit=OrderGetDouble(ORDER_PRICE_STOPLIMIT);            break;
      case O_VOLUME_INITIAL  : ord.volume_initial=OrderGetDouble(ORDER_VOLUME_INITIAL);              break;
      case O_VOLUME_CURRENT  : ord.volume_current=OrderGetDouble(ORDER_VOLUME_CURRENT);              break;
      case O_SL              : ord.sl=OrderGetDouble(ORDER_SL);                                      break;
      case O_TP              : ord.tp=OrderGetDouble(ORDER_TP);                                      break;
      case O_TIME_SETUP      : ord.time_setup=(datetime)OrderGetInteger(ORDER_TIME_SETUP);           break;
      case O_TIME_EXPIRATION : ord.time_expiration=(datetime)OrderGetInteger(ORDER_TIME_EXPIRATION); break;
      case O_TIME_SETUP_MSC  : ord.time_setup_msc=(datetime)OrderGetInteger(ORDER_TIME_SETUP_MSC);   break;
      case O_TYPE_TIME       : ord.type_time=(datetime)OrderGetInteger(ORDER_TYPE_TIME);             break;
      case O_TYPE            : ord.type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);                break;
      case O_ALL             :
         ord.symbol=OrderGetString(ORDER_SYMBOL);
         ord.magic=OrderGetInteger(ORDER_MAGIC);
         ord.comment=OrderGetString(ORDER_COMMENT);
         ord.price_open=OrderGetDouble(ORDER_PRICE_OPEN);
         ord.price_current=OrderGetDouble(ORDER_PRICE_CURRENT);
         ord.price_stoplimit=OrderGetDouble(ORDER_PRICE_STOPLIMIT);
         ord.volume_initial=OrderGetDouble(ORDER_VOLUME_INITIAL);
         ord.volume_current=OrderGetDouble(ORDER_VOLUME_CURRENT);
         ord.sl=OrderGetDouble(ORDER_SL);
         ord.tp=OrderGetDouble(ORDER_TP);
         ord.time_setup=(datetime)OrderGetInteger(ORDER_TIME_SETUP);
         ord.time_expiration=(datetime)OrderGetInteger(ORDER_TIME_EXPIRATION);
         ord.time_setup_msc=(datetime)OrderGetInteger(ORDER_TIME_SETUP_MSC);
         ord.type_time=(datetime)OrderGetInteger(ORDER_TYPE_TIME);
         ord.type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);                                      break;
         //---
      default: Print("Переданное свойство отложенного ордера не учтено в перечислении!");            return;
     }
  }
//---

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

Функция SetPendingOrder() устанавливает отложенный ордер и выводит сообщение в журнал с кодом ошибки и её описанием, если ордер не удалось установить:

//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер                                   |
//+------------------------------------------------------------------+
void SetPendingOrder(int                  symbol_number,   // Номер символа
                     ENUM_ORDER_TYPE      order_type,      // Тип ордера
                     double               lot,             // Объём
                     double               price_stoplimit, // Уровень StopLimit ордера
                     double               price,           // Цена
                     double               sl,              // Стоп Лосс
                     double               tp,              // Тейк Профит
                     ENUM_ORDER_TYPE_TIME type_time,       // Срок действия ордера
                     string               comment)         // Комментарий
  {
//--- Установим номер мэджика в торговую структуру
   trade.SetExpertMagicNumber(MagicNumber);
//--- Если отложенный ордер установить не удалось, вывести сообщение об этом
   if(!trade.OrderOpen(Symbols[symbol_number],
                       order_type,lot,price_stoplimit,price,sl,tp,type_time,0,comment))
      Print("Ошибка при установке отложенного ордера: ",GetLastError()," - ",ErrorDescription(GetLastError()));
  }
//---

Функция ModifyPendingOrder() модифицирует отложенный ордер. Сделаем так, чтобы была возможность с помощью этой функции не только изменять цену ордера, но и его объём. Для этого в качестве последнего параметра функции нужно указать объём (volume). Если передать значение больше нуля, то это будет означать, что отложенный ордер нужно удалить и установить новый с указанным объёмом. Если же передано нулевое значение или меньше нуля, то это будет означать, что нужно изменить цену ордера.

//+------------------------------------------------------------------+
//| Изменяет отложенный ордер                                        |
//+------------------------------------------------------------------+
void ModifyPendingOrder(int                  symbol_number,   // Номер символа
                        ulong                ticket,          // Тикет ордера
                        ENUM_ORDER_TYPE      type,            // Тип ордера
                        double               price,           // Цена ордера
                        double               sl,              // Стоп Лосс ордера
                        double               tp,              // Тэйк Профит ордера
                        ENUM_ORDER_TYPE_TIME type_time,       // Срок действия ордера
                        datetime             time_expiration, // Время истечения ордера
                        double               price_stoplimit, // Цена
                        string               comment,         // Комментарий
                        double               volume)          // Объём
  {
//--- Если передан не нулевой объём, переустановим ордер
   if(volume>0)
     {
      //--- Если не удалось удалить ордер, выйдем
      if(!DeletePendingOrder(ticket))
         return;
      //--- Установим отложенный ордер
      SetPendingOrder(symbol_number,type,volume,0,price,sl,tp,type_time,comment);
      //--- Скорректируем Stop Loss позиции относительно ордера
      CorrectStopLossByOrder(symbol_number,price,type);
     }
//--- Если передан нулевой объём, модифицируем ордер
   else
     {
      //--- Если отложенный ордер изменить не удалось, вывести сообщение об этом
      if(!trade.OrderModify(ticket,price,sl,tp,type_time,time_expiration,price_stoplimit))
         Print("Ошибка при изменении цены отложенного ордера: ",
         GetLastError()," - ",ErrorDescription(GetLastError()));
      //--- Иначе скорректируем Stop Loss позиции относительно ордера
      else
         CorrectStopLossByOrder(symbol_number,price,type);
     }
  }
//---

В коде выше выделены ещё две новые функции: DeletePendingOrder() и CorrectStopLossByOrder(). Первая удаляет отложенный ордер, а вторая корректирует Stop Loss позиции относительно отложенного ордера. Ниже можно ознакомиться с их кодом:

//+------------------------------------------------------------------+
//| Удаляет отложенный ордер                                         |
//+------------------------------------------------------------------+
bool DeletePendingOrder(ulong ticket)
  {
//--- Если отложенный ордер удалить не удалось, вывести сообщение об этом
   if(!trade.OrderDelete(ticket))
     {
      Print("Ошибка при удалении отложенного ордера: ",GetLastError()," - ",ErrorDescription(GetLastError()));
      return(false);
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Корректирует Stop Loss позиции относительно отложенного ордера   |
//+------------------------------------------------------------------+
void CorrectStopLossByOrder(int             symbol_number, // Номер символа
                            double          price,         // Цена ордера
                            ENUM_ORDER_TYPE type)          // Тип ордера
  {
//--- Если Stop Loss отключен, выйдем
   if(StopLoss[symbol_number]==0)
      return;
//--- Если Stop Loss включен
   double new_sl=0.0; // Новое значение для Stop Loss
//--- Получим значение одного пункта и
   GetSymbolProperties(symbol_number,S_POINT);
//--- Количество знаков в цене после запятой
   GetSymbolProperties(symbol_number,S_DIGITS);
//--- Получим Take Profit позиции
   GetPositionProperties(symbol_number,P_TP);
//--- Рассчитаем относительно типа ордера
   switch(type)
     {
      case ORDER_TYPE_BUY_STOP  :
         new_sl=NormalizeDouble(price+CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits);
         break;
      case ORDER_TYPE_SELL_STOP :
         new_sl=NormalizeDouble(price-CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits);
         break;
     }
//--- Модифицируем позицию
   if(!trade.PositionModify(Symbols[symbol_number],new_sl,pos.tp))
      Print("Ошибка при модификации позиции: ",GetLastError()," - ",ErrorDescription(GetLastError()));
  }
//---

Перед тем, как устанавливать отложенный ордер нужно проверить, есть ли отложенный ордер с таким же комментарием. Как уже упоминалось в начале статьи, верхний ордер будем устанавливать с комментарием "top_order", а нижний ордер с комментарием "bottom_order". Для такой проверки напишем функцию CheckExistPendingOrderByComment(). С кодом этой функции можно ознакомиться ниже:

//+------------------------------------------------------------------+
//| Проверяет существование отложенного ордера по комментарию        |
//+------------------------------------------------------------------+
bool CheckExistPendingOrderByComment(int symbol_number,string comment)
  {
   int    total_orders  =0;  // Общее количество отложенных ордеров
   string symbol_order  =""; // Символ ордера
   string comment_order =""; // Комментарий ордера
//--- Получим количество отложенных ордеров
   total_orders=OrdersTotal();
//--- Пройдёмся в цикле по всем ордерам
   for(int i=total_orders-1; i>=0; i--)
     {
      //--- Выберем ордер по тикету
      if(OrderGetTicket(i)>0)
        {
         //--- Получим имя символа
         symbol_order=OrderGetString(ORDER_SYMBOL);
         //--- Если символы равны
         if(symbol_order==Symbols[symbol_number])
           {
            //--- Получим комментарий ордера
            comment_order=OrderGetString(ORDER_COMMENT);
            //--- Если комментарии совпадают
            if(comment_order==comment)
               return(true);
           }
        }
     }
//--- Ордер с указанным комментарием не найден
   return(false);
  }
//---

В коде выше в выделенной строке видно, что общее количество ордеров можно получить с помощью функции OrdersTotal(). А вот чтобы получить количество ордеров на конкретном символе нужно написать пользовательскую функцию. Назовём её OrdersTotalBySymbol(). Ниже представлен её код:

//+------------------------------------------------------------------+
//| Возвращает количество ордеров на указанном символе               |
//+------------------------------------------------------------------+
int OrdersTotalBySymbol(string symbol)
  {
   int   count        =0; // Счётчик ордеров
   int   total_orders =0; // Количество отложенных ордеров
//--- Получим количество отложенных ордеров
   total_orders=OrdersTotal();
//--- Пройдёмся в цикле по всем ордерам
   for(int i=total_orders-1; i>=0; i--)
     {
      //--- Если ордер выбран
      if(OrderGetTicket(i)>0)
        {
         //--- Получим символ ордера
         GetOrderProperties(O_SYMBOL);
         //--- Если символ ордера и указанный символ совпадают
         if(ord.symbol==symbol)
            //--- Увеличим счётчик
            count++;
        }
     }
//--- Вернём количество ордеров
   return(count);
  }
//---

Перед установкой отложенного ордера нужно рассчитать для него цену, а также, при необходимости, уровни Stop Loss и Take Profit. Если включен трейлинг противоположного отложенного ордера для переворота позиции, то тоже понадобится отдельная пользовательская функция для этого.

Для расчёта цены отложенного ордера напишем функцию CalculatePendingOrder():

//+------------------------------------------------------------------+
//| Рассчитывает уровень (цену) отложенного ордера                   |
//+------------------------------------------------------------------+
double CalculatePendingOrder(int symbol_number,ENUM_ORDER_TYPE order_type)
  {
//--- Для рассчитанного значения Pending Order
   double price=0.0;
//--- Если нужно рассчитать значение для ордера SELL STOP
   if(order_type==ORDER_TYPE_SELL_STOP)
     {
      //--- Рассчитаем уровень
      price=
      NormalizeDouble(symb.bid-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
      //--- Вернем рассчитанное значение, если оно ниже нижней границы stops level
      //    Если значение выше или равно, вернем скорректированное значение
      return(price<symb.down_level ? price : symb.down_level-symb.offset);
     }
//--- Если нужно рассчитать значение для ордера BUY STOP
   if(order_type==ORDER_TYPE_BUY_STOP)
     {
      //--- Рассчитаем уровень
      price=
      NormalizeDouble(symb.ask+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
      //--- Вернем рассчитанное значение, если оно выше верхней границы stops level
      //    Если значение ниже или равно, вернем скорректированное значение
      return(price>symb.up_level ? price : symb.up_level+symb.offset);
     }
//---
   return(0.0);
  }
//---

Ниже представлен код функций для расчёта уровней Stop Loss и Take Profit в отложенном ордере:

//+------------------------------------------------------------------+
//| Рассчитывает уровень Take Profit для отложенного ордера          |
//+------------------------------------------------------------------+
double CalculateOrderTakeProfit(int symbol_number,ENUM_ORDER_TYPE order_type,double price)
  {
//--- Если Take Profit нужен
   if(TakeProfit[symbol_number]>0)
     {
      double tp         =0.0; // Для рассчитанного значения Take Profit
      double up_level   =0.0; // Верхний уровень Stop Levels
      double down_level =0.0; // Нижний уровень Stop Levels
      //--- Если нужно рассчитать значение для ордера SELL STOP
      if(order_type==ORDER_TYPE_SELL_STOP)
        {
         //--- Определим нижний порог
         down_level=NormalizeDouble(price-symb.stops_level*symb.point,symb.digits);
         //--- Рассчитаем уровень
         tp=NormalizeDouble(price-CorrectValueBySymbolDigits(TakeProfit[symbol_number]*symb.point),symb.digits);
         //--- Вернем рассчитанное значение, если оно ниже нижней границы stops level
         //    Если значение выше или равно, вернем скорректированное значение
         return(tp<down_level ? tp : NormalizeDouble(down_level-symb.offset,symb.digits));
        }
      //--- Если нужно рассчитать значение для ордера BUY STOP
      if(order_type==ORDER_TYPE_BUY_STOP)
        {
         //--- Определим верхний порог
         up_level=NormalizeDouble(price+symb.stops_level*symb.point,symb.digits);
         //--- Рассчитаем уровень
         tp=NormalizeDouble(price+CorrectValueBySymbolDigits(TakeProfit[symbol_number]*symb.point),symb.digits);
         //--- Вернем рассчитанное значение, если оно выше верхней границы stops level
         //    Если значение ниже или равно, вернем скорректированное значение
         return(tp>up_level ? tp : NormalizeDouble(up_level+symb.offset,symb.digits));
        }
     }
//---
   return(0.0);
  }
//+------------------------------------------------------------------+
//| Рассчитывает уровень Stop Loss для отложенного ордера            |
//+------------------------------------------------------------------+
double CalculateOrderStopLoss(int symbol_number,ENUM_ORDER_TYPE order_type,double price)
  {
//--- Если Stop Loss нужен
   if(StopLoss[symbol_number]>0)
     {
      double sl         =0.0; // Для рассчитанного значения Stop Loss
      double up_level   =0.0; // Верхний уровень Stop Levels
      double down_level =0.0; // Нижний уровень Stop Levels
      //--- Если нужно рассчитать значение для ордера BUY STOP
      if(order_type==ORDER_TYPE_BUY_STOP)
        {
         //--- Определим нижний порог
         down_level=NormalizeDouble(price-symb.stops_level*symb.point,symb.digits);
         //--- Рассчитаем уровень
         sl=NormalizeDouble(price-CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits);
         //--- Вернем рассчитанное значение, если оно ниже нижней границы stops level
         //    Если значение выше или равно, вернем скорректированное значение
         return(sl<down_level ? sl : NormalizeDouble(down_level-symb.offset,symb.digits));
        }
      //--- Если нужно рассчитать значение для ордера SELL STOP
      if(order_type==ORDER_TYPE_SELL_STOP)
        {
         //--- Определим верхний порог
         up_level=NormalizeDouble(price+symb.stops_level*symb.point,symb.digits);
         //--- Рассчитаем уровень
         sl=NormalizeDouble(price+CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits);
         //--- Вернем рассчитанное значение, если оно выше верхней границы stops level
         //    Если значение ниже или равно, вернем скорректированное значение
         return(sl>up_level ? sl : NormalizeDouble(up_level+symb.offset,symb.digits));
        }
     }
//---
   return(0.0);
  }
//---

Для трейлинга противоположного отложенного ордера и расчёта его уровня (цены) напишем функции CalculateTrailingReverseOrder() и ModifyTrailingPendingOrder(). С кодом этих функций можно ознакомиться ниже.

Код функции CalculateTrailingReverseOrder():

//+------------------------------------------------------------------+
//| Рассчитывает уровень Trailing Reverse Order                      |
//+------------------------------------------------------------------+
double CalculateTrailingReverseOrder(int symbol_number,ENUM_POSITION_TYPE position_type)
  {
//--- Переменные для расчётов
   double    level       =0.0;
   double    buy_point   =low[symbol_number].value[1];  // Значение Low для Buy
   double    sell_point  =high[symbol_number].value[1]; // Значение High для Sell
//--- Рассчитаем уровень для позиции BUY
   if(position_type==POSITION_TYPE_BUY)
     {
      //--- Минимум бара минус указанное количество пунктов
      level=
      NormalizeDouble(buy_point-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
      //--- Если рассчитанный уровень ниже, чем нижний уровень ограничения (stops level), 
      //    то расчет закончен, вернем текущее значение уровня
      if(level<symb.down_level)
         return(level);
      //--- Если же не ниже, то попробуем рассчитать от цены bid
      else
        {
         level=
         NormalizeDouble(symb.bid-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
         //--- Если рассчитанный уровень ниже ограничителя, вернем текущее значение уровня
         //    Иначе установим максимально возможный близкий
         return(level<symb.down_level ? level : symb.down_level-symb.offset);
        }
     }
//--- Рассчитаем уровень для позиции SELL
   if(position_type==POSITION_TYPE_SELL)
     {
      // Максимум бара плюс указанное кол-во пунктов
      level=
      NormalizeDouble(sell_point+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
      //--- Если рассчитанный уровень выше, чем верхний уровень ограничения (stops level), 
      //    то расчёт закончен, вернем текущее значение уровня
      if(level>symb.up_level)
         return(level);
      //--- Если же не выше, то попробуем рассчитать от цены ask
      else
        {
         level=
         NormalizeDouble(symb.ask+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
         //--- Если рассчитанный уровень выше ограничителя, вернем текущее значение уровня
         //    Иначе установим максимально возможный близкий
         return(level>symb.up_level ? level : symb.up_level+symb.offset);
        }
     }
//---
   return(0.0);
  }
//---

Код функции ModifyTrailingPendingOrder():

//+------------------------------------------------------------------+
//| Изменяет уровень Trailing Pending Order                          |
//+------------------------------------------------------------------+
void ModifyTrailingPendingOrder(int symbol_number)
  {
//--- Если переворот позиции отключен или
//    Trailing Stop отключен, выйдем
   if(!Reverse[symbol_number] || TrailingStop[symbol_number]==0)
      return;
//--- Если переворот позиции включен или Trailing Stop включен
   double          new_level              =0.0;         // Для расчета нового уровня отложенного ордера
   bool            condition              =false;       // Для проверки условия на модификацию
   int             total_orders           =0;           // Общее количество отложенных ордеров
   ulong           order_ticket           =0;           // Тикет ордера
   string          opposite_order_comment ="";          // Комментарий противоположного ордера
   ENUM_ORDER_TYPE opposite_order_type    =WRONG_VALUE; // Тип ордера

//--- Получим флаг наличия/отсутствия позиции
   pos.exists=PositionSelect(Symbols[symbol_number]);
//--- Если нет позиции
   if(!pos.exists)
      return;
//--- Получим количество отложенных ордеров
   total_orders=OrdersTotal();
//--- Получим свойства символа
   GetSymbolProperties(symbol_number,S_ALL);
//--- Получим свойства позиции
   GetPositionProperties(symbol_number,P_ALL);
//--- Получим уровень для Stop Loss
   new_level=CalculateTrailingReverseOrder(symbol_number,pos.type);
//--- Пройдёмся в цикле по всем ордерам от последнего к первому
   for(int i=total_orders-1; i>=0; i--)
     {
      //--- Если ордер выбран
      if((order_ticket=OrderGetTicket(i))>0)
        {
         //--- Получим символ ордера
         GetOrderProperties(O_SYMBOL);
         //--- Получим комментарий ордера
         GetOrderProperties(O_COMMENT);
         //--- Получим цену ордера
         GetOrderProperties(O_PRICE_OPEN);
         //--- В зависимости от типа позиции
         //    проверим соответствующее условие на модификацию Trailing Stop
         switch(pos.type)
           {
            case POSITION_TYPE_BUY  :
               //--- Если новое значение для ордера выше,
               //    чем текущее значение плюс установленный шаг, то условие исполнено
               condition=
               new_level>ord.price_open+CorrectValueBySymbolDigits(TrailingStop[symbol_number]*symb.point);
               //--- Определим тип и комментарий противоположного отложенного ордера для проверки
               opposite_order_type    =ORDER_TYPE_SELL_STOP;
               opposite_order_comment =comment_bottom_order;
               break;
            case POSITION_TYPE_SELL :
               //--- Если новое значение для ордера ниже,
               //    чем текущее значение минус установленный шаг, то условие исполнено
               condition=
               new_level<ord.price_open-CorrectValueBySymbolDigits(TrailingStop[symbol_number]*symb.point);
               //--- Определим тип и комментарий противоположного отложенного ордера для проверки
               opposite_order_type    =ORDER_TYPE_BUY_STOP;
               opposite_order_comment =comment_top_order;
               break;
           }
         //--- Если условие исполняется и 
         //    символ ордера и позиции совпадают и
         //    комментарий противоположного ордера, то
         if(condition && 
            ord.symbol==Symbols[symbol_number] && 
            ord.comment==opposite_order_comment)
           {
            double sl=0.0; // Стоп лосс
            double tp=0.0; // Тейк профит
            //--- Получим уровни Take Profit и Stop Loss
            sl=CalculateOrderStopLoss(symbol_number,opposite_order_type,new_level);
            tp=CalculateOrderTakeProfit(symbol_number,opposite_order_type,new_level);
            //--- Изменить ордер
            ModifyPendingOrder(symbol_number,order_ticket,opposite_order_type,new_level,sl,tp,
                               ORDER_TIME_GTC,ord.time_expiration,ord.price_stoplimit,ord.comment,0);
            return;
           }
        }
     }
  }
//---

Иногда может понадобится определить была ли позиция закрыта по Stop Loss или Take Profit, и в нашем случае такие ситуации будут, поэтому напишем функции, которые будут определять это событие по комментарию последней сделки. Для получения комментария последней сделки на указанном символе напишем отдельную функцию, которую назовём LastDealComment():

//+------------------------------------------------------------------+
//| Возвращает комментарий последней сделки на указанном символе     |
//+------------------------------------------------------------------+
string LastDealComment(int symbol_number)
  {
   int    total_deals  =0;  // Всего сделок в списке выбранной истории
   string deal_symbol  =""; // Символ сделки 
   string deal_comment =""; // Комментарий сделки
//--- Если история сделок получена
   if(HistorySelect(0,TimeCurrent()))
     {
      //--- Получим количество сделок в полученном списке
      total_deals=HistoryDealsTotal();
      //--- Пройдемся по всем сделкам в полученном списке
      //    от последней сделки к первой
      for(int i=total_deals-1; i>=0; i--)
        {
         //--- Получим комментарий сделки
         deal_comment=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_COMMENT);
         //--- Получим символ сделки
         deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL);
         //--- Если символ сделки и текущий символ равны, остановим цикл
         if(deal_symbol==Symbols[symbol_number])
            break;
        }
     }
//---
   return(deal_comment);
  }
//---

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

//+------------------------------------------------------------------+
//| Возвращает причину закрытия позиции по Take Profit               |
//+------------------------------------------------------------------+
bool GetEventTakeProfit(int symbol_number)
  {
   string last_comment="";
//--- Получим комментарий последней сделки на указанном символе
   last_comment=LastDealComment(symbol_number);
//--- Если в комментарии есть строка "tp"
   if(StringFind(last_comment,"tp",0)>-1)
      return(true);
//--- Если нет строки "tp"
   return(false);
  }
//+------------------------------------------------------------------+
//| Возвращает причину закрытия позиции по Stop Loss                 |
//+------------------------------------------------------------------+
bool GetEventStopLoss(int symbol_number)
  {
   string last_comment="";
//--- Получим комментарий последней сделки на указанном символе
   last_comment=LastDealComment(symbol_number);
//--- Если в комментарии есть строка "sl"
   if(StringFind(last_comment,"sl",0)>-1)
      return(true);
//--- Если нет строки "sl"
   return(false);
  }
//---

И ещё одна проверка, которую будем использовать состоит в том, что нужно проверить тикет последней сделки истории указанного символа. Тикет последней сделки будем держать в памяти и для этого на глобальном уровне программы нужно объявить массив:

//--- Массив для проверки тикета последней сделки на каждом символе
ulong last_ticket_deal[NUMBER_OF_SYMBOLS];
//---

Функция GetEventLastDealTicket() для проверки тикета последней сделки будет выглядеть так, как показано в коде ниже:

//+------------------------------------------------------------------+
//| Возвращает событие последней сделки на указанном символе         |
//+------------------------------------------------------------------+
bool GetEventLastDealTicket(int symbol_number)
  {
   int    total_deals =0;  // Всего сделок в списке выбранной истории
   string deal_symbol =""; // Символ сделки
   ulong  deal_ticket =0;  // Тикет сделки
//--- Если история сделок получена
   if(HistorySelect(0,TimeCurrent()))
     {
      //--- Получим количество сделок в полученном списке
      total_deals=HistoryDealsTotal();
      //--- Пройдемся по всем сделкам в полученном списке
      //    от последней сделки к первой
      for(int i=total_deals-1; i>=0; i--)
        {
         //--- Получим тикет сделки
         deal_ticket=HistoryDealGetTicket(i);
         //--- Получим символ сделки
         deal_symbol=HistoryDealGetString(deal_ticket,DEAL_SYMBOL);
         //--- Если символ сделки и текущий символ равны, остановим цикл
         if(deal_symbol==Symbols[symbol_number])
           {
            //--- Если тикеты равны, выйдем
            if(deal_ticket==last_ticket_deal[symbol_number])
               return(false);
            //--- Если тикеты не равны, сообщим об этом и
            else
              {
               //--- Запомним тикет последней сделки
               last_ticket_deal[symbol_number]=deal_ticket;
               return(true);
              }
           }
        }
     }
//---
   return(false);
  }
//---

Когда время выходит за пределы указанного торгового диапазона, позиция будет закрываться независимо от того, в прибыли она или в убытке. Для закрытия позиции напишем функцию ClosePosition():

//+------------------------------------------------------------------+
//| Закрывает позицию                                                |
//+------------------------------------------------------------------+
void ClosePosition(int symbol_number)
  {
//--- Узнаем, есть ли позиция  
   pos.exists=PositionSelect(Symbols[symbol_number]);
//--- Если позиции нет, выходим
   if(!pos.exists)
      return;
//--- Установим размер проскальзывания в пунктах
   trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation));
//--- Если позиция не закрылась, вывести сообщение об этом
   if(!trade.PositionClose(Symbols[symbol_number]))
      Print("Ошибка при закрытии позиции: ",GetLastError()," - ",ErrorDescription(GetLastError()));
  }
//---

Когда позиция закрывается при выходе из торгового временного диапазона нужно удалить отложенные ордера. Для этого напишем функцию, которая будет удалять все отложенные ордера на указанном символе. Назовём её DeleteAllPendingOrders():

//+------------------------------------------------------------------+
//| Удаляет все отложенные ордера                                    |
//+------------------------------------------------------------------+
void DeleteAllPendingOrders(int symbol_number)
  {
   int   total_orders =0; // Количество отложенных ордеров
   ulong order_ticket =0; // Тикет ордера
//--- Получим количество отложенных ордеров
   total_orders=OrdersTotal();
//--- Пройдёмся в цикле по всем ордерам
   for(int i=total_orders-1; i>=0; i--)
     {
      //--- Если ордер выбран
      if((order_ticket=OrderGetTicket(i))>0)
        {
         //--- Получим символ ордера
         GetOrderProperties(O_SYMBOL);
         //--- Если символ ордера и текущий символ совпадают
         if(ord.symbol==Symbols[symbol_number])
            //--- Удалим ордер
            DeletePendingOrder(order_ticket);
        }
     }
  }
//---

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

Функция TradingBlock() для текущей схемы выглядит так:

//+------------------------------------------------------------------+
//| Торговый блок                                                    |
//+------------------------------------------------------------------+
void TradingBlock(int symbol_number)
  {
   double          tp=0.0;                 // Take Profit
   double          sl=0.0;                 // Stop Loss
   double          lot=0.0;                // Объем для расчета позиции в случае переворота позиции
   double          order_price=0.0;        // Цена для установки ордера
   ENUM_ORDER_TYPE order_type=WRONG_VALUE; // Тип ордера для открытия позиции
//--- Если вне временного диапазона
//    для установки отложенных ордеров
   if(!CheckTimeOpenOrders(symbol_number))
      return;
//--- Узнаем, есть ли позиция
   pos.exists=PositionSelect(Symbols[symbol_number]);
//--- Если позиции нет
   if(!pos.exists)
     {
      //--- Получим свойства символа
      GetSymbolProperties(symbol_number,S_ALL);
      //--- Скорректируем объём
      lot=CalculateLot(symbol_number,Lot[symbol_number]);
      //--- Если нет верхнего отложенного ордера
      if(!CheckExistPendingOrderByComment(symbol_number,comment_top_order))
        {
         //--- Получим цену для установки отложенного ордера
         order_price=CalculatePendingOrder(symbol_number,ORDER_TYPE_BUY_STOP);
         //--- Получим уровни Take Profit и Stop Loss
         sl=CalculateOrderStopLoss(symbol_number,ORDER_TYPE_BUY_STOP,order_price);
         tp=CalculateOrderTakeProfit(symbol_number,ORDER_TYPE_BUY_STOP,order_price);
         //--- Установим отложенный ордер
         SetPendingOrder(symbol_number,ORDER_TYPE_BUY_STOP,
                         lot,0,order_price,sl,tp,ORDER_TIME_GTC,comment_top_order);
        }
      //--- Если нет нижнего отложенного ордера
      if(!CheckExistPendingOrderByComment(symbol_number,comment_bottom_order))
        {
         //--- Получим цену для установки отложенного ордера
         order_price=CalculatePendingOrder(symbol_number,ORDER_TYPE_SELL_STOP);
         //--- Получим уровни Take Profit и Stop Loss
         sl=CalculateOrderStopLoss(symbol_number,ORDER_TYPE_SELL_STOP,order_price);
         tp=CalculateOrderTakeProfit(symbol_number,ORDER_TYPE_SELL_STOP,order_price);
         //--- Установим отложенный ордер
         SetPendingOrder(symbol_number,ORDER_TYPE_SELL_STOP,
                         lot,0,order_price,sl,tp,ORDER_TIME_GTC,comment_bottom_order);
        }
     }
  }
//---

Код функции ManagementPendingOrders() для управления отложенными ордерами:

//+------------------------------------------------------------------+
//| Управление отложенными ордерами                                  |
//+------------------------------------------------------------------+
void ManagementPendingOrders()
  {
//--- Пройдёмся в цикле по всем символам
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- Если торговля по этому символу не разрешена,
      //    перейдём к следующему
      if(Symbols[s]=="")
         continue;
      //--- Узнаем, есть ли позиция
      pos.exists=PositionSelect(Symbols[s]);
      //--- Если позиции нет
      if(!pos.exists)
        {
         //--- Если последняя сделка новая и
         //    выход из позиции был по Take Profit или Stop Loss
         if(GetEventLastDealTicket(s) && 
            (GetEventStopLoss(s) || GetEventTakeProfit(s)))
            //--- Удалим все отложенные ордера на символе
            DeleteAllPendingOrders(s);
         //--- Перейдём к следующему символу
         continue;
        }
      //--- Если позиция есть
      ulong           order_ticket           =0;           // Тикет ордера
      int             total_orders           =0;           // Общее количество отложенных ордеров
      int             symbol_total_orders    =0;           // Количество отложенных ордеров на указанном символе
      string          opposite_order_comment ="";          // Комментарий противоположного ордера
      ENUM_ORDER_TYPE opposite_order_type    =WRONG_VALUE; // Тип ордера
      //--- Получим общее количество отложенных ордеров
      total_orders=OrdersTotal();
      //--- Получим количество отложенных ордеров на указанном символе
      symbol_total_orders=OrdersTotalBySymbol(Symbols[s]);
      //--- Получим свойства символа
      GetSymbolProperties(s,S_ASK);
      GetSymbolProperties(s,S_BID);
      //--- Получим комментарий выбранной позиции
      GetPositionProperties(s,P_COMMENT);
      //--- Если комментарий позиции от верхнего ордера,
      //    значит нужно удалить/изменить/установить нижний ордер
      if(pos.comment==comment_top_order)
        {
         opposite_order_type    =ORDER_TYPE_SELL_STOP;
         opposite_order_comment =comment_bottom_order;
        }
      //--- Если комментарий позиции от нижнего ордера,
      //    значит нужно удалить/изменить/установить верхний ордер
      if(pos.comment==comment_bottom_order)
        {
         opposite_order_type    =ORDER_TYPE_BUY_STOP;
         opposite_order_comment =comment_top_order;
        }
      //--- Если отложенных ордеров на этом символе нет
      if(symbol_total_orders==0)
        {
         //--- Если переворот позиции включен, установим противоположный ордер
         if(Reverse[s])
           {
            double tp=0.0;          // Take Profit
            double sl=0.0;          // Stop Loss
            double lot=0.0;         // Объем для расчета позиции в случае переворота позиции
            double order_price=0.0; // Цена для установки ордера
            //--- Получим цену для установки отложенного ордера
            order_price=CalculatePendingOrder(s,opposite_order_type);
            //--- Получим уровни Take Profit и Stop Loss
            sl=CalculateOrderStopLoss(s,opposite_order_type,order_price);
            tp=CalculateOrderTakeProfit(s,opposite_order_type,order_price);
            //--- Посчитаем двойной объём
            lot=CalculateLot(s,pos.volume*2);
            //--- Установим отложенный ордер
            SetPendingOrder(s,opposite_order_type,lot,0,order_price,sl,tp,ORDER_TIME_GTC,opposite_order_comment);
            //--- Скорректируем Stop Loss относительно ордера
            CorrectStopLossByOrder(s,order_price,opposite_order_type);
           }
         return;
        }
      //--- Если отложенные ордера на этом символе есть, то
      //    в зависимости от условий удалим или
      //    модифицируем противоположный отложенный ордер
      if(symbol_total_orders>0)
        {
         //--- Пройдёмся в цикле по всем ордерам от последнего к первому
         for(int i=total_orders-1; i>=0; i--)
           {
            //--- Если ордер выбран
            if((order_ticket=OrderGetTicket(i))>0)
              {
               //--- Получим символ ордера
               GetOrderProperties(O_SYMBOL);
               //--- Получим комментарий ордера
               GetOrderProperties(O_COMMENT);
               //--- Если символ ордера и позиции совпадают и
               //    комментарий противоположного ордера, то
               if(ord.symbol==Symbols[s] && 
                  ord.comment==opposite_order_comment)
                 {
                  //--- Если переворот позиции отключен
                  if(!Reverse[s])
                     //--- Удалим ордер
                     DeletePendingOrder(order_ticket);
                  //--- Если переворот позиции включен
                  else
                    {
                     double lot=0.0;
                     //--- Получим свойства текущего ордера
                     GetOrderProperties(O_ALL);
                     //--- Получим объём текущей позиции
                     GetPositionProperties(s,P_VOLUME);
                     //--- Если ордер уже был изменён, выйдем из цикла
                     if(ord.volume_initial>pos.volume)
                        break;
                     //--- Посчитаем двойной объём
                     lot=CalculateLot(s,pos.volume*2);
                     //--- Изменить (переустановить) ордер
                     ModifyPendingOrder(s,order_ticket,opposite_order_type,
                                        ord.price_open,ord.sl,ord.tp,
                                        ORDER_TIME_GTC,ord.time_expiration,
                                        ord.price_stoplimit,opposite_order_comment,lot);
                    }
                 }
              }
           }
        }
     }
  }
//---

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

//+------------------------------------------------------------------+
//| Обработка торговых событий                                       |
//+------------------------------------------------------------------+
void OnTrade()
  {
//--- Проверим состояние отложенных ордеров
   ManagementPendingOrders();
  }
//---

Также функция ManagementPendingOrders() будет использоваться и в обработчике пользовательских событий OnChartEvent():

//+------------------------------------------------------------------+
//| Обработчик пользовательских событий и событий графика            |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // Идентификатор события
                  const long &lparam,   // Параметр события типа long
                  const double &dparam, // Параметр события типа double
                  const string &sparam) // Параметр события типа string
  {
//--- Если это пользовательское событие
   if(id>=CHARTEVENT_CUSTOM)
     {
      //--- Выйти, если запрещено торговать
      if(CheckTradingPermission()>0)
         return;
      //--- Если было событие "тик"
      if(lparam==CHARTEVENT_TICK)
        {
         //--- Проверим состояние отложенных ордеров
         ManagementPendingOrders();
         //--- Проверяет сигналы и торгует по ним
         CheckSignalAndTrade();
         return;
        }
     }
  }
//---

И наконец, изменения произошли и в функции CheckSignalAndTrade(). В коде ниже выделены строки с новыми функциями, которые были рассмотрены в этой статье.

//+------------------------------------------------------------------+
//| Проверяет сигналы и торгует по событию новый бар                 |
//+------------------------------------------------------------------+
void CheckSignalAndTrade()
  {
//--- Пройдёмся по всем указанным символам
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- Если торговля по этому символу не разрешена, выйдем
      if(Symbols[s]=="")
         return;
      //--- Если бар не новый, перейти к следующему символу
      if(!CheckNewBar(s))
         continue;
      //--- Если есть новый бар
      else
        {
         //--- Если вне временного диапазона
         if(!CheckTimeRange(s))
           {
            //--- Закроем позицию
            ClosePosition(s);
            //--- Удалим все отложенные ордера и...
            DeleteAllPendingOrders(s);
            //--- ...перейдём к следующему символу
            continue;
           }
         //--- Получим данные баров
         GetBarsData(s);
         //--- Проверим условия и торгуем
         TradingBlock(s);
         //--- Если включен переворот позиции
         if(Reverse[s])
            //--- Трейлинг отложенного ордера
            ModifyTrailingPendingOrder(s);
         //--- Если отключен переворот позиции
         else
         //--- Трейлинг стоп лосса
            ModifyTrailingStop(s);
        }
     }
  }
//---

Теперь всё готово и можно попробовать оптимизировать параметры этого мультивалютного советника и провести тесты. Настройки тестера установим, как показано ниже:

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

Оптимизацию параметров сначала проведём для валютной пары EURUSD, а затем для AUDUSD. На скриншоте ниже показано, какие параметры установим для оптимизации EURUSD:

Установка параметров для оптимизации мультивалютного советника

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

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

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

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

Возможно Вас заинтересуют также статьи по разработке мультивалютных индикаторов:

Разработка мультивалютного индикатора волатильности на MQL5
Разработка мультивалютного индикатора для анализа расхождения цен 




Скачать архив MultiSymbolPendingOrders.zip с файлами мультивалютного советника.

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

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