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

11 октября 2019, 15:38
Artyom Trishkin
30
2 803

Содержание

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

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

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


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


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

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

Озвучивание торговых событий

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

//--- Воспроизводит звук (1) открытия/установки для заданного типа позиции/ордера,
//--- (2) закрытия/удаления для заданного типа позиции/ордера (3) модификации StopLoss для заданного типа позиции/ордера,
//--- (4) модификации TakeProfit для заданного типа позиции/ордера (5) модификации цены установки для заданного типа ордера
   void                       PlaySoundOpen(const int action);
   void                       PlaySoundClose(const int action);
   void                       PlaySoundModifySL(const int action);
   void                       PlaySoundModifyTP(const int action);
   void                       PlaySoundModifyPrice(const int action);
//--- Воспроизводит звук ошибки (1) открытия/установки для заданного типа позиции/ордера,
//--- (2) закрытия/удаления для заданного типа позиции/ордера (3) модификации StopLoss для заданного типа позиции/ордера,
//--- (4) модификации TakeProfit для заданного типа позиции/ордера (5) модификации цены установки для заданного типа ордера
   void                       PlaySoundErrorOpen(const int action);
   void                       PlaySoundErrorClose(const int action);
   void                       PlaySoundErrorModifySL(const int action);
   void                       PlaySoundErrorModifyTP(const int action);
   void                       PlaySoundErrorModifyPrice(const int action);

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

//+------------------------------------------------------------------+
//| Воспроизводит звук открытия/установки                            |
//| для заданного типа позиции/ордера                                |
//+------------------------------------------------------------------+
void CTradeObj::PlaySoundOpen(const int action)
  {
   switch(action)
     {
      case ORDER_TYPE_BUY              :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.Buy.SoundOpen());             break;
      case ORDER_TYPE_BUY_STOP         :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.BuyStop.SoundOpen());         break;
      case ORDER_TYPE_BUY_LIMIT        :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.BuyLimit.SoundOpen());        break;
      case ORDER_TYPE_BUY_STOP_LIMIT   :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundOpen());    break;
      case ORDER_TYPE_SELL             :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.Sell.SoundOpen());            break;
      case ORDER_TYPE_SELL_STOP        :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.SellStop.SoundOpen());        break;
      case ORDER_TYPE_SELL_LIMIT       :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.SellLimit.SoundOpen());       break;
      case ORDER_TYPE_SELL_STOP_LIMIT  :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.SellStopLimit.SoundOpen());   break;
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Воспроизводит звук закрытия/удаления                             |
//| для заданного типа позиции/ордера                                |
//+------------------------------------------------------------------+
void CTradeObj::PlaySoundClose(const int action)
  {
   switch(action)
     {
      case ORDER_TYPE_BUY              :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.Buy.SoundClose());            break;
      case ORDER_TYPE_BUY_STOP         :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.BuyStop.SoundClose());        break;
      case ORDER_TYPE_BUY_LIMIT        :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.BuyLimit.SoundClose());       break;
      case ORDER_TYPE_BUY_STOP_LIMIT   :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundClose());   break;
      case ORDER_TYPE_SELL             :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.Sell.SoundClose());           break;
      case ORDER_TYPE_SELL_STOP        :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.SellStop.SoundClose());       break;
      case ORDER_TYPE_SELL_LIMIT       :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.SellLimit.SoundClose());      break;
      case ORDER_TYPE_SELL_STOP_LIMIT  :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.SellStopLimit.SoundClose());  break;
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Воспроизводит звук модификации StopLoss                          |
//| для заданного типа позиции/ордера                                |
//+------------------------------------------------------------------+
void CTradeObj::PlaySoundModifySL(const int action)
  {
   switch(action)
     {
      case ORDER_TYPE_BUY              :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.Buy.SoundModifySL());            break;
      case ORDER_TYPE_BUY_STOP         :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.BuyStop.SoundModifySL());        break;
      case ORDER_TYPE_BUY_LIMIT        :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.BuyLimit.SoundModifySL());       break;
      case ORDER_TYPE_BUY_STOP_LIMIT   :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundModifySL());   break;
      case ORDER_TYPE_SELL             :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.Sell.SoundModifySL());           break;
      case ORDER_TYPE_SELL_STOP        :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.SellStop.SoundModifySL());       break;
      case ORDER_TYPE_SELL_LIMIT       :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.SellLimit.SoundModifySL());      break;
      case ORDER_TYPE_SELL_STOP_LIMIT  :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.SellStopLimit.SoundModifySL());  break;
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Воспроизводит звук модификации TakeProfit                        |
//| для заданного типа позиции/ордера                                |
//+------------------------------------------------------------------+
void CTradeObj::PlaySoundModifyTP(const int action)
  {
   switch(action)
     {
      case ORDER_TYPE_BUY              :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.Buy.SoundModifyTP());            break;
      case ORDER_TYPE_BUY_STOP         :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.BuyStop.SoundModifyTP());        break;
      case ORDER_TYPE_BUY_LIMIT        :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.BuyLimit.SoundModifyTP());       break;
      case ORDER_TYPE_BUY_STOP_LIMIT   :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundModifyTP());   break;
      case ORDER_TYPE_SELL             :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.Sell.SoundModifyTP());           break;
      case ORDER_TYPE_SELL_STOP        :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.SellStop.SoundModifyTP());       break;
      case ORDER_TYPE_SELL_LIMIT       :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.SellLimit.SoundModifyTP());      break;
      case ORDER_TYPE_SELL_STOP_LIMIT  :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.SellStopLimit.SoundModifyTP());  break;
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Воспроизводит звук модификации цены установки                    |
//| для заданного типа ордера                                        |
//+------------------------------------------------------------------+
void CTradeObj::PlaySoundModifyPrice(const int action)
  {
   switch(action)
     {
      case ORDER_TYPE_BUY_STOP         :   if(this.UseSoundModifyPrice(action))  CMessage::PlaySound(this.m_datas.BuyStop.SoundModifyPrice());        break;
      case ORDER_TYPE_BUY_LIMIT        :   if(this.UseSoundModifyPrice(action))  CMessage::PlaySound(this.m_datas.BuyLimit.SoundModifyPrice());       break;
      case ORDER_TYPE_BUY_STOP_LIMIT   :   if(this.UseSoundModifyPrice(action))  CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundModifyPrice());   break;
      case ORDER_TYPE_SELL_STOP        :   if(this.UseSoundModifyPrice(action))  CMessage::PlaySound(this.m_datas.SellStop.SoundModifyPrice());       break;
      case ORDER_TYPE_SELL_LIMIT       :   if(this.UseSoundModifyPrice(action))  CMessage::PlaySound(this.m_datas.SellLimit.SoundModifyPrice());      break;
      case ORDER_TYPE_SELL_STOP_LIMIT  :   if(this.UseSoundModifyPrice(action))  CMessage::PlaySound(this.m_datas.SellStopLimit.SoundModifyPrice());  break;
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Воспроизводит звук ошибки открытия/установки                     |
//| для заданного типа позиции/ордера                                |
//+------------------------------------------------------------------+
void CTradeObj::PlaySoundErrorOpen(const int action)
  {
   switch(action)
     {
      case ORDER_TYPE_BUY              :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.Buy.SoundErrorOpen());              break;
      case ORDER_TYPE_BUY_STOP         :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.BuyStop.SoundErrorOpen());          break;
      case ORDER_TYPE_BUY_LIMIT        :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.BuyLimit.SoundErrorOpen());         break;
      case ORDER_TYPE_BUY_STOP_LIMIT   :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundErrorOpen());     break;
      case ORDER_TYPE_SELL             :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.Sell.SoundErrorOpen());             break;
      case ORDER_TYPE_SELL_STOP        :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.SellStop.SoundErrorOpen());         break;
      case ORDER_TYPE_SELL_LIMIT       :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.SellLimit.SoundErrorOpen());        break;
      case ORDER_TYPE_SELL_STOP_LIMIT  :   if(this.UseSoundOpen(action))   CMessage::PlaySound(this.m_datas.SellStopLimit.SoundErrorOpen());    break;
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Воспроизводит звук ошибки закрытия/удаления                      |
//| для заданного типа позиции/ордера                                |
//+------------------------------------------------------------------+
void CTradeObj::PlaySoundErrorClose(const int action)
  {
   switch(action)
     {
      case ORDER_TYPE_BUY              :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.Buy.SoundErrorClose());             break;
      case ORDER_TYPE_BUY_STOP         :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.BuyStop.SoundErrorClose());         break;
      case ORDER_TYPE_BUY_LIMIT        :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.BuyLimit.SoundErrorClose());        break;
      case ORDER_TYPE_BUY_STOP_LIMIT   :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundErrorClose());    break;
      case ORDER_TYPE_SELL             :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.Sell.SoundErrorClose());            break;
      case ORDER_TYPE_SELL_STOP        :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.SellStop.SoundErrorClose());        break;
      case ORDER_TYPE_SELL_LIMIT       :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.SellLimit.SoundErrorClose());       break;
      case ORDER_TYPE_SELL_STOP_LIMIT  :   if(this.UseSoundClose(action))  CMessage::PlaySound(this.m_datas.SellStopLimit.SoundErrorClose());   break;
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Воспроизводит звук ошибки модификации StopLoss                   |
//| для заданного типа позиции/ордера                                |
//+------------------------------------------------------------------+
void CTradeObj::PlaySoundErrorModifySL(const int action)
  {
   switch(action)
     {
      case ORDER_TYPE_BUY              :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.Buy.SoundErrorModifySL());             break;
      case ORDER_TYPE_BUY_STOP         :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.BuyStop.SoundErrorModifySL());         break;
      case ORDER_TYPE_BUY_LIMIT        :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.BuyLimit.SoundErrorModifySL());        break;
      case ORDER_TYPE_BUY_STOP_LIMIT   :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundErrorModifySL());    break;
      case ORDER_TYPE_SELL             :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.Sell.SoundErrorModifySL());            break;
      case ORDER_TYPE_SELL_STOP        :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.SellStop.SoundErrorModifySL());        break;
      case ORDER_TYPE_SELL_LIMIT       :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.SellLimit.SoundErrorModifySL());       break;
      case ORDER_TYPE_SELL_STOP_LIMIT  :   if(this.UseSoundModifySL(action))  CMessage::PlaySound(this.m_datas.SellStopLimit.SoundErrorModifySL());   break;
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Воспроизводит звук ошибки модификации TakeProfit                 |
//| для заданного типа позиции/ордера                                |
//+------------------------------------------------------------------+
void CTradeObj::PlaySoundErrorModifyTP(const int action)
  {
   switch(action)
     {
      case ORDER_TYPE_BUY              :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.Buy.SoundErrorModifyTP());             break;
      case ORDER_TYPE_BUY_STOP         :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.BuyStop.SoundErrorModifyTP());         break;
      case ORDER_TYPE_BUY_LIMIT        :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.BuyLimit.SoundErrorModifyTP());        break;
      case ORDER_TYPE_BUY_STOP_LIMIT   :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundErrorModifyTP());    break;
      case ORDER_TYPE_SELL             :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.Sell.SoundErrorModifyTP());            break;
      case ORDER_TYPE_SELL_STOP        :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.SellStop.SoundErrorModifyTP());        break;
      case ORDER_TYPE_SELL_LIMIT       :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.SellLimit.SoundErrorModifyTP());       break;
      case ORDER_TYPE_SELL_STOP_LIMIT  :   if(this.UseSoundModifyTP(action))  CMessage::PlaySound(this.m_datas.SellStopLimit.SoundErrorModifyTP());   break;
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Воспроизводит звук ошибки модификации цены установки             |
//| для заданного типа ордера                                        |
//+------------------------------------------------------------------+
void CTradeObj::PlaySoundErrorModifyPrice(const int action)
  {
   switch(action)
     {
      case ORDER_TYPE_BUY_STOP         :   if(this.UseSoundModifyPrice(action))  CMessage::PlaySound(this.m_datas.BuyStop.SoundErrorModifyPrice());         break;
      case ORDER_TYPE_BUY_LIMIT        :   if(this.UseSoundModifyPrice(action))  CMessage::PlaySound(this.m_datas.BuyLimit.SoundErrorModifyPrice());        break;
      case ORDER_TYPE_BUY_STOP_LIMIT   :   if(this.UseSoundModifyPrice(action))  CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundErrorModifyPrice());    break;
      case ORDER_TYPE_SELL_STOP        :   if(this.UseSoundModifyPrice(action))  CMessage::PlaySound(this.m_datas.SellStop.SoundErrorModifyPrice());        break;
      case ORDER_TYPE_SELL_LIMIT       :   if(this.UseSoundModifyPrice(action))  CMessage::PlaySound(this.m_datas.SellLimit.SoundErrorModifyPrice());       break;
      case ORDER_TYPE_SELL_STOP_LIMIT  :   if(this.UseSoundModifyPrice(action))  CMessage::PlaySound(this.m_datas.SellStopLimit.SoundErrorModifyPrice());   break;
      default: break;
     }
  }
