English 中文 Español Deutsch 日本語 Português
Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXV): Обработка ошибок, возвращаемых торговым сервером

Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXV): Обработка ошибок, возвращаемых торговым сервером

MetaTrader 5Примеры | 12 ноября 2019, 13:55
4 092 15
Artyom Trishkin
Artyom Trishkin

Содержание

Концепция

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

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

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

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

Закончим с теорией и начнём.

Реализация

В класс аккаунта CAccount в файле Account.mqh в раздел упрощённого доступа к свойствам объекта-аккаунта
добавим метод, возвращающий флаг работы на счёте с типом хедж:

//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта-аккаунта          |
//+------------------------------------------------------------------+
//--- Возвращает целочисленные свойства аккаунта
   ENUM_ACCOUNT_TRADE_MODE    TradeMode(void)                        const { return (ENUM_ACCOUNT_TRADE_MODE)this.GetProperty(ACCOUNT_PROP_TRADE_MODE);           }
   ENUM_ACCOUNT_STOPOUT_MODE  MarginSOMode(void)                     const { return (ENUM_ACCOUNT_STOPOUT_MODE)this.GetProperty(ACCOUNT_PROP_MARGIN_SO_MODE);     }
   ENUM_ACCOUNT_MARGIN_MODE   MarginMode(void)                       const { return (ENUM_ACCOUNT_MARGIN_MODE)this.GetProperty(ACCOUNT_PROP_MARGIN_MODE);         }
   long              Login(void)                                     const { return this.GetProperty(ACCOUNT_PROP_LOGIN);                                         }
   long              Leverage(void)                                  const { return this.GetProperty(ACCOUNT_PROP_LEVERAGE);                                      }
   long              LimitOrders(void)                               const { return this.GetProperty(ACCOUNT_PROP_LIMIT_ORDERS);                                  }
   long              TradeAllowed(void)                              const { return this.GetProperty(ACCOUNT_PROP_TRADE_ALLOWED);                                 }
   long              TradeExpert(void)                               const { return this.GetProperty(ACCOUNT_PROP_TRADE_EXPERT);                                  }
   long              CurrencyDigits(void)                            const { return this.GetProperty(ACCOUNT_PROP_CURRENCY_DIGITS);                               }
   long              ServerType(void)                                const { return this.GetProperty(ACCOUNT_PROP_SERVER_TYPE);                                   }
   long              FIFOClose(void)                                 const { return this.GetProperty(ACCOUNT_PROP_FIFO_CLOSE);                                    }
   bool              IsHedge(void)                                   const { return this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING;                        }
//--- Возвращает вещественные свойства аккаунта

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

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
//--- "Описание функции с номером строки ошибки"
#define DFUN_ERR_LINE                  (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Стр. " : ", Line ")+(string)__LINE__+": ")
#define DFUN                           (__FUNCTION__+": ")        // "Описание функции"
#define COUNTRY_LANG                   ("Russian")                // Язык страны
#define END_TIME                       (D'31.12.3000 23:59:59')   // Конечная дата для запросов данных истории счёта
#define TIMER_FREQUENCY                (16)                       // Минимальная частота таймера библиотеки в милисекундах
#define TOTAL_TRY                      (5)                        // Количество торговых попыток по умолчанию
//--- Стандартные звуки
#define SND_ALERT                      "alert.wav"
#define SND_ALERT2                     "alert2.wav"
#define SND_CONNECT                    "connect.wav"
#define SND_DISCONNECT                 "disconnect.wav"
#define SND_EMAIL                      "email.wav"
#define SND_EXPERT                     "expert.wav"
#define SND_NEWS                       "news.wav"
#define SND_OK                         "ok.wav"
#define SND_REQUEST                    "request.wav"
#define SND_STOPS                      "stops.wav"
#define SND_TICK                       "tick.wav"
#define SND_TIMEOUT                    "timeout.wav"
#define SND_WAIT                       "wait.wav"
//--- Параметры таймера коллекции ордеров и сделок
#define COLLECTION_ORD_PAUSE           (250)                      // Пауза таймера коллекции ордеров и сделок в милисекундах
#define COLLECTION_ORD_COUNTER_STEP    (16)                       // Шаг приращения счётчика таймера коллекции ордеров и сделок
#define COLLECTION_ORD_COUNTER_ID      (1)                        // Идентификатор счётчика таймера коллекции ордеров и сделок
//--- Параметры таймера коллекции аккаунтов
#define COLLECTION_ACC_PAUSE           (1000)                     // Пауза таймера коллекции аккаунтов в милисекундах
#define COLLECTION_ACC_COUNTER_STEP    (16)                       // Шаг приращения счётчика таймера аккаунтов
#define COLLECTION_ACC_COUNTER_ID      (2)                        // Идентификатор счётчика таймера аккаунтов
//--- Параметры таймера1 коллекции символов
#define COLLECTION_SYM_PAUSE1          (100)                      // Пауза таймера1 коллекции символов в милисекундах (для сканирования символов в обзоре рынка)
#define COLLECTION_SYM_COUNTER_STEP1   (16)                       // Шаг приращения счётчика таймера1 символов
#define COLLECTION_SYM_COUNTER_ID1     (3)                        // Идентификатор счётчика таймера1 символов
//--- Параметры таймера2 коллекции символов
#define COLLECTION_SYM_PAUSE2          (300)                      // Пауза таймера2 коллекции символов в милисекундах (для событий списка символов в обзоре рынка)
#define COLLECTION_SYM_COUNTER_STEP2   (16)                       // Шаг приращения счётчика таймера2 символов
#define COLLECTION_SYM_COUNTER_ID2     (4)                        // Идентификатор счётчика таймера2 символов
//--- Параметры таймера торгового класса    
#define COLLECTION_REQ_PAUSE           (300)                      // Пауза таймера торгового класса в милисекундах
#define COLLECTION_REQ_COUNTER_STEP    (16)                       // Шаг приращения счётчика таймера торгового класса
#define COLLECTION_REQ_COUNTER_ID      (5)                        // Идентификатор счётчика таймера торгового класса
//--- Идентификаторы списков коллекций
#define COLLECTION_HISTORY_ID          (0x7779)                   // Идентификатор списка исторической коллекции
#define COLLECTION_MARKET_ID           (0x777A)                   // Идентификатор списка рыночной коллекции
#define COLLECTION_EVENTS_ID           (0x777B)                   // Идентификатор списка коллекции событий
#define COLLECTION_ACCOUNT_ID          (0x777C)                   // Идентификатор списка коллекции аккаунтов
#define COLLECTION_SYMBOLS_ID          (0x777D)                   // Идентификатор списка коллекции символов
//--- Параметры данных для файловых операций
#define DIRECTORY                      ("DoEasy\\")               // Каталог библиотеки для расположения папок объектов классов
#define RESOURCE_DIR                   ("DoEasy\\Resource\\")     // Каталог библиотеки для расположения папок ресурсов
//--- Параметры символов
#define CLR_DEFAULT                    (0xFF000000)               // Цвет по умолчанию
#define SYMBOLS_COMMON_TOTAL           (1000)                     // Общее количество рабочих символов
//+------------------------------------------------------------------+

В список флагов методов обработки ошибок торгового сервера добавим два флага — флаг ошибки в цене отложенного ордера и флаг ошибки в цене стоп-лимитного ордера, а в методы обработки ошибок и кодов возврата торгового сервера добавим метод коррекции параметров торгового приказа:

//+------------------------------------------------------------------+
//| Флаги, указывающие на методы обработки ошибок торгового запроса  |
//+------------------------------------------------------------------+
enum ENUM_TRADE_REQUEST_ERR_FLAGS
  {
   TRADE_REQUEST_ERR_FLAG_NO_ERROR                 =  0,    // Нет ошибки
   TRADE_REQUEST_ERR_FLAG_FATAL_ERROR              =  1,    // Запретить торговлю для эксперта (фатальная ошибка) - выход
   TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR             =  2,    // Внутренняя ошибка в библиотеке - выход
   TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST            =  4,    // Ошибка находится в списке - обработать (ENUM_ERROR_CODE_PROCESSING_METHOD)
   TRADE_REQUEST_ERR_FLAG_PRICE_ERROR              =  8,    // Ошибка в цене установки
   TRADE_REQUEST_ERR_FLAG_LIMIT_ERROR              =  16,   // Ошибка в цене установки limit-ордера
  };
