Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXXIII): Отложенные торговые запросы - закрытие позиций по условиям

31 января 2020, 18:04
Artyom Trishkin
0
1 776

Содержание

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

Концепция

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

Так, например, для того чтобы верно определять, что отложенный запрос уже отработал и его нужно удалить, мы проверяли последнее торговое событие на счёте, и при совпадении данных, записанных в объекте-отложенном запросе с последним событием, считалось, что запрос отработан, и он удалялся. Оказалось, что такая логика не всегда правильная. Например, при частичных закрытиях позиций при помощи отложенных запросов, когда остаётся закрыть последнюю часть открытой позиции (прошлое закрытие было на 0.01 лота, и оставшаяся часть так же равна 0.01 лота), метод проверки актуальности торгового запроса считал, что этот запрос уже отработан — данные в нём совпадали с прошлым закрытием.

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

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

Реализация

В файле PendRequest.mqh класса абстрактного отложенного запроса, в его конструкторе добавим инициализацию (обнуление всех полей) структуры торгового запроса:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CPendRequest::CPendRequest(const ENUM_PEND_REQ_STATUS status,
                           const uchar id,
                           const double price,
                           const ulong time,
                           const MqlTradeRequest &request,
                           const int retcode)
  {
   ::ZeroMemory(this.m_request);
   this.CopyRequest(request);
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_digits=(int)::SymbolInfoInteger(this.GetProperty(PEND_REQ_PROP_MQL_REQ_SYMBOL),SYMBOL_DIGITS);
   int dg=(int)DigitsLots(this.GetProperty(PEND_REQ_PROP_MQL_REQ_SYMBOL));
   this.m_digits_lot=(dg==0 ? 1 : dg);
   this.SetProperty(PEND_REQ_PROP_STATUS,status);
   this.SetProperty(PEND_REQ_PROP_ID,id);
   this.SetProperty(PEND_REQ_PROP_RETCODE,retcode);
   this.SetProperty(PEND_REQ_PROP_TYPE,this.GetProperty(PEND_REQ_PROP_RETCODE)>0 ? PEND_REQ_TYPE_ERROR : PEND_REQ_TYPE_REQUEST);
   this.SetProperty(PEND_REQ_PROP_TIME_CREATE,time);
   this.SetProperty(PEND_REQ_PROP_PRICE_CREATE,price);
   this.m_pause.SetTimeBegin(this.GetProperty(PEND_REQ_PROP_TIME_CREATE));
   this.m_pause.SetWaitingMSC(this.GetProperty(PEND_REQ_PROP_WAITING));
   ::ArrayResize(this.m_activated_control,0,10);
   this.m_follow=true;
  }
//+------------------------------------------------------------------+

Без обнуления всех полей структуры в некоторых случаях создавался неправильный тип отложенного запроса, так как при создании объекта на закрытие позиции, если в структуре торгового запроса поле position_by имеет ненулевое значение, то создаётся объект-отложенный запрос на закрытие позиции встречным. Без предварительного обнуления полей иногда создавался запрос на закрытие позиции встречным вместо простого закрытия позиции. Впрочем, это правильно — никогда нельзя надеяться, что простое объявление переменной без её инициализации впоследствии не приведёт к непредсказуемым результатам, что и обнаружилось здесь, когда я забыл инициализировать структуру торгового запроса в конструкторе класса.

В файле PendReqControl.mqh класса управления торговлей, в его публичной секции объявим два метода —
метод создания отложенного запроса на полное и частичное закрытие позиции и метод на закрытие позиции встречным:

public:
//--- Возвращает себя
   CTradingControl     *GetObject(void)            { return &this;   }
//--- Таймер
   virtual void         OnTimer(void);
//--- Конструктор
                        CTradingControl();
//--- (1) Создаёт отложенный запрос (1) на открытие позиции, (2) на установку отложенного ордера
   template<typename SL,typename TP> 
   int                  CreatePReqPosition(const ENUM_POSITION_TYPE type,
                                        const double volume,
                                        const string symbol,
                                        const ulong magic=ULONG_MAX,
                                        const SL sl=0,
                                        const TP tp=0,
                                        const uchar group_id1=0,
                                        const uchar group_id2=0,
                                        const string comment=NULL,
                                        const ulong deviation=ULONG_MAX,
                                        const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
   template<typename PS,typename PL,typename SL,typename TP>
   int                  CreatePReqOrder(const ENUM_ORDER_TYPE order_type,
                                        const double volume,
                                        const string symbol,
                                        const PS price_set,
                                        const PL price_limit=0,
                                        const SL sl=0,
                                        const TP tp=0,
                                        const ulong magic=ULONG_MAX,
                                        const uchar group_id1=0,
                                        const uchar group_id2=0,
                                        const string comment=NULL,
                                        const datetime expiration=0,
                                        const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
                                        const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
//--- Создаёт отложенный запрос (1) на полное и частичное закрытие позиции, (2) на закрытие позиции встречной
   int                  CreatePReqClose(const ulong ticket,const double volume=WRONG_VALUE,const string comment=NULL,const ulong deviation=ULONG_MAX);
   int                  CreatePReqCloseBy(const ulong ticket,const ulong ticket_by);
   
//--- Устанавливает критерии активации отложенного запроса
   bool                 SetNewActivationProperties(const uchar id,
                                                   const ENUM_PEND_REQ_ACTIVATION_SOURCE source,
                                                   const int property,
                                                   const double control_value,
                                                   const ENUM_COMPARER_TYPE comparer_type,
                                                   const double actual_value);
  };
//+------------------------------------------------------------------+

В методе проверки актуальности отложенного запроса доработаем блок обработки объектов-отложенных запросов при частичном и встречном закрытии позиций:

//+------------------------------------------------------------------+
//| Проверка актуальности отложенного запроса                        |
//+------------------------------------------------------------------+
bool CTradingControl::CheckPReqRelevance(CPendRequest *req_obj,const MqlTradeRequest &request,const int index)
  {
//--- Если это открытие позиции или установка отложенного ордера
   if((req_obj.Action()==TRADE_ACTION_DEAL && req_obj.Position()==0) || req_obj.Action()==TRADE_ACTION_PENDING)
     {
      //--- Получаем идентификатор отложенного запроса
      uchar id=this.GetPendReqID((uint)request.magic);
      //--- Получаем список ордеров/позиций, содержащий ордер/позицию с таким идентификатором отложенного запроса
      CArrayObj *list=this.m_market.GetList(ORDER_PROP_PEND_REQ_ID,id,EQUAL);
      if(::CheckPointer(list)==POINTER_INVALID)
         return false;
      //--- Если есть такой ордер/позиция - запрос отработан: удаляем его и переходим к следующему (уходим из метода во внешний цикл)
      if(list.Total()>0)
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED));
         this.m_list_request.Delete(index);
         return false;
        }
     }