//+------------------------------------------------------------------+

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

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

//--- Воспроизводит звук указанного торгового события для заданного типа позиции/ордера
   void                       PlaySoundSuccess(const ENUM_ACTION_TYPE action,const int order,bool sl=false,bool tp=false,bool pr=false);
//--- Воспроизводит звук ошибки указанного торгового события для заданного типа позиции/ордера
   void                       PlaySoundError(const ENUM_ACTION_TYPE action,const int order,bool sl=false,bool tp=false,bool pr=false);

//--- Устанавливает/возвращает флаг использования звуков

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

//+------------------------------------------------------------------+
//| Воспроизводит звук указанного торгового события                  |
//| для заданного типа позиции/ордера                                |
//+------------------------------------------------------------------+
void CTradeObj::PlaySoundSuccess(const ENUM_ACTION_TYPE action,const int order,bool sl=false,bool tp=false,bool pr=false)
  {
   if(!this.m_use_sound)
      return;
   switch((int)action)
     {
      //--- Открытие/установка
      case ACTION_TYPE_BUY             :
      case ACTION_TYPE_BUY_LIMIT       :
      case ACTION_TYPE_BUY_STOP        :
      case ACTION_TYPE_BUY_STOP_LIMIT  :
      case ACTION_TYPE_SELL            :
      case ACTION_TYPE_SELL_LIMIT      :
      case ACTION_TYPE_SELL_STOP       :
      case ACTION_TYPE_SELL_STOP_LIMIT :
        this.PlaySoundOpen(order);  
        break;
      //--- Закрытие/удаление
      case ACTION_TYPE_CLOSE           :
      case ACTION_TYPE_CLOSE_BY        :
        this.PlaySoundClose(order); 
        break;
      //--- Модификация
      case ACTION_TYPE_MODIFY          :
        if(sl) { this.PlaySoundModifySL(order);    return; }
        if(tp) { this.PlaySoundModifyTP(order);    return; }
        if(pr) { this.PlaySoundModifyPrice(order); return; }
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+
//| Воспроизводит звук ошибки указанного торгового события           |
//| для заданного типа позиции/ордера                                |
//+------------------------------------------------------------------+
void CTradeObj::PlaySoundError(const ENUM_ACTION_TYPE action,const int order,bool sl=false,bool tp=false,bool pr=false)
  {
   if(!this.m_use_sound)
      return;
   switch((int)action)
     {
      //--- Открытие/установка
      case ACTION_TYPE_BUY             :
      case ACTION_TYPE_BUY_LIMIT       :
      case ACTION_TYPE_BUY_STOP        :
      case ACTION_TYPE_BUY_STOP_LIMIT  :
      case ACTION_TYPE_SELL            :
      case ACTION_TYPE_SELL_LIMIT      :
      case ACTION_TYPE_SELL_STOP       :
      case ACTION_TYPE_SELL_STOP_LIMIT :
        this.PlaySoundErrorOpen(order);
        break;
      //--- Закрытие/удаление
      case ACTION_TYPE_CLOSE           :
      case ACTION_TYPE_CLOSE_BY        :
        this.PlaySoundErrorClose(order);
        break;
      //--- Модификация
      case ACTION_TYPE_MODIFY          :
        if(sl) { this.PlaySoundErrorModifySL(order);    return; }
        if(tp) { this.PlaySoundErrorModifyTP(order);    return; }
        if(pr) { this.PlaySoundErrorModifyPrice(order); return; }
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

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

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

//--- Открывает позицию
   bool                       OpenPosition(const ENUM_POSITION_TYPE type,
                                           const double volume,
                                           const double sl=0,
                                           const double tp=0,
                                           const ulong magic=ULONG_MAX,
                                           const string comment=NULL,
                                           const ulong deviation=ULONG_MAX);

Это было проделано со всеми торговыми методами — все файлы приложены в конце статьи.

В методе установки стандартных звуков всем торговым событиям установим флаг, разрешающий воспроизведение звуков:

//+------------------------------------------------------------------+
//| Разрешает работу со звуками и устанавливает стандартные звуки    |
//+------------------------------------------------------------------+
void CTradeObj::SetSoundsStandart(void)
  {
   this.SetUseSound(true);
   this.m_datas.Buy.UseSoundClose(true);

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

Доработка класса базового торгового объекта завершена.

При отправке торговых запросов из программы нам необходимо задавать дистанцию установки отложенных ордеров и устанавливать размеры стоп-приказов. Для указания этих размеров можно передать в параметрах торгового приказа конкретную цену, на которой должен быть установлен ордер или стоп-приказ, либо можно передать размер дистанции в пунктах от цены — для установки отложенного ордера, или дистанцию в пунктах от цены открытия позиции/установки отложенного ордера, на которой должны будут располагаться стоп-приказы.

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

Но этот путь не очень хорош: во-первых, нам придётся сделать минимум по два одинаковых метода — в один передаются вещественные значения, а в другой — целочисленные.
А во-вторых — такой путь ограничивает нас в комбинации параметров: если передаём вещественные значения, то они должны быть для каждого из параметров вещественными — для цены установки ордера, для цены расположения StopLoss и для цены расположения TakeProfit, и точно так же мы ограничены при передаче в методы дистанций в пунктах — все величины должны быть переданы в целочисленных значениях.
Ну или придётся делать множество методов, в которых будут прописаны все возможные комбинации цен и дистанций. Это не практично.
Поэтому мы пойдём иным путём: мы все торговые методы сделаем шаблонными, и внутри методов будем определять типы переменных, в которых переданы значения установки ордера и значения стоп-приказов. Таким образом мы сможем передавать в методы нужные нам значения в любых комбинациях, например — цену установки ордера и дистанцию в пунктах от цены установки для стоп-приказов. Или наоборот. Это даст гораздо большую гибкость при рассчёте значений установки ордеров и стоп-приказов.
Внутри же торговых методов все поступившие значения будут приведены к значению цен, и уже в торговый приказ будут отправлены значения в ценах.

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

  • Проверка ограничений для торговли
  • Проверка достаточности средств для открытия позиций/установки ордеров
  • Проверка значений параметров по уровням StopLevel и FreezeLevel

Первые два пункта у нас уже созданы, проверку по уровням StopLevel и FreezeLevel добавим сегодня. Единственное, что хочется отметить, это то, что две готовые проверки — проверка ограничений для торговли и проверка достаточности средств у нас на данный момент запускаются прямо из торговых методов поочерёдно, что в общем-то нормально, но не практично.
Поэтому сегодня мы сделаем единый метод проверки всех ограничений, который будет запускаться из торговых методов, а внутри него будут поочерёдно выполняться все три проверки, создаваться список найденных ограничений и ошибок, и при наличии таковых, из метода будет выводиться в журнал полный список ошибок и ограничений, и возвращаться флаг неуспешности прохождения всех трёх проверок. Либо же будет возвращаться флаг успеха.

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

Контроль неверных значений, автоматизация выбора и использования входных параметров торговых методов

В торговые методы класса CTrading у нас передаются вещественные значения цен установки ордеров и стоп-приказов. Мы добавим возможность передавать ещё и дистанцию в пунктах. Поддерживаемые типы параметров, которые можно будет передавать в торговые классы для указания цен или дистанций — double, long, ulong, int и uint. Все остальные типы будут восприниматься ошибочными.

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

//+------------------------------------------------------------------+
//| Торговый класс                                                   |
//+------------------------------------------------------------------+
class CTrading
  {
private:
   CAccount            *m_account;           // Указатель на объект-текущий аккаунт
   CSymbolsCollection  *m_symbols;           // Указатель на список коллекции символов
   CMarketCollection   *m_market;            // Указатель на список коллекции рыночных ордеров и позиций
   CHistoryCollection  *m_history;           // Указатель на список коллекции исторических ордеров и сделок
   CArrayInt            m_list_errors;       // Список ошибок
   bool                 m_is_trade_enable;   // Флаг разрешения торговли
   bool                 m_use_sound;         // Флаг использования звуков торговых событий объекта
   ENUM_LOG_LEVEL       m_log_level;         // Уровень логирования
//---
   struct SDataPrices
     {
      double            open;                // Цена установки
      double            limit;               // Цена установки limit-ордера
      double            sl;                  // Цена StopLoss
      double            tp;                  // Цена TakeProfit
     };
   SDataPrices          m_req_price;         // Цены торгового запроса
//--- Добавляет код ошибки в список

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

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

//--- Возвращает объект-ордер по тикету
   COrder              *GetOrderObjByTicket(const ulong ticket);
//--- Возвращает количество позиций (1) всех, (2) на покупку, (3) на продажу

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

//--- Устанавливает цены торгового запроса
   template <typename PR,typename SL,typename TP,typename PL> 
   bool                 SetPrices(const ENUM_ORDER_TYPE action,const PR price,const SL sl,const TP tp,const PL limit,const string source_method,CSymbol *symbol_obj);
//--- Возвращает флаг проверки разрешённости по дистанции (1) StopLoss, (2) TakeProfit, (3) цены установки ордера от цены по уровню StopLevel
   bool                 CheckStopLossByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const double sl,const CSymbol *symbol_obj);
   bool                 CheckTakeProfitByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const double tp,const CSymbol *symbol_obj);
   bool                 CheckPriceByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj);
//--- Возвращает флаг проверки разрешённости дистанции от цены до (1) StopLoss, (2) TakeProfit, (3) цены установки ордера по уровню FreezeLevel
   bool                 CheckStopLossByFreezeLevel(const ENUM_ORDER_TYPE order_type,const double sl,const CSymbol *symbol_obj);
   bool                 CheckTakeProfitByFreezeLevel(const ENUM_ORDER_TYPE order_type,const double tp,const CSymbol *symbol_obj);
   bool                 CheckPriceByFreezeLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj);
//--- Проверка ограничений для торговли
   bool                 CheckTradeConstraints(const double volume,
                                              const ENUM_ACTION_TYPE action,
                                              const CSymbol *symbol_obj,
                                              const string source_method,
                                              double sl=0,
                                              double tp=0);
//--- Проверка достаточности средств
   bool                 CheckMoneyFree(const double volume,const double price,const ENUM_ORDER_TYPE order_type,const CSymbol *symbol_obj,const string source_method);
//--- Проверка значений параметров по уровням StopLevel и FreezeLevel
   bool                 CheckLevels(const ENUM_ACTION_TYPE action,
                                    const ENUM_ORDER_TYPE order_type,
                                    double price,
                                    double limit,
                                    double sl,
                                    double tp,
                                    const CSymbol *symbol_obj,
                                    const string source_method);

public:

В публичной секции класса объявим метод проверки разрешённости торговли и ошибок торгового запроса и методы установки и возврата флага разрешённости использования звуков:

public:
//--- Конструктор
                        CTrading();
//--- Получение указателей на списки (вызывать метод обязательно в OnInit() программы, так как список коллекции символов создаётся там)
   void                 OnInit(CAccount *account,CSymbolsCollection *symbols,CMarketCollection *market,CHistoryCollection *history)
                          {
                           this.m_account=account;
                           this.m_symbols=symbols;
                           this.m_market=market;
                           this.m_history=history;
                          }
//--- Возвращает список ошибок
   CArrayInt           *GetListErrors(void)                                { return &this.m_list_errors; }
//--- Проверка ошибок
   bool                 CheckErrors(const double volume,
                                    const double price,
                                    const ENUM_ACTION_TYPE action,
                                    const ENUM_ORDER_TYPE order_type,
                                    const CSymbol *symbol_obj,
                                    const string source_method,
                                    const double limit=0,
                                    double sl=0,
                                    double tp=0);

//--- Устанавливает для торговых объектов символов:
//--- (1) корректное значение политики исполнения, (2) значение политики исполнения,
//--- (3) корректный тип истечения ордера, (4) тип истечения ордера,
//--- (5) магик, (6) Комментарий, (7) размер проскальзывания, (8) объём, (9) срок истечения ордера,
//--- (10) флаг асинхронной отправки торгового запроса, (11) уровень логирования
   void                 SetCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol=NULL);
   void                 SetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol=NULL);
   void                 SetCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol=NULL);
   void                 SetTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol=NULL);
   void                 SetMagic(const ulong magic,const string symbol=NULL);
   void                 SetComment(const string comment,const string symbol=NULL);
   void                 SetDeviation(const ulong deviation,const string symbol=NULL);
   void                 SetVolume(const double volume=0,const string symbol=NULL);
   void                 SetExpiration(const datetime expiration=0,const string symbol=NULL);
   void                 SetAsyncMode(const bool mode=false,const string symbol=NULL);
   void                 SetLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol=NULL);

