Скачать MetaTrader 5

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

1 апреля 2014, 10:56
Anatoli Kazharski
8
6 629

Введение

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

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


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

В качестве шаблона возьмем код из статьи Рецепты 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
  };

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

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

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

//--- Внешние параметры эксперта
sinput long       MagicNumber       = 777;      // Магический номер
sinput int        Deviation         = 10;       // Проскальзывание
//---
sinput string delimeter_00=""; // --------------------------------
sinput string     Symbol_01            ="EURUSD";  // Символ 1
input  bool       TradeInTimeRange_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       TradeInTimeRange_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       TradeInTimeRange[NUMBER_OF_SYMBOLS]; // Торговля во временном диапазоне
ENUM_HOURS StartTrade[NUMBER_OF_SYMBOLS];       // Час начала торговли
ENUM_HOURS StopOpenOrders[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) при срабатывании одного из отложенных ордеров, противоположный отложенный ордер переустанавливался. У нас нет возможности изменить объем отложенного ордера, как в случае изменения его ценовых уровней (цена ордера, Стоп Лосс, Тейк Профит), поэтому его сначала нужно удалить и затем установить новый отложенный ордер с нужным объемом.

Также, если включен режим разворота позиции и одновременно с этим установлен уровень Трейлинг Стоп, то вслед за ценой будет перемещаться отложенный ордер. Если при этом будет еще установлен и Стоп Лосс, то он будет рассчитываться и устанавливаться от отложенного ордера.

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

//--- Комментарии отложенных ордеров
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]=="" || !TradeInTimeRange[s])
         continue;
      //--- Проверим корректность времени начала и окончания торговли
      if(StartTrade[s]>=EndTrade[s])
        {
         Print(Symbols[s],
               ": Час начала торговли ("+IntegerToString(StartTrade[s])+") "
               "должен быть меньше часа окончания торговли ("+IntegerToString(EndTrade[s])+")!");
         return(false);
        }
      //--- Начинать торговлю можно не позднее чем за 1 час до времени установки отложенных ордеров.
      //    Установка отложенных ордеров должна выполняться не позднее чем за 1 час до окончания торговли.
      if(StopOpenOrders[s]>=EndTrade[s] ||
         StopOpenOrders[s]<=StartTrade[s])
        {
         Print(Symbols[s],
               ": Час окончания установки ордеров ("+IntegerToString(StopOpenOrders[s])+") "
               "должен быть меньше часа окончания ("+IntegerToString(EndTrade[s])+") и "
               "больше часа начала торговли  ("+IntegerToString(StartTrade[s])+")!");
         return(false);
        }
     }
//--- Параметры корректны
   return(true);
  }

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

//+------------------------------------------------------------------+
//| Проверяет, находимся ли в торговом временном диапазоне           |
//+------------------------------------------------------------------+
bool IsInTradeTimeRange(int symbol_number)
  {
//--- Если включена торговля во временном диапазоне
   if(TradeInTimeRange[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 IsInOpenOrdersTimeRange(int symbol_number)
  {
//--- Если включена торговля во временном диапазоне
   if(TradeInTimeRange[symbol_number])
     {
      //--- Структура даты и времени
      MqlDateTime last_date; 
      //--- Получим последние данные даты и времени
      TimeTradeServer(last_date);
      //--- Вне разрешенного временного диапазона
      if(last_date.hour<StartTrade[symbol_number] ||
         last_date.hour>=StopOpenOrders[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;            // Тип позиции
  };
//--- переменная свойств ордера
pending_order_properties ord;

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

//+------------------------------------------------------------------+
//| Получает свойства предварительно выбранного отложенного ордера   |
//+------------------------------------------------------------------+
void GetPendingOrderProperties(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               stoplimit_price, // Уровень 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,stoplimit_price,price,sl,tp,type_time,0,comment))
      Print("Ошибка при установке отложенного ордера: ",GetLastError()," - ",ErrorDescription(GetLastError()));
  }

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

//+------------------------------------------------------------------+
//| Изменяет отложенный ордер                                        |
//+------------------------------------------------------------------+
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               stoplimit_price, // Цена
                        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,stoplimit_price))
         Print("Ошибка при изменении цены отложенного ордера: ",
         GetLastError()," - ",ErrorDescription(GetLastError()));
      //--- Иначе скорректируем Stop Loss позиции относительно ордера
      else
         CorrectStopLossByOrder(symbol_number,price,type);
     }
  }

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

//+------------------------------------------------------------------+
//| Удаляет отложенный ордер                                         |
//+------------------------------------------------------------------+
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()));
  }

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

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

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

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