//--- Иначе: полное и частичное закрытие позиции, удаление ордера, модификация параметров ордера и стоп приказов позиции
   else
     {
      CArrayObj *list=NULL;
      //--- если это закрытие позиции, в том числе и встречным
      if((req_obj.Action()==TRADE_ACTION_DEAL && req_obj.Position()>0) || req_obj.Action()==TRADE_ACTION_CLOSE_BY)
        {
         //--- Получаем из списка открытых позиций позицию с нужным тикетом
         list=this.m_market.GetList(ORDER_PROP_TICKET,req_obj.Position(),EQUAL);
         if(::CheckPointer(list)==POINTER_INVALID)
            return false;
         //--- Если в рынке нет такой позиции - запрос отработан: удаляем его и переходим к следующему (уходим из метода во внешний цикл)
         if(list.Total()==0)
           {
            if(this.m_log_level>LOG_LEVEL_NO_MSG)
               ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED));
            this.m_list_request.Delete(index);
            return false;
           }
         //--- Иначе, если позиция ещё существует, то это частичное закрытие
         else
           {
            //--- Если есть событие
            if(this.m_events.IsEvent())
              {
               //--- Получаем список всех торговых событий аккаунта
               list=this.m_events.GetList();
               if(list==NULL)
                  return false;
               //--- В цикле с конца списка торговых событий аккаунта
               int events_total=list.Total();
               for(int j=events_total-1; j>WRONG_VALUE; j--)
                 {
                  //--- получаем очередное торговое событие
                  CEvent *event=list.At(j);
                  if(event==NULL)
                     continue;
                  //--- Если это событие - частичное закрытие, или при закрытии встречным было частичное закрытие
                  if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL || event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS)
                    {
                     //--- Если тикет позиции в торговом событии совпадает с тикетом в отложенном торговом запросе
                     if(event.TicketFirstOrderPosition()==req_obj.Position())
                       {
                        //--- Получаем объект-позицию из списка рыночных позиций
                        CArrayObj *list_orders=this.m_market.GetList(ORDER_PROP_TICKET,req_obj.Position(),EQUAL);
                        if(list_orders==NULL || list_orders.Total()==0)
                           break;
                        COrder *order=list_orders.At(list_orders.Total()-1);
                        if(order==NULL)
                           break;
                        //--- Устанавливаем фактические данные позиции в объект-отложенный запрос
                        this.SetOrderActualProperties(req_obj,order);
                        //--- Если (исполненный объём заявки + неисполненный объём заявки) равен запрашиваемому объёму в отложенном запросе -
                        //--- запрос отработан: удаляем его и прерываем цикл по списку торговых событий аккаунта
                        if(req_obj.GetProperty(PEND_REQ_PROP_MQL_REQ_VOLUME)==event.VolumeOrderExecuted()+event.VolumeOrderCurrent())
                          {
                           if(this.m_log_level>LOG_LEVEL_NO_MSG)
                              ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED));
                           this.m_list_request.Delete(index);
                           break;
                          }
                       }
                    }
                 }
               //--- Если в цикле по списку торговых событий был удалён отработанный объект-отложенный запрос - переходим к следующему (уходим из метода во внешний цикл)
               if(::CheckPointer(req_obj)==POINTER_INVALID)
                  return false;
              }
           }
        }
      //--- Если это модификация стоп-приказов позиции
      if(req_obj.Action()==TRADE_ACTION_SLTP)
        {
         //--- Получаем список всех торговых событий аккаунта
         list=this.m_events.GetList();
         if(list==NULL)
            return false;
         //--- В цикле с конца списка торговых событий аккаунта
         int events_total=list.Total();
         for(int j=events_total-1; j>WRONG_VALUE; j--)
           {
            //--- получаем очередное торговое событие
            CEvent *event=list.At(j);
            if(event==NULL)
               continue;
            //--- Если это событие - любое изменение стоп-приказов позиции
            if(event.TypeEvent()>TRADE_EVENT_MODIFY_ORDER_TP)
              {
               //--- Если тикет позиции в торговом событии совпадает с тикетом в отложенном торговом запросе
               if(event.TicketFirstOrderPosition()==req_obj.Position())
                 {
                  //--- Получаем объект-позицию из списка рыночных позиций
                  CArrayObj *list_orders=this.m_market.GetList(ORDER_PROP_TICKET,req_obj.Position(),EQUAL);
                  if(list_orders==NULL || list_orders.Total()==0)
                     break;
                  COrder *order=list_orders.At(list_orders.Total()-1);
                  if(order==NULL)
                     break;
                  //--- Устанавливаем фактические данные позиции в объект-отложенный запрос
                  this.SetOrderActualProperties(req_obj,order);
                  //--- Если все модификации отработаны -
                  //--- запрос отработан: удаляем его и прерываем цикл по списку торговых событий аккаунта
                  if(req_obj.IsCompleted())
                    {
                     if(this.m_log_level>LOG_LEVEL_NO_MSG)
                        ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED));
                     this.m_list_request.Delete(index);
                     break;
                    }
                 }
              }
           }
         //--- Если в цикле по списку торговых событий был удалён отработанный объект-отложенный запрос - переходим к следующему (уходим из метода во внешний цикл)
         if(::CheckPointer(req_obj)==POINTER_INVALID)
            return false;
        }
      //--- Если это удаление отложенного ордера
      if(req_obj.Action()==TRADE_ACTION_REMOVE)
        {
         //--- Получаем список удалённых отложенных ордеров из исторического списка
         list=this.m_history.GetList(ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL);
         if(::CheckPointer(list)==POINTER_INVALID)
            return false;
         //--- Оставляем в списке только один ордер с нужным тикетом
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,req_obj.Order(),EQUAL);
         //--- Если есть такой ордер - запрос отработан: удаляем его и переходим к следующему (уходим из метода во внешний цикл)
         if(list.Total()>0)
           {
            if(this.m_log_level>LOG_LEVEL_NO_MSG)
               ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED));
            this.m_list_request.Delete(index);
            return false;
           }
        }
      //--- Если это модификация отложенного ордера
      if(req_obj.Action()==TRADE_ACTION_MODIFY)
        {
         //--- Получаем список всех торговых событий аккаунта
         list=this.m_events.GetList();
         if(list==NULL)
            return false;
         //--- В цикле с конца списка торговых событий аккаунта
         int events_total=list.Total();
         for(int j=events_total-1; j>WRONG_VALUE; j--)
           {
            //--- получаем очередное торговое событие
            CEvent *event=list.At(j);
            if(event==NULL)
               continue;
            //--- Если это событие - любое изменение модифицируемых параметров отложенного ордера
            if(event.TypeEvent()>TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER && event.TypeEvent()<TRADE_EVENT_MODIFY_POSITION_SL_TP)
              {
               //--- Если тикет ордера в торговом событии совпадает с тикетом в отложенном торговом запросе
               if(event.TicketOrderEvent()==req_obj.Order())
                 {
                  //--- Получаем объект-ордер из списка
                  CArrayObj *list_orders=this.m_market.GetList(ORDER_PROP_TICKET,req_obj.Order(),EQUAL);
                  if(list_orders==NULL || list_orders.Total()==0)
                     break;
                  COrder *order=list_orders.At(0);
                  if(order==NULL)
                     break;
                  //--- Устанавливаем фактические данные ордера в объект-отложенный запрос
                  this.SetOrderActualProperties(req_obj,order);
                  //--- Если все модификации отработаны -
                  //--- запрос отработан: удаляем его и прерываем цикл по списку торговых событий аккаунта
                  if(req_obj.IsCompleted())
                    {
                     if(this.m_log_level>LOG_LEVEL_NO_MSG)
                        ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED));
                     this.m_list_request.Delete(index);
                     break;
                    }
                 }
              }
           }
        }
     }
//--- Если после проверки отработки запроса объект-отложенный запрос удалён - выходим (уходим из метода во внешний цикл)
   return(::CheckPointer(req_obj)==POINTER_INVALID ? false : true);
  }
//+------------------------------------------------------------------+

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

За пределами тела класса напишем реализацию методов создания отложенных запросов на полное и частичное закрытие позиции и на закрытие позиции встречным:

//+------------------------------------------------------------------+
//| Создаёт отложенный запрос на закрытие позиции                    |
//+------------------------------------------------------------------+
int CTradingControl::CreatePReqClose(const ulong ticket,const double volume=WRONG_VALUE,const string comment=NULL,const ulong deviation=ULONG_MAX)
  {
//--- Если установлен флаг глобального запрета торговли - выходим с возвратом WRONG_VALUE
   if(this.IsTradingDisable())
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE));
      return WRONG_VALUE;
     }
//--- Устанавливаем флаг ошибки как "нет ошибок"
   this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR;
   ENUM_ACTION_TYPE action=ACTION_TYPE_CLOSE;
//--- Получаем объект-ордер по тикету
   COrder *order=this.GetOrderObjByTicket(ticket);
   if(order==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_ORD_OBJ));
      return false;
     }
   ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)order.TypeOrder();
//--- Получаем объект-символ по тикету позиции
   CSymbol *symbol_obj=this.GetSymbolObjByPosition(ticket,DFUN);
   //--- Если не удалось получить объект-символ - выводим сообщение и возвращаем false
   if(symbol_obj==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ));
      return false;
     }
//--- получаем торговый объект из объекта-символа
   CTradeObj *trade_obj=symbol_obj.GetTradeObj();
   if(trade_obj==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
//--- Обновляем котировки по символу
   if(!symbol_obj.RefreshRates())
     {
      trade_obj.SetResultRetcode(10021);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      this.AddErrorCodeToList(10021);  // Отсутствуют котировки для обработки запроса
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(10021));
      return false;
     }
//--- Ищем наименьший из возможных идентификаторов, и если найти не удалось - возвращаем WRONG_VALUE
   int id=this.GetFreeID();
   if(id<1)
     {
      //--- Нет свободных идентификаторов для создания отложенного запроса
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_NO_FREE_IDS));
      return WRONG_VALUE;
     }

//--- Записываем объём,  отклонение и комментарий в структуру запроса
   this.m_request.deviation=(deviation==ULONG_MAX ? trade_obj.GetDeviation() : deviation);
   this.m_request.comment=(comment==NULL ? trade_obj.GetComment() : comment);
   this.m_request.volume=(volume==WRONG_VALUE || volume>order.Volume() ? order.Volume() : symbol_obj.NormalizedLot(volume));
//--- Записываем в структуру запроса магик, наименование символа,
//--- тип торговой операции, тип и тикет ордера
   this.m_request.magic=order.Magic();
   this.m_request.symbol=symbol_obj.Name();
   this.m_request.action=TRADE_ACTION_DEAL;
   this.m_request.type=order_type;
   this.m_request.position=ticket;
   this.m_request.position_by=0;
//--- В результате создания отложенного торгового запроса возвращаем либо его идентификатор, либо -1 при неудаче
   if(this.CreatePendingRequest(PEND_REQ_STATUS_CLOSE,(uchar)id,1,ulong(END_TIME-(ulong)::TimeCurrent()),this.m_request,0,symbol_obj,order))
      return id;
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+
//| Создаёт отложенный запрос на закрытие позиции встречной          |
//+------------------------------------------------------------------+
int CTradingControl::CreatePReqCloseBy(const ulong ticket,const ulong ticket_by)
  {
//--- Если установлен флаг глобального запрета торговли - выходим с возвратом WRONG_VALUE
   if(this.IsTradingDisable())
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE));
      return WRONG_VALUE;
     }
//--- Устанавливаем флаг ошибки как "нет ошибок"
   this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR;
   ENUM_ACTION_TYPE action=ACTION_TYPE_CLOSE_BY;
//--- Получаем объект-ордер по тикету
   COrder *order=this.GetOrderObjByTicket(ticket);
   if(order==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_ORD_OBJ));
      return false;
     }
   ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)order.TypeOrder();
//--- Получаем объект-символ по тикету позиции
   CSymbol *symbol_obj=this.GetSymbolObjByPosition(ticket,DFUN);
   if(symbol_obj==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ));
      return false;
     }