//--- Устанавливает стандартные звуки (1 symbol=NULL) торговым объектам всех символов, (2 symbol!=NULL) торговому объекту символа
   void                 SetSoundsStandart(const string symbol=NULL);
//--- Устанавливает звук для указанного типа ордера/позиции и символа
//--- Режим mode указывает для какого именно события устанавливается звук
//--- (symbol=NULL) торговым объектам всех символов,
//--- (symbol!=NULL) торговому объекту указанного символа
   void                 SetSound(const ENUM_MODE_SET_SOUND mode,const ENUM_ORDER_TYPE action,const string sound,const string symbol=NULL);
//--- Устанавливает/возвращает флаг разрешения использования звуков
   void                 SetUseSounds(const bool flag);
   bool                 IsUseSounds(void)                            const { return this.m_use_sound; }

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

Метод, возвращающий объект-ордер по тикету:

//+------------------------------------------------------------------+
//| Возвращает объект-ордер по тикету                                |
//+------------------------------------------------------------------+
COrder *CTrading::GetOrderObjByTicket(const ulong ticket)
  {
   CArrayObj *list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,ticket,EQUAL);
   if(list==NULL || list.Total()==0)
      return NULL;
   return list.At(0);
  }
//+------------------------------------------------------------------+

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

Шаблонный метод, рассчитывающий и записывающий в структуру m_req_price цены торгового запроса:

//+------------------------------------------------------------------+
//| Устанавливает цены торгового запроса                             |
//+------------------------------------------------------------------+
template <typename PR,typename SL,typename TP,typename PL> 
bool CTrading::SetPrices(const ENUM_ORDER_TYPE action,const PR price,const SL sl,const TP tp,const PL limit,const string source_method,CSymbol *symbol_obj)
  {
//--- Обнуляем цены и проверяем тип ордера - если некорректный тип - сообщаем и возвращаем false
   ::ZeroMemory(this.m_req_price);
   if(action>ORDER_TYPE_SELL_STOP_LIMIT)
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(source_method,CMessage::Text(4003));
      return false;
     }
   
//--- Цена открытия/установки
   if(price>0)
     {
      //--- тип (double) параметра цены - нормализуем цену до Digits(), так как передана цена
      if(typename(price)=="double")
         this.m_req_price.open=::NormalizeDouble(price,symbol_obj.Digits());
      //--- тип (int) параметра цены - передана дистанция
      else if(typename(price)=="int" || typename(price)=="uint" || typename(price)=="long" || typename(price)=="ulong")
        {
         //--- Рассчитываем цену установки ордера
         switch((int)action)
           {
            //--- Отложенный ордер
            case ORDER_TYPE_BUY_LIMIT       :  this.m_req_price.open=::NormalizeDouble(symbol_obj.Ask()-price*symbol_obj.Point(),symbol_obj.Digits());      break;
            case ORDER_TYPE_BUY_STOP        :
            case ORDER_TYPE_BUY_STOP_LIMIT  :  this.m_req_price.open=::NormalizeDouble(symbol_obj.Ask()+price*symbol_obj.Point(),symbol_obj.Digits());      break;
            
            case ORDER_TYPE_SELL_LIMIT      :  this.m_req_price.open=::NormalizeDouble(symbol_obj.BidLast()+price*symbol_obj.Point(),symbol_obj.Digits());  break;
            case ORDER_TYPE_SELL_STOP       :
            case ORDER_TYPE_SELL_STOP_LIMIT :  this.m_req_price.open=::NormalizeDouble(symbol_obj.BidLast()-price*symbol_obj.Point(),symbol_obj.Digits());  break;
            //--- По умолчанию - текущие цены открытия позиции
            default  :  this.m_req_price.open=
              (
               this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY ? ::NormalizeDouble(symbol_obj.Ask(),symbol_obj.Digits()) : 
               ::NormalizeDouble(symbol_obj.BidLast(),symbol_obj.Digits())
              ); break;
           }
        }
      //--- неподдерживаемые типы цены - выводим сообщение и возвращаем false
      else
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_PR_TYPE));
         return false;
        }
     }
   //--- Если цена не указана - используем текущие цены
   else
     {
      this.m_req_price.open=
        (
         this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY ? 
         ::NormalizeDouble(symbol_obj.Ask(),symbol_obj.Digits())              : 
         ::NormalizeDouble(symbol_obj.BidLast(),symbol_obj.Digits())
        );
     }
   
//--- Цена или дистанция установки StopLimit-ордера
   if(limit>0)
     {
      //--- тип (double) параметра цены limit-ордера - нормализуем цену до Digits(), так как передана цена
      if(typename(limit)=="double")
         this.m_req_price.limit=::NormalizeDouble(limit,symbol_obj.Digits());
      //--- тип (int) параметра цены limit-ордера - передана дистанция
      else if(typename(limit)=="int" || typename(limit)=="uint" || typename(limit)=="long" || typename(limit)=="ulong")
        {
         //--- Рассчитываем цену установки limit-ордера
         if(this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY)
            this.m_req_price.limit=::NormalizeDouble(this.m_req_price.open-limit*symbol_obj.Point(),symbol_obj.Digits());
         else
            this.m_req_price.limit=::NormalizeDouble(this.m_req_price.open+limit*symbol_obj.Point(),symbol_obj.Digits());
        }
      //--- неподдерживаемые типы цены limit-ордера - выводим сообщение и возвращаем false
      else
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_PL_TYPE));
         return false;
        }
     }  
     
//--- Цена установки ордера, от которой рассчитывать цены стоп-приказов
   double price_open=
     (
      (action==ORDER_TYPE_BUY_STOP_LIMIT || action==ORDER_TYPE_SELL_STOP_LIMIT) && limit>0 ? this.m_req_price.limit : this.m_req_price.open
     );
     
//--- StopLoss
   if(sl>0)
     {
      //--- тип (double) параметра StopLoss - нормализуем цену до Digits(), так как передана цена
      if(typename(sl)=="double")
         this.m_req_price.sl=::NormalizeDouble(sl,symbol_obj.Digits());
      //--- тип (int) параметра StopLoss - рассчитываем дистанцию установки
      else if(typename(sl)=="int" || typename(sl)=="uint" || typename(sl)=="long" || typename(sl)=="ulong")
        {
         //--- Рассчитываем цену установки StopLoss
         if(this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY)
            this.m_req_price.sl=::NormalizeDouble(price_open-sl*symbol_obj.Point(),symbol_obj.Digits());
         else
            this.m_req_price.sl=::NormalizeDouble(price_open+sl*symbol_obj.Point(),symbol_obj.Digits());
        }
      //--- неподдерживаемые типы StopLoss - выводим сообщение и возвращаем false
      else
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_SL_TYPE));
         return false;
        }
      
     }
     
//--- TakeProfit
   if(tp>0)
     {
      //--- тип (double) параметра TakeProfit - нормализуем цену до Digits(), так как передана цена
      if(typename(tp)=="double")
         this.m_req_price.tp=::NormalizeDouble(tp,symbol_obj.Digits());
      //--- тип (int) параметра TakeProfit - рассчитываем дистанцию установки
      else if(typename(tp)=="int" || typename(tp)=="uint" || typename(tp)=="long" || typename(tp)=="ulong")
        {
         if(this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY)
            this.m_req_price.tp=::NormalizeDouble(price_open+tp*symbol_obj.Point(),symbol_obj.Digits());
         else
            this.m_req_price.tp=::NormalizeDouble(price_open-tp*symbol_obj.Point(),symbol_obj.Digits());
        }
      //--- неподдерживаемые типы TakeProfit - выводим сообщение и возвращаем false
      else
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_TP_TYPE));
         return false;
        }
     }
      
//--- Все цены записаны
   return true;
  }
//+------------------------------------------------------------------+

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

Методы, возвращающие корректность дистанции StopLoss, TakeProfit или уровня установки ордера относительно StopLevel:

//+------------------------------------------------------------------+
//| Возвращает флаг проверки разрешённости дистанции                 |
//| от цены до StopLoss по уровню StopLevel                          |
//+------------------------------------------------------------------+
bool CTrading::CheckStopLossByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const double sl,const CSymbol *symbol_obj)
  {
   double lv=symbol_obj.TradeStopLevel()*symbol_obj.Point();
   double pr=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : order_type==ORDER_TYPE_SELL ? symbol_obj.Ask() : price);
   return(this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? sl<(pr-lv) : sl>(pr+lv));
  }
//+------------------------------------------------------------------+
//| Возвращает флаг проверки разрешённости дистанции                 |
//| от цены до TakeProfit по уровню StopLevel                        |
//+------------------------------------------------------------------+
bool CTrading::CheckTakeProfitByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const double tp,const CSymbol *symbol_obj)
  {
   double lv=symbol_obj.TradeStopLevel()*symbol_obj.Point();
   double pr=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : order_type==ORDER_TYPE_SELL ? symbol_obj.Ask() : price);
   return(this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? tp>(pr+lv) : tp<(pr-lv));
  }
//+------------------------------------------------------------------+
//| Возвращает флаг проверки разрешённости дистанции                 |
//| установки ордера от цены до цены установки по уровню StopLevel   |
//+------------------------------------------------------------------+
bool CTrading::CheckPriceByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj)
  {
   double lv=symbol_obj.TradeStopLevel()*symbol_obj.Point();
   double pr=(this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? symbol_obj.Ask() : symbol_obj.BidLast());
   return
     (
      order_type==ORDER_TYPE_SELL_STOP       ||
      order_type==ORDER_TYPE_SELL_STOP_LIMIT ||
      order_type==ORDER_TYPE_BUY_LIMIT       ?  price<(pr-lv)  :
      order_type==ORDER_TYPE_BUY_STOP        ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT  ||
      order_type==ORDER_TYPE_SELL_LIMIT      ?  price>(pr+lv)  :
      true
     );
  }
//+------------------------------------------------------------------+

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

Методы, возвращающие корректность дистанции StopLoss, TakeProfit или уровня установки ордера относительно FreezeLevel:

//+------------------------------------------------------------------+
//| Возвращает флаг проверки разрешённости                           |
//| дистанции от цены до StopLoss по уровню FreezeLevel              |
//+------------------------------------------------------------------+
bool CTrading::CheckStopLossByFreezeLevel(const ENUM_ORDER_TYPE order_type,const double sl,const CSymbol *symbol_obj)
  {
   if(symbol_obj.TradeFreezeLevel()==0 || order_type>ORDER_TYPE_SELL)
      return true;
   double lv=symbol_obj.TradeFreezeLevel()*symbol_obj.Point();
   double pr=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : symbol_obj.Ask());
   return(this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? sl<(pr-lv) : sl>(pr+lv));
  }
//+------------------------------------------------------------------+
//| Возвращает флаг проверки разрешённости                           |
//| дистанции от цены до TakeProfit по уровню FreezeLevel            |
//+------------------------------------------------------------------+
bool CTrading::CheckTakeProfitByFreezeLevel(const ENUM_ORDER_TYPE order_type,const double tp,const CSymbol *symbol_obj)
  {
   if(symbol_obj.TradeFreezeLevel()==0 || order_type>ORDER_TYPE_SELL)
      return true;
   double lv=symbol_obj.TradeFreezeLevel()*symbol_obj.Point();
   double pr=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : symbol_obj.Ask());
   return(this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? tp>(pr+lv) : tp<(pr-lv));
  }