//+------------------------------------------------------------------+
//| Рассчитывает уровень (цену) отложенного ордера                   |
//+------------------------------------------------------------------+
double CalculatePendingOrder(int symbol_number,ENUM_ORDER_TYPE order_type)
  {
//--- Для рассчитанного значения отложенного ордера
   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 для отложенного ордера            |
//+------------------------------------------------------------------+
double CalculatePendingOrderStopLoss(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);
  }
//+------------------------------------------------------------------+
//| Рассчитывает уровень Take Profit для отложенного ордера          |
//+------------------------------------------------------------------+
double CalculatePendingOrderTakeProfit(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);
  }

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

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

//+----------------------------------------------------------------------------+
//| Рассчитывает уровень Trailing Stop для противоположно направленного ордера |
//+----------------------------------------------------------------------------+
double CalculateReverseOrderTrailingStop(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);
  }

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

//+------------------------------------------------------------------+
//| Изменяет уровень Trailing Stop для отложенного ордера            |
//+------------------------------------------------------------------+
void ModifyPendingOrderTrailingStop(int symbol_number)
  {
//--- Выйдем, если переворот позиции отключен или Trailing Stop не задан
   if(!Reverse[symbol_number] || TrailingStop[symbol_number]==0)
      return;
//--- 
   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=CalculateReverseOrderTrailingStop(symbol_number,pos.type);
//--- Пройдемся в цикле по всем ордерам от последнего к первому
   for(int i=total_orders-1; i>=0; i--)
     {
      //--- Если ордер выбран
      if((order_ticket=OrderGetTicket(i))>0)
        {
         //--- Получим символ ордера
         GetPendingOrderProperties(O_SYMBOL);
         //--- Получим комментарий ордера
         GetPendingOrderProperties(O_COMMENT);
         //--- Получим цену ордера
         GetPendingOrderProperties(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=CalculatePendingOrderStopLoss(symbol_number,opposite_order_type,new_level);
            tp=CalculatePendingOrderTakeProfit(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;
           }
        }
     }
  }

Иногда может понадобится определить, была ли позиция закрыта по Стоп Лосс или Тейк Профит. В нашем случае такие ситуации будут, поэтому напишем функции, которые будут определять это событие по комментарию последней сделки. Для получения комментария последней сделки на указанном символе напишем отдельную функцию, которую назовем GetLastDealComment():

//+------------------------------------------------------------------+
//| Возвращает комментарий последней сделки на указанном символе     |
//+------------------------------------------------------------------+
string GetLastDealComment(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);
  }

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

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

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

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

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