//+------------------------------------------------------------------+
//| Методы обработки ошибок и кодов возврата сервера                 |
//+------------------------------------------------------------------+
enum ENUM_ERROR_CODE_PROCESSING_METHOD
  {
   ERROR_CODE_PROCESSING_METHOD_OK,                         // Ошибки нет
   ERROR_CODE_PROCESSING_METHOD_DISABLE,                    // Запретить торговлю экспертом
   ERROR_CODE_PROCESSING_METHOD_EXIT,                       // Выйти из торгового метода
   ERROR_CODE_PROCESSING_METHOD_CORRECT,                    // Скорректировать параметры торгового запроса и повторить
   ERROR_CODE_PROCESSING_METHOD_REFRESH,                    // Обновить данные и повторить
   ERROR_CODE_PROCESSING_METHOD_PENDING,                    // Создать отложенный запрос
   ERROR_CODE_PROCESSING_METHOD_WAIT,                       // Подождать и повторить
  };
//+------------------------------------------------------------------+

В файле 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_REQUEST_REJECTED_DUE,                 // Запрос отклонён до отправки на сервер по причине:
   MSG_LIB_TEXT_INVALID_REQUEST,                      // Ошибочный запрос:
   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_PRICE_LESS_STOP_LEVEL,                // Дистанция установки ордера в пунктах меньше разрешённой параметром StopLevel символа
   MSG_LIB_TEXT_LIMIT_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,        // Неподдерживаемый тип параметра цены в запросе
   MSG_LIB_TEXT_TRADING_DISABLE,                      // Торговля отключена для эксперта до устранения причины запрета
   MSG_LIB_TEXT_TRADING_OPERATION_ABORTED,            // Торговая операция прервана
   MSG_LIB_TEXT_CORRECTED_TRADE_REQUEST,              // Корректировка параметров торгового запроса
   MSG_LIB_TEXT_CREATE_PENDING_REQUEST,               // Создание отложенного запроса
   MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT,          // Нет возможности скорректировать лот
   MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ,           // Не удалось создать отложенный запрос
   MSG_LIB_TEXT_TRY_N,                                // Торговая попытка #
   
  };

и тексты этих сообщений:

   {"Дистанция установки ордера в пунктах меньше разрешённой параметром StopLevel символа","The distance to place an order in points is less than the symbol allowed by the StopLevel parameter"},
   {"Дистанция установки лимит-ордера относительно стоп-ордера меньше разрешённой параметром StopLevel символа","The distance to place the limit order relative to the stop order 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"},
   {"Торговля отключена для эксперта до устранения причины запрета","Trading for the expert is disabled until this ban is eliminated"},
   {"Торговая операция прервана","Trading operation aborted"},
   {"Корректировка параметров торгового запроса ...","Correction of trade request parameters ..."},
   {"Создание отложенного запроса","Create a pending request"},
   {"Нет возможности скорректировать лот","There is no possibility to correct the lot"},
   {"Не удалось создать отложенный запрос","Failed to create pending request"},
   {"Торговая попытка #","Trading attempt #"},
   
  };

В файл базового торгового объекта TradeObj.mqh внесены незначительные изменения.
В метод установки отложенного ордера добавлен параметр типа ордера по исполнению
(почему-то забыл про него и сразу не сделал — использовался установленный по умолчанию):

//--- Устанавливает ордер
   bool                       SetOrder(const ENUM_ORDER_TYPE type,
                                       const double volume,
                                       const double price,
                                       const double sl=0,
                                       const double tp=0,
                                       const double price_stoplimit=0,
                                       const ulong magic=ULONG_MAX,
                                       const string comment=NULL,
                                       const datetime expiration=0,
                                       const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
                                       const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);

Теперь, если передано значение больше, чем -1, то будет использовано переданное в метод значение, иначе — значение параметра по умолчанию:

//+------------------------------------------------------------------+
//| Устанавливает ордер                                              |
//+------------------------------------------------------------------+
bool CTradeObj::SetOrder(const ENUM_ORDER_TYPE type,
                         const double volume,
                         const double price,
                         const double sl=0,
                         const double tp=0,
                         const double price_stoplimit=0,
                         const ulong magic=ULONG_MAX,
                         const string comment=NULL,
                         const datetime expiration=0,
                         const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
                         const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
  {
   ::ResetLastError();
   //--- Если передан не правильный тип ордера - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
   if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL || type==ORDER_TYPE_CLOSE_BY 
      #ifdef __MQL4__ || type==ORDER_TYPE_BUY_STOP_LIMIT || type==ORDER_TYPE_SELL_STOP_LIMIT #endif )
     {
      this.m_result.retcode=MSG_LIB_SYS_INVALID_ORDER_TYPE;
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_INVALID_ORDER_TYPE),OrderTypeDescription(type));
      return false;
     }
   //--- Очищаем структуры
   ::ZeroMemory(this.m_request);
   ::ZeroMemory(this.m_result);
   //--- Заполняем структуру запроса
   this.m_request.action      =  TRADE_ACTION_PENDING;
   this.m_request.symbol      =  this.m_symbol;
   this.m_request.magic       =  (magic==ULONG_MAX ? this.m_magic : magic);
   this.m_request.volume      =  volume;
   this.m_request.type        =  type;
   this.m_request.stoplimit   =  price_stoplimit;
   this.m_request.price       =  price;
   this.m_request.sl          =  sl;
   this.m_request.tp          =  tp;
   this.m_request.expiration  =  expiration;
   this.m_request.type_time   =  (type_time>WRONG_VALUE ? type_time : this.m_type_time);
   this.m_request.type_filling=  (type_filling>WRONG_VALUE ? type_filling : this.m_type_filling);
   this.m_request.comment     =  (comment==NULL ? this.m_comment : comment);
   //--- Возвращаем результат отсылки запроса на сервер
#ifdef __MQL5__
   return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result));
#else 
   ::ResetLastError();
   int ticket=::OrderSend(m_request.symbol,m_request.type,m_request.volume,m_request.price,(int)m_request.deviation,m_request.sl,m_request.tp,m_request.comment,(int)m_request.magic,m_request.expiration,clrNONE);
   ::SymbolInfoTick(this.m_symbol,this.m_tick);
   if(ticket!=WRONG_VALUE)
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.ask=this.m_tick.ask;
      this.m_result.bid=this.m_tick.bid;
      this.m_result.order=ticket;
      this.m_result.price=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderOpenPrice() : this.m_request.price);
      this.m_result.volume=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume);
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      return true;
     }
   else
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.ask=this.m_tick.ask;
      this.m_result.bid=this.m_tick.bid;
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      return false;
     }
#endif 
  }
//+------------------------------------------------------------------+

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

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

В файле Trading.mqh торгового класса CTrading в его приватную секцию впишем список отложенных запросов и переменную для хранения количества торговых попыток:

//+------------------------------------------------------------------+
//| Торговый класс                                                   |
//+------------------------------------------------------------------+
class CTrading
  {
private:
   CAccount            *m_account;                       // Указатель на объект-текущий аккаунт
   CSymbolsCollection  *m_symbols;                       // Указатель на список коллекции символов
   CMarketCollection   *m_market;                        // Указатель на список коллекции рыночных ордеров и позиций
   CHistoryCollection  *m_history;                       // Указатель на список коллекции исторических ордеров и сделок
   CArrayObj            m_list_request;                  // Список отложенных запросов
   CArrayInt            m_list_errors;                   // Список ошибок
   bool                 m_is_trade_disable;              // Флаг запрета торговли
   bool                 m_use_sound;                     // Флаг использования звуков торговых событий объекта
   uchar                m_total_try;                     // Количество торговых попыток
   ENUM_LOG_LEVEL       m_log_level;                     // Уровень логирования
   MqlTradeRequest      m_request;                       // Цены торгового запроса
   ENUM_TRADE_REQUEST_ERR_FLAGS m_error_reason_flags;    // Флаги причин ошибок в торговом методе
   ENUM_ERROR_HANDLING_BEHAVIOR m_err_handling_behavior; // Поведение при обработке ошибок

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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CTrading::CTrading()
  {
   this.m_list_errors.Clear();
   this.m_list_errors.Sort();
   this.m_list_request.Clear();
   this.m_list_request.Sort();
   this.m_total_try=TOTAL_TRY;
   this.m_log_level=LOG_LEVEL_ALL_MSG;
   this.m_is_trade_disable=false;
   this.m_err_handling_behavior=ERROR_HANDLING_BEHAVIOR_CORRECT;
   ::ZeroMemory(this.m_request);
  }
//+------------------------------------------------------------------+

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

В параметры метода проверки цены относительно уровня StopLevel добавим цену установки лимит-ордера для ордера с типом StopLimit:

bool CheckPriceByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj,const double limit=0);

И в сам метод добавим проверку:

//+------------------------------------------------------------------+
//| Возвращает флаг проверки разрешённости дистанции                 |
//| установки ордера от цены до цены установки по уровню StopLevel   |
//+------------------------------------------------------------------+
bool CTrading::CheckPriceByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj,const double limit=0)
  {
   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.Bid());
   return
     (limit==0 ?
      //--- Цены установки ордеров относительно цены
      (
       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
      ) : 
      //--- Цены установки лимитных ордеров относительно цены стоп-ордеров
      (
       order_type==ORDER_TYPE_BUY_STOP_LIMIT    ?  limit<(price-lv)  :  
       order_type==ORDER_TYPE_SELL_STOP_LIMIT   ?  limit>(price+lv)  :
      true
      )
     );
  }
//+------------------------------------------------------------------+

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

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

//--- Возвращает метод обработки ошибки
   ENUM_ERROR_CODE_PROCESSING_METHOD   ResultProccessingMethod(const uint result_code);
//--- Корректировка ошибок
   ENUM_ERROR_CODE_PROCESSING_METHOD   RequestErrorsCorrecting(MqlTradeRequest &request,const ENUM_ORDER_TYPE order_type,const uint spread_multiplier,CSymbol *symbol_obj,CTradeObj *trade_obj);

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

//--- (1) Открывает позицию, (2) устанавливает отложенный ордер
   template<typename SL,typename TP> 
   bool                 OpenPosition(const ENUM_POSITION_TYPE type,
                                    const double volume,
                                    const string symbol,
                                    const ulong magic=ULONG_MAX,
                                    const SL sl=0,
                                    const TP tp=0,
                                    const string comment=NULL,
                                    const ulong deviation=ULONG_MAX);
   template<typename PS,typename PL,typename SL,typename TP>
   bool                 PlaceOrder( const ENUM_ORDER_TYPE order_type,
                                    const double volume,
                                    const string symbol,
                                    const PS price_stop,
                                    const PL price_limit=0,
                                    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=WRONG_VALUE,
                                    const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
public:
//--- Конструктор

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

public:
//--- Конструктор
                        CTrading();
//--- Таймер
   void                 OnTimer(void);
//--- Получение указателей на списки (вызывать метод обязательно в 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;
                          }
//--- Возвращает список (1) ошибок, (2) отложенных запросов
   CArrayInt           *GetListErrors(void)                             { return &this.m_list_errors; }
   CArrayObj           *GetListRequests(void)                           { return &this.m_list_request;}
//--- Устанавливает количество торговых попыток
   void                 SetTotalTry(const uchar number)                 { this.m_total_try=number;    }
//--- Проверка ограничений и ошибок

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

bool ClosePosition(const ulong ticket,const double volume=WRONG_VALUE,const string comment=NULL,const ulong deviation=ULONG_MAX);

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