//+------------------------------------------------------------------+
//| Возвращает флаг проверки разрешённости по дистанции              |
//| от цены до цены установки ордера по уровню FreezeLevel           |
//+------------------------------------------------------------------+
bool CTrading::CheckPriceByFreezeLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj)
  {
   if(symbol_obj.TradeFreezeLevel()==0 || order_type<ORDER_TYPE_BUY_LIMIT)
      return true;
   double lv=symbol_obj.TradeFreezeLevel()*symbol_obj.Point();
   double pr=(this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? symbol_obj.Ask() : symbol_obj.BidLast());
   return
     (
      order_type==ORDER_TYPE_SELL_STOP       ||
      order_type==ORDER_TYPE_SELL_STOP_LIMIT ||
      order_type==ORDER_TYPE_BUY_LIMIT       ?  price<(pr-lv)  :
      order_type==ORDER_TYPE_BUY_STOP        ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT  ||
      order_type==ORDER_TYPE_SELL_LIMIT      ?  price>(pr+lv)  :
      true
     );
  }
//+------------------------------------------------------------------+

Точно так же, как и при проверке дистанции по уровню StopLevel, здесь проверяется расстояние от текущей цены для типа ордера до цены установки ордера или до стоп-приказов.
Если уровень заморозки для символа установлен в 0 — это означает отсутствие уровня заморозки. Поэтому сначала проверяем нулевое значение StopLevel и возвращаем true при подтверждении отсутствия уровня заморозки торговых операций.

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

Метод проверки значений параметров по уровням StopLevel и FreezeLevel:

//+------------------------------------------------------------------+
//| Проверка значений параметров  по уровням StopLevel и FreezeLevel |
//+------------------------------------------------------------------+
bool CTrading::CheckLevels(const ENUM_ACTION_TYPE action,
                           const ENUM_ORDER_TYPE order_type,
                           double price,
                           double limit,
                           double sl,
                           double tp,
                           const CSymbol *symbol_obj,
                           const string source_method)
  {
//--- результат проведения всех проверок
   bool res=true;
//--- StopLevel
//--- Если не закрытие позиции/удаление ордера
   if(action!=ACTION_TYPE_CLOSE && action!=ACTION_TYPE_CLOSE_BY)
     {
      //--- Если установка отложенного ордера
      if(action>ACTION_TYPE_SELL)
        {
         //--- Если дистанция установки в пунктах меньше размера StopLevel
         if(!this.CheckPriceByStopLevel(order_type,price,symbol_obj))
           {
            //--- добавляем код ошибки в список и записываем в результат false
            this.AddErrorCodeToList(MSG_LIB_TEXT_PR_LESS_STOP_LEVEL);
            res &=false;
           }
        }
      //--- Если есть StopLoss
      if(sl>0)
        {
         //--- Если дистанция StopLoss в пунктах от цены открытия меньше размера StopLevel
         double price_open=(action==ACTION_TYPE_BUY_STOP_LIMIT || action==ACTION_TYPE_SELL_STOP_LIMIT ? limit : price);
         if(!this.CheckStopLossByStopLevel(order_type,price_open,sl,symbol_obj))
           {
            //--- добавляем код ошибки в список и записываем в результат false
            this.AddErrorCodeToList(MSG_LIB_TEXT_SL_LESS_STOP_LEVEL);
            res &=false;
           }
        }
      //--- Если есть TakeProfit
      if(tp>0)
        {
         double price_open=(action==ACTION_TYPE_BUY_STOP_LIMIT || action==ACTION_TYPE_SELL_STOP_LIMIT ? limit : price);
         //--- Если дистанция TakeProfit в пунктах от цены открытия меньше размера StopLevel
         if(!this.CheckTakeProfitByStopLevel(order_type,price_open,tp,symbol_obj))
           {
            //--- добавляем код ошибки в список и записываем в результат false
            this.AddErrorCodeToList(MSG_LIB_TEXT_TP_LESS_STOP_LEVEL);
            res &=false;
           }
        }
     }
//--- FreezeLevel
//--- Если закрытие позиции/удаление ордера/модификация
   if(action>ACTION_TYPE_SELL_STOP_LIMIT)
     {
      //--- Если это позиция
      if(order_type<ORDER_TYPE_BUY_LIMIT)
        {
         //--- Модификация StopLoss
         if(sl>0)
           {
            //--- Если дистанция от цены до StopLoss меньше размера FreezeLevel
            if(!this.CheckStopLossByFreezeLevel(order_type,sl,symbol_obj))
              {
               //--- добавляем код ошибки в список и записываем в результат false
               this.AddErrorCodeToList(MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL);
               res &=false;
              }
           }
         //--- Модификация TakeProfit
         if(tp>0)
           {
            //--- Если дистанция от цены до StopLoss меньше размера FreezeLevel
            if(!this.CheckTakeProfitByFreezeLevel(order_type,tp,symbol_obj))
              {
               //--- добавляем код ошибки в список и записываем в результат false
               this.AddErrorCodeToList(MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL);
               res &=false;
              }
           }
        }
      //--- Если это отложенный ордер
      else
        {
         //--- Модификация цены установки
         if(price>0)
           {
            //--- Если дистанция от цены до цены срабатывания ордера меньше размера FreezeLevel
            if(!this.CheckPriceByFreezeLevel(order_type,price,symbol_obj))
              {
               //--- добавляем код ошибки в список и записываем в результат false
               this.AddErrorCodeToList(MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL);
               res &=false;
              }
           }
        }
     }
   return res;
  }
//+------------------------------------------------------------------+

В зависимости от типа проводимой торговой операции и типа ордера/позиции, проверяются уровни цены относительно уровней StopLevel и FreezeLevel, и при некорректности цен в список ошибок записывается код ошибки, и к результату добавляется false. По окончании всех проверок, итоговый результат возвращается в вызывающий метод.

Общий метод проверки всех ограничений и ошибок:

//+------------------------------------------------------------------+
//| Проверка ограничений и ошибок                                    |
//+------------------------------------------------------------------+
bool CTrading::CheckErrors(const double volume,
                           const double price,
                           const ENUM_ACTION_TYPE action,
                           const ENUM_ORDER_TYPE order_type,
                           const CSymbol *symbol_obj,
                           const string source_method,
                           const double limit=0,
                           double sl=0,
                           double tp=0)
  {
//--- результат проведения всех проверок
   bool res=true;
//--- Очищаем список ошибок
   this.m_list_errors.Clear();
   this.m_list_errors.Sort();
   
//--- Проверка ограничений для торговли
   res &=this.CheckTradeConstraints(volume,action,symbol_obj,source_method,sl,tp);
//--- Проверка достаточности средств для открытия позиций/установки ордеров
   if(action<ACTION_TYPE_CLOSE_BY)
      res &=this.CheckMoneyFree(volume,price,order_type,symbol_obj,source_method);
//--- Проверка значений параметров по уровням StopLevel и FreezeLevel
   res &=this.CheckLevels(action,order_type,price,limit,sl,tp,symbol_obj,source_method);

//--- Если есть ограничения, выводим заголовок и список ошибок
   if(!res)
     {
      //--- Запрос отклонён до отправки на сервер по причине:
      int total=this.m_list_errors.Total();
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
        {
         //--- Для MQL5 сначала выводится заголовок списка, затем список ошибок
         #ifdef __MQL5__
         ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_REQUEST_REJECTED_DUE));
         for(int i=0;i<total;i++)
            ::Print((total>1 ? string(i+1)+". " : ""),CMessage::Text(m_list_errors.At(i)));
         //--- Для MQL4, так как в журнал выводится всё в обратном порядке, то сначала выводится список ошибок в обратном цикле, а затем заголовок списка
         #else    
         for(int i=total-1;i>WRONG_VALUE;i--)
            ::Print((total>1 ? string(i+1)+". " : ""),CMessage::Text(m_list_errors.At(i)));
         ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_REQUEST_REJECTED_DUE));
         #endif 
        }
     }
   return res;
  }
//+------------------------------------------------------------------+

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

Метод, устанавливающий флаг разрешения использования звуков для всех торговых объектов всех используемых символов:

//+------------------------------------------------------------------+
//| Устанавливает флаг разрешения использования звуков               |
//+------------------------------------------------------------------+
void CTrading::SetUseSounds(const bool flag)
  {
   //--- Устанавливаем флаг использования звуков
   this.m_use_sound=flag;
   //--- Получаем список символов
   CArrayObj *list=this.m_symbols.GetList();
   if(list==NULL || list.Total()==0)
      return;
   //--- В цикле по списку символов
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- получаем очередной объект-символ
      CSymbol *symbol_obj=list.At(i);
      if(symbol_obj==NULL)
         continue;
      //--- получаем торговый объект символа
      CTradeObj *trade_obj=symbol_obj.GetTradeObj();
      if(trade_obj==NULL)
         continue;
      //--- устанавливаем торговому объекту флаг использования звуков
      trade_obj.SetUseSound(flag);
     }
  }
//+------------------------------------------------------------------+

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

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

//--- Открывает позицию (1) Buy, (2) Sell
   template<typename SL,typename TP> 
   bool                 OpenBuy(const double volume,
                                const string symbol,
                                const ulong magic=ULONG_MAX,
                                const SL sl=0,
                                const TP tp=0,
                                const string comment=NULL,
                                const ulong deviation=ULONG_MAX);
   
   template<typename SL,typename TP> 
   bool                 OpenSell(const double volume,
                                 const string symbol,
                                 const ulong magic=ULONG_MAX,
                                 const SL sl=0,
                                 const TP tp=0,
                                 const string comment=NULL,
                                 const ulong deviation=ULONG_MAX);

//--- Модифицирует позицию
   template<typename SL,typename TP> 
   bool                 ModifyPosition(const ulong ticket,const SL sl=WRONG_VALUE,const TP tp=WRONG_VALUE);

//--- Закрывает позицию (1) полностью, (2) частично, (3) встречной
   bool                 ClosePosition(const ulong ticket,const string comment=NULL,const ulong deviation=ULONG_MAX);
   bool                 ClosePositionPartially(const ulong ticket,const double volume,const string comment=NULL,const ulong deviation=ULONG_MAX);
   bool                 ClosePositionBy(const ulong ticket,const ulong ticket_by);

//--- Устанавливает отложенный ордер (1) BuyStop, (2) BuyLimit, (3) BuyStopLimit
   template<typename PR,typename SL,typename TP>
   bool                 PlaceBuyStop(const double volume,
                                           const string symbol,
                                           const PR price,
                                           const SL sl=0,
                                           const TP tp=0,
                                           const ulong magic=ULONG_MAX,
                                           const string comment=NULL,
                                           const datetime expiration=0,
                                           const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
   template<typename PR,typename SL,typename TP>
   bool                 PlaceBuyLimit(const double volume,
                                           const string symbol,
                                           const PR price,
                                           const SL sl=0,
                                           const TP tp=0,
                                           const ulong magic=ULONG_MAX,
                                           const string comment=NULL,
                                           const datetime expiration=0,
                                           const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
   template<typename PR,typename PL,typename SL,typename TP>
   bool                 PlaceBuyStopLimit(const double volume,
                                           const string symbol,
                                           const PR price_stop,
                                           const PL price_limit,
                                           const SL sl=0,
                                           const TP tp=0,
                                           const ulong magic=ULONG_MAX,
                                           const string comment=NULL,
                                           const datetime expiration=0,
                                           const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);

//--- Устанавливает отложенный ордер (1) SellStop, (2) SellLimit, (3) SellStopLimit
   template<typename PR,typename SL,typename TP>
   bool                 PlaceSellStop(const double volume,
                                           const string symbol,
                                           const PR price,
                                           const SL sl=0,
                                           const TP tp=0,
                                           const ulong magic=ULONG_MAX,
                                           const string comment=NULL,
                                           const datetime expiration=0,
                                           const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
   template<typename PR,typename SL,typename TP>
   bool                 PlaceSellLimit(const double volume,
                                           const string symbol,
                                           const PR price,
                                           const SL sl=0,
                                           const TP tp=0,
                                           const ulong magic=ULONG_MAX,
                                           const string comment=NULL,
                                           const datetime expiration=0,
                                           const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
   template<typename PR,typename PL,typename SL,typename TP>
   bool                 PlaceSellStopLimit(const double volume,
                                           const string symbol,
                                           const PR price_stop,
                                           const PL price_limit,
                                           const SL sl=0,
                                           const TP tp=0,
                                           const ulong magic=ULONG_MAX,
                                           const string comment=NULL,
                                           const datetime expiration=0,
                                           const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
//--- Модифицирует отложенный ордер
   template<typename PR,typename PL,typename SL,typename TP>
   bool                 ModifyOrder(const ulong ticket,
                                          const PR price=WRONG_VALUE,
                                          const SL sl=WRONG_VALUE,
                                          const TP tp=WRONG_VALUE,
                                          const PL limit=WRONG_VALUE,
                                          datetime expiration=WRONG_VALUE,
                                          ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE);
//--- Удаляет отложенный ордер
   bool                 DeleteOrder(const ulong ticket);
  };

Рассмотрим реализацию метода открытия позиции Buy:

//+------------------------------------------------------------------+
//| Открывает позицию Buy                                            |
//+------------------------------------------------------------------+
template<typename SL,typename TP> 
bool CTrading::OpenBuy(const double volume,
                       const string symbol,
                       const ulong magic=ULONG_MAX,
                       const SL sl=0,
                       const TP tp=0,
                       const string comment=NULL,
                       const ulong deviation=ULONG_MAX)
  {
   ENUM_ACTION_TYPE action=ACTION_TYPE_BUY;
   ENUM_ORDER_TYPE order=ORDER_TYPE_BUY;
//--- Получаем объект-символ по имени символа
   CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol);
   if(symbol_obj==NULL)
     {
      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)
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
//--- Обновляем котировки по символу
   symbol_obj.RefreshRates();
//--- Устанавливаем цены
   if(!this.SetPrices(order,0,sl,tp,0,DFUN,symbol_obj))
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ));
      return false;
     }
//--- Если есть ограничения по разрешённости торговли, не хватает средств,
//--- есть ограничения по уровням StopLevel или FreezeLevel - воспроизводим звук ошибки и уходим
   if(!this.CheckErrors(volume,symbol_obj.Ask(),action,ORDER_TYPE_BUY,symbol_obj,DFUN,0,this.m_req_price.sl,this.m_req_price.tp))
     {
      if(this.IsUseSounds())
         trade_obj.PlaySoundError(action,order);
      return false;
     }

//--- Отсылаем запрос
   bool res=trade_obj.OpenPosition(POSITION_TYPE_BUY,volume,this.m_req_price.sl,this.m_req_price.tp,magic,comment,deviation);
//--- Если запрос выполнен успешно - проиграем звук успеха, установленный торговому объекту символа для данного типа торговой операции
   if(res)
     {
      if(this.IsUseSounds())
         trade_obj.PlaySoundSuccess(action,order);
     }
//--- Если запрос не выполнен - выведем сообщение и проиграем звук ошибки, установленный торговому объекту символа для данного типа торговой операции
   else
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode()));
      if(this.IsUseSounds())
         trade_obj.PlaySoundError(action,order);
     }