//+------------------------------------------------------------------+
//| Возвращает событие последней сделки на указанном символе         |
//+------------------------------------------------------------------+
bool IsLastDealTicket(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_deal_ticket[symbol_number])
               return(false);
            //--- Если тикеты не равны, сообщим об этом и
            else
              {
               //--- Запомним тикет последней сделки
               last_deal_ticket[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(), которая существенно изменилась, и новую функцию для управления отложенными ордерами ManagePendingOrders(). В ней будет производиться полный контроль за текущей ситуацией относительно отложенных ордеров.

Функция 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(!IsInOpenOrdersTimeRange(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(!CheckPendingOrderByComment(symbol_number,comment_top_order))
        {
         //--- Получим цену для установки отложенного ордера
         order_price=CalculatePendingOrder(symbol_number,ORDER_TYPE_BUY_STOP);
         //--- Получим уровни Take Profit и Stop Loss
         sl=CalculatePendingOrderStopLoss(symbol_number,ORDER_TYPE_BUY_STOP,order_price);
         tp=CalculatePendingOrderTakeProfit(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(!CheckPendingOrderByComment(symbol_number,comment_bottom_order))
        {
         //--- Получим цену для установки отложенного ордера
         order_price=CalculatePendingOrder(symbol_number,ORDER_TYPE_SELL_STOP);
         //--- Получим уровни Take Profit и Stop Loss
         sl=CalculatePendingOrderStopLoss(symbol_number,ORDER_TYPE_SELL_STOP,order_price);
         tp=CalculatePendingOrderTakeProfit(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);
        }
     }
  }

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

//+------------------------------------------------------------------+
//| Управляет отложенными ордерами                                   |
//+------------------------------------------------------------------+
void ManagePendingOrders()
  {
//--- Пройдемся в цикле по всем символам
   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(IsLastDealTicket(s) && 
            (IsClosedByStopLoss(s) || IsClosedByTakeProfit(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=CalculatePendingOrderStopLoss(s,opposite_order_type,order_price);
            tp=CalculatePendingOrderTakeProfit(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)
              {
               //--- Получим символ ордера
               GetPendingOrderProperties(O_SYMBOL);
               //--- Получим комментарий ордера
               GetPendingOrderProperties(O_COMMENT);
               //--- Если совпадают символ ордера и позиции, 
               //    а также совпадают комментарий ордера и противоположно направленного ордера
               if(ord.symbol==Symbols[s] && 
                  ord.comment==opposite_order_comment)
                 {
                  //--- Если переворот позиции отключен
                  if(!Reverse[s])
                     //--- Удалим ордер
                     DeletePendingOrder(order_ticket);
                  //--- Если переворот позиции включен
                  else
                    {
                     double lot=0.0;
                     //--- Получим свойства текущего ордера
                     GetPendingOrderProperties(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()
  {
//--- Проверим состояние отложенных ордеров
   ManagePendingOrders();
  }

Также функция ManagePendingOrders() будет использоваться и в обработчике пользовательских событий 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)
        {
         //--- Проверим состояние отложенных ордеров
         ManagePendingOrders();
         //--- Проверим сигналы и торгуем по ним
         CheckSignalsAndTrade();
         return;
        }
     }
  }

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

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

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

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

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

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

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

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

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

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

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


Заключение

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

Прикрепленные файлы |
755.set (1.59 KB)
Anatoli Kazharski
Anatoli Kazharski | 3 апр 2014 в 20:30
revers45:

Фактически сплошной программный код, ИМХО как статья это не катит, скорее это для раздела Code Base.

Ну что же. Удачной работы тогда. Пишите и выкладывайте всё в Code Base. ;)
Andrey Khatimlianskii
Andrey Khatimlianskii | 5 апр 2014 в 17:14
revers45:

Фактически сплошной программный код, ИМХО как статья это не катит, скорее это для раздела Code Base.

Такие статьи удобны для изучения языка.

Проще читать и текст и код, и сразу смотреть иллюстрации, чем отдельно скроллить статью, а отдельно коды из разных файлов.

 

Но соглашусь, что не хватает какой-то описательной части. Концепции и основных моментов реализации в виде обычного текста. 

Anatoli Kazharski
Anatoli Kazharski | 5 апр 2014 в 18:11
komposter:

Такие статьи удобны для изучения языка.

Проще читать и текст и код, и сразу смотреть иллюстрации, чем отдельно скроллить статью, а отдельно коды из разных файлов.


Но соглашусь, что не хватает какой-то описательной части. Концепции и основных моментов реализации в виде обычного текста. 

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

Даже не представляю, что можно было бы ещё добавить в статью. Но ничего страшного. ))

Alexander
Alexander | 18 дек 2014 в 16:53
  Зачем нагорожено столько кода для нескольких символов? Не проще ли по одному эксперту поставить на разных символах, по-моему так будет и программа быстрее исполняться. В связи с этим, уважаемый автор, не подскажите  как преобразовать эти функции для работы с отложенными ордерами, чтобы они были только для одного символа, кроме способа поставить в переменной  #define NUMBER_OF_SYMBOLS один?
Anatoli Kazharski
Anatoli Kazharski | 18 дек 2014 в 17:02
kuva:
Зачем нагорожено столько кода для нескольких символов?

Чтобы была возможность протестировать систему в тестере сразу на нескольких символах.

kuva:
Не проще ли по одному эксперту поставить на разных символах, по-моему так будет и программы быстрее исполняться. В связи с этим, уважаемый автор, не подскажите  как преобразовать этот код, чтобы он был работал только на  одном символе, кроме способа поставить в переменной  #define NUMBER_OF_SYMBOLS один?

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

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

Переход на новые рельсы: пользовательские индикаторы в MQL5 Переход на новые рельсы: пользовательские индикаторы в MQL5

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

Вот мы и получили долгожданные MetaTrader 5 и MQL5 Вот мы и получили долгожданные MetaTrader 5 и MQL5

Это очень краткий обзор MetaTrader 5. Я не могу описать все новшества системы за столь короткий период времени - тестирование стартовало 09-09-2009. Это символическая дата, и я уверен, что это будет счастливым числом. Всего несколько дней у меня на руках бета-версия терминала MetaTrader 5 и MQL5. Я не успел опробовать все, что в нем есть нового, но то, что есть, уже впечатляет.

Работа с корзинами валютных пар на рынке Форекс Работа с корзинами валютных пар на рынке Форекс

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

Текстовые файлы для хранения входных параметров советников, индикаторов и скриптов Текстовые файлы для хранения входных параметров советников, индикаторов и скриптов

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