//--- Устанавливает отложенный ордер (1) BuyStop, (2) BuyLimit, (3) BuyStopLimit
   template<typename PS,typename SL,typename TP>
   bool                 PlaceBuyStop(const double volume,
                                           const string symbol,
                                           const PS 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=WRONG_VALUE,
                                           const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
   template<typename PS,typename SL,typename TP>
   bool                 PlaceBuyLimit(const double volume,
                                           const string symbol,
                                           const PS 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=WRONG_VALUE,
                                           const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
   template<typename PS,typename PL,typename SL,typename TP>
   bool                 PlaceBuyStopLimit(const double volume,
                                           const string symbol,
                                           const PS 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=WRONG_VALUE,
                                           const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);

//--- Устанавливает отложенный ордер (1) SellStop, (2) SellLimit, (3) SellStopLimit
   template<typename PS,typename SL,typename TP>
   bool                 PlaceSellStop(const double volume,
                                           const string symbol,
                                           const PS 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=WRONG_VALUE,
                                           const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
   template<typename PS,typename SL,typename TP>
   bool                 PlaceSellLimit(const double volume,
                                           const string symbol,
                                           const PS 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=WRONG_VALUE,
                                           const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
   template<typename PS,typename PL,typename SL,typename TP>
   bool                 PlaceSellStopLimit(const double volume,
                                           const string symbol,
                                           const PS 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=WRONG_VALUE,
                                           const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
//--- Модифицирует отложенный ордер
   template<typename PS,typename PL,typename SL,typename TP>
   bool                 ModifyOrder(const ulong ticket,
                                          const PS price=WRONG_VALUE,
                                          const SL sl=WRONG_VALUE,
                                          const TP tp=WRONG_VALUE,
                                          const PL limit=WRONG_VALUE,
                                          datetime expiration=WRONG_VALUE,
                                          const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
                                          const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);

Напишем реализацию таймера — пока лишь просто заготовку обработки списка отложенных запросов:

//+------------------------------------------------------------------+
//| Таймер                                                           |
//+------------------------------------------------------------------+
void CTrading::OnTimer(void)
  {
   int total=this.m_list_request.Total();
   for(int i=total-1;i>WRONG_VALUE;i--)
     {
      
     }
  }
//+------------------------------------------------------------------+

Реализация метода, возвращающего способы обработки кодов возврата торгового сервера:

//+------------------------------------------------------------------+
//| Возвращает метод обработки ошибки                                |
//+------------------------------------------------------------------+
ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::ResultProccessingMethod(const uint result_code)
  {
   switch(result_code)
     {
   #ifdef __MQL4__
      //--- Недопустимая операция, нарушающая функционирование сервера
      case 9   :
      //--- Счет заблокирован
      case 64  :
      //--- Неправильный номер счета
      case 65  :  return ERROR_CODE_PROCESSING_METHOD_DISABLE;
      
      //--- Нет ошибки, но результат неизвестен
      case 1   :
      //--- Общая ошибка
      case 2   :
      //--- Старая версия клиентского терминала
      case 5   :
      //--- Недостаточно прав
      case 7   :
      //--- Рынок закрыт
      case 132 :
      //--- Торговля запрещена
      case 133 :
      //--- Ордер заблокирован и уже обрабатывается
      case 139 :
      //--- Разрешена только покупка
      case 140 :
      //--- Количество открытых и отложенных ордеров достигло предела, установленного брокером
      case 148 :
      //--- Попытка открыть противоположный ордер в случае, если хеджирование запрещено
      case 149 :
      //--- Попытка закрыть позицию по инструменту в противоречии с правилом FIFO
      case 150 :  return ERROR_CODE_PROCESSING_METHOD_EXIT;
      
      //--- Неправильные параметры торгового запроса
      case 3   :
      //--- Неправильная цена
      case 129 :
      //--- Неправильные стопы
      case 130 :
      //--- Неправильный объем
      case 131 :
      //--- Недостаточно денег для совершения операции
      case 134 :
      //--- Использование даты истечения ордера запрещено брокером
      case 147 :  return ERROR_CODE_PROCESSING_METHOD_CORRECT;
      
      //--- Торговый сервер занят
      case 4   :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Нет связи с торговым сервером
      case 6   :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Слишком частые запросы
      case 8   :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;   // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Нет цен
      case 136 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Брокер занят
      case 137 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Слишком много запросов
      case 141 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;   // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Модификация запрещена, так как ордер слишком близок к рынку
      case 145 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Подсистема торговли занята
      case 146 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)1000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      
      //--- Истек срок ожидания совершения сделки
      case 128 :
      //--- Цена изменилась
      case 135 :
      //--- Новые цены
      case 138 :  return ERROR_CODE_PROCESSING_METHOD_REFRESH;

   //--- MQL5
   #else 
      //--- Автотрейдинг запрещен сервером
      case 10026  :  return ERROR_CODE_PROCESSING_METHOD_DISABLE;
      
      //--- Запрос отменен трейдером
      case 10007  :
      //--- Запрос отменен по истечению времени
      case 10012  :
      //--- Торговля запрещена
      case 10017 :
      //--- Рынок закрыт
      case 10018  :
      //--- Состояние ордера изменилось
      case 10023  :
      //--- В запросе нет изменений
      case 10025  :
      //--- Запрос заблокирован для обработки
      case 10028  :
      //--- Операция разрешена только для реальных счетов
      case 10032  :
      //--- Достигнут лимит на количество отложенных ордеров
      case 10033  :
      //--- Достигнут лимит на объем ордеров и позиций для данного символа
      case 10034  :
      //--- Неверный или запрещённый тип ордера
      case 10035  :
      //--- Позиция с указанным идентификатором уже закрыта
      case 10036  :
      //--- Для указанной позиции уже есть ордер на закрытие
      case 10039  :
      //--- Достигнут лимит на количество открытых позиций
      case 10040  :
      //--- Запрос на активацию отложенного ордера отклонен, а сам ордер отменен
      case 10041  :
      //--- Запрос отклонен, так как на символе установлено правило "Разрешены только длинные позиции"
      case 10042  :
      //--- Запрос отклонен, так как на символе установлено правило "Разрешены только короткие позиции"
      case 10043  :
      //--- Запрос отклонен, так как на символе установлено правило "Разрешено только закрывать существующие позиции"
      case 10044  :
      //--- Запрос отклонен, так как для торгового счета установлено правило "Разрешено закрывать существующие позиции только по правилу FIFO"
      case 10045  :  return ERROR_CODE_PROCESSING_METHOD_EXIT;

      //--- Реквота
      case 10004  :
      //--- Запрос отклонен
      case 10006  :
      //--- Цены изменились
      case 10020  :  return ERROR_CODE_PROCESSING_METHOD_REFRESH;

      //--- Неправильный запрос
      case 10013  :
      //--- Неправильный объем в запросе
      case 10014  :
      //--- Неправильная цена в запросе
      case 10015  :
      //--- Неправильные стопы в запросе
      case 10016  :
      //--- Нет достаточных денежных средств для выполнения запроса
      case 10019  :
      //--- Неверная дата истечения ордера в запросе
      case 10022  :
      //--- Указан неподдерживаемый тип исполнения ордера по остатку
      case 10030  :
      //--- Закрываемый объем превышает текущий объем позиции
      case 10038  :  return ERROR_CODE_PROCESSING_METHOD_CORRECT;

      //--- Отсутствуют котировки для обработки запроса
      case 10021  :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT;
      //--- Слишком частые запросы
      case 10024  :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;   // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Ордер или позиция заморожены
      case 10029  :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;   // ERROR_CODE_PROCESSING_METHOD_WAIT;

      //--- Ошибка обработки запроса
      case 10011  :  return ERROR_CODE_PROCESSING_METHOD_PENDING;
      //--- Автотрейдинг запрещен клиентским терминалом
      case 10027  :  return ERROR_CODE_PROCESSING_METHOD_PENDING;
      //--- Нет соединения с торговым сервером
      case 10031  :  return ERROR_CODE_PROCESSING_METHOD_PENDING;

      //--- Ордер размещен
      case 10008  :
      //--- Заявка выполнена
      case 10009  :
      //--- Заявка выполнена частично
      case 10010  :
   #endif 
      //--- "OK"
      default:
        break;
     }
   return ERROR_CODE_PROCESSING_METHOD_OK;
  }
//+------------------------------------------------------------------+

Здесь всё просто: в метод передаётся код, полученный от сервера после отправки на него торгового запроса, и далее те коды, при получении которых есть возможность исправить ошибку, будут обрабатывться методом исправления ошибки, коды, треюущие обновления данных и повторной отправки запроса, будут обрабатываться соответственно, и т.д.
Так как MQL5- и MQL4-серверы возвращают разные коды ошибок, то и в методе организована условная компиляция для MQL4 и MQL5.
Все коды, требующие однотипной обработки, сгруппированы в единый case оператора switch, и возвращают единый для них метод обработки кода возврата торгового сервера.

Реализация метода обработки ошибок торгового сервера:

//+------------------------------------------------------------------+
//| Корректировка ошибок                                             |
//+------------------------------------------------------------------+
ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::RequestErrorsCorrecting(MqlTradeRequest &request,
                                                                    const ENUM_ORDER_TYPE order_type,
                                                                    const uint spread_multiplier,
                                                                    CSymbol *symbol_obj,
                                                                    CTradeObj *trade_obj)
  {
//--- Если список ошибок пуст - нет ошибок, возвращаем успешность
   int total=this.m_list_errors.Total();
   if(total==0)
      return ERROR_CODE_PROCESSING_METHOD_OK;

//--- Для текущего счёта запрещена торговля
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED))
     {
      trade_obj.SetResultRetcode(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Для советников на текущем счёте запрещена торговля на стороне торгового сервера
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED))
     {
      trade_obj.SetResultRetcode(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- В терминале нет разрешения на проведение торговых операций
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED))
     {
      trade_obj.SetResultRetcode(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Для советника нет разрешения на проведение торговых пераций
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED))
     {
      trade_obj.SetResultRetcode(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Торговля по символу запрещена
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_SYM_TRADE_MODE_DISABLED))
     {
      trade_obj.SetResultRetcode(MSG_SYM_TRADE_MODE_DISABLED);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Разрешены только операции закрытия позиций
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_SYM_TRADE_MODE_CLOSEONLY))
     {
      trade_obj.SetResultRetcode(MSG_SYM_TRADE_MODE_CLOSEONLY);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Торговля рыночными ордерами запрещена
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_SYM_MARKET_ORDER_DISABLED))
     {
      trade_obj.SetResultRetcode(MSG_SYM_MARKET_ORDER_DISABLED);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Установка Limit-ордеров запрещена
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_SYM_LIMIT_ORDER_DISABLED))
     {
      trade_obj.SetResultRetcode(MSG_SYM_LIMIT_ORDER_DISABLED);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Установка Stop-ордеров запрещена
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_SYM_STOP_ORDER_DISABLED))
     {
      trade_obj.SetResultRetcode(MSG_SYM_STOP_ORDER_DISABLED);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Установка StopLimit-ордеров запрещена
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_SYM_STOP_LIMIT_ORDER_DISABLED))
     {
      trade_obj.SetResultRetcode(MSG_SYM_STOP_LIMIT_ORDER_DISABLED);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Разрешены только продажи
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_SYM_TRADE_MODE_SHORTONLY))
     {
      trade_obj.SetResultRetcode(MSG_SYM_TRADE_MODE_SHORTONLY);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Разрешены только покупки
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_SYM_TRADE_MODE_LONGONLY))
     {
      trade_obj.SetResultRetcode(MSG_SYM_TRADE_MODE_LONGONLY);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Установка CloseBy-ордеров запрещена
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_SYM_CLOSE_BY_ORDER_DISABLED))
     {
      trade_obj.SetResultRetcode(MSG_SYM_CLOSE_BY_ORDER_DISABLED);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Превышен максимальный совокупный объём ордеров и позиций в одном направлении
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED))
     {
      trade_obj.SetResultRetcode(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Закрытие встречным запрещено
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED))
     {
      trade_obj.SetResultRetcode(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Символы встречных позиций не равны
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL))
     {
      trade_obj.SetResultRetcode(MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Неподдерживаемый тип параметра цены в запросе
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ))
     {
      trade_obj.SetResultRetcode(MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Торговля отключена для эксперта до устранения причины запрета
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(MSG_LIB_TEXT_TRADING_DISABLE))
     {
      trade_obj.SetResultRetcode(MSG_LIB_TEXT_TRADING_DISABLE);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Достигнут лимит на количество отложенных ордеров
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(10033))
     {
      trade_obj.SetResultRetcode(10033);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }
//--- Достигнут лимит на объем ордеров и позиций для данного символа
//--- записываем в объект базового торгового класса код ошибки и возвращаем "выход из торгового метода"
   if(this.IsPresentErorCode(10034))
     {
      trade_obj.SetResultRetcode(10034);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      return ERROR_CODE_PROCESSING_METHOD_EXIT;
     }

//--- Корректировка переметров торгового запроса
//--- Цена, относительно которой ставим стоп-приказы
   double price_set=(this.IsPresentErrorFlag(TRADE_REQUEST_ERR_FLAG_PRICE_ERROR) ? request.price : request.stoplimit);
//--- В первую очередь корректируем стоп-приказы относительно уровня установки ордера/открытия позиции
   if(this.IsPresentErorCode(MSG_LIB_TEXT_SL_LESS_STOP_LEVEL))
      request.sl=this.CorrectStopLoss(order_type,price_set,request.sl,symbol_obj,spread_multiplier);
   if(this.IsPresentErorCode(MSG_LIB_TEXT_TP_LESS_STOP_LEVEL))
      request.tp=this.CorrectTakeProfit(order_type,price_set,request.tp,symbol_obj,spread_multiplier);
//--- Цена установки отложенных ордеров
   double shift=0;
   if(this.IsPresentErrorFlag(TRADE_REQUEST_ERR_FLAG_PRICE_ERROR))
     {
      price_set=request.price;
      request.price=this.CorrectPricePending(order_type,price_set,0,symbol_obj,spread_multiplier);   
      shift=request.price-price_set; 
      //--- Если это не стоп-лимитный ордер, то передвинем стоп-приказы на размер рассчитанного корректирующего смещения уровня установки ордера
      if(request.stoplimit==0)
        {
         if(request.sl>0)
            request.sl=this.CorrectStopLoss(order_type,request.price,request.sl+shift,symbol_obj,spread_multiplier);
         if(request.tp>0)
            request.tp=this.CorrectTakeProfit(order_type,request.price,request.tp+shift,symbol_obj,spread_multiplier);                                                               
        }
     }
//--- Указан неподдерживаемый тип исполнения ордера по остатку
   if(this.IsPresentErorCode(10030))
      request.type_filling=symbol_obj.GetCorrectTypeFilling();
//--- Неверная дата истечения ордера в запросе -
   if(this.IsPresentErorCode(10022))
     {
      //--- если тип истечения не поддерживается как указываемый датой истечения, и задана дата истечения, то сбросим дату истечения
      if(!symbol_obj.IsExpirationModeSpecified() && request.expiration>0)
         request.expiration=0;
     }
//--- Просмотрим список оставшихся ошибок и скорректируем параметры торгового запроса
   for(int i=0;i<total;i++)
     {
      int err=this.m_list_errors.At(i);
      if(err==NULL)
         continue;
      switch(err)
        {
         //--- Неправильный объём и запрет стоп-уровней скорректируем в торговом запросе
         case MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME :
         case MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME :
         case MSG_LIB_TEXT_INVALID_VOLUME_STEP     :  request.volume=symbol_obj.NormalizedLot(request.volume);                                              break;
         case MSG_SYM_SL_ORDER_DISABLED            :  request.sl=0;                                                                                         break;
         case MSG_SYM_TP_ORDER_DISABLED            :  request.tp=0;                                                                                         break;
         
         //--- Если не получится подобрать допустимый лот позиции, то вернём "прервать торговую попытку", так как денег нет даже на минимальный лот
         case MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR    :  request.volume=this.CorrectVolume(request.price,order_type,symbol_obj,DFUN);
                                                      if(request.volume==0)
                                                        {
                                                         trade_obj.SetResultRetcode(MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT);
                                                         trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
                                                         return ERROR_CODE_PROCESSING_METHOD_EXIT;                                                                                      break;
                                                        }
         //--- Отсутствуют котировки для обработки запроса
         case 10021                                :  trade_obj.SetResultRetcode(10021);
                                                      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
                                                      return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT - ждём 5 секунд
         //--- Нет соединения с торговым сервером
         case 10031                                :  trade_obj.SetResultRetcode(10031);
                                                      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
                                                      return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT - ждём 5 секунд
                                                      
         //--- Близость к уровню срабатывания ордера обрабатываем пятисекундным ожиданием - цена может выйти из зоны заморозки за это время
         case MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL    :
         case MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL    :
         case MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL    :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT - ждём 5 секунд
         default:
           break;
        }
     }
//--- Ошибок нет - возвращаем ОК
   trade_obj.SetResultRetcode(0);
   trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
   return ERROR_CODE_PROCESSING_METHOD_OK;
  }