//--- Возвращаем результат отправки торгового запроса в торговом объекте символа
   return res;
  }
//+------------------------------------------------------------------+

В метод в качестве значений параметров StopLoss и TakeProfit можно передать либо double-значение, либо long-, ulong-, int-, uint-значение. Метод установки цен запишет в структуру цен m_req_price правильные расчитанные и нормализованные цены, и уже эти рассчитанные цены будут передаваться в торговый метод торгового объекта символа. Остальные действия расписаны в комментариях к коду, и думаю, не вызывают вопросов и недопониманий. В любом случае — всё можно обсудить в комментариях к статье.

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

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

В Datas.mqh была добавлена константа кода ошибки, которую в прошлой статье упустили и не добавили:

//--- CTrading
   MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED,           // В терминале нет разрешения на проведение торговых операций (отключена кнопка "Авто-торговля")
   MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED,                 // Для советника нет разрешения на проведение торговых операций (F7 --> Общие --> "Разрешить автоматическую торговлю")
   MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED,            // Для текущего счёта запрещена торговля
   MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED,         // Для советников на текущем счёте запрещена торговля на стороне торгового сервера
   MSG_LIB_TEXT_TERMINAL_NOT_CONNECTED,               // Нет связи с торговым сервером
   MSG_LIB_TEXT_REQUEST_REJECTED_DUE,                 // Запрос отклонён до отправки на сервер по причине:
   MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR,                 // Недостаточно средств для совершения торговой операции
   MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED,            // Превышен максимальный совокупный объём ордеров и позиций в одном направлении
   MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME,              // Объём в запросе меньше минимально-допустимого
   MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME,              // Объём в запросе больше максимально-допустимого
   MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED,             // Закрытие встречным запрещено
   MSG_LIB_TEXT_INVALID_VOLUME_STEP,                  // Объём в запросе не кратен минимальной градации шага изменения лота
   MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL,             // Символы встречных позиций не равны
   MSG_LIB_TEXT_SL_LESS_STOP_LEVEL,                   // Размер StopLoss в пунктах меньше разрешённого параметром StopLevel символа
   MSG_LIB_TEXT_TP_LESS_STOP_LEVEL,                   // Размер TakeProfit в пунктах меньше разрешённого параметром StopLevel символа
   MSG_LIB_TEXT_PR_LESS_STOP_LEVEL,                   // Дистанция установки ордера в пунктах меньше разрешённого параметром StopLevel символа
   MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL,                 // Дистанция от цены до StopLoss меньше разрешённой параметром FreezeLevel символа
   MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL,                 // Дистанция от цены до TakeProfit меньше разрешённой параметром FreezeLevel символа
   MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL,                 // Дистанция от цены до цены срабатывания ордера меньше разрешённой параметром FreezeLevel символа
   MSG_LIB_TEXT_UNSUPPORTED_SL_TYPE,                  // Неподдерживаемый тип параметра StopLoss (необходимо int или double)
   MSG_LIB_TEXT_UNSUPPORTED_TP_TYPE,                  // Неподдерживаемый тип параметра TakeProfit (необходимо int или double)
   MSG_LIB_TEXT_UNSUPPORTED_PR_TYPE,                  // Неподдерживаемый тип параметра цены (необходимо int или double)
   MSG_LIB_TEXT_UNSUPPORTED_PL_TYPE,                  // Неподдерживаемый тип параметра цены limit-ордера (необходимо int или double)
   MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ,        // Неподдерживаемый тип параметра цены в запросе
   
  };

И текст, соответствующий данному коду:

//--- CEngine
   {"С момента последнего запуска ЕА торговых событий не было","There have been no trade events since the last launch of EA"},
   {"Не удалось получить описание последнего торгового события","Failed to get the description of the last trading event"},
   {"Не удалось получить список открытых позиций","Failed to get open positions list"},
   {"Не удалось получить список установленных ордеров","Failed to get pending orders list"},
   {"Нет открытых позиций","No open positions"},
   {"Нет установленных ордеров","No placed orders"},
   {"В терминале нет разрешения на проведение торговых операций (отключена кнопка \"Авто-торговля\")","There is no permission to conduct trading operations in the terminal (the \"AutoTrading\" button is disabled)"},
   {"Для советника нет разрешения на проведение торговых операций (F7 --> Общие --> \"Разрешить автоматическую торговлю\")","EA does not have permission to conduct trading operations (F7 --> Common --> \"Allow Automatic Trading\")"},
   {"Для текущего счёта запрещена торговля","Trading is prohibited for the current account"},
   {"Для советников на текущем счёте запрещена торговля на стороне торгового сервера","From the side of the trading server, trading for EA on the current account is prohibited"},
   {"Нет связи с торговым сервером","No connection to the trading server"},
   {"Запрос отклонён до отправки на сервер по причине:","The request was rejected before being sent to the server due to:"},
   {"Недостаточно средств для совершения торговой операции","Not enough money to perform trading operation"},
   {"Превышен максимальный совокупный объём ордеров и позиций в одном направлении","Exceeded the maximum total volume of orders and positions in one direction"},
   {"Объём в запросе меньше минимально-допустимого","The volume in the request is less than the minimum allowable"},
   {"Объём в запросе больше максимально-допустимого","The volume in the request is greater than the maximum allowable"},
   {"Закрытие встречным запрещено","CloseBy orders is prohibited"},
   {"Объём в запросе не кратен минимальной градации шага изменения лота","The volume in the request is not a multiple of the minimum gradation of the step for changing the lot"},
   {"Символы встречных позиций не равны","Symbols of the two opposite positions are not equal"},
   {"Размер StopLoss в пунктах меньше разрешённого параметром StopLevel символа","The StopLoss size in points is less than that allowed by the StopLevel parameter of the symbol"},
   {"Размер TakeProfit в пунктах меньше разрешённого параметром StopLevel символа","The TakeProfit size in points is less than that allowed by the StopLevel parameter of the symbol"},
   {"Дистанция установки ордера в пунктах меньше разрешённой параметром StopLevel символа","The distance to place an order in points is less than the symbol allowed by the StopLevel parameter"},
   {"Дистанция от цены до StopLoss меньше разрешённой параметром FreezeLevel символа","The distance from the price to StopLoss is less than the symbol allowed by the FreezeLevel parameter"},
   {"Дистанция от цены до TakeProfit меньше разрешённой параметром FreezeLevel символа","The distance from the price to TakeProfit is less than the symbol allowed by the FreezeLevel parameter"},
   {"Дистанция от цены до цены срабатывания ордера меньше разрешённой параметром FreezeLevel символа","The distance from the price to the order triggering price is less than the symbol allowed by the FreezeLevel parameter"},
   {"Неподдерживаемый тип параметра StopLoss (необходимо int или double)","Unsupported StopLoss parameter type (int or double required)"},
   {"Неподдерживаемый тип параметра TakeProfit (необходимо int или double)","Unsupported TakeProfit parameter type (int or double required)"},
   {"Неподдерживаемый тип параметра цены (необходимо int или double)","Unsupported price parameter type (int or double required)"},
   {"Неподдерживаемый тип параметра цены limit-ордера (необходимо int или double)","Unsupported type of price parameter for limit order (int or double required)"},
   {"Неподдерживаемый тип параметра цены в запросе","Unsupported price parameter type in request"},
   
  };

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

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

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

Откроем файл коллекции событий EventsCollection.mqh и допишем необходимые доработки.

В конструкторе класса сбросим флаг последнего события:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CEventsCollection::CEventsCollection(void) : m_trade_event(TRADE_EVENT_NO_EVENT),m_trade_event_code(TRADE_EVENT_FLAG_NO_EVENT)
  {
   this.m_list_trade_events.Clear();
   this.m_list_trade_events.Sort(SORT_BY_EVENT_TIME_EVENT);
   this.m_list_trade_events.Type(COLLECTION_EVENTS_ID);
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_chart_id=::ChartID();
   this.m_is_event=false;
   ::ZeroMemory(this.m_tick);
  }
//+------------------------------------------------------------------+

В самом начале метода Refresh() также сбросим флаг последнего события:

//+------------------------------------------------------------------+
//| Обновляет список событий                                         |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                CArrayObj* list_changes,
                                CArrayObj* list_control,
                                const bool is_history_event,
                                const bool is_market_event,
                                const int  new_history_orders,
                                const int  new_market_pendings,
                                const int  new_market_positions,
                                const int  new_deals,
                                const double changed_volume)
  {
//--- Если списки пустые - выход
   if(list_history==NULL || list_market==NULL)
      return;
//---
   this.m_is_event=false;
//--- Если событие в рыночном окружении
   if(is_market_event)
     {

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

      //--- Если объекта-события нет в списке - добавляем
      if(!this.IsPresentEventInList(event))
        {
         this.m_list_trade_events.InsertSort(event);
         //--- Отправляем сообщение о событии и устанавливаем значение последнего торгового события
         event.SendEvent();
         this.m_trade_event=event.TradeEvent();
        }

допишем установку флага торгового события:

      if(!this.IsPresentEventInList(event))
        {
         this.m_list_trade_events.InsertSort(event);
         //--- Отправляем сообщение о событии и устанавливаем значение последнего торгового события
         this.m_trade_event=event.TradeEvent();
         this.m_is_event=true;
         event.SendEvent();
        }

Для удобства поиска всех мест, где необходимо установить флаг, можно воспользоваться поиском Ctrl+F. В строку поиска ввести "event.SendEvent();" без кавычек. И в каждое найденное место в коде дописать установку флага события как показано в листинге выше.

В файле Engine.mqh также необходимо дописать некоторые изменения.

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

//--- Возвращает флаг (1) счёта-хедж, (2) работы в тестере, (3) события аккаунта, (4) события символа, (5) торгового события
   bool                 IsHedge(void)                             const { return this.m_is_hedge;                             }
   bool                 IsTester(void)                            const { return this.m_is_tester;                            }
   bool                 IsAccountsEvent(void)                     const { return this.m_accounts.IsEvent();                   }
   bool                 IsSymbolsEvent(void)                      const { return this.m_symbols.IsEvent();                    }
   bool                 IsTradeEvent(void)                        const { return this.m_events.IsEvent();                     }

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

//--- Устанавливает стандартные звуки (symbol==NULL) торговому объекту символа, (symbol!=NULL) торговым объектам всех символов
   void                 SetSoundsStandart(const string symbol=NULL)
                          {
                           this.m_trading.SetSoundsStandart(symbol);
                          }
//--- Устанавливает флаг использования звуков
   void                 SetUseSounds(const bool flag) { this.m_trading.SetUseSounds(flag);   }
//--- Устанавливает звук для указанного типа ордера/позиции и символа. Режим mode указывает для какого события устанавливается звук
//--- (symbol=NULL) торговым объектам всех символов, (symbol!=NULL) торговому объекту указанного символа
   void                 SetSound(const ENUM_MODE_SET_SOUND mode,const ENUM_ORDER_TYPE action,const string sound,const string symbol=NULL)
                          {
                           this.m_trading.SetSound(mode,action,sound,symbol);
                          }
//--- Воспроизводит звук по его описанию
   bool                 PlaySoundByDescription(const string sound_description);

Метод просто вызывает одноимённый метод класса CTrading, рассмотренный нами выше.

В начале метода проверки торговых событий сбросим флаг торгового события:

//+------------------------------------------------------------------+
//| Проверка торговых событий                                        |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Инициализация флагов торговых событий
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
   this.m_events.SetEvent(false);
//--- Обновление списков 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- Действия при первом запуске
   if(this.IsFirstStart())
     {
      this.m_last_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Проверка изменений в рыночном состоянии и в истории счёта 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- Если есть любое событие, отправляем списки, флаги и количество новых ордеров и сделок в коллекцию событий и обновляем коллекцию событий
   int change_total=0;
   CArrayObj* list_changes=this.m_market.GetListChanges();
   if(list_changes!=NULL)
      change_total=list_changes.Total();
   if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes,this.m_market.GetListControl(),
                            this.m_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewPositions(),this.m_history.NewDeals(),
                            this.m_market.ChangedVolumeValue());
      //--- Получаем последнее торговое событие на счёте
      this.m_last_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+

Методы работы с торговым классом тоже претерпели изменения — теперь они тоже имеют шаблонные параметры:

//--- Открывает позицию (1) Buy, (2) Sell
   template<typename SL,typename TP>
   bool                 OpenBuy(const double volume,
                                const string symbol,
                                const ulong magic=ULONG_MAX,
                                SL sl=0,
                                TP tp=0,
                                const string comment=NULL,
                                const ulong deviation=ULONG_MAX);
   template<typename SL,typename TP>
   bool                 OpenSell(const double volume,
                                 const string symbol,
                                 const ulong magic=ULONG_MAX,
                                 SL sl=0,
                                 TP tp=0,
                                 const string comment=NULL,
                                 const ulong deviation=ULONG_MAX);
//--- Модифицирует позицию
   template<typename SL,typename TP>
   bool                 ModifyPosition(const ulong ticket,const SL sl=WRONG_VALUE,const TP tp=WRONG_VALUE);
//--- Закрывает позицию (1) полностью, (2) частично, (3) встречной
   bool                 ClosePosition(const ulong ticket,const string comment=NULL,const ulong deviation=ULONG_MAX);
   bool                 ClosePositionPartially(const ulong ticket,const double volume,const string comment=NULL,const ulong deviation=ULONG_MAX);
   bool                 ClosePositionBy(const ulong ticket,const ulong ticket_by);
//--- Устанавливает отложенный ордер (1) BuyStop, (2) BuyLimit, (3) BuyStopLimit
   template<typename PR,typename SL,typename TP>
   bool                 PlaceBuyStop(const double volume,
                                     const string symbol,
                                     const PR price,
                                     const SL sl=0,
                                     const TP tp=0,
                                     const ulong magic=ULONG_MAX,
                                     const string comment=NULL,
                                     const datetime expiration=0,
                                     const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
   template<typename PR,typename SL,typename TP>
   bool                 PlaceBuyLimit(const double volume,
                                     const string symbol,
                                     const PR price,
                                     const SL sl=0,
                                     const TP tp=0,
                                     const ulong magic=ULONG_MAX,
                                     const string comment=NULL,
                                     const datetime expiration=0,
                                     const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
   template<typename PR,typename PL,typename SL,typename TP>
   bool                 PlaceBuyStopLimit(const double volume,
                                     const string symbol,
                                     const PR price_stop,
                                     const PL price_limit,
                                     const SL sl=0,
                                     const TP tp=0,
                                     const ulong magic=ULONG_MAX,
                                     const string comment=NULL,
                                     const datetime expiration=0,
                                     const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
//--- Устанавливает отложенный ордер (1) SellStop, (2) SellLimit, (3) SellStopLimit
   template<typename PR,typename SL,typename TP>
   bool                 PlaceSellStop(const double volume,
                                     const string symbol,
                                     const PR price,
                                     const SL sl=0,
                                     const TP tp=0,
                                     const ulong magic=ULONG_MAX,
                                     const string comment=NULL,
                                     const datetime expiration=0,
                                     const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
   template<typename PR,typename SL,typename TP>
   bool                 PlaceSellLimit(const double volume,
                                     const string symbol,
                                     const PR price,
                                     const SL sl=0,
                                     const TP tp=0,
                                     const ulong magic=ULONG_MAX,
                                     const string comment=NULL,
                                     const datetime expiration=0,
                                     const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
   template<typename PR,typename PL,typename SL,typename TP>
   bool                 PlaceSellStopLimit(const double volume,
                                     const string symbol,
                                     const PR price_stop,
                                     const PL price_limit,
                                     const SL sl=0,
                                     const TP tp=0,
                                     const ulong magic=ULONG_MAX,
                                     const string comment=NULL,
                                     const datetime expiration=0,
                                     const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
//--- Модифицирует отложенный ордер
   template<typename PR,typename SL,typename TP,typename PL>
   bool                 ModifyOrder(const ulong ticket,
                                    const PR price=WRONG_VALUE,
                                    const SL sl=WRONG_VALUE,
                                    const TP tp=WRONG_VALUE,
                                    const PL stoplimit=WRONG_VALUE,
                                    datetime expiration=WRONG_VALUE,
                                    ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE);
//--- Удаляет отложенный ордер
   bool                 DeleteOrder(const ulong ticket);

И реализация торговых методов так же была изменена в соответствии с шаблонными данными, передаваемыми в торговые методы класса CTrading:

//+------------------------------------------------------------------+
//| Открывает позицию Buy                                            |
//+------------------------------------------------------------------+
template<typename SL,typename TP>
bool CEngine::OpenBuy(const double volume,const string symbol,const ulong magic=ULONG_MAX,SL sl=0,TP tp=0,const string comment=NULL,const ulong deviation=ULONG_MAX)
  {
   return this.m_trading.OpenBuy(volume,symbol,magic,sl,tp,comment,deviation);
  }
//+------------------------------------------------------------------+
//| Открывает позицию Sell                                           |
//+------------------------------------------------------------------+
template<typename SL,typename TP>
bool CEngine::OpenSell(const double volume,const string symbol,const ulong magic=ULONG_MAX,SL sl=0,TP tp=0,const string comment=NULL,const ulong deviation=ULONG_MAX)
  {
   return this.m_trading.OpenSell(volume,symbol,magic,sl,tp,comment,deviation);
  }
//+------------------------------------------------------------------+
//| Модифицирует позицию                                             |
//+------------------------------------------------------------------+
template<typename SL,typename TP>
bool CEngine::ModifyPosition(const ulong ticket,const SL sl=WRONG_VALUE,const TP tp=WRONG_VALUE)
  {
   return this.m_trading.ModifyPosition(ticket,sl,tp);
  }
//+------------------------------------------------------------------+
//| Закрывает позицию полностью                                      |
//+------------------------------------------------------------------+
bool CEngine::ClosePosition(const ulong ticket,const string comment=NULL,const ulong deviation=ULONG_MAX)
  {
   return this.m_trading.ClosePosition(ticket,comment,deviation);
  }
//+------------------------------------------------------------------+
//| Закрывает позицию частично                                       |
//+------------------------------------------------------------------+
bool CEngine::ClosePositionPartially(const ulong ticket,const double volume,const string comment=NULL,const ulong deviation=ULONG_MAX)
  {
   return this.m_trading.ClosePositionPartially(ticket,volume,comment,deviation);
  }
//+------------------------------------------------------------------+
//| Закрывает позицию встречной                                      |
//+------------------------------------------------------------------+
bool CEngine::ClosePositionBy(const ulong ticket,const ulong ticket_by)
  {
   return this.m_trading.ClosePositionBy(ticket,ticket_by);
  }
//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер BuyStop                           |
//+------------------------------------------------------------------+
template<typename PR,typename SL,typename TP>
bool CEngine::PlaceBuyStop(const double volume,
                           const string symbol,
                           const PR price,
                           const SL sl=0,
                           const TP tp=0,
                           const ulong magic=WRONG_VALUE,
                           const string comment=NULL,
                           const datetime expiration=0,
                           const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC)
  {
   return this.m_trading.PlaceBuyStop(volume,symbol,price,sl,tp,magic,comment,expiration,type_time);
  }
//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер BuyLimit                          |
//+------------------------------------------------------------------+
template<typename PR,typename SL,typename TP>
bool CEngine::PlaceBuyLimit(const double volume,
                            const string symbol,
                            const PR price,
                            const SL sl=0,
                            const TP tp=0,
                            const ulong magic=WRONG_VALUE,
                            const string comment=NULL,
                            const datetime expiration=0,
                            const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC)
  {
   return this.m_trading.PlaceBuyLimit(volume,symbol,price,sl,tp,magic,comment,expiration,type_time);
  }
//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер BuyStopLimit                      |
//+------------------------------------------------------------------+
template<typename PR,typename PL,typename SL,typename TP>
bool CEngine::PlaceBuyStopLimit(const double volume,
                                const string symbol,
                                const PR price_stop,
                                const PL price_limit,
                                const SL sl=0,
                                const TP tp=0,
                                const ulong magic=WRONG_VALUE,
                                const string comment=NULL,
                                const datetime expiration=0,
                                const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC)
  {
   return this.m_trading.PlaceBuyStopLimit(volume,symbol,price_stop,price_limit,sl,tp,magic,comment,expiration,type_time);
  }
//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер SellStop                          |
//+------------------------------------------------------------------+
template<typename PR,typename SL,typename TP>
bool CEngine::PlaceSellStop(const double volume,
                            const string symbol,
                            const PR price,
                            const SL sl=0,
                            const TP tp=0,
                            const ulong magic=WRONG_VALUE,
                            const string comment=NULL,
                            const datetime expiration=0,
                            const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC)
  {
   return this.m_trading.PlaceSellStop(volume,symbol,price,sl,tp,magic,comment,expiration,type_time);
  }
//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер SellLimit                         |
//+------------------------------------------------------------------+
template<typename PR,typename SL,typename TP>
bool CEngine::PlaceSellLimit(const double volume,
                             const string symbol,
                             const PR price,
                             const SL sl=0,
                             const TP tp=0,
                             const ulong magic=WRONG_VALUE,
                             const string comment=NULL,
                             const datetime expiration=0,
                             const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC)
  {
   return this.m_trading.PlaceSellLimit(volume,symbol,price,sl,tp,magic,comment,expiration,type_time);
  }
//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер SellStopLimit                     |
//+------------------------------------------------------------------+
template<typename PR,typename PL,typename SL,typename TP>
bool CEngine::PlaceSellStopLimit(const double volume,
                                 const string symbol,
                                 const PR price_stop,
                                 const PL price_limit,
                                 const SL sl=0,
                                 const TP tp=0,
                                 const ulong magic=WRONG_VALUE,
                                 const string comment=NULL,
                                 const datetime expiration=0,
                                 const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC)
  {
   return this.m_trading.PlaceSellStopLimit(volume,symbol,price_stop,price_limit,sl,tp,magic,comment,expiration,type_time);
  }
//+------------------------------------------------------------------+
//| Модифицирует отложенный ордер                                    |
//+------------------------------------------------------------------+
template<typename PR,typename SL,typename TP,typename PL>
bool CEngine::ModifyOrder(const ulong ticket,
                          const PR price=WRONG_VALUE,
                          const SL sl=WRONG_VALUE,
                          const TP tp=WRONG_VALUE,
                          const PL stoplimit=WRONG_VALUE,
                          datetime expiration=WRONG_VALUE,
                          ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE)
  {
   return this.m_trading.ModifyOrder(ticket,price,sl,tp,stoplimit,expiration,type_time);
  }
//+------------------------------------------------------------------+
//| Удаляет отложенный ордер                                         |
//+------------------------------------------------------------------+
bool CEngine::DeleteOrder(const ulong ticket)
  {
   return this.m_trading.DeleteOrder(ticket);
  }
//+------------------------------------------------------------------+

На этом доработка торговых классов и корректировка получения торговых событий завершены.

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

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

Здесь мы немного наведём порядок — все действия по инициализации библиотеки перенесём в отдельную функцию OnInitDoEasy():

//+------------------------------------------------------------------+
//| Инициализация библиотеки DoEasy                                  |
//+------------------------------------------------------------------+
void OnInitDoEasy()
  {
//--- Проверка на выбор работы с полным списком
   used_symbols_mode=InpModeUsedSymbols;
   if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL)
     {
      int total=SymbolsTotal(false);
      string ru_n="\nКоличество символов на сервере "+(string)total+".\nМаксимальное количество: "+(string)SYMBOLS_COMMON_TOTAL+" символов.";
      string en_n="\nThe number of symbols on server "+(string)total+".\nMaximal number: "+(string)SYMBOLS_COMMON_TOTAL+" symbols.";
      string caption=TextByLanguage("Внимание!","Attention!");
      string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списка коллекции символов может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\"";
      string en="Full list mode selected.\nIn this mode, the initial preparation of the collection symbols list may take a long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\"";
      string message=TextByLanguage(ru,en);
      int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2);
      int mb_res=MessageBox(message,caption,flags);
      switch(mb_res)
        {
         case IDNO : 
           used_symbols_mode=SYMBOLS_MODE_CURRENT; 
           break;
         default:
           break;
        }
     }
//--- Заполнение массива используемых символов
   used_symbols=InpUsedSymbols;
   CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,used_symbols,array_used_symbols);

//--- Установка типа используемого списка символов в коллекции символов
   engine.SetUsedSymbols(array_used_symbols);
//--- Отображение выбранного режима работы с коллекцией объектов-символов
   Print(engine.ModeSymbolsListDescription(),TextByLanguage(". Количество используемых символов: ",". The number of symbols used: "),engine.GetSymbolsCollectionTotal());
   
//--- Создание тестовых файлов ресурсов
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","The sound of a falling coin 1"),sound_array_coin_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Sound fallen coins"),sound_array_coin_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Sound of coins"),sound_array_coin_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","The sound of a falling coin 2"),sound_array_coin_04);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Click on the button sound 1"),sound_array_click_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Click on the button sound 1"),sound_array_click_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Click on the button sound 1"),sound_array_click_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","The sound of the cash machine"),sound_array_cash_machine_01);
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green);
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red);