//--- торговый объект закрываемой позиции
   CTradeObj *trade_obj_pos=this.GetTradeObjByPosition(ticket,DFUN);
   if(trade_obj_pos==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
   if(!this.m_account.IsHedge())
     {
      trade_obj_pos.SetResultRetcode(MSG_ACC_UNABLE_CLOSE_BY);
      trade_obj_pos.SetResultComment(CMessage::Text(trade_obj_pos.GetResultRetcode()));
      return false;
     }
//--- проверим наличие встречной позиции
   if(!this.CheckPositionAvailablity(ticket_by,DFUN))
     {
      trade_obj_pos.SetResultRetcode(MSG_LIB_SYS_ERROR_POSITION_BY_ALREADY_CLOSED);
      trade_obj_pos.SetResultComment(CMessage::Text(trade_obj_pos.GetResultRetcode()));
      return false;
     }
//--- торговый объект встречной позиции
   CTradeObj *trade_obj_pos_by=this.GetTradeObjByPosition(ticket_by,DFUN);
   if(trade_obj_pos_by==NULL)
     {
      trade_obj_pos.SetResultRetcode(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ);
      trade_obj_pos.SetResultComment(CMessage::Text(trade_obj_pos.GetResultRetcode()));
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
//--- Если символ закрываемой позиции не равен символу встречной позиции - сообщаем и уходим
   if(symbol_obj.Name()!=trade_obj_pos_by.GetSymbol())
     {
      trade_obj_pos.SetResultRetcode(MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL);
      trade_obj_pos.SetResultComment(CMessage::Text(trade_obj_pos.GetResultRetcode()));
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL));
      return false;
     }
//--- Обновляем котировки по символу
   if(!symbol_obj.RefreshRates())
     {
      trade_obj_pos.SetResultRetcode(10021);
      trade_obj_pos.SetResultComment(CMessage::Text(trade_obj_pos.GetResultRetcode()));
      this.AddErrorCodeToList(10021);  // Отсутствуют котировки для обработки запроса
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(10021));
      return false;
     }
//--- Ищем наименьший из возможных идентификаторов, и если найти не удалось - возвращаем WRONG_VALUE
   int id=this.GetFreeID();
   if(id<1)
     {
      //--- Нет свободных идентификаторов для создания отложенного запроса
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_NO_FREE_IDS));
      return WRONG_VALUE;
     }

//--- Записываем в структуру запроса тип торговой операции, символ, тикеты двух позиций, тип и объём закрываемой позиции
   this.m_request.action=TRADE_ACTION_CLOSE_BY;
   this.m_request.symbol=symbol_obj.Name();
   this.m_request.position=ticket;
   this.m_request.position_by=ticket_by;
   this.m_request.type=order_type;
   this.m_request.volume=order.Volume();
//--- В результате создания отложенного торгового запроса возвращаем либо его идентификатор, либо -1 при неудаче
   if(this.CreatePendingRequest(PEND_REQ_STATUS_CLOSE,(uchar)id,1,ulong(END_TIME-(ulong)::TimeCurrent()),this.m_request,0,symbol_obj,order))
      return id;
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

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

В файле Trading.mqh класса основного торгового объекта бибилотеки перенесём методы из приватной секции класса в защищённую:

private:
   CArrayInt            m_list_errors;                   // Список ошибок
   bool                 m_is_trade_disable;              // Флаг запрета торговли
   bool                 m_use_sound;                     // Флаг использования звуков торговых событий объекта
   ENUM_ERROR_HANDLING_BEHAVIOR m_err_handling_behavior; // Поведение при обработке ошибок
   
//--- Добавляет код ошибки в список
   bool                 AddErrorCodeToList(const int error_code);
//--- Возвращает объект-символ по тикету (1) позиции, (2) ордера
   CSymbol             *GetSymbolObjByPosition(const ulong ticket,const string source_method);
   CSymbol             *GetSymbolObjByOrder(const ulong ticket,const string source_method);
//--- Возвращает торговый объект символа по тикету (1) позиции, (2) ордера, (3) по имени символа
   CTradeObj           *GetTradeObjByPosition(const ulong ticket,const string source_method);
   CTradeObj           *GetTradeObjByOrder(const ulong ticket,const string source_method);
   CTradeObj           *GetTradeObjBySymbol(const string symbol,const string source_method);
//--- Возвращает объект-ордер по тикету
   COrder              *GetOrderObjByTicket(const ulong ticket);


//--- Возвращает количество позиций (1) всех, (2) на покупку, (3) на продажу
   int                  PositionsTotalAll(void)          const;
   int                  PositionsTotalLong(void)         const;
   int                  PositionsTotalShort(void)        const;
//--- Возвращает количество отложенных ордеров (1) всех, (2) на покупку, (3) на продажу
   int                  OrdersTotalAll(void)             const;
   int                  OrdersTotalLong(void)            const;
   int                  OrdersTotalShort(void)           const;
//--- Возвращает совокупный объём позиций (1) на покупку, (2) на продажу
   double               PositionsTotalVolumeLong(void)   const;
   double               PositionsTotalVolumeShort(void)  const;
//--- Возвращает совокупный объём ордеров (1) на покупку, (2) на продажу
   double               OrdersTotalVolumeLong(void)      const;
   double               OrdersTotalVolumeShort(void)     const;
//--- Возвращает направление ордера по типу операции
   ENUM_ORDER_TYPE      DirectionByActionType(const ENUM_ACTION_TYPE action)  const;
//--- Проверяет наличие (1) позиции, (2) ордера по тикету
   bool                 CheckPositionAvailablity(const ulong ticket,const string source_method);
   bool                 CheckOrderAvailablity(const ulong ticket,const string source_method);
//--- Устанавливает торговому объекту нужный звук

Теперь перенесённые методы находятся в защищённой секции класса:

//+------------------------------------------------------------------+
//| Торговый класс                                                   |
//+------------------------------------------------------------------+
class CTrading : public CBaseObj
  {
protected:
   CAccount            *m_account;                       // Указатель на объект-текущий аккаунт
   CSymbolsCollection  *m_symbols;                       // Указатель на список коллекции символов
   CMarketCollection   *m_market;                        // Указатель на список коллекции рыночных ордеров и позиций
   CHistoryCollection  *m_history;                       // Указатель на список коллекции исторических ордеров и сделок
   CEventsCollection   *m_events;                        // Указатель на список коллекции событий
   CArrayObj            m_list_request;                  // Список отложенных запросов
   uchar                m_total_try;                     // Количество торговых попыток
   MqlTradeRequest      m_request;                       // Структура торгового запроса
   ENUM_TRADE_REQUEST_ERR_FLAGS m_error_reason_flags;    // Флаги причин ошибок в торговом методе
   
//--- Добавляет код ошибки в список
   bool                 AddErrorCodeToList(const int error_code);
//--- Ищет первый свободный идентификатор отложенного запроса
   int                  GetFreeID(void);
//--- Возвращает флаг наличия рыночного ордера/позиции с идентификатором отложенного запроса
   bool                 IsPresentOrderByID(const uchar id);
//--- Возвращает объект-ордер по тикету
   COrder              *GetOrderObjByTicket(const ulong ticket);
//--- Возвращает объект-символ по тикету (1) позиции, (2) ордера
   CSymbol             *GetSymbolObjByPosition(const ulong ticket,const string source_method);
   CSymbol             *GetSymbolObjByOrder(const ulong ticket,const string source_method);
//--- Возвращает торговый объект символа по тикету (1) позиции, (2) ордера, (3) по имени символа
   CTradeObj           *GetTradeObjByPosition(const ulong ticket,const string source_method);
   CTradeObj           *GetTradeObjByOrder(const ulong ticket,const string source_method);
   CTradeObj           *GetTradeObjBySymbol(const string symbol,const string source_method);
//--- Проверяет наличие (1) позиции, (2) ордера по тикету
   bool                 CheckPositionAvailablity(const ulong ticket,const string source_method);
   bool                 CheckOrderAvailablity(const ulong ticket,const string source_method);
   
private:

Эти методы используются дочерним классом CTradingControl, и должны находиться в защищённой секции.

В классе основного объекта библиотеки CEngine, в его публичную секцию добавим метод, возвращающий полный список всех отложенных запросов:

//--- Возвращает (1) список ссылок на ресурсы, (2) индекс объекта ресурса по его описанию
   CArrayObj           *GetListResource(void)                                 { return this.m_resource.GetList();                               }
   int                  GetIndexResObjByDescription(const string file_name)   { return this.m_resource.GetIndexResObjByDescription(file_name);  }

//--- Возвращает список отложенных запросов
   CArrayObj           *GetListPendingRequests(void)                          { return this.m_trading.GetListRequests();                        }

//--- Устанавливает для торговых классов:
//--- (1) корректное значение политики исполнения, (2) значение политики исполнения,
//--- (3) корректный тип истечения ордера, (4) тип истечения ордера,
//--- (5) магик, (6) Комментарий, (7) размер проскальзывания, (8) объём, (9) срок истечения ордера,
//--- (10) флаг асинхронной отправки торгового запроса, (11) уровень логирования, (12) количество торговых попыток

Метод возвращает список отложенных запросов при помощи вызова метода торгового класса GetListRequests().

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

В публичной секции класса объявим три метода для создания отложенных запросов:
на полное закрытие позиции, на частичное закрытие позиции, и на закрытие позиции встречным:

//--- Создаёт отложенный запрос (1) на открытие позиции Buy, (2) на открытие позиции Sell
   template<typename SL,typename TP> 
   int                  OpenBuyPending(const double volume,
                                       const string symbol,
                                       const ulong magic=ULONG_MAX,
                                       const SL sl=0,
                                       const TP tp=0,
                                       const uchar group_id1=0,
                                       const uchar group_id2=0,
                                       const string comment=NULL,
                                       const ulong deviation=ULONG_MAX,
                                       const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
   template<typename SL,typename TP> 
   int                  OpenSellPending(const double volume,
                                       const string symbol,
                                       const ulong magic=ULONG_MAX,
                                       const SL sl=0,
                                       const TP tp=0,
                                       const uchar group_id1=0,
                                       const uchar group_id2=0,
                                       const string comment=NULL,
                                       const ulong deviation=ULONG_MAX,
                                       const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
                                       
//--- Создаёт отложенный запрос на  закрытие позиции (1) полностью, (2) частично, (3) встречной
   int                  ClosePositionPending(const ulong ticket,const string comment=NULL,const ulong deviation=ULONG_MAX);
   int                  ClosePositionPartiallyPending(const ulong ticket,const double volume,const string comment=NULL,const ulong deviation=ULONG_MAX);
   int                  ClosePositionByPending(const ulong ticket,const ulong ticket_by);
                                    
//--- Создаёт отложенный запрос на установку ордера (1) BuyLimit, (2) BuyStop, (3) BuyStopLimit

А за пределами тела класса напишем их реализацию:

//+------------------------------------------------------------------+
//| Создаёт отложенный запрос на закрытие позиции полностью          |
//+------------------------------------------------------------------+
int CEngine::ClosePositionPending(const ulong ticket,const string comment=NULL,const ulong deviation=WRONG_VALUE)
  {
   return this.m_trading.CreatePReqClose(ticket,WRONG_VALUE,comment,deviation);
  }
//+------------------------------------------------------------------+
//| Создаёт отложенный запрос на закрытие позиции частично           |
//+------------------------------------------------------------------+
int CEngine::ClosePositionPartiallyPending(const ulong ticket,const double volume,const string comment=NULL,const ulong deviation=WRONG_VALUE)
  {
   return this.m_trading.CreatePReqClose(ticket,volume,comment,deviation);
  }
//+------------------------------------------------------------------+
//| Создаёт отложенный запрос на закрытие позиции встречной          |
//+------------------------------------------------------------------+
int CEngine::ClosePositionByPending(const ulong ticket,const ulong ticket_by)
  {
   return this.m_trading.CreatePReqCloseBy(ticket,ticket_by);
  }
//+------------------------------------------------------------------+

Методы просто вызывают соответствующие методы создания отложенных запросов класса CTradingControl.
Для создания отложенного запроса на полное закрытие позиции в метод CreatePReqClose() класса управления торговлей передаётся WRONG_VALUE в качестве значения закрываемого объёма , а для частичного закрытия — значение закрываемого объёма, передаваемое в метод входным параметром.

Теперь создадим методы для поиска и сортировки объектов-отложенных запросов в списке отложенных запросов.
К файлу \MQL5\Include\DoEasy\ Services\Select.mqh подключим класс объекта абстрактного отложенного запроса и объявим методы для работы с отложенными запросами:

//+------------------------------------------------------------------+
//|                                                       Select.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\Event.mqh"
#include "..\Objects\Accounts\Account.mqh"
#include "..\Objects\Symbols\Symbol.mqh"
#include "..\Objects\PendRequest\PendRequest.mqh"
//+------------------------------------------------------------------+
//| Список-хранилище                                                 |
//+------------------------------------------------------------------+
CArrayObj   ListStorage; // Объект-хранилище для хранения сортированных списков коллекций
//+------------------------------------------------------------------+
//| Класс для выборки объектов, удовлетворяющих критерию             |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- Метод сравнения двух величин
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:
//+------------------------------------------------------------------+
//| Методы работы с ордерами                                         |
//+------------------------------------------------------------------+
   //--- Возвращает список ордеров, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс ордера в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property);
   static int        FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property);
   static int        FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property);
   //--- Возвращает индекс ордера в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property);
   static int        FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property);
   static int        FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property);