//+------------------------------------------------------------------+

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

Реализация приватного метода для открытия позиции:

//+------------------------------------------------------------------+
//| Открывает позицию                                                |
//+------------------------------------------------------------------+
template<typename SL,typename TP> 
bool CTrading::OpenPosition(const ENUM_POSITION_TYPE type,
                            const double volume,
                            const string symbol,
                            const ulong magic=ULONG_MAX,
                            const SL sl=0,
                            const TP tp=0,
                            const string comment=NULL,
                            const ulong deviation=ULONG_MAX)
  {
//--- Устанавливаем результат торгового запроса как true и флаг ошибки как "нет ошибок"
   bool res=true;
   this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR;
   ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)type;
   ENUM_ACTION_TYPE action=(ENUM_ACTION_TYPE)order_type;
//--- Получаем объект-символ по имени символа. Если получить не удалось
   CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol);
//--- Если получить не удалось - записываем флаг "внутренняя ошибка", выводим сообщение в журнал и возвращаем false
   if(symbol_obj==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ));
      return false;
     }
//--- получаем торговый объект из объекта-символа
   CTradeObj *trade_obj=symbol_obj.GetTradeObj();
//--- Если получить не удалось - записываем флаг "внутренняя ошибка", выводим сообщение в журнал и возвращаем false
   if(trade_obj==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
//--- Устанавливаем цены
//--- Если установить не удалось - записываем флаг "внутренняя ошибка" устанавливаем код ошибки в структуру возврата,
//--- выводим сообщение в журнал и возвращаем false
   if(!this.SetPrices(order_type,0,sl,tp,0,DFUN,symbol_obj))
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      trade_obj.SetResultRetcode(10021);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(10021));   // Отсутствуют котировки для обработки запроса
      return false;
     }

//--- Записываем объём в структуру запроса
   this.m_request.volume=volume;
//--- Получаем метод обработки ошибок из метода CheckErrors(), одновременно проверяя ошибки в параметрах запроса
   ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume,symbol_obj.Ask(),action,order_type,symbol_obj,trade_obj,DFUN,0,this.m_request.sl,this.m_request.tp);
//--- Если есть ограничения по разрешённости торговли, не хватает средств,
//--- есть ограничения по уровням StopLevel или FreezeLevel ...
   if(method!=ERROR_CODE_PROCESSING_METHOD_OK)
     {
      //--- Если полный запрет торговли - устанавливаем код ошибки в структуру возврата,
      //--- выводим сообщение в журнал, воспроизводим звук ошибки и уходим
      if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE)
        {
         trade_obj.SetResultRetcode(MSG_LIB_TEXT_TRADING_DISABLE);
         trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE));
         if(this.IsUseSounds())
            trade_obj.PlaySoundError(action,order_type);
         return false;
        }
      //--- Если результат проверки "прервать торговую операцию" - устанавливаем код последней ошибки в структуру возврата,
      //--- выводим сообщение в журнал, воспроизводим звук ошибки и уходим
      if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
        {
         int code=this.m_list_errors.At(this.m_list_errors.Total()-1);
         if(code!=NULL)
           {
            trade_obj.SetResultRetcode(code);
            trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
           }
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_OPERATION_ABORTED));
         if(this.IsUseSounds())
            trade_obj.PlaySoundError(action,order_type);
         return false;
        }
      //--- Если результат проверки "ожидание" - устанавливаем код последней ошибки в структуру возврата И выводим сообщение в журнал
      if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
        {
         int code=this.m_list_errors.At(this.m_list_errors.Total()-1);
         if(code!=NULL)
           {
            trade_obj.SetResultRetcode(code);
            trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
           }
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST));
         //--- Временно, вместо создания отложенного запроса, ожидаем требуемое время (возвращается результатом метода CheckErrors())
         ::Sleep(method);
         //--- после ожидания обновляем все данные символа
         symbol_obj.Refresh();
        }
      //--- Если результат проверки "создать отложенный запрос" - временно ничего не делаем
      if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST)
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST));
        }
     }
   
//--- В цикле по количеству попыток
   for(int i=0;i<this.m_total_try;i++)
     {                
      //--- Отсылаем запрос
      res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation);
      //--- Если запрос выполнен успешно или асинхронный режим отправки ордеров - проиграем звук успеха,
      //--- установленный торговому объекту символа для данного типа торговой операции и вернём true
      if(res || trade_obj.IsAsyncMode())
        {
         if(this.IsUseSounds())
            trade_obj.PlaySoundSuccess(action,order_type);
         return true;
        }
      //--- Если запрос не выполнен - выведем сообщение и проиграем звук ошибки, установленный торговому объекту символа для данного типа торговой операции
      else
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_TRY_N),string(i+1),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode()));
         if(this.IsUseSounds())
            trade_obj.PlaySoundError(action,order_type);
         
         //--- Получаем метод обработки ошибки
         method=this.ResultProccessingMethod(trade_obj.GetResultRetcode());
         //--- Если в результате отправки запроса получили "Запретить торговлю экспертом" - взводим флаг запрета и прерываем цикл попыток
         if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE)
           {
            this.SetTradingDisableFlag(true);
            break;
           }
         //--- Если в результате отправки запроса получили "Выйти из торгового метода" - прерываем цикл попыток
         if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
           {
            break;
           }
         //--- Если в результате отправки запроса получили "Скорректировать параметры и повторить" -
         //--- корректируем параметры и идём на следующую итерацию
         if(method==ERROR_CODE_PROCESSING_METHOD_CORRECT)
           {
            this.RequestErrorsCorrecting(this.m_request,order_type,trade_obj.SpreadMultiplier(),symbol_obj,trade_obj);
            continue;
           }
         //--- Если в результате отправки запроса получили "Обновить данные и повторить" -
         //--- обновляем данные и идём на следующую итерацию
         if(method==ERROR_CODE_PROCESSING_METHOD_REFRESH)
           {
            symbol_obj.Refresh();
            continue;
           }
         //--- Если в результате отправки запроса получили "Подождать и повторить" -
         //--- в данном исполнении ожидаем количество милисекунд, равным значению method и идём на следующую итерацию
         if(method==ERROR_CODE_PROCESSING_METHOD_WAIT)
           {
            ::Sleep(method);
            continue;
           }
         //--- Если в результате отправки запроса получили "Создать отложенный запрос" -
         //--- создаём отложенный запрос с параметрами торгового запроса и прерываем цикл попыток
         if(method==ERROR_CODE_PROCESSING_METHOD_PENDING)
           {
            break;
           }
        }
     }