//--- Передача в торговый класс всех имеющихся коллекций
   engine.TradingOnInit();
//--- Установка синхронной передачи приказов для всех используемых символов
   engine.TradingSetAsyncMode(false);
//--- Установка стандартных звуков торговым объектам всех используемых символов
   engine.SetSoundsStandart();
//--- Установка общего флага использования звуков
   engine.SetUseSounds(InpUseSounds);
      
//--- Установка контрольных значений для символов
   //--- Получаем список всех символов коллекции
   CArrayObj *list=engine.GetListAllUsedSymbols();
   if(list!=NULL && list.Total()!=0)
     {
      //--- В цикле по списку устанавливаем нужные значения для отслеживаемых свойств символов
      //--- По умолчанию всем свойствам установлены значения LONG_MAX, что означает "Не отслеживать данное свойство" 
      //--- Включить или выключить (задать величину меньше LONG_MAX или наоборот - установить значение LONG_MAX) можно в любое время в любом месте программы
      for(int i=0;i<list.Total();i++)
        {
         CSymbol* symbol=list.At(i);
         if(symbol==NULL)
            continue;
         //--- Установка контроля увеличения цены символа на 100 пунктов
         symbol.SetControlBidInc(100*symbol.Point());
         //--- Установка контроля уменьшения цены символа на 100 пунктов
         symbol.SetControlBidDec(100*symbol.Point());
         //--- Установка контроля увеличения спреда символа на 40 пунктов
         symbol.SetControlSpreadInc(40);
         //--- Установка контроля уменьшения спреда символа на 40 пунктов
         symbol.SetControlSpreadDec(40);
         //--- Установка контроля размера спреда по значению 40 пунктов
         symbol.SetControlSpreadLevel(40);
        }
     }
//--- Установка контрольных значений для текущего аккаунта
   CAccount* account=engine.GetAccountCurrent();
   if(account!=NULL)
     {
      //--- Установка контроля увеличения значения прибыли на 10
      account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0);
      //--- Установка контроля увеличения значения средств на 15
      account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0);
      //--- Установка контрольного уровня прибыли на 20
      account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0);
     }
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Вызов данной функции выводит в журнал список констант перечисления, 
//--- заданного в файле DELib.mqh в строках 22 и 25, для проверки корректности констант
   //EnumNumbersTest();

//--- Установка глобальных переменных советника
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
   trailing_stop=InpTrailingStop*Point();
   trailing_step=InpTrailingStep*Point();
   trailing_start=InpTrailingStart;
   stoploss_to_modify=InpStopLossModify;
   takeprofit_to_modify=InpTakeProfitModify;

//--- Инициализация библиотеки DoEasy
   OnInitDoEasy();
   
//--- Проверка и удаление неудалённых графических объектов советника
   if(IsPresentObects(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Создание панели кнопок
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- Установка состояния кнопки активизации трейлингов
   ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on);

//--- Проверка воспроизведения стандартного звука по макроподстановке и пользовательского звука по описанию
   engine.PlaySoundByDescription(SND_OK);
   Sleep(600);
   engine.PlaySoundByDescription(TextByLanguage("Звук упавшей монетки 2","The sound of a falling coin 2"));

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Обработка событий библиотеки DoEasy                              |
//+------------------------------------------------------------------+
void OnDoEasyEvent(const int id,
                   const long &lparam,
                   const double &dparam,
                   const string &sparam)
  {
   int idx=id-CHARTEVENT_CUSTOM;
   string event="::"+string(idx);
   
//--- Извлекаем из lparam (1) милисекунды времени события, (2) причину, (3) источник события и (4) устанавливаем точное время события
   ushort msc=engine.EventMSC(lparam);
   ushort reason=engine.EventReason(lparam);
   ushort source=engine.EventSource(lparam);
   long time=TimeCurrent()*1000+msc;
   
//--- Обработка событий символов
   if(source==COLLECTION_SYMBOLS_ID)
     {
      CSymbol *symbol=engine.GetSymbolObjByName(sparam);
      if(symbol==NULL)
         return;
      //--- Количество знаков после запятой в значении события - если long-событие, то 0, иначе - Digits() символа
      int digits=(idx<SYMBOL_PROP_INTEGER_TOTAL ? 0 : symbol.Digits());
      //--- Текстовое описание события
      string id_descr=(idx<SYMBOL_PROP_INTEGER_TOTAL ? symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_INTEGER)idx) : symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_DOUBLE)idx));
      //--- Текстовое значение величины изменения свойства
      string value=DoubleToString(dparam,digits);
      
      //--- Проверка причин события и просто вывод в журнал его описания
      if(reason==BASE_EVENT_REASON_INC)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_DEC)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_MORE_THEN)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_LESS_THEN)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_EQUALS)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
     }   
     
//--- Обработка событий аккаунта
   else if(source==COLLECTION_ACCOUNT_ID)
     {
      CAccount *account=engine.GetAccountCurrent();
      if(account==NULL)
         return;
      //--- Количество знаков после запятой в значении события - если long-событие, то 0, иначе - Digits() символа
      int digits=int(idx<ACCOUNT_PROP_INTEGER_TOTAL ? 0 : account.CurrencyDigits());
      //--- Текстовое описание события
      string id_descr=(idx<ACCOUNT_PROP_INTEGER_TOTAL ? account.GetPropertyDescription((ENUM_ACCOUNT_PROP_INTEGER)idx) : account.GetPropertyDescription((ENUM_ACCOUNT_PROP_DOUBLE)idx));
      //--- Текстовое значение величины изменения свойства
      string value=DoubleToString(dparam,digits);
      
      //--- Проверка причин события и обработка увеличения средств на заданную величину,
      
      //--- Если это увеличение значения свойства
      if(reason==BASE_EVENT_REASON_INC)
        {
         //--- Распечатаем событие в журнал
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
         //--- если это увеличение средств
         if(idx==ACCOUNT_PROP_EQUITY)
           {
            //--- Получаем список всех открытых позиций
            CArrayObj* list_positions=engine.GetListMarketPosition();
            //--- Выбираем позиции с прибылью болше нуля
            list_positions=CSelect::ByOrderProperty(list_positions,ORDER_PROP_PROFIT_FULL,0,MORE);
            if(list_positions!=NULL)
              {
               //--- Сортируем список по прибыли с учётом комиссии и свопа
               list_positions.Sort(SORT_BY_ORDER_PROFIT_FULL);
               //--- Получаем индекс позиции с наибольшей прибылью
               int index=CSelect::FindOrderMax(list_positions,ORDER_PROP_PROFIT_FULL);
               if(index>WRONG_VALUE)
                 {
                  COrder* position=list_positions.At(index);
                  if(position!=NULL)
                    {
                     //--- Получаем тикет позиции с наибольшей прибылью и закрываем позицию по тикету
                     engine.ClosePosition(position.Ticket());
                    }
                 }
              }
           }
        }
      //--- Остальные события просто выводим в журнал
      if(reason==BASE_EVENT_REASON_DEC)
        {
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_MORE_THEN)
        {
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_LESS_THEN)
        {
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_EQUALS)
        {
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
     } 
     
//--- Обработка событий окна обзор рынка
   else if(idx>MARKET_WATCH_EVENT_NO_EVENT && idx<SYMBOL_EVENTS_NEXT_CODE)
     {
      //--- Событие окна "Обзор рынка"
      string descr=engine.GetMWEventDescription((ENUM_MW_EVENT)idx);
      string name=(idx==MARKET_WATCH_EVENT_SYMBOL_SORT ? "" : ": "+sparam);
      Print(TimeMSCtoString(lparam)," ",descr,name);
     }
     
//--- Обработка торговых событий
   else if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE)
     {
      Print(DFUN,engine.GetLastTradeEventDescription());
     }
  }
//+------------------------------------------------------------------+

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

Хочу отметить, что функция OnDoEasyEvent() вызывается из обработчика советника OnChartEvent() при работе не в тестере.
А при работе в тестере из OnTick() вызывается функция EventsHandling():

//+------------------------------------------------------------------+
//| Работа с событиями в тестере                                     |
//+------------------------------------------------------------------+
void EventsHandling(void)
  {
//--- Если есть торговое событие
   if(engine.IsTradeEvent())
     {
      long lparam=0;
      double dparam=0;
      string sparam="";
      OnDoEasyEvent(CHARTEVENT_CUSTOM+engine.LastTradeEvent(),lparam,dparam,sparam);
     }
//--- Если есть событие аккаунта
   if(engine.IsAccountsEvent())
     {
      //--- Получим список всех событий аккаунта, произошедших одновременно
      CArrayObj* list=engine.GetListAccountEvents();
      if(list!=NULL)
        {
         //--- В цикле получаем очередное событие
         int total=list.Total();
         for(int i=0;i<total;i++)
           {
            //--- берём событие из списка
            CEventBaseObj *event=list.At(i);
            if(event==NULL)
               continue;
            //--- Отправляем событие в обработчик событий
            long lparam=event.LParam();
            double dparam=event.DParam();
            string sparam=event.SParam();
            OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
           }
        }
     }
//--- Если есть событие коллекции символов
   if(engine.IsSymbolsEvent())
     {
      //--- Получим список всех событий символов, произошедших одновременно
      CArrayObj* list=engine.GetListSymbolsEvents();
      if(list!=NULL)
        {
         //--- В цикле получаем очередное событие
         int total=list.Total();
         for(int i=0;i<total;i++)
           {
            //--- берём событие из списка
            CEventBaseObj *event=list.At(i);
            if(event==NULL)
               continue;
            //--- Отправляем событие в обработчик событий
            long lparam=event.LParam();
            double dparam=event.DParam();
            string sparam=event.SParam();

            OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
           }
        }
     }
  }
//+------------------------------------------------------------------+