//+------------------------------------------------------------------+
//| Методы работы с событиями                                        |
//+------------------------------------------------------------------+
   //--- Возвращает список событий, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс события в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства события
   static int        FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property);
   static int        FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property);
   static int        FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property);
   //--- Возвращает индекс события в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства события
   static int        FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property);
   static int        FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property);
   static int        FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property);
//+------------------------------------------------------------------+
//| Методы работы с аккаунтами                                       |
//+------------------------------------------------------------------+
   //--- Возвращает список аккаунтов, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс события в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства события
   static int        FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property);
   static int        FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property);
   static int        FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property);
   //--- Возвращает индекс события в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства события
   static int        FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property);
   static int        FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property);
   static int        FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property);
//+------------------------------------------------------------------+
//| Методы работы с символами                                        |
//+------------------------------------------------------------------+
   //--- Возвращает список символов, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс символа в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER property);
   static int        FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE property);
   static int        FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING property);
   //--- Возвращает индекс символа в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER property);
   static int        FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE property);
   static int        FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING property);
//+------------------------------------------------------------------+
//| Методы работы с отложенными запросами                            |
//+------------------------------------------------------------------+
   //--- Возвращает список отложенных запросов, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс отложенного запроса в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_INTEGER property);
   static int        FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_DOUBLE property);
   static int        FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_STRING property);
   //--- Возвращает индекс отложенного запроса в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindPendReqMin(CArrayObj *list_source,ENUM_PEND_REQ_PROP_INTEGER property);
   static int        FindPendReqMin(CArrayObj *list_source,ENUM_PEND_REQ_PROP_DOUBLE property);
   static int        FindPendReqMin(CArrayObj *list_source,ENUM_PEND_REQ_PROP_STRING property);
//---
  };
//+------------------------------------------------------------------+

За пределами тела класса напишем реализацию методов для сортировки и поиска в списке отложенных запросов:

//+------------------------------------------------------------------+
//| Методы работы со списками отложенных торговых запросов           |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Возвращает список запросов, у которых одно из целочисленных      |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   int total=list_source.Total();
   for(int i=0; i<total; i++)
     {
      CPendRequest *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      long obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Возвращает список запросов, у которых одно из вещественных       |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      CPendRequest *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      double obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Возвращает список запросов, у которых одно из строковых          |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      CPendRequest *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      string obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс запроса в списке                               |
//| с максимальным значением целочисленного свойства                 |
//+------------------------------------------------------------------+
int CSelect::FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_INTEGER property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CPendRequest *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CPendRequest *obj=list_source.At(i);
      long obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      long obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс запроса в списке                               |
//| с максимальным значением вещественного свойства                  |
//+------------------------------------------------------------------+
int CSelect::FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_DOUBLE property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CPendRequest *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CPendRequest *obj=list_source.At(i);
      double obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      double obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс запроса в списке                               |
//| с максимальным значением строкового свойства                     |
//+------------------------------------------------------------------+
int CSelect::FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_STRING property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CPendRequest *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CPendRequest *obj=list_source.At(i);
      string obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      string obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс запроса в списке                               |
//| с минимальным значением целочисленного свойства                  |
//+------------------------------------------------------------------+
int CSelect::FindPendReqMin(CArrayObj* list_source,ENUM_PEND_REQ_PROP_INTEGER property)
  {
   int index=0;
   CPendRequest *min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CPendRequest *obj=list_source.At(i);
      long obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      long obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс запроса в списке                               |
//| с минимальным значением вещественного свойства                   |
//+------------------------------------------------------------------+
int CSelect::FindPendReqMin(CArrayObj* list_source,ENUM_PEND_REQ_PROP_DOUBLE property)
  {
   int index=0;
   CPendRequest *min_obj=NULL;
   int total=list_source.Total();
   if(total== 0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CPendRequest *obj=list_source.At(i);
      double obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      double obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс запроса в списке                               |
//| с минимальным значением строкового свойства                      |
//+------------------------------------------------------------------+
int CSelect::FindPendReqMin(CArrayObj* list_source,ENUM_PEND_REQ_PROP_STRING property)
  {
   int index=0;
   CPendRequest *min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CPendRequest *obj=list_source.At(i);
      string obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      string obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+

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

Это все изменения классов библиотеки для организации закрытия позиций по условиям при помощи отложенных торговых запросов.

Тестирование

Для тестирования закрытия позиций по условиям возьмём советник из прошлой статьи и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part33\ под новым именем TestDoEasyPart33.mq5.

В блоке глобальных переменных советника я поменял наименования переменных, хранящих флаги состояний кнопок активации режимов торговли при помощи отложенных запросов:

//--- global variables
CEngine        engine;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ushort         magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           distance_pending_request;
uint           bars_delay_pending_request;
uint           slippage;
bool           trailing_on;
bool           pending_buy;
bool           pending_buy_limit;
bool           pending_buy_stop;
bool           pending_buy_stoplimit;
bool           pending_close_buy;
bool           pending_close_buy2;
bool           pending_close_buy_by_sell;
bool           pending_sell;
bool           pending_sell_limit;
bool           pending_sell_stop;
bool           pending_sell_stoplimit;
bool           pending_close_sell;
bool           pending_close_sell2;
bool           pending_close_sell_by_buy;
double         trailing_stop;
double         trailing_step;
uint           trailing_start;
uint           stoploss_to_modify;
uint           takeprofit_to_modify;
int            used_symbols_mode;
string         used_symbols;
string         array_used_symbols[];
bool           testing;
uchar          group1;
uchar          group2;
//+------------------------------------------------------------------+

Теперь эти переменные имеют более читабельные наименования:

//--- global variables
CEngine        engine;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ushort         magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           distance_pending_request;
uint           bars_delay_pending_request;
uint           slippage;
bool           trailing_on;
bool           pressed_pending_buy;
bool           pressed_pending_buy_limit;
bool           pressed_pending_buy_stop;
bool           pressed_pending_buy_stoplimit;
bool           pressed_pending_close_buy;
bool           pressed_pending_close_buy2;
bool           pressed_pending_close_buy_by_sell;
bool           pressed_pending_sell;
bool           pressed_pending_sell_limit;
bool           pressed_pending_sell_stop;
bool           pressed_pending_sell_stoplimit;
bool           pressed_pending_close_sell;
bool           pressed_pending_close_sell2;
bool           pressed_pending_close_sell_by_buy;
double         trailing_stop;
double         trailing_step;
uint           trailing_start;
uint           stoploss_to_modify;
uint           takeprofit_to_modify;
int            used_symbols_mode;
string         used_symbols;
string         array_used_symbols[];
bool           testing;
uchar          group1;
uchar          group2;
//+------------------------------------------------------------------+

И по всему тексту при помощи Ctrl+H был выполнен поиск "pending_" и замена этого вхождения на "pressed_pending_" чтобы все эти переменные переименовать по всему коду советника где они встречаются.

В функции PressButtonEvents() для обработки нажатий кнопок в советнике есть однотипные блоки кода для установки условий активации для вновь созданных объектов отложенных торговых запросов:

   //--- Если кнопка в нажатом состоянии
   if(ButtonState(button_name))
     {
      //--- Если нажата кнопка BUTT_BUY: Открыть позицию Buy
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Если не нажаты кнопки создания отложенного запроса - открываем позицию Buy 
         if(!pending_buy)
            engine.OpenBuy(lot,Symbol(),magic,stoploss,takeprofit);   // Нет комментария - будет установлен комментарий по умолчанию
         //--- Иначе - создаём отложенный запрос на открытие позиции Buy
         else
           {
            int id=engine.OpenBuyPending(lot,Symbol(),magic,stoploss,takeprofit);
            if(id>0)
              {
               //--- Если выбран критерий по цене
               if(ButtonState(prefix+EnumToString(BUTT_BUY)+"_PRICE"))
                 {
                  double ask=SymbolInfoDouble(NULL,SYMBOL_ASK);
                  double control_value=NormalizeDouble(ask-distance_pending_request*SymbolInfoDouble(NULL,SYMBOL_POINT),(int)SymbolInfoInteger(NULL,SYMBOL_DIGITS));
                  engine.SetNewActivationProperties((uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_ASK,control_value,EQUAL_OR_LESS,ask);
                 }
               //--- Если выбран критерий по времени
               if(ButtonState(prefix+EnumToString(BUTT_BUY)+"_TIME"))
                 {
                  ulong control_time=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
                  engine.SetNewActivationProperties((uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_TIME,control_time,EQUAL_OR_MORE,TimeCurrent());
                 }
               CPendRequest *req_obj=engine.GetPendRequestByID((uchar)id);
               if(req_obj==NULL)
                  return;
               if(engine.TradingGetLogLevel(Symbol())>LOG_LEVEL_NO_MSG)
                 {
                  ::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS)," #",req_obj.ID(),":");
                  req_obj.PrintActivations();
                 }
              }
           }
        }
      //--- Если нажата кнопка BUTT_BUY_LIMIT: Выставить BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {

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

Напишем такую функцию:

//+------------------------------------------------------------------+
//| Установка условий активации отложенного запроса                  |
//+------------------------------------------------------------------+
void SetPReqCriterion(const uchar id,const double price_activation,const ulong time_activation,ENUM_BUTTONS button,ENUM_COMPARER_TYPE comp_type,const double price_curr,const datetime time_curr)
  {
   double point=SymbolInfoDouble(NULL,SYMBOL_POINT);
   int    digits=(int)SymbolInfoInteger(NULL,SYMBOL_DIGITS);
//--- Если выбран критерий по цене
   if(ButtonState(prefix+EnumToString(button)+"_PRICE"))
     {
      //--- устанавливаем цену активации отложенного запроса
      engine.SetNewActivationProperties((uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_BID,price_activation,comp_type,price_curr);
     }
//--- Если выбран критерий по времени
   if(ButtonState(prefix+EnumToString(button)+"_TIME"))
     {
      //--- устанавливаем время активации отложенного запроса
      engine.SetNewActivationProperties((uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_TIME,time_activation,EQUAL_OR_MORE,time_curr);
     }
//--- Получим созданный отложенный запрос по идентификатору и выведем сообщение о добавлении условий в журнал
   CPendRequest *req_obj=engine.GetPendRequestByID((uchar)id);
   if(req_obj==NULL)
      return;
   if(engine.TradingGetLogLevel(Symbol())>LOG_LEVEL_NO_MSG)
     {
      ::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS),", ID #",req_obj.ID(),":");
      req_obj.PrintActivations();
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Обработка нажатий кнопок                                         |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   bool comp_magic=true;   // Временная переменная для выбора использования составного магика со случайными идентификаторами групп
   string comment="";
   double point=SymbolInfoDouble(NULL,SYMBOL_POINT);
   int    digits=(int)SymbolInfoInteger(NULL,SYMBOL_DIGITS);
   //--- Преобразуем имя кнопки в её строковый идентификатор
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- Случайные номера групп 1 и 2 в диапазоне 0 - 15
   group1=(uchar)Rand();
   group2=(uchar)Rand();
   uint magic=(comp_magic ? engine.SetCompositeMagicNumber(magic_number,group1,group2) : magic_number);
   //--- Если кнопка в нажатом состоянии
   if(ButtonState(button_name))
     {
      //--- Если нажата кнопка BUTT_BUY: Открыть позицию Buy
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Если не нажаты кнопки создания отложенного запроса - открываем позицию Buy 
         if(!pressed_pending_buy)
            engine.OpenBuy(lot,Symbol(),magic,stoploss,takeprofit);   // Нет комментария - будет установлен комментарий по умолчанию
         //--- Иначе - создаём отложенный запрос на открытие позиции Buy
         else
           {
            int id=engine.OpenBuyPending(lot,Symbol(),magic,stoploss,takeprofit);
            if(id>0)
              {
               //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации
               double ask=SymbolInfoDouble(NULL,SYMBOL_ASK);
               double price_activation=NormalizeDouble(ask-distance_pending_request*point,digits);
               ulong  time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
               SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_BUY,EQUAL_OR_LESS,ask,TimeCurrent());
              }
           }
        }
      //--- Если нажата кнопка BUTT_BUY_LIMIT: Выставить BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем ордер BuyLimit
         if(!pressed_pending_buy_limit)
            engine.PlaceBuyLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyLimit","Pending order BuyLimit"));
         //--- Иначе - создаём отложенный запрос на установку ордера BuyLimit со значением дистанции установки
         //--- и задаём условия в зависимости от активных кнопок
         else
           {
            int id=engine.PlaceBuyLimitPending(lot,Symbol(),distance_pending,stoploss,takeprofit,magic);
            if(id>0)
              {
               //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации
               double ask=SymbolInfoDouble(NULL,SYMBOL_ASK);
               double price_activation=NormalizeDouble(ask-distance_pending_request*point,digits);
               ulong  time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
               SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_BUY_LIMIT,EQUAL_OR_LESS,ask,TimeCurrent());
              }
           }
        }
      //--- Если нажата кнопка BUTT_BUY_STOP: Выставить BuyStop
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем ордер BuyStop
         if(!pressed_pending_buy_stop)
            engine.PlaceBuyStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStop","Pending order BuyStop"));
         //--- Иначе - создаём отложенный запрос на установку ордера BuyStop со значением дистанции установки
         //--- и задаём условия в зависимости от активных кнопок
         else
           {
            int id=engine.PlaceBuyStopPending(lot,Symbol(),distance_pending,stoploss,takeprofit,magic);
            if(id>0)
              {
               //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации
               double ask=SymbolInfoDouble(NULL,SYMBOL_ASK);
               double price_activation=NormalizeDouble(ask-distance_pending_request*point,digits);
               ulong  time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
               SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_BUY_STOP,EQUAL_OR_LESS,ask,TimeCurrent());
              }
           }
        }
      //--- Если нажата кнопка BUTT_BUY_STOP_LIMIT: Выставить BuyStopLimit
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем ордер BuyStopLimit
         if(!pressed_pending_buy_stoplimit)
            engine.PlaceBuyStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStopLimit","Pending order BuyStopLimit"));
         //--- Иначе - создаём отложенный запрос на установку ордера BuyStopLimit со значениями дистанций установки
         //--- и задаём условия в зависимости от активных кнопок
         else
           {
            int id=engine.PlaceBuyStopLimitPending(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic);
            if(id>0)
              {
               //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации
               double ask=SymbolInfoDouble(NULL,SYMBOL_ASK);
               double price_activation=NormalizeDouble(ask-distance_pending_request*point,digits);
               ulong  time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
               SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_BUY_STOP_LIMIT,EQUAL_OR_LESS,ask,TimeCurrent());
              }
           }
        }
      //--- Если нажата кнопка BUTT_SELL: Открыть позицию Sell
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- Если не нажаты кнопки создания отложенного запроса - открываем позицию Sell
         if(!pressed_pending_sell)
            engine.OpenSell(lot,Symbol(),magic,stoploss,takeprofit);  // Нет комментария - будет установлен комментарий по умолчанию
         //--- Иначе - создаём отложенный запрос на открытие позиции Sell
         else
           {
            int id=engine.OpenSellPending(lot,Symbol(),magic,stoploss,takeprofit);
            if(id>0)
              {
               //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации
               double bid=SymbolInfoDouble(NULL,SYMBOL_BID);
               double price_activation=NormalizeDouble(bid+distance_pending_request*point,digits);
               ulong  time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
               SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_SELL,EQUAL_OR_MORE,bid,TimeCurrent());
              }
           }
        }
      //--- Если нажата кнопка BUTT_SELL_LIMIT: Выставить SellLimit
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем ордер SellLimit
         if(!pressed_pending_sell_limit)
            engine.PlaceSellLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellLimit","Pending order SellLimit"));
         //--- Иначе - создаём отложенный запрос на установку ордера SellLimit со значением дистанции установки
         //--- и задаём условия в зависимости от активных кнопок
         else
           {
            int id=engine.PlaceSellLimitPending(lot,Symbol(),distance_pending,stoploss,takeprofit,magic);
            if(id>0)
              {
               //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации
               double bid=SymbolInfoDouble(NULL,SYMBOL_BID);
               double price_activation=NormalizeDouble(bid+distance_pending_request*point,digits);
               ulong  time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
               SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_SELL_LIMIT,EQUAL_OR_MORE,bid,TimeCurrent());
              }
           }
        }
      //--- Если нажата кнопка BUTT_SELL_STOP: Выставить SellStop
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем ордер SellStop
         if(!pressed_pending_sell_stop)
            engine.PlaceSellStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStop","Pending order SellStop"));
         //--- Иначе - создаём отложенный запрос на установку ордера SellStop со значением дистанции установки
         //--- и задаём условия в зависимости от активных кнопок
         else
           {
            int id=engine.PlaceSellStopPending(lot,Symbol(),distance_pending,stoploss,takeprofit,magic);
            if(id>0)
              {
               //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации
               double bid=SymbolInfoDouble(NULL,SYMBOL_BID);
               double price_activation=NormalizeDouble(bid+distance_pending_request*point,digits);
               ulong  time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
               SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_SELL_STOP,EQUAL_OR_MORE,bid,TimeCurrent());
              }
           }
        }
      //--- Если нажата кнопка BUTT_SELL_STOP_LIMIT: Выставить SellStopLimit
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем ордер SellStopLimit
         if(!pressed_pending_sell_stoplimit)
            engine.PlaceSellStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStopLimit","Pending order SellStopLimit"));
         //--- Иначе - создаём отложенный запрос на установку ордера SellStopLimit со значениями дистанций установки
         //--- и задаём условия в зависимости от активных кнопок
         else
           {
            int id=engine.PlaceSellStopLimitPending(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic);
            if(id>0)
              {
               //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации
               double bid=SymbolInfoDouble(NULL,SYMBOL_BID);
               double price_activation=NormalizeDouble(bid+distance_pending_request*point,digits);
               ulong  time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
               SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_SELL_STOP_LIMIT,EQUAL_OR_MORE,bid,TimeCurrent());
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_BUY: Закрыть Buy с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_BUY))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Buy и только по текущему символу
         list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL);
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Buy с наибольшей прибылью
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            //--- Получаем объект-позицию Buy и закрываем позицию по тикету
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Если не нажаты кнопки создания отложенного запроса - закрываем позицию
               if(!pressed_pending_close_buy)
                  engine.ClosePosition((ulong)position.Ticket());
               //--- Иначе - создаём отложенный запрос на закрытие позиции по тикету
               //--- и задаём условия в зависимости от активных кнопок
               else
                 {
                  int id=engine.ClosePositionPending(position.Ticket());
                  if(id>0)
                    {
                     //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации
                     double bid=SymbolInfoDouble(NULL,SYMBOL_BID);
                     double price_activation=NormalizeDouble(bid+distance_pending_request*point,digits);
                     ulong  time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
                     SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_CLOSE_BUY,EQUAL_OR_MORE,bid,TimeCurrent());
                    }
                 }
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_BUY2: Закрыть половину Buy с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_BUY2))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Buy и только по текущему символу
         list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL);
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Buy с наибольшей прибылью
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            //--- Получаем объект-позицию Buy и закрываем позицию по тикету
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Если не нажаты кнопки создания отложенного запроса - закрываем позицию по тикету
               if(!pressed_pending_close_buy2)
                  engine.ClosePositionPartially((ulong)position.Ticket(),position.Volume()/2.0);
               //--- Иначе - создаём отложенный запрос на частичное закрытие позиции по тикету
               //--- и задаём условия в зависимости от активных кнопок
               else
                 {
                  int id=engine.ClosePositionPartiallyPending(position.Ticket(),position.Volume()/2.0);
                  if(id>0)
                    {
                     //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации
                     double bid=SymbolInfoDouble(NULL,SYMBOL_BID);
                     double price_activation=NormalizeDouble(bid+distance_pending_request*point,digits);
                     ulong  time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
                     SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_CLOSE_BUY2,EQUAL_OR_MORE,bid,TimeCurrent());
                    }
                 }
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_BUY_BY_SELL: Закрыть Buy с максимальной прибылью встречной Sell с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL))
        {
         //--- Если счёт хеджевый
         if(engine.IsHedge())
           {
            CArrayObj *list_buy=NULL, *list_sell=NULL;
            //--- Получаем список всех открытых позиций
            CArrayObj* list=engine.GetListMarketPosition();
            if(list==NULL)
               return;
            //--- Выбираем из списка только позиции по текущему символу
            list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL);
            
            //--- Выбираем из списка только позиции Buy
            list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
            if(list_buy==NULL)
               return;
            //--- Сортируем список по прибыли с учётом комиссии и свопа
            list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
            //--- Получаем индекс позиции Buy с наибольшей прибылью
            int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
            
            //--- Выбираем из списка только позиции Sell
            list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
            if(list_sell==NULL)
               return;
            //--- Сортируем список по прибыли с учётом комиссии и свопа
            list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
            //--- Получаем индекс позиции Sell с наибольшей прибылью
            int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
            if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE)
              {
               //--- Выбираем позицию Buy с наибольшей прибылью
               COrder* position_buy=list_buy.At(index_buy);
               //--- Выбираем позицию Sell с наибольшей прибылью
               COrder* position_sell=list_sell.At(index_sell);
               if(position_buy!=NULL && position_sell!=NULL)
                 {
                  //--- Если не нажаты кнопки создания отложенного запроса - закрываем позиции по тикету
                  if(!pressed_pending_close_buy_by_sell)
                     engine.ClosePositionBy((ulong)position_buy.Ticket(),(ulong)position_sell.Ticket());
                  //--- Иначе - создаём отложенный запрос на закрытие позиции Buy встречной позицией Sell
                  //--- и задаём условия в зависимости от активных кнопок
                  else
                    {
                     int id=engine.ClosePositionByPending(position_buy.Ticket(),position_sell.Ticket());
                     if(id>0)
                       {
                        //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации
                        double bid=SymbolInfoDouble(NULL,SYMBOL_BID);
                        double price_activation=NormalizeDouble(bid+distance_pending_request*point,digits);
                        ulong  time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
                        SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_CLOSE_BUY_BY_SELL,EQUAL_OR_MORE,bid,TimeCurrent());
                       }
                    }
                 }
              }
           }
        }
        
      //--- Если нажата кнопка BUTT_CLOSE_SELL: Закрыть Sell с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_SELL))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Sell и только по текущему символу
         list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL);
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Sell с наибольшей прибылью
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            //--- Получаем объект-позицию Sell и закрываем позицию по тикету
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Если не нажаты кнопки создания отложенного запроса - закрываем позицию
               if(!pressed_pending_close_sell)
                  engine.ClosePosition((ulong)position.Ticket());
               //--- Иначе - создаём отложенный запрос на закрытие позиции по тикету
               //--- и задаём условия в зависимости от активных кнопок
               else
                 {
                  int id=engine.ClosePositionPending(position.Ticket());
                  if(id>0)
                    {
                     //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации
                     double ask=SymbolInfoDouble(NULL,SYMBOL_ASK);
                     double price_activation=NormalizeDouble(ask-distance_pending_request*point,digits);
                     ulong  time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
                     SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_CLOSE_SELL,EQUAL_OR_LESS,ask,TimeCurrent());
                    }
                 }
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_SELL2: Закрыть половину Sell с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_SELL2))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Sell и только по текущему символу
         list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL);
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Sell с наибольшей прибылью
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            //--- Получаем объект-позицию Sell и закрываем позицию по тикету
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Если не нажаты кнопки создания отложенного запроса - закрываем позицию по тикету
               if(!pressed_pending_close_sell2)
                  engine.ClosePositionPartially((ulong)position.Ticket(),position.Volume()/2.0);
               //--- Иначе - создаём отложенный запрос на частичное закрытие позиции по тикету
               //--- и задаём условия в зависимости от активных кнопок
               else
                 {
                  int id=engine.ClosePositionPartiallyPending(position.Ticket(),position.Volume()/2.0);
                  if(id>0)
                    {
                     //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации
                     double ask=SymbolInfoDouble(NULL,SYMBOL_ASK);
                     double price_activation=NormalizeDouble(ask-distance_pending_request*point,digits);
                     ulong  time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
                     SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_CLOSE_SELL2,EQUAL_OR_LESS,ask,TimeCurrent());
                    }
                 }
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_SELL_BY_BUY: Закрыть Sell с максимальной прибылью встречной Buy с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY))
        {
         //--- Если счёт хеджевый
         if(engine.IsHedge())
           {
            CArrayObj *list_buy=NULL, *list_sell=NULL;
            //--- Получаем список всех открытых позиций
            CArrayObj* list=engine.GetListMarketPosition();
            if(list==NULL)
               return;
            //--- Выбираем из списка только позиции по текущему символу
            list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL);
            
            //--- Выбираем из списка только позиции Sell
            list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
            if(list_sell==NULL)
               return;
            //--- Сортируем список по прибыли с учётом комиссии и свопа
            list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
            //--- Получаем индекс позиции Sell с наибольшей прибылью
            int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
            
            //--- Выбираем из списка только позиции Buy
            list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
            if(list_buy==NULL)
               return;
            //--- Сортируем список по прибыли с учётом комиссии и свопа
            list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
            //--- Получаем индекс позиции Buy с наибольшей прибылью
            int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
            if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE)
              {
               //--- Выбираем позицию Sell с наибольшей прибылью
               COrder* position_sell=list_sell.At(index_sell);
               //--- Выбираем позицию Buy с наибольшей прибылью
               COrder* position_buy=list_buy.At(index_buy);
               if(position_sell!=NULL && position_buy!=NULL)
                 {
                  //--- Если не нажаты кнопки создания отложенного запроса - закрываем позиции по тикету
                  if(!pressed_pending_close_sell_by_buy)
                     engine.ClosePositionBy((ulong)position_sell.Ticket(),(ulong)position_buy.Ticket());
                  //--- Иначе - создаём отложенный запрос на закрытие позиции Sell встречной позицией Buy
                  //--- и задаём условия в зависимости от активных кнопок
                  else
                    {
                     int id=engine.ClosePositionByPending(position_sell.Ticket(),position_buy.Ticket());
                     if(id>0)
                       {
                        //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации
                        double ask=SymbolInfoDouble(NULL,SYMBOL_ASK);
                        double price_activation=NormalizeDouble(ask-distance_pending_request*point,digits);
                        ulong  time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
                        SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_CLOSE_SELL_BY_BUY,EQUAL_OR_LESS,ask,TimeCurrent());
                       }
                    }
                 }
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_ALL: Закрыть все позиции, начиная от позиции с наименьшим профитом
      else if(button==EnumToString(BUTT_CLOSE_ALL))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции по текущему символу
         list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL);
         if(list!=NULL)
           {
            //--- Сортируем список по прибыли с учётом комиссии и свопа
            list.Sort(SORT_BY_ORDER_PROFIT_FULL);

            int total=list.Total();
            //--- В цикле от позиции с наименьшей прибылью
            for(int i=0;i<total;i++)
              {
               COrder* position=list.At(i);
               if(position==NULL)
                  continue;
               //--- закрываем каждую позицию по её тикету
               engine.ClosePosition((ulong)position.Ticket());
              }
           }
        }
      //--- Если нажата кнопка BUTT_DELETE_PENDING: Удалить отложенные ордера, начиная с самого раннего
      else if(button==EnumToString(BUTT_DELETE_PENDING))
        {
         //--- Получаем список всех ордеров
         CArrayObj* list=engine.GetListMarketPendings();
         //--- Выбираем из списка только ордера по текущему символу
         list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL);
         if(list!=NULL)
           {
            //--- Сортируем список по времени установки
            list.Sort(SORT_BY_ORDER_TIME_OPEN);
            int total=list.Total();
            //--- В цикле от ордера с наибольшим временем
            for(int i=total-1;i>=0;i--)
              {
               COrder* order=list.At(i);
               if(order==NULL)
                  continue;
               //--- удаяем ордер по его тикету
               engine.DeleteOrder((ulong)order.Ticket());
              }
           }
        }
      //--- Если нажата кнопка BUTT_PROFIT_WITHDRAWAL: Вывести средства со счёта
      if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL))
        {
         //--- Если программа запущена в тестере
         if(MQLInfoInteger(MQL_TESTER))
           {
            //--- Эмулируем вывод средств
            TesterWithdrawal(withdrawal);
           }
        }
      //--- Если нажата кнопка BUTT_SET_STOP_LOSS: Установить StopLoss всем ордерам и позициям, где его нету
      if(button==EnumToString(BUTT_SET_STOP_LOSS))
        {
         SetStopLoss();
        }
      //--- Если нажата кнопка BUTT_SET_TAKE_PROFIT: Установить TakeProfit всем ордерам и позициям, где его нету
      if(button==EnumToString(BUTT_SET_TAKE_PROFIT))
        {
         SetTakeProfit();
        }
      //--- Подождём 1/10 секунды
      Sleep(100);
      //--- "Отожмём" кнопку (если это не кнопка трейлинга и не кнопки активации работы отложенными запросами)
      if(button!=EnumToString(BUTT_TRAILING_ALL) && StringFind(button,"_PRICE")<0 && StringFind(button,"_TIME")<0)
         ButtonState(button_name,false);
      //--- Если нажата кнопка BUTT_TRAILING_ALL или кнопки включения работы отложенными запросами
      else
        {
         //--- Поставим цвет активной кнопки для кнопки включения трейлинга
         if(button==EnumToString(BUTT_TRAILING_ALL))
           {
            ButtonState(button_name,true);
            trailing_on=true;
           }
         
         //--- Покупки
         //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для открытия Buy по цене или времени
         if(button==EnumToString(BUTT_BUY)+"_PRICE" || button==EnumToString(BUTT_BUY)+"_TIME")
           {
            ButtonState(button_name,true);
            pressed_pending_buy=true;
           }
         //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для установки BuyLimit по цене или времени
         if(button==EnumToString(BUTT_BUY_LIMIT)+"_PRICE" || button==EnumToString(BUTT_BUY_LIMIT)+"_TIME")
           {
            ButtonState(button_name,true);
            pressed_pending_buy_limit=true;
           }
         //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для установки BuyStop по цене или времени
         if(button==EnumToString(BUTT_BUY_STOP)+"_PRICE" || button==EnumToString(BUTT_BUY_STOP)+"_TIME")
           {
            ButtonState(button_name,true);
            pressed_pending_buy_stop=true;
           }
         //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для установки BuyStopLimit по цене или времени
         if(button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_PRICE" || button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_TIME")
           {
            ButtonState(button_name,true);
            pressed_pending_buy_stoplimit=true;
           }
         //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для закрытия Buy по цене или времени
         if(button==EnumToString(BUTT_CLOSE_BUY)+"_PRICE" || button==EnumToString(BUTT_CLOSE_BUY)+"_TIME")
           {
            ButtonState(button_name,true);
            pressed_pending_close_buy=true;
           }
         //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для закрытия 1/2 Buy по цене или времени
         if(button==EnumToString(BUTT_CLOSE_BUY2)+"_PRICE" || button==EnumToString(BUTT_CLOSE_BUY2)+"_TIME")
           {
            ButtonState(button_name,true);
            pressed_pending_close_buy2=true;
           }
         //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для закрытия Buy встречной Sell по цене или времени
         if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_PRICE" || button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_TIME")
           {
            ButtonState(button_name,true);
            pressed_pending_close_buy_by_sell=true;
           }
         
         //--- Продажи
         //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для открытия Sell по цене или времени
         if(button==EnumToString(BUTT_SELL)+"_PRICE" || button==EnumToString(BUTT_SELL)+"_TIME")
           {
            ButtonState(button_name,true);
            pressed_pending_sell=true;
           }
         //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для установки SellLimit по цене или времени
         if(button==EnumToString(BUTT_SELL_LIMIT)+"_PRICE" || button==EnumToString(BUTT_SELL_LIMIT)+"_TIME")
           {
            ButtonState(button_name,true);
            pressed_pending_sell_limit=true;
           }
         //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для установки SellStop по цене или времени
         if(button==EnumToString(BUTT_SELL_STOP)+"_PRICE" || button==EnumToString(BUTT_SELL_STOP)+"_TIME")
           {
            ButtonState(button_name,true);
            pressed_pending_sell_stop=true;
           }
         //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для установки SellStopLimit по цене или времени
         if(button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_PRICE" || button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME")
           {
            ButtonState(button_name,true);
            pressed_pending_sell_stoplimit=true;
           }
         //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для закрытия Sell по цене или времени
         if(button==EnumToString(BUTT_CLOSE_SELL)+"_PRICE" || button==EnumToString(BUTT_CLOSE_SELL)+"_TIME")
           {
            ButtonState(button_name,true);
            pressed_pending_close_sell=true;
           }
         //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для закрытия 1/2 Sell по цене или времени
         if(button==EnumToString(BUTT_CLOSE_SELL2)+"_PRICE" || button==EnumToString(BUTT_CLOSE_SELL2)+"_TIME")
           {
            ButtonState(button_name,true);
            pressed_pending_close_sell2=true;
           }
         //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для закрытия Sell встречной Buy по цене или времени
         if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_PRICE" || button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_TIME")
           {
            ButtonState(button_name,true);
            pressed_pending_close_sell_by_buy=true;
           }
        }
      //--- перерисуем чарт
      ChartRedraw();
     }
   //--- Вернём цвет неактивным кнопкам
   else 
     {
      //--- кнопка трейлинга
      if(button==EnumToString(BUTT_TRAILING_ALL))
        {
         ButtonState(button_name,false);
         trailing_on=false;
        }
      
      //--- Покупки
      //--- кнопка активации работы отложенными запросами для открытия Buy по цене
      if(button==EnumToString(BUTT_BUY)+"_PRICE")
        {
         ButtonState(button_name,false);
         pressed_pending_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY)+"_TIME"));
        }
      //--- кнопка активации работы отложенными запросами для открытия Buy по времени
      if(button==EnumToString(BUTT_BUY)+"_TIME")
        {
         ButtonState(button_name,false);
         pressed_pending_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY)+"_PRICE"));
        }
      
      //--- кнопка активации работы отложенными запросами для установки BuyLimit по цене
      if(button==EnumToString(BUTT_BUY_LIMIT)+"_PRICE")
        {
         ButtonState(button_name,false);
         pressed_pending_buy_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_LIMIT)+"_TIME"));
        }
      //--- кнопка активации работы отложенными запросами для установки BuyLimit по времени
      if(button==EnumToString(BUTT_BUY_LIMIT)+"_TIME")
        {
         ButtonState(button_name,false);
         pressed_pending_buy_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_LIMIT)+"_PRICE"));
        }
      
      //--- кнопка активации работы отложенными запросами для установки BuyStop по цене
      if(button==EnumToString(BUTT_BUY_STOP)+"_PRICE")
        {
         ButtonState(button_name,false);
         pressed_pending_buy_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP)+"_TIME"));
        }
      //--- кнопка активации работы отложенными запросами для установки BuyStop по времени
      if(button==EnumToString(BUTT_BUY_STOP)+"_TIME")
        {
         ButtonState(button_name,false);
         pressed_pending_buy_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP)+"_PRICE"));
        }
      
      //--- кнопка активации работы отложенными запросами для установки BuyStopLimit по цене
      if(button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_PRICE")
        {
         ButtonState(button_name,false);
         pressed_pending_buy_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP_LIMIT)+"_TIME"));
        }
      //--- кнопка активации работы отложенными запросами для установки BuyStopLimit по времени
      if(button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_TIME")
        {
         ButtonState(button_name,false);
         pressed_pending_buy_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP_LIMIT)+"_PRICE"));
        }
      
      //--- кнопка активации работы отложенными запросами для закрытия Buy по цене
      if(button==EnumToString(BUTT_CLOSE_BUY)+"_PRICE")
        {
         ButtonState(button_name,false);
         pressed_pending_close_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY)+"_TIME"));
        }
      //--- кнопка активации работы отложенными запросами для закрытия Buy по времени
      if(button==EnumToString(BUTT_CLOSE_BUY)+"_TIME")
        {
         ButtonState(button_name,false);
         pressed_pending_close_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY)+"_PRICE"));
        }
      
      //--- кнопка активации работы отложенными запросами для закрытия 1/2 Buy по цене
      if(button==EnumToString(BUTT_CLOSE_BUY2)+"_PRICE")
        {
         ButtonState(button_name,false);
         pressed_pending_close_buy2=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY2)+"_TIME"));
        }
      //--- кнопка активации работы отложенными запросами для закрытия 1/2 Buy по времени
      if(button==EnumToString(BUTT_CLOSE_BUY2)+"_TIME")
        {
         ButtonState(button_name,false);
         pressed_pending_close_buy2=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY2)+"_PRICE"));
        }
      
      //--- кнопка активации работы отложенными запросами для закрытия Buy встречной Sell по цене
      if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_PRICE")
        {
         ButtonState(button_name,false);
         pressed_pending_close_buy_by_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_TIME"));
        }
      //--- кнопка активации работы отложенными запросами для закрытия Buy встречной Sell по времени
      if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_TIME")
        {
         ButtonState(button_name,false);
         pressed_pending_close_buy_by_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_PRICE"));
        }

      //--- Продажи
      //--- кнопка активации работы отложенными запросами для открытия Sell по цене
      if(button==EnumToString(BUTT_SELL)+"_PRICE")
        {
         ButtonState(button_name,false);
         pressed_pending_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL)+"_TIME"));
        }
      //--- кнопка активации работы отложенными запросами для открытия Sell по времени
      if(button==EnumToString(BUTT_SELL)+"_TIME")
        {
         ButtonState(button_name,false);
         pressed_pending_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL)+"_PRICE"));
        }
      
      //--- кнопка активации работы отложенными запросами для установки SellLimit по цене
      if(button==EnumToString(BUTT_SELL_LIMIT)+"_PRICE")
        {
         ButtonState(button_name,false);
         pressed_pending_sell_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_LIMIT)+"_TIME"));
        }
      //--- кнопка активации работы отложенными запросами для установки SellLimit по времени
      if(button==EnumToString(BUTT_SELL_LIMIT)+"_TIME")
        {
         ButtonState(button_name,false);
         pressed_pending_sell_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_LIMIT)+"_PRICE"));
        }
      
      //--- кнопка активации работы отложенными запросами для установки SellStop по цене
      if(button==EnumToString(BUTT_SELL_STOP)+"_PRICE")
        {
         ButtonState(button_name,false);
         pressed_pending_sell_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP)+"_TIME"));
        }
      //--- кнопка активации работы отложенными запросами для установки SellStop по времени
      if(button==EnumToString(BUTT_SELL_STOP)+"_TIME")
        {
         ButtonState(button_name,false);
         pressed_pending_sell_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP)+"_PRICE"));
        }
      
      //--- кнопка активации работы отложенными запросами для установки SellStopLimit по цене
      if(button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_PRICE")
        {
         ButtonState(button_name,false);
         pressed_pending_sell_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME"));
        }
      //--- кнопка активации работы отложенными запросами для установки SellStopLimit по времени
      if(button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME")
        {
         ButtonState(button_name,false);
         pressed_pending_sell_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP_LIMIT)+"_PRICE"));
        }
      
      //--- кнопка активации работы отложенными запросами для закрытия Sell по цене
      if(button==EnumToString(BUTT_CLOSE_SELL)+"_PRICE")
        {
         ButtonState(button_name,false);
         pressed_pending_close_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL)+"_TIME"));
        }
      //--- кнопка активации работы отложенными запросами для закрытия Sell по времени
      if(button==EnumToString(BUTT_CLOSE_SELL)+"_TIME")
        {
         ButtonState(button_name,false);
         pressed_pending_close_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL)+"_PRICE"));
        }
      
      //--- кнопка активации работы отложенными запросами для закрытия 1/2 Sell по цене
      if(button==EnumToString(BUTT_CLOSE_SELL2)+"_PRICE")
        {
         ButtonState(button_name,false);
         pressed_pending_close_sell2=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL2)+"_TIME"));
        }
      //--- кнопка активации работы отложенными запросами для закрытия 1/2 Sell по времени
      if(button==EnumToString(BUTT_CLOSE_SELL2)+"_TIME")
        {
         ButtonState(button_name,false);
         pressed_pending_close_sell2=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL2)+"_PRICE"));
        }
      
      //--- кнопка активации работы отложенными запросами для закрытия Sell встречной Buy по цене
      if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_PRICE")
        {
         ButtonState(button_name,false);
         pressed_pending_close_sell_by_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_TIME"));
        }
      //--- кнопка активации работы отложенными запросами для закрытия Sell встречной Buy по времени
      if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_TIME")
        {
         ButtonState(button_name,false);
         pressed_pending_close_sell_by_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_PRICE"));
        }
      //--- перерисуем чарт
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+

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

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

  1. откроем позицию на продажу и создадим отложенный запрос на закрытие части этой позиции по значению цены;
  2. после частичного закрытия короткой позиции откроем позицию на покупку и создадим отложенный запрос на закрытие этой позиции встречной — наполовину закрытой короткой позицией по значению цены;
  3. после закрытия части длинной позиции встречной позицией на продажу создадим новый отложенный запрос на полное закрытие длинной позиции с условием активации запроса по времени.