//--- Возвращаем результат отправки торгового запроса в торговом объекте символа
   return res;
  }
//+------------------------------------------------------------------+
Данный метод подробно прокомментирован прямо в листинге и будет использоваться для открытия позиций Buy и Sell:
//+------------------------------------------------------------------+
//| Открывает позицию 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)
  {
//--- Возвращаем результат отправки торгового запроса из метода OpenPosition()
   return this.OpenPosition(POSITION_TYPE_BUY,volume,symbol,magic,sl,tp,comment,deviation);
  }
//+------------------------------------------------------------------+
//| Открывает позицию Sell                                           |
//+------------------------------------------------------------------+
template<typename SL,typename TP> 
bool CTrading::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)
  {
//--- Возвращаем результат отправки торгового запроса из метода OpenPosition()
   return this.OpenPosition(POSITION_TYPE_SELL,volume,symbol,magic,sl,tp,comment,deviation);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер                                   |
//+------------------------------------------------------------------+
template<typename PS,typename PL,typename SL,typename TP>
bool CTrading::PlaceOrder(const ENUM_ORDER_TYPE order_type,
                          const double volume,
                          const string symbol,
                          const PS price_stop,
                          const PL price_limit=0,
                          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=WRONG_VALUE,
                          const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
  {
   bool res=true;
   this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR;
   ENUM_ACTION_TYPE action=(ENUM_ACTION_TYPE)order_type;
//--- Получаем объект-символ по имени символа
   CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol);
   if(symbol_obj==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ));
      return false;
     }
//--- Получаем торговый объект из объекта-символа
   CTradeObj *trade_obj=symbol_obj.GetTradeObj();
   if(trade_obj==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
//--- Устанавливаем цены
//--- Если установить не удалось - записываем флаг "внутренняя ошибка" устанавливаем код ошибки в структуру возврата,
//--- выводим сообщение в журнал и возвращаем false
   if(!this.SetPrices(order_type,price_stop,sl,tp,price_limit,DFUN,symbol_obj))
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      trade_obj.SetResultRetcode(10021);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(10021));   // Отсутствуют котировки для обработки запроса
      return false;
     }
     
//--- Если есть ограничения по разрешённости торговли, не хватает средств,
//--- есть ограничения по уровню StopLevel - воспроизводим звук ошибки и уходим
   this.m_request.volume=volume;
   this.m_request.type_filling=type_filling;
   this.m_request.type_time=type_time;
   this.m_request.expiration=expiration;
   ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume,
                                                             this.m_request.price,
                                                             action,
                                                             order_type,
                                                             symbol_obj,
                                                             trade_obj,
                                                             DFUN,
                                                             this.m_request.stoplimit,
                                                             this.m_request.sl,
                                                             this.m_request.tp);
   if(method!=ERROR_CODE_PROCESSING_METHOD_OK)
     {
      //--- Если полный запрет торговли
      if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE)
        {
         trade_obj.SetResultRetcode(MSG_LIB_TEXT_TRADING_DISABLE);
         trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE));
         if(this.IsUseSounds())
            trade_obj.PlaySoundError(action,order_type);
         return false;
        }
      //--- Если результат проверки "прервать торговую операцию" - устанавливаем код последней ошибки в структуру возврата,
      //--- выводим сообщение в журнал, воспроизводим звук ошибки и уходим
      if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
        {
         int code=this.m_list_errors.At(this.m_list_errors.Total()-1);
         if(code!=NULL)
           {
            trade_obj.SetResultRetcode(code);
            trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
           }
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_OPERATION_ABORTED));
         if(this.IsUseSounds())
            trade_obj.PlaySoundError(action,order_type);
         return false;
        }
      //--- Если результат проверки "ожидание" - устанавливаем код последней ошибки в структуру возврата И выводим сообщение в журнал
      if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
        {
         int code=this.m_list_errors.At(this.m_list_errors.Total()-1);
         if(code!=NULL)
           {
            trade_obj.SetResultRetcode(code);
            trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
           }
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST));
         //--- Временно, вместо создания отложенного запроса, ожидаем требуемое время (возвращается результатом метода CheckErrors())
         ::Sleep(method);           
         symbol_obj.Refresh();
        }
      //--- Если результат проверки "создать отложенный запрос" - временно ничего не делаем
      if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST)
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST));
        }
     }

//--- В цикле по количеству попыток
   for(int i=0;i<this.m_total_try;i++)
     {                
      //--- Отсылаем запрос
      res=trade_obj.SetOrder(order_type,
                             this.m_request.volume,
                             this.m_request.price,
                             this.m_request.sl,
                             this.m_request.tp,
                             this.m_request.stoplimit,
                             magic,
                             comment,
                             this.m_request.expiration,
                             this.m_request.type_time,
                             this.m_request.type_filling);
      //--- Если запрос выполнен успешно или асинхронный режим отправки ордеров - проиграем звук успеха,
      //--- установленный торговому объекту символа для данного типа торговой операции и вернём true
      if(res || trade_obj.IsAsyncMode())
        {
         if(this.IsUseSounds())
            trade_obj.PlaySoundSuccess(action,order_type);
         return true;
        }
      //--- Если запрос не выполнен - выведем сообщение и проиграем звук ошибки, установленный торговому объекту символа для данного типа торговой операции
      else
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_TRY_N),string(i+1),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode()));
         if(this.IsUseSounds())
            trade_obj.PlaySoundError(action,order_type);
         
         method=this.ResultProccessingMethod(trade_obj.GetResultRetcode());
         //--- Если в результате отправки запроса получили "Запретить торговлю экспертом" - взводим флаг запрета и прерываем цикл попыток
         if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE)
           {
            this.SetTradingDisableFlag(true);
            break;
           }
         //--- Если в результате отправки запроса получили "Выйти из торгового метода" - прерываем цикл попыток
         if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
           {
            break;
           }
         //--- Если в результате отправки запроса получили "Скорректировать параметры и повторить" -
         //--- корректируем параметры и идём на следующую итерацию
         if(method==ERROR_CODE_PROCESSING_METHOD_CORRECT)
           {
            this.RequestErrorsCorrecting(this.m_request,order_type,trade_obj.SpreadMultiplier(),symbol_obj,trade_obj);
            continue;
           }
         //--- Если в результате отправки запроса получили "Обновить данные и повторить" -
         //--- обновляем данные и идём на следующую итерацию
         if(method==ERROR_CODE_PROCESSING_METHOD_REFRESH)
           {
            symbol_obj.Refresh();
            continue;
           }
         //--- Если в результате отправки запроса получили "Подождать и повторить" -
         //--- в данном исполнении ожидаем количество милисекунд, равным значению method и идём на следующую итерацию
         if(method==ERROR_CODE_PROCESSING_METHOD_WAIT)
           {
            Sleep(method);
            continue;
           }
         //--- Если в результате отправки запроса получили "Создать отложенный запрос" -
         //--- создаём отложенный запрос с параметрами торгового запроса и прерываем цикл попыток
         if(method==ERROR_CODE_PROCESSING_METHOD_PENDING)
           {
            break;
           }
        }
     }