В функции просматриваются списки соответствующих событий, заполняются параметры события, которое затем отправляется в функцию OnDoEasyEvent(). Таким образом у нас организована работа с событиями в функции OnDoEasyEvent() независимо от того где запущен советник — в тестере или нет. Если в тестере, то работа с событиями происходит из OnTick(), если не в тестере — то работа с событиями происходит из OnChartEvent().

Таким образом, функция OnTick() теперь приняла такой вид:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Если работа в тестере
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer();       // Работа в таймере
      PressButtonsControl();  // Контроль нажатия кнопок
      EventsHandling();       // Работа с событиями
     }
//--- Если установлен флаг трейлинга
   if(trailing_on)
     {
      TrailingPositions();    // Трейлинг позиций
      TrailingOrders();       // Трейлинг отложенных ордеров
     }
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Обработка нажатий кнопок                                         |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   string comment="";
   //--- Преобразуем имя кнопки в её строковый идентификатор
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- Если кнопка в нажатом состоянии
   if(ButtonState(button_name))
     {
      //--- Если нажата кнопка BUTT_BUY: Открыть позицию Buy
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня StopLevel
         double sl=stoploss;//CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=takeprofit;//CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- Открываем позицию Buy
         engine.OpenBuy(lot,Symbol(),magic_number,sl,tp);   // Нет комментария - будет установлен комментарий по умолчанию
        }
      //--- Если нажата кнопка BUTT_BUY_LIMIT: Выставить BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Получаем корректную цену установки ордера относительно уровня StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=stoploss;//CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
         double tp=takeprofit;//CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
         //--- Устанавливаем ордер BuyLimit
         engine.PlaceBuyLimit(lot,Symbol(),price_set,sl,tp,magic_number,TextByLanguage("Отложенный BuyLimit","Pending order BuyLimit"));
        }
      //--- Если нажата кнопка BUTT_BUY_STOP: Выставить BuyStop
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- Получаем корректную цену установки ордера относительно уровня StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit);
         //--- Устанавливаем ордер BuyStop
         engine.PlaceBuyStop(lot,Symbol(),price_set,sl,tp,magic_number,TextByLanguage("Отложенный BuyStop","Pending order BuyStop"));
        }
      //--- Если нажата кнопка BUTT_BUY_STOP_LIMIT: Выставить BuyStopLimit
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- Получаем корректную цену установки ордера BuyStop относительно уровня StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Рассчитываем цену установки ордера BuyLimit относительно уровня установки BuyStop с учётом уровня StopLevel
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=stoploss;//CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss);
         double tp=takeprofit;//CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit);
         //--- Устанавливаем ордер BuyStopLimit
         engine.PlaceBuyStopLimit(lot,Symbol(),price_set_stop,price_set_limit,sl,tp,magic_number,TextByLanguage("Отложенный BuyStopLimit","Pending order BuyStopLimit"));
        }
      //--- Если нажата кнопка BUTT_SELL: Открыть позицию Sell
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня StopLevel
         double sl=stoploss;//CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss);
         double tp=takeprofit;//CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit);
         //--- Открываем позицию Sell
         engine.OpenSell(lot,Symbol(),magic_number,sl,tp);  // Нет комментария - будет установлен комментарий по умолчанию
        }
      //--- Если нажата кнопка BUTT_SELL_LIMIT: Выставить SellLimit
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- Получаем корректную цену установки ордера относительно уровня StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=stoploss;//CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss);
         double tp=takeprofit;//CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit);
         //--- Устанавливаем ордер SellLimit
         engine.PlaceSellLimit(lot,Symbol(),price_set,sl,tp,magic_number,TextByLanguage("Отложенный SellLimit","Pending order SellLimit"));
        }
      //--- Если нажата кнопка BUTT_SELL_STOP: Выставить SellStop
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- Получаем корректную цену установки ордера относительно уровня StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=stoploss;//CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss);
         double tp=takeprofit;//CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit);
         //--- Устанавливаем ордер SellStop
         engine.PlaceSellStop(lot,Symbol(),price_set,sl,tp,magic_number,TextByLanguage("Отложенный SellStop","Pending order SellStop"));
        }
      //--- Если нажата кнопка BUTT_SELL_STOP_LIMIT: Выставить SellStopLimit
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- Получаем корректную цену установки ордера SellStop относительно уровня StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Рассчитываем цену установки ордера SellLimit относительно уровня установки SellStop с учётом уровня StopLevel
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=stoploss;//CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss);
         double tp=takeprofit;//CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit);
         //--- Устанавливаем ордер SellStopLimit
         engine.PlaceSellStopLimit(lot,Symbol(),price_set_stop,price_set_limit,sl,tp,magic_number,TextByLanguage("Отложенный SellStopLimit","Pending order SellStopLimit"));
        }
      //--- Если нажата кнопка BUTT_CLOSE_BUY: Закрыть Buy с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_BUY))
        {

По сути, нам нужно оставить только вызов торговых методов с теми параметрами, которые заданы в настройках советника:

//+------------------------------------------------------------------+
//| Обработка нажатий кнопок                                         |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   string comment="";
   //--- Преобразуем имя кнопки в её строковый идентификатор
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- Если кнопка в нажатом состоянии
   if(ButtonState(button_name))
     {
      //--- Если нажата кнопка BUTT_BUY: Открыть позицию Buy
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Открываем позицию Buy
         engine.OpenBuy(lot,Symbol(),magic_number,stoploss,takeprofit);   // Нет комментария - будет установлен комментарий по умолчанию
        }
      //--- Если нажата кнопка BUTT_BUY_LIMIT: Выставить BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Устанавливаем ордер BuyLimit
         engine.PlaceBuyLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic_number,TextByLanguage("Отложенный BuyLimit","Pending order BuyLimit"));
        }
      //--- Если нажата кнопка BUTT_BUY_STOP: Выставить BuyStop
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- Устанавливаем ордер BuyStop
         engine.PlaceBuyStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic_number,TextByLanguage("Отложенный BuyStop","Pending order BuyStop"));
        }
      //--- Если нажата кнопка BUTT_BUY_STOP_LIMIT: Выставить BuyStopLimit
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- Устанавливаем ордер BuyStopLimit
         engine.PlaceBuyStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic_number,TextByLanguage("Отложенный BuyStopLimit","Pending order BuyStopLimit"));
        }
      //--- Если нажата кнопка BUTT_SELL: Открыть позицию Sell
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- Открываем позицию Sell
         engine.OpenSell(lot,Symbol(),magic_number,stoploss,takeprofit);  // Нет комментария - будет установлен комментарий по умолчанию
        }
      //--- Если нажата кнопка BUTT_SELL_LIMIT: Выставить SellLimit
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- Устанавливаем ордер SellLimit
         engine.PlaceSellLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic_number,TextByLanguage("Отложенный SellLimit","Pending order SellLimit"));
        }
      //--- Если нажата кнопка BUTT_SELL_STOP: Выставить SellStop
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- Устанавливаем ордер SellStop
         engine.PlaceSellStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic_number,TextByLanguage("Отложенный SellStop","Pending order SellStop"));
        }
      //--- Если нажата кнопка BUTT_SELL_STOP_LIMIT: Выставить SellStopLimit
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- Устанавливаем ордер SellStopLimit
         engine.PlaceSellStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic_number,TextByLanguage("Отложенный SellStopLimit","Pending order SellStopLimit"));
        }
      //--- Если нажата кнопка BUTT_CLOSE_BUY: Закрыть Buy с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_BUY))
        {

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

Скомпилируем советник и запустим его в тестере, предварительно установив в параметрах значение Lots, равным 10,
а для значений StopLoss in points и TakeProfit in points зададим значения по 1 пункту:


Таким образом мы попробуем открыть позицию с недопустимым размеров лота — чтобы не хватало средств на её открытие, и попробуем нарушить требования по минимальной дистанции установки стоп-приказов, регламентируемой параметром StopLevel символа:


Советник нам вывел в журнал две ошибки — "не хватает средств на выполнение торговой операции", и "значение StopLoss нарушает требования по параметру StopLevel символа". Но мы же задали и для TakeProfit значение в один пункт. Почему же он нам не сказал об этой ошибке? Да потому, что тут нет ошибки, так как установка уровней TakeProfit и StopLoss в пределах минимального уровня SYMBOL_TRADE_STOPS_LEVEL производится по правилу

уровни TakeProfit и StopLoss необходимо сравнивать с текущей ценой, по которой можно совершить операцию противоположного направления

  • Покупка совершается по цене Ask — уровни TakeProfit и StopLoss нужно сравнивать с текущей ценой продажи Bid.
  • Продажа совершается по цене Bid — уровни TakeProfit и StopLoss нужно сравнивать с текущей ценой покупки Ask.
Покупка происходит по цене Ask
Продажа происходит по цене Bid
TakeProfit >= Bid
StopLoss <= Bid
TakeProfit <= Ask
StopLoss >= Ask

Так как мы нажали кнопку открытия позиции Buy, то цена закрытия для неё Bid, а цена открытия — Ask. Уровни стоп-приказов мы откладываем от цены открытия — в данном случае от Ask. Таким образом, TakeProfit у нас попал на уровень Ask+1 пункт, а StopLoss — на уровень Ask-1 пункт. Отсюда следует, что TakeProfit оказался выше цены Bid, от которой мы отсчитываем разрешённую дистанцию, а вот StopLoss был бы в пределах требований только при нулевом спреде. А так как спред был в районе десяти пунктов на момент открытия, то естественно, мы попали под ограничение требований по минимальной дистанции для StopLoss.

Что дальше

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

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

К содержанию

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

Часть 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. Торговые классы - Основной торговый класс, контроль ограничений

Прикрепленные файлы |
MQL5.zip (3607.82 KB)
MQL4.zip (3607.82 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (30)
Artyom Trishkin
Artyom Trishkin | 27 окт 2019 в 21:42
Алексей Тарабанов:
Никто не обратил внимание на то, что тип ордера StopLimit?

Да, я не обратил внимания. А для него есть что-то иное? По сути же стоп-лимитный ордер это стоповый ордер, при срабатывании которого выставляется лимитный. Для этой связки разве не те же условия, что и для раздельного использования стоп- и лимит-ордеров?

Алексей Тарабанов
Алексей Тарабанов | 27 окт 2019 в 21:48
Artyom Trishkin:

Да, я не обратил внимания. А для него есть что-то иное? По сути же стоп-лимитный ордер это стоповый ордер, при срабатывании которого выставляется лимитный. Для этой связки разве не те же условия, что и для раздельного использования стоп- и лимит-ордеров?

Лимиты другие. И тайм-аут. 

Artyom Trishkin
Artyom Trishkin | 27 окт 2019 в 22:03
Алексей Тарабанов:

Лимиты другие. И тайм-аут. 

Где это есть в спецификации символа? Как узнать нужные данные?
Алексей Тарабанов
Алексей Тарабанов | 27 окт 2019 в 22:10
Нигде. К разработчикам, либо к логике. 
Artyom Trishkin
Artyom Trishkin | 28 окт 2019 в 00:54
Alexander:

В OnInit'e советника Part23 записал 2 строки

 engine.TradingSetCorrectTypeExpiration();

 engine.TradingSetCorrectTypeFilling();

ничего не помогло, пишет в журнале тоже самое.

В общем, проверил на CFD на сервере Binary.com-Server. Ставит стоп-лимитные отложенные ордера (щёлкнуть для просмотра видео):

Как в "Открытие" проверить? Счёт открывать?

Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXII): Торговые классы - Основной торговый класс, контроль ограничений Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXII): Торговые классы - Основной торговый класс, контроль ограничений

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

Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXI): Торговые классы - Базовый кроссплатформенный торговый объект Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXI): Торговые классы - Базовый кроссплатформенный торговый объект

В статье начнём новый раздел библиотеки - торговые классы, и рассмотрим создание единого базового торгового объекта для платформ MetaTrader 5 и MetaTrader 4. Такой торговый объект будет подразумевать при отправке запроса на сервер, что в него переданы уже проверенные и корректные параметры торгового запроса.

Практическое применение нейронных сетей в трейдинге Практическое применение нейронных сетей в трейдинге

В статье рассмотрены основные моменты интеграции нейронных сетей и торгового терминала с целью создания полноценного торгового робота.

Создаем кроссплатформенный советник-сеточник (Окончание): диверсификация как способ повышения прибыльности Создаем кроссплатформенный советник-сеточник (Окончание): диверсификация как способ повышения прибыльности

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