Как видим из примера, все запросы отрабатывают по заданным условиям и удаляются после их активации.

Что дальше

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

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

К содержанию

Статьи этой серии:

Часть 1. Концепция, организация данных
Часть 2. Коллекция исторических ордеров и сделок
Часть 3. Коллекция рыночных ордеров и позиций, организация поиска
Часть 4. Торговые события. Концепция
Часть 5. Классы и коллекция торговых событий. Отправка событий в программу
Часть 6. События на счёте с типом неттинг
Часть 7. События срабатывания StopLimit-ордеров, подготовка функционала для регистрации событий модификации ордеров и позиций
Часть 8. События модификации ордеров и позиций
Часть 9. Совместимость с MQL4 — Подготовка данных
Часть 10. Совместимость с MQL4 — События открытия позиций и активации отложенных ордеров
Часть 11. Совместимость с MQL4 — События закрытия позиций
Часть 12. Класс объекта "аккаунт", коллекция объектов-аккаунтов
Часть 13. События объекта "аккаунт"
Часть 14. Объект "Символ"
Часть 15. Коллекция объектов-символов
Часть 16. События коллекции символов
Часть 17. Интерактивность объектов библиотеки
Часть 18. Интерактивность объекта-аккаунт и любых других объектов библиотеки
Часть 19. Класс сообщений библиотеки
Часть 20. Создание и хранение ресурсов программы
Часть 21. Торговые классы — Базовый кроссплатформенный торговый объект
Часть 22. Торговые классы — Основной торговый класс, контроль ограничений
Часть 23. Торговые классы — Основной торговый класс, контроль допустимых параметров
Часть 24. Торговые классы — Основной торговый класс, автоматическая коррекция ошибочных параметров
Часть 25. Торговые классы — Основной торговый класс, обработка ошибок, возвращаемых торговым сервером
Часть 26. Работа с отложенными торговыми запросами - первая реализация (открытие позиций)
Часть 27. Работа с отложенными торговыми запросами - выставление отложенных ордеров
Часть 28. Работа с отложенными торговыми запросами - закрытие, удаление, модификации
Часть 29. Работа с отложенными торговыми запросами - классы объектов-запросов
Часть 30. Работа с отложенными запросами - управление объектами-запросами
Часть 31. Работа с отложенными запросами - открытие позиций по условиям
Часть 32. Работа с отложенными запросами - установка отложенных ордеров по условиям


Прикрепленные файлы |
MQL5.zip (3687.92 KB)
MQL4.zip (3687.92 KB)
Мультивалютный мониторинг торговых сигналов (Часть 2): Реализация визуальной части приложения Мультивалютный мониторинг торговых сигналов (Часть 2): Реализация визуальной части приложения

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

Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXXII): Отложенные торговые запросы - установка ордеров по условиям Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXXII): Отложенные торговые запросы - установка ордеров по условиям

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

Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXXIV): Отложенные торговые запросы - удаление ордеров, модификация ордеров и позиций по условиям Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXXIV): Отложенные торговые запросы - удаление ордеров, модификация ордеров и позиций по условиям

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

Непрерывная скользящая оптимизация (Часть 4): Программа для управления оптимизацией (автооптимизатор) Непрерывная скользящая оптимизация (Часть 4): Программа для управления оптимизацией (автооптимизатор)

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