//--- Возвращаем результат отправки торгового запроса в торговом объекте символа
   return res;
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер BuyStop                           |
//+------------------------------------------------------------------+
template<typename PS,typename SL,typename TP>
bool CTrading::PlaceBuyStop(const double volume,
                            const string symbol,
                            const PS 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=WRONG_VALUE,
                            const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
  {
//--- Возвращаем результат отправки торгового запроса методом PlaceOrder()
   return this.PlaceOrder(ORDER_TYPE_BUY_STOP,volume,symbol,price,0,sl,tp,magic,comment,expiration,type_time,type_filling);
  }
//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер BuyLimit                          |
//+------------------------------------------------------------------+
template<typename PS,typename SL,typename TP>
bool CTrading::PlaceBuyLimit(const double volume,
                             const string symbol,
                             const PS 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=WRONG_VALUE,
                             const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
  {
//--- Возвращаем результат отправки торгового запроса методом PlaceOrder()
   return this.PlaceOrder(ORDER_TYPE_BUY_LIMIT,volume,symbol,price,0,sl,tp,magic,comment,expiration,type_time,type_filling);
  }
//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер BuyStopLimit                      |
//+------------------------------------------------------------------+
template<typename PS,typename PL,typename SL,typename TP>
bool CTrading::PlaceBuyStopLimit(const double volume,
                                 const string symbol,
                                 const PS 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=WRONG_VALUE,
                                 const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
  {
#ifdef __MQL5__
//--- Возвращаем результат отправки торгового запроса методом PlaceOrder()
   return this.PlaceOrder(ORDER_TYPE_BUY_STOP_LIMIT,volume,symbol,price_stop,price_limit,sl,tp,magic,comment,expiration,type_time,type_filling);
//--- MQL4
#else 
   return true;
#endif 
  }
//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер SellStop                          |
//+------------------------------------------------------------------+
template<typename PS,typename SL,typename TP>
bool CTrading::PlaceSellStop(const double volume,
                             const string symbol,
                             const PS 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=WRONG_VALUE,
                             const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
  {
//--- Возвращаем результат отправки торгового запроса методом PlaceOrder()
   return this.PlaceOrder(ORDER_TYPE_SELL_STOP,volume,symbol,price,0,sl,tp,magic,comment,expiration,type_time,type_filling);
  }
//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер SellLimit                         |
//+------------------------------------------------------------------+
template<typename PS,typename SL,typename TP>
bool CTrading::PlaceSellLimit(const double volume,
                              const string symbol,
                              const PS 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=WRONG_VALUE,
                              const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
  {
//--- Возвращаем результат отправки торгового запроса методом PlaceOrder()
   return this.PlaceOrder(ORDER_TYPE_SELL_LIMIT,volume,symbol,price,0,sl,tp,magic,comment,expiration,type_time,type_filling);
  }
//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер SellStopLimit                     |
//+------------------------------------------------------------------+
template<typename PS,typename PL,typename SL,typename TP>
bool CTrading::PlaceSellStopLimit(const double volume,
                                  const string symbol,
                                  const PS 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=WRONG_VALUE,
                                  const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE)
  {
#ifdef __MQL5__
//--- Возвращаем результат отправки торгового запроса методом PlaceOrder()
   return this.PlaceOrder(ORDER_TYPE_SELL_STOP_LIMIT,volume,symbol,price_stop,price_limit,sl,tp,magic,comment,expiration,type_time,type_filling);
   //--- MQL4
#else 
   return true;
#endif 
  }
//+------------------------------------------------------------------+

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

С торговым классом на данном этапе мы завершили.

Теперь необходимо внести некоторые изменения в класс основного объекта библиотеки CEngine.

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

//--- Устанавливает множитель спреда торговым объектам символов в коллекции символов
   void                 SetSpreadMultiplier(const uint value=1,const string symbol=NULL)  { this.m_trading.SetSpreadMultiplier(value,symbol);   }

//--- Открывает позицию (1) Buy, (2) Sell

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

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

//+------------------------------------------------------------------+
//| CEngine конструктор                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),
                     m_last_trade_event(TRADE_EVENT_NO_EVENT),
                     m_last_account_event(WRONG_VALUE),
                     m_last_symbol_event(WRONG_VALUE),
                     m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE);
   this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE);

   this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2);
   this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE);
   
   ::ResetLastError();
   #ifdef __MQL5__
      if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   //---__MQL4__
   #else 
      if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   #endif 
   //---
  }
//+------------------------------------------------------------------+

В таймер класса CEngine впишем блок работы с таймером торгового класса:

//+------------------------------------------------------------------+
//| CEngine таймер                                                   |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Таймер коллекций исторических ордеров и сделок и рыночных ордеров и позиций
   int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- Если это не тестер
         if(!this.IsTester())
           {
            //--- Если пауза завершилась - работаем с событиями коллекций ордеров, сделок и позиций
            if(counter.IsTimeDone())
               this.TradeEventsControl();
           }
         //--- Если тестер - работаем с событиями коллекций по тику
         else
            this.TradeEventsControl();
        }
     }
//--- Таймер коллекции аккаунтов
   index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- Если это не тестер
         if(!this.IsTester())
           {
            //--- Если пауза завершилась - работаем с событиями коллекции аккаунтов
            if(counter.IsTimeDone())
               this.AccountEventsControl();
           }
         //--- Если тестер - работаем с событиями коллекций по тику
         else
            this.AccountEventsControl();
        }
     }
     
//--- Таймер1 коллекции символов (обновление котировочных данных символов в коллекции)
   index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- Если это не тестер
         if(!this.IsTester())
           {
            //--- Если пауза завершилась - обновляем котировочные данные всех символов в коллекции
            if(counter.IsTimeDone())
               this.m_symbols.RefreshRates();
           }
         //--- Если тестер - обновляем котировочные данные всех символов в коллекции по тику
         else
            this.m_symbols.RefreshRates();
        }
     }
//--- Таймер2 коллекции символов (обновление всех данных всех символов в коллекции и отслеживание событий символов и списка символов в окне обзора рынка)
   index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- Если это не тестер
         if(!this.IsTester())
           {
            //--- Если пауза завершилась
            if(counter.IsTimeDone())
              {
               //--- обновляем данные и работаем с событиями всех символов в коллекции
               this.SymbolEventsControl();
               //--- Если работаем со списком из обзора рынка - проверяем события окна обзора рынка
               if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH)
                  this.MarketWatchEventsControl();
              }
           }
         //--- Если тестер - работаем с событиями всех символов в коллекции по тику
         else
            this.SymbolEventsControl();
        }
     }
//--- Таймер торгового класса
   index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- Если это не тестер
         if(!this.IsTester())
           {
            //--- Если пауза завершилась - работаем со списком отложенных запросов
            if(counter.IsTimeDone())
               this.m_trading.OnTimer();
           }
         //--- Если тестер - работаем со списком отложенных запросов по тику
         else
            this.m_trading.OnTimer();
        }
     }   
  }
//+------------------------------------------------------------------+

Чуть изменим метод для полного закрытия позиции:

//+------------------------------------------------------------------+
//| Закрывает позицию полностью                                      |
//+------------------------------------------------------------------+
bool CEngine::ClosePosition(const ulong ticket,const string comment=NULL,const ulong deviation=ULONG_MAX)
  {
   return this.m_trading.ClosePosition(ticket,WRONG_VALUE,comment,deviation);
  }
//+------------------------------------------------------------------+

Так как метод для закрытия позиции у нас теперь един для полного и частичного закрытия, то для полного закрытия позиции нам необходимо в качестве объёма закрываемой позиции передать -1, что мы тут и делаем.

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

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

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

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

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

//--- input variables
input    ulong             InpMagic             =  123;  // Magic number
input    double            InpLots              =  0.1;  // Lots
input    uint              InpStopLoss          =  50;   // StopLoss in points
input    uint              InpTakeProfit        =  50;   // TakeProfit in points
input    uint              InpDistance          =  50;   // Pending orders distance (points)
input    uint              InpDistanceSL        =  50;   // StopLimit orders distance (points)
input    uint              InpSlippage          =  5;    // Slippage in points
input    uint              InpSpreadMultiplier  =  1;    // Spread multiplier for adjusting stop-orders by StopLevel
sinput   double            InpWithdrawal        =  10;   // Withdrawal funds (in tester)
sinput   uint              InpButtShiftX        =  40;   // Buttons X shift 
sinput   uint              InpButtShiftY        =  10;   // Buttons Y shift 
input    uint              InpTrailingStop      =  50;   // Trailing Stop (points)
input    uint              InpTrailingStep      =  20;   // Trailing Step (points)
input    uint              InpTrailingStart     =  0;    // Trailing Start (points)
input    uint              InpStopLossModify    =  20;   // StopLoss for modification (points)
input    uint              InpTakeProfitModify  =  60;   // TakeProfit for modification (points)
sinput   ENUM_SYMBOLS_MODE InpModeUsedSymbols   =  SYMBOLS_MODE_CURRENT;   // Mode of used symbols list
sinput   string            InpUsedSymbols       =  "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)
sinput   bool              InpUseSounds         =  true; // Use sounds

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

//+------------------------------------------------------------------+
//| Инициализация библиотеки 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);
//--- Установка множителя спреда торговым объектам символов в коллекции символов
   engine.SetSpreadMultiplier(InpSpreadMultiplier);
      
//--- Установка контрольных значений для символов
   //--- Получаем список всех символов коллекции
   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(100000*symbol.Point());
         //--- Установка контроля уменьшения цены символа на 100 пунктов
         symbol.SetControlBidDec(100000*symbol.Point());
         //--- Установка контроля увеличения спреда символа на 40 пунктов
         symbol.SetControlSpreadInc(400);
         //--- Установка контроля уменьшения спреда символа на 40 пунктов
         symbol.SetControlSpreadDec(400);
         //--- Установка контроля размера спреда по значению 40 пунктов
         symbol.SetControlSpreadLevel(400);
        }
      */
     }
//--- Установка контрольных значений для текущего аккаунта
   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);
     }
  }
//+------------------------------------------------------------------+

Установим в тестере стратегий задержку исполнения в 4 секунды.
Для этого в выпадающем меню выберем "Пользовательская задержка..."

... и в появившемся поле ввода введём 4000 милисекунд:


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

Запустим советник в визуальном режиме и попробуем открыть несколько позиций, а потом закрыть их на быстром рынке:

Как видим, здесь не всегда удаётся открыть позицию с первого раза — получаем реквоту. Советник делает нужное количество торговых попыток, но не более пяти (задано по умолчанию), и это видно по записям "Trading attempt" с номером попытки и пояснением ошибки "Requote". При одновременном закрытии позиций тоже получили реквоты, и последняя позиция по истечению пяти попыток так и не была закрыта. Далее уже вручную её удалось закрыть — так же не с первой попытки. Но советник "честно" отработал заложенный в библиотеку алгоритм с установленным количеством повторных торговых попыток.

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

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

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

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


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

Всё это, и многое другое, связанное с параметрами символа, возможно протестировать в тестере бета-версий терминала, начиная с билда 2201.
Чтобы получить последнюю бета-версию терминала, необходимо просто подключиться к серверу MetaQuotes-Demo и в меню "Помощь" выбрать пункт "Проверить последнюю бета-версию":


Что дальше

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

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

К содержанию

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

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

Прикрепленные файлы |
MQL5.zip (3622.71 KB)
MQL4.zip (3622.71 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (15)
Igor Ryabchikov
Igor Ryabchikov | 22 дек. 2019 в 16:00
Artyom Trishkin:

Про информацию, записанную в значение Magic Number:

вы можете для создания различных групп использовать для каждой группы свой собственный магик. Например, если магик советника 123, то магик первой группы будет 124, магик второй группы 125, третьей - 126, и т.д..
Библиотека предлагает иной способ создания различных групп - номер каждой подгруппы хранится прямо в значении Magic Number. ТОгда магик советника - это тоже идентификатор группы, но он вынесен в независимую группу, которая называется MagicID - идентификатор магика советника. И есть ещё две группы. В каждой из них есть 15 подгрупп. И каждой из подгрупп можно задать свой идентификатор.

Это даст большую гибкость при работе с группами.

Например: Мы хотим перемещать сетку отложенных ордеров за ценой - добавляем их в группу 1 в подгруппу 1. Группа 1 перемещается за ценой. Подгруппа 1 перемещается по МА. Теперь какие-то из тех ордеров, которые перемещаются за ценой (группа 1) мы хотим перемещать по Parabolic SAR. Даём им подгруппу 2. Тогда группа 1 - перемещается за ценой, но подгруппа 1 перемещается по МА, а подгруппа 2 - по Parabolic SAR.

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

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

По второму вопросу:

Есть класс CSelect. Он доступен из программы, и предоставляет способы выбора и поиска, о которых вы пишете, из всех существующих коллекций: Account, Event, Order, Symbol.

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

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

Но пока - только посредством CSelect, и тогда, когда вам это нужно. Класс статический, поэтому доступ к к его методам через "::" Например, CSelect::ByOrderProperty().

Да, кстати, и пример использования в программе есть прямо в тестовом советнике - например, в его функциях трейлинга:

С CSelect я разобрался, но получается, придется проделывать выбор в каждом месте, где нам нужны агрегированные величины: скажем, мне нужно вычислять общий профит сетки позиций, чтобы его тралить, это одна функция. А в другой я, скажем, подправляю общий тейк сетки после открытия, или закрытия одного из ордеров - и для этого мне нужен общий профит и совокупный объем.

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

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

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

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

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

Artyom Trishkin
Artyom Trishkin | 22 дек. 2019 в 17:49
rigal:

С CSelect я разобрался, но получается, придется проделывать выбор в каждом месте, где нам нужны агрегированные величины: скажем, мне нужно вычислять общий профит сетки позиций, чтобы его тралить, это одна функция. А в другой я, скажем, подправляю общий тейк сетки после открытия, или закрытия одного из ордеров - и для этого мне нужен общий профит и совокупный объем.

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

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

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

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

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

А вы предлагаете утяжелить расчёты в самой библиотеке? Там и так много всего считается.

Почему бы не сделать предлагаемое прямо в советнике вместо библиотеки? Разницы-то нет где всё это считать. Но кому-то это нужно (вам), а кому-то - нет. И зачем ему лишние вычисления?

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

Igor Ryabchikov
Igor Ryabchikov | 23 дек. 2019 в 04:43
Artyom Trishkin:

А вы предлагаете утяжелить расчёты в самой библиотеке? Там и так много всего считается.

Почему бы не сделать предлагаемое прямо в советнике вместо библиотеки? Разницы-то нет где всё это считать. Но кому-то это нужно (вам), а кому-то - нет. И зачем ему лишние вычисления?

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

Ну я предлагаю считать опционально.

Заводить "подписку".

Но если Вам эта идея не кажется достойной внимания, я, конечно, сам напишу обертку.

Еще раз спасибо за то, что уже сделано - и наперед за все, что еще в планах (я то тут то там встречаю оговорки о планах продолжать развитие библиотеки)

Artyom Trishkin
Artyom Trishkin | 23 дек. 2019 в 05:16
rigal:

Ну я предлагаю считать опционально.

Заводить "подписку".

Но если Вам эта идея не кажется достойной внимания, я, конечно, сам напишу обертку.

Еще раз спасибо за то, что уже сделано - и наперед за все, что еще в планах (я то тут то там встречаю оговорки о планах продолжать развитие библиотеки)

В планах будет похожий функционал. И, да, планов ещё очень много - сделано меньше трети запланированного.
Igor Ryabchikov
Igor Ryabchikov | 24 дек. 2019 в 07:19
Я внимательно слежу :)
Непрерывная скользящая оптимизация (Часть 1): Механизм работы с отчетами оптимизации Непрерывная скользящая оптимизация (Часть 1): Механизм работы с отчетами оптимизации
Первая часть статьи посвящена созданию инструментария для работы с отчетностью оптимизации, ее импорта из терминала, а также процессам фильтрации и сортировки полученных данных. MetaTrader 5 позволяет выгружать отчет проходов оптимизаций, но хотелось бы иметь возможность добавления в отчет собственных данных.
Разработка Pivot Mean Oscillator: новый осциллятор на кумулятивном скользящем среднем Разработка Pivot Mean Oscillator: новый осциллятор на кумулятивном скользящем среднем
В статье описывается осциллятор Pivot Mean Oscillator (PMO), который представляет собой реализацию торговых сигналов на основе индикатора кумулятивного скользящего среднего для платформ MetaTrader. В частности, сначала будет рассмотрено понятие Pivot Mean (PM) — индекс нормализации временных рядов, который вычисляет соотношение между любой точкой данных и скользящей CMA. Затем построим осциллятор PMO как разницу между скользящими средними, построенными по двум сигналам PM. Также в статье будут показаны эксперименты на символе EURUSD, которые проводились для проверки эффективности индикатора.
Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXVI): Работа с отложенными торговыми запросами - первая реализация (открытие позиций) Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXVI): Работа с отложенными торговыми запросами - первая реализация (открытие позиций)
В статье организуем хранение некоторых данных в значении магического номера ордеров и позиций и приступим к реализации отложенных запросов. Для проверки концепции создадим первый тестовый отложенный запрос на открытие рыночных позиций при получении от сервера ошибки, требующей ожидания и отправки повторного запроса.
Построение советника с использованием отдельных модулей Построение советника с использованием отдельных модулей
В процессе разработки индикаторов, советников и скриптов, разработчику приходится постоянно создавать законченные фрагменты кода, непосредственного отношения к стратегии торговли не имеющих. В статье рассмотрен способ проектирования советников с использованием ранее спроектированных отдельных блоков - тралов, фильтров, расписаний и т.п. Разобрана полезная особенность такого рода проектирования.