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

30 октября 2019, 07:24
Artyom Trishkin
0
1 845

Содержание

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

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

  1. При обнаружении ошибки в торговом приказе, просто выйти из торгового метода, предоставив пользователю самому создать обработчик неверных параметров ошибочного приказа.
  2. Определив возможность исправления некорректного значения в торговом приказе, сразу же исправить значения и отослать корректный торговый приказ,
  3. Либо, если того требует ситуация и суть полученной ошибки — повторить заново торговый запрос после паузы, либо просто повторить запрос с теми же параметрами.

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

  • Невозможность продолжения торговли экспертом до исправления пользователем причины ошибок.
  • Невозможность отсылки торгового приказа — выход из торгового метода.
  • Корректировка неверных значений и отсылка исправленного торгового приказа.
  • Немедленная отсылка торгового приказа с изначальными параметрами (с предположением, что торговые условия улучшились).
  • Ожидание, обновление котировочных данных и отсылка торгового приказа с изначальными параметрами.

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

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

Концепция

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

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

Корректировка ошибочных параметров будет работать следующим образом: при проверке корректности торгового запроса у нас составляется список всех найденных ошибок. Метод проверки параметров будет просматривать все ошибки из данного списка и возвращать код поведения торгового метода. Если будет обнаружена ошибка, при которой нет возможности дальнейшей торговли, то метод будет возвращать код выхода из торгового метода, так как отсылка торгового приказа всё равно не приведёт к положительному результату. Если будет встречена исправимая ошибка, то будут вызваны методы коррекции соответствующих значений торгового приказа и возвращён результат успешного прохождения проверки. Также метод будет возвращать коды поведения торгового метода как "Подождать и повторить", "Обновить данные и повторить" и "Создать отложенный запрос".

Что это означает?

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

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

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

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

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

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

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

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

Исправление определения торговых событий

Так как наши объекты практически все создаются на основе базового объекта всех объектов библиотеки, а в нём уже имеется список событий и метод, возвращающий флаг произошедшего события этого объекта, то в список событий базового объекта мы добавляем все торговые события, а флаг события можем получать из данного класса методом IsEvent(). Флаги событий устанавливаются классом автоматически. Но нам нужно будет иметь возможность и самостоятельно устанавливать флаг произошедшего торгового события из других классов и их обработчиков событий.
Для этого добавим метод установки флага события базового объекта в класс CEventBaseObj в файле BaseObj.mqh:

//--- Устанавливает/Возвращает флаг произошедшего события в данных объекта
   void              SetEvent(const bool flag)                       { this.m_is_event=flag;                   }
   bool              IsEvent(void)                             const { return this.m_is_event;                 }

Теперь в классе коллекции торговых событий CEventsCollection при появлении любого нового события нам необходимо создать описание этого события, поместить его в список новых событий базового класса всех объектов и выставить флаг нового события.

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

Внесём все необходимые доработки в файл класса торговых событий EventsCollection.mqh.

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

//--- Возвращает (1) последнее торговое событие на счёте, (2) объект базового события по индексу, (3) количество новых событий
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)                const { return this.m_trade_event;                 }
   CEventBaseObj    *GetTradeEventByIndex(const int index)        { return this.GetEvent(index,false);         }
   int               GetTradeEventsTotal(void)              const { return this.m_list_events.Total();         }
//--- Сбрасывает последнее торговое событие

Метод, возвращающий объект базового события по индексу, вызывает метод базового объекта GetEvent(), в который передаём индекс требуемого события, и сброшенный флаг (false) проверки выхода индекса за пределы списка событий — чтобы не корректировать возвращаемое событие, если индекс выходит за пределы списка событий. Т.е. если передаём несуществующий индекс, то метод вернёт NULL. Если бы мы в значении флага передали true, то метод бы вернул последнее событие, что здесь нам не нужно.

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

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

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

Во всех методах создания нового события CreateNewEvent() после строки отправки события необходимо дописать добавление этого события в список базовых событий:

         event.SendEvent();
         CBaseObj::EventAdd(this.m_trade_event,order.Ticket(),order.Price(),order.Symbol());

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

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

//--- Возвращает (1) список событий ордеров, сделок и позиций, (2) объект базового торгового события по индексу, (3) количество новых торговых событий
   CArrayObj           *GetListAllOrdersEvents(void)                    { return this.m_events.GetList();                     }
   CEventBaseObj       *GetTradeEventByIndex(const int index)           { return this.m_events.GetTradeEventByIndex(index);   }
   int                  GetTradeEventsTotal(void)                 const { return this.m_events.GetTradeEventsTotal();         }
//--- Сбрасывает последнее торговое событие

Эти методы просто вызывают одноимённые методы класса коллекции торговых событий, рассмотренные выше.

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

Теперь можно приступать к дальнейшей доработке торгового класса.

Обработчик ошибок в параметрах торгового запроса

В первую очередь добавим индексы необходимых сообщений в файл Datas.mqh:

   MSG_LIB_TEXT_REQUEST_REJECTED_DUE,                 // Запрос отклонён до отправки на сервер по причине:
   MSG_LIB_TEXT_INVALID_REQUEST,                      // Ошибочный запрос:
   MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR,                 // Недостаточно средств для совершения торговой операции
   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,          // Нет возможности скорректировать лот
   
  };

и соответствующие этим индексам тексты:

   {"Запрос отклонён до отправки на сервер по причине:","The request was rejected before being sent to the server due to:"},
   {"Ошибочный запрос:","Invalid request:"},
   {"Недостаточно средств для совершения торговой операции","Not enough money to perform trading operation"},
   {"Неподдерживаемый тип параметра цены в запросе","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"},
   
  };

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

Чтобы задать непосредственно эксперту поведение при получении ошибки
добавим перечисление, описывающее возможное поведение эксперта при обнаружении ошибки в торговом запросе, или при возврате ошибки торговым сервером:

//+------------------------------------------------------------------+
//| Поведение эксперта при обработке ошибок                          |
//+------------------------------------------------------------------+
enum ENUM_ERROR_HANDLING_BEHAVIOR
  {
   ERROR_HANDLING_BEHAVIOR_BREAK,                           // Прервать торговую попытку
   ERROR_HANDLING_BEHAVIOR_CORRECT,                         // Скорректировать ошибочные параметры
   ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST,                 // Создать отложенный запрос
  };
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Флаги, указывающие на методы обработки ошибок торгового запроса  |
//+------------------------------------------------------------------+
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)
  };
//+------------------------------------------------------------------+

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

  • 0 — нет ошибки, а значит можно отправлять торговый приказ,
  • 1 — фатальная ошибка — при такой ошибке продолжать торговые попытки абсолютно бесполезно, и эксперта нужно переводить в режим аналитического неторгового помощника,
  • 2 — что-то пошло не так, и был сбой в библиотеке — просто прервать дальнейшее выполнение торгового метода во избежание неправильной работы торгового класса,
  • 4 — ошибку возможно исправить, и она записана в список ошибок для вызова метода их исправления.

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

//+------------------------------------------------------------------+
//| Методы обработки ошибок и кодов возврата сервера                 |
//+------------------------------------------------------------------+
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_REFRESH,                    // Обновить данные и повторить
   ERROR_CODE_PROCESSING_METHOD_WAIT,                       // Подождать и повторить
   ERROR_CODE_PROCESSING_METHOD_PENDING,                    // Создать отложенный запрос
  };
//+------------------------------------------------------------------+

Из описаний констант перечисления понятны и методы обработки ошибочных ситуаций.

Также доработаем базовый торговый объект.

В зависимости от способа построения баров на чарте, торговля осуществляется либо по ценам Ask и Bid, либо по ценам Ask и Last. На данный момент в базовом торговом классе организована торговля только по ценам Ask и Bid. Добавим проверку того, по каким ценам строится график, и исправим цены, по которым будем торговать. Также, для проверки кодов, возвращаемых торговым сервером — уже после отправки торгового приказа на сервер, в MQL5 существует структура результата торгового запроса MqlTradeResult, поля retcode и comment которой будут содержать код ошибки и описание кода ошибки соответственно. Для MQL4 такой возможности не предусмотрено — там код ошибки нужно читать функцией GetLastError(), возвращающей код последней ошибки. Так как библиотека у нас мультиплатформенная, то для MQL4 нам необходимо самостоятельно заполнить поля структуры результата торгового запроса после его отправки на сервер.

При проверке дистанции установки стоп-приказов относительно цены мы также соблюдаем дистанции установки минимально-разрешённого размера стоп-уровней (StopLevel), который установлен для символа. Если значение StopLevel, возвращаемое функцией SymbolInfoInteger() с идентификатором свойства SYMBOL_TRADE_STOPS_LEVEL, равно нулю, то это не означает отсутствие уровня минимального отступа в пунктах от цены для стоп-приказов, а только то, что этот уровень плавающий. Таким образом, для корректировки размера отступа стоп-приказов от цены нужно подбирать уровень "по месту" или использовать для величины отступа размер текущего спреда, помноженный на некоторое значение. Обычно, для беспроблемной корректировки уровня установки стопов используется величина двойного спреда. Для установки каждому торговому объекту каждого символа величины этого множителя добавим в торговый объект сам множитель, и методы его возврата и установки.

Внесём необходимые правки в класс базового торгового объекта CTradeObj в файле TradeObj.mqh.

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

   SActions                   m_datas;
   MqlTick                    m_tick;                                            // Структура тика для получения цен
   MqlTradeRequest            m_request;                                         // Структура торгового запроса
   MqlTradeResult             m_result;                                          // Структура результата исполнения торгового запроса
   ENUM_SYMBOL_CHART_MODE     m_chart_mode;                                      // Тип цены для построения баров
   ENUM_ACCOUNT_MARGIN_MODE   m_margin_mode;                                     // Режим расчёта маржи
   ENUM_ORDER_TYPE_FILLING    m_type_filling;                                    // Политика исполнения
   ENUM_ORDER_TYPE_TIME       m_type_expiration;                                 // Тип истечения ордера
   int                        m_symbol_expiration_flags;                         // Флаги режимов истечения ордера для символа торгового объекта
   ulong                      m_magic;                                           // Магик
   string                     m_symbol;                                          // Символ
   string                     m_comment;                                         // Комментарий
   ulong                      m_deviation;                                       // Размер проскальзывания в пунктах
   double                     m_volume;                                          // Объём
   datetime                   m_expiration;                                      // Срок истечения ордера (для ордеров типа ORDER_TIME_SPECIFIED)
   bool                       m_async_mode;                                      // Флаг асинхронной отправки торгового запроса
   ENUM_LOG_LEVEL             m_log_level;                                       // Уровень логирования
   int                        m_stop_limit;                                      // Дистанция установки StopLimit ордера в пунктах
   bool                       m_use_sound;                                       // Флаг использования звуков торговых событий объекта
   uint                       m_multiplier;                                      // Множитель спреда для корректировки уровней относительно StopLevel
   

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

public:
//--- Конструктор
                              CTradeObj();

//--- Устанавливает/возвращает множитель спреда
   void                       SetSpreadMultiplier(const uint value)        { this.m_multiplier=(value==0 ? 1 : value);  }
   uint                       SpreadMultiplier(void)                 const { return this.m_multiplier;                  }
//--- Устанавливает значения по умолчанию

При установке значения множителю спреда, проверяем переданное в метод значение на равенство нулю, и если равно, то присваиваем значение 1.

Также в публичной секции класса впишем два метода — метод, устанавливающий код ошибки торгового запроса, и
метод, устанавливающий описание кода ошибки торгового запроса:

//--- Устанавливает код ошибки в результат последнего запроса
   void                       SetResultRetcode(const uint retcode)                     { this.m_result.retcode=retcode;       }
   void                       SetResultComment(const string comment)                   { this.m_result.comment=comment;       }
//--- Данные результата последнего запроса:

В конструкторе класса присвоим умолчательное значение множителю спреда, равное 1:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CTradeObj::CTradeObj(void) : m_magic(0),
                             m_deviation(5),
                             m_stop_limit(0),
                             m_expiration(0),
                             m_async_mode(false),
                             m_type_filling(ORDER_FILLING_FOK),
                             m_type_expiration(ORDER_TIME_GTC),
                             m_comment(::MQLInfoString(MQL_PROGRAM_NAME)+" by DoEasy"),
                             m_log_level(LOG_LEVEL_ERROR_MSG)
  {
   //--- Режим расчёта маржи
   this.m_margin_mode=
     (
      #ifdef __MQL5__ (ENUM_ACCOUNT_MARGIN_MODE)::AccountInfoInteger(ACCOUNT_MARGIN_MODE)
      #else /* MQL4 */ ACCOUNT_MARGIN_MODE_RETAIL_HEDGING #endif 
     );
   //--- Множитель спреда
   this.m_multiplier=1;
   //--- Установка звуков и флагов использования звуков по умолчанию
   this.m_use_sound=false;
   this.InitSounds();
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Устанавливает значения по умолчанию                              |
//+------------------------------------------------------------------+
void CTradeObj::Init(const string symbol,
                     const ulong magic,
                     const double volume,
                     const ulong deviation,
                     const int stoplimit,
                     const datetime expiration,
                     const bool async_mode,
                     const ENUM_ORDER_TYPE_FILLING type_filling,
                     const ENUM_ORDER_TYPE_TIME type_expiration,
                     ENUM_LOG_LEVEL log_level)
  {
   this.SetSymbol(symbol);
   this.SetMagic(magic);
   this.SetDeviation(deviation);
   this.SetVolume(volume);
   this.SetExpiration(expiration);
   this.SetTypeFilling(type_filling);
   this.SetTypeExpiration(type_expiration);
   this.SetAsyncMode(async_mode);
   this.SetLogLevel(log_level);
   this.m_symbol_expiration_flags=(int)::SymbolInfoInteger(this.m_symbol,SYMBOL_EXPIRATION_MODE);
   this.m_volume=::SymbolInfoDouble(this.m_symbol,SYMBOL_VOLUME_MIN);
   this.m_chart_mode=#ifdef __MQL5__ (ENUM_SYMBOL_CHART_MODE)::SymbolInfoInteger(this.m_symbol,SYMBOL_CHART_MODE) #else SYMBOL_CHART_MODE_BID #endif ;
  }
//+------------------------------------------------------------------+

Здесь: для MQL5 получаем данные при помощи функции SymbolInfoInteger() с идентификатором SYMBOL_CHART_MODE, а
для MQL4записываем, что бары строятся по цене Bid.

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

//+------------------------------------------------------------------+
//| Открывает позицию                                                |
//+------------------------------------------------------------------+
bool CTradeObj::OpenPosition(const ENUM_POSITION_TYPE type,
                             const double volume,
                             const double sl=0,
                             const double tp=0,
                             const ulong magic=ULONG_MAX,
                             const string comment=NULL,
                             const ulong deviation=ULONG_MAX)
  {
   ::ResetLastError();
   //--- Если не удалось получить текущие цены - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
   if(!::SymbolInfoTick(this.m_symbol,this.m_tick))
     {
      this.m_result.retcode=::GetLastError();
      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_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode));
      return false;
     }
   //--- Очищаем структуры
   ::ZeroMemory(this.m_request);
   ::ZeroMemory(this.m_result);
   //--- Заполняем структуру запроса
   this.m_request.action   =  TRADE_ACTION_DEAL;
   this.m_request.symbol   =  this.m_symbol;
   this.m_request.magic    =  (magic==ULONG_MAX ? this.m_magic : magic);
   this.m_request.type     =  OrderTypeByPositionType(type);
   this.m_request.price    =  (type==POSITION_TYPE_BUY ? this.m_tick.ask : (this.m_chart_mode==SYMBOL_CHART_MODE_BID ? this.m_tick.bid : this.m_tick.last));
   this.m_request.volume   =  volume;
   this.m_request.sl       =  sl;
   this.m_request.tp       =  tp;
   this.m_request.deviation=  (deviation==ULONG_MAX ? this.m_deviation : deviation);
   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);
   if(ticket!=WRONG_VALUE)
     {
      ::SymbolInfoTick(this.m_symbol,this.m_tick);
      this.m_result.retcode=::GetLastError();
      this.m_result.ask=this.m_tick.ask;
      this.m_result.bid=this.m_tick.bid;
      this.m_result.deal=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
     {
      ::SymbolInfoTick(this.m_symbol,this.m_tick);
      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 
  }
//+------------------------------------------------------------------+

Здесь: для MQL5 мы, как и ранее, возвращаем результат работы функции OrderSend(), а для MQL4 проверяем номер тикета, который вернула MQL4-функция отправки ордера. В случае успешного выполнения торгового запроса, функция возвратит тикет открытого ордера. При ошибке — WRONG_VALUE. Поэтому проверяем что функция нам вернула не -1, и если да, то обновляем котировки по символу, заполняем поля структуры результата торгового запроса соответствующими данными и возвращаем true — функция выполнена успешно.
Если же функция отправки ордера вернула -1, то в структуру результата торгового запроса записываем код последней ошибки, текущие цены, расшифровку кода последней ошибки. Все остальные поля структуры оставляем нулевыми. В итоге возвращаем false — ошибка отправки торгового приказа.

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

//--- Данные результата последнего запроса:
//--- Возвращает (1) код результата операции, (2) тикет сделки, если она совершена, (3) тикет ордера, если он выставлен,
//--- (4) объем сделки, подтверждённый брокером, (5) цена в сделке, подтверждённая брокером,
//--- (6) текущая рыночная цена предложения (цена реквоты), (7) текущая рыночная цена спроса (цена реквоты)
//--- (8) комментарий брокера к операции (по умолчанию заполняется расшифровкой кода возврата торгового сервера),
//--- (9) идентификатор запроса, устанавливаемый терминалом при отправке, (10) код ответа внешней торговой системы
   uint                       GetResultRetcode(void)                             const { return this.m_result.retcode;        }
   ulong                      GetResultDeal(void)                                const { return this.m_result.deal;           }
   ulong                      GetResultOrder(void)                               const { return this.m_result.order;          }
   double                     GetResultVolume(void)                              const { return this.m_result.volume;         }
   double                     GetResultPrice(void)                               const { return this.m_result.price;          }
   double                     GetResultBid(void)                                 const { return this.m_result.bid;            }
   double                     GetResultAsk(void)                                 const { return this.m_result.ask;            }
   string                     GetResultComment(void)                             const { return this.m_result.comment;        }
   uint                       GetResultRequestID(void)                           const { return this.m_result.request_id;     }
   uint                       GetResultRetcodeEXT(void)                          const { return this.m_result.retcode_external;}

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

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

//+------------------------------------------------------------------+
//| Возвращает размер маржи, требуемый для открытия позиции          |
//| или установки отложенного ордера                                 |
//+------------------------------------------------------------------+
double CAccount::MarginForAction(const ENUM_ORDER_TYPE action,const string symbol,const double volume,const double price) const
  {
   double margin=EMPTY_VALUE;
   #ifdef __MQL5__
      return(!::OrderCalcMargin(ENUM_ORDER_TYPE(action%2),symbol,volume,price,margin) ? EMPTY_VALUE : margin);
   #else 
      return this.MarginFree()-::AccountFreeMarginCheck(symbol,ENUM_ORDER_TYPE(action%2),volume);
   #endif
  }
//+------------------------------------------------------------------+

Всё, что нам здесь потребовалось добавить — это преобразование типа ордера, передаваемого в метод, к двум возможным значениям — ORDER_TYPE_BUY или ORDER_TYPE_SELL, так как функции MQL5 и MQL4, с которыми работает метод, требуют только такой тип ордеров.
Напомню, что остаток от деления на 2 значения константы типа ордера возвращает всегда одно из двух значений:

  • либо 0 (ORDER_TYPE_BUY),
  • либо 1 (ORDER_TYPE_SELL).

Что нам и нужно для преобразования в правильный тип ордера.

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

   struct SDataPrices
     {
      double            open;                // Цена установки
      double            limit;               // Цена установки limit-ордера
      double            sl;                  // Цена StopLoss
      double            tp;                  // Цена TakeProfit
     };
   SDataPrices          m_req_price;         // Цены торгового запроса

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

//+------------------------------------------------------------------+
//| Торговый класс                                                   |
//+------------------------------------------------------------------+
class CTrading
  {
private:
   CAccount            *m_account;                       // Указатель на объект-текущий аккаунт
   CSymbolsCollection  *m_symbols;                       // Указатель на список коллекции символов
   CMarketCollection   *m_market;                        // Указатель на список коллекции рыночных ордеров и позиций
   CHistoryCollection  *m_history;                       // Указатель на список коллекции исторических ордеров и сделок
   CArrayInt            m_list_errors;                   // Список ошибок
   bool                 m_is_trade_disable;              // Флаг запрета торговли
   bool                 m_use_sound;                     // Флаг использования звуков торговых событий объекта
   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; // Поведение при обработке ошибок
//--- Добавляет код ошибки в список
   bool                 AddErrorCodeToList(const int error_code);

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

//--- Возвращает факт наличия флага в причине ошибки торгового события
   bool                 IsPresentErrorFlag(const int code)     const { return (this.m_error_reason_flags & code)==code;                               }
//--- Возвращает наличие кода ошибки в списке
   bool                 IsPresentErorCode(const int code)            { this.m_list_errors.Sort(); return this.m_list_errors.Search(code)>WRONG_VALUE; }
//--- Устанавливает/Возвращает действие при обработке ошибок
   void                 SetErrorHandlingBehavior(const ENUM_ERROR_HANDLING_BEHAVIOR behavior) { this.m_err_handling_behavior=behavior;                }
   ENUM_ERROR_HANDLING_BEHAVIOR  ErrorHandlingBehavior(void)   const { return this.m_err_handling_behavior;                                           }

//--- Проверка ограничений для торговли

Из метода проверки достаточности средств удалим код вывода сообщения о нехватке средств в журнал:

   if(money_free<=0 #ifdef __MQL4__ || ::GetLastError()==134 #endif )
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
        {
         //--- создаём текст сообщения
         string message=
           (
            symbol_obj.Name()+" "+::DoubleToString(volume,symbol_obj.DigitsLot())+" "+
            (
             order_type>ORDER_TYPE_SELL ? OrderTypeDescription(order_type,false,false) : 
             PositionTypeDescription(PositionTypeByOrderType(order_type))
            )+" ("+::DoubleToString(money_free,(int)this.m_account.CurrencyDigits())+")"
           );
         //--- выводим сообщение в журнал
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR),": ",message);
         this.AddErrorCodeToList(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR);
        }
      return false;
     }

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

//+------------------------------------------------------------------+
//| Проверка достаточности средств                                   |
//+------------------------------------------------------------------+
bool CTrading::CheckMoneyFree(const double volume,
                              const double price,
                              const ENUM_ORDER_TYPE order_type,
                              const CSymbol *symbol_obj,
                              const string source_method,
                              const bool mess=true)
  {
   ::ResetLastError();
   //--- Получаем тип маркет-ордера по типу торговой операции
   ENUM_ORDER_TYPE action=this.DirectionByActionType((ENUM_ACTION_TYPE)order_type);
   //--- Получаем размер свободных средств, которые останутся после выполнения торговой операции
   double money_free=
     (
      //--- Для MQL5 рассчитываем разницу между свободными средствами и средствами, требующимися на проведение торговой операции
      #ifdef __MQL5__  this.m_account.MarginFree()-this.m_account.MarginForAction(action,symbol_obj.Name(),volume,price)
      //--- Для MQL4 используем результат работы стандартной функции, возвращающей количество средств, которые останутся
      #else/*__MQL4__*/::AccountFreeMarginCheck(symbol_obj.Name(),action,volume) #endif 
     );
   //--- Если свободных средств не останется - сообщаем и возвращаем false
   if(money_free<=0 #ifdef __MQL4__ || ::GetLastError()==134 #endif )
     {
      this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
      this.AddErrorCodeToList(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR);
      return false;
     }
   //--- Проверка пройдена успешно
   return true;
  }
//+------------------------------------------------------------------+

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

//--- Возвращает корректный (1) StopLoss, (2) TakeProfit, (3) цену постановки ордера
   double               CorrectStopLoss(const ENUM_ORDER_TYPE order_type,
                                       const double price_set,
                                       const double stop_loss,
                                       const CSymbol *symbol_obj,
                                       const uint spread_multiplier=1);
   double               CorrectTakeProfit(const ENUM_ORDER_TYPE order_type,
                                       const double price_set,
                                       const double take_profit,
                                       const CSymbol *symbol_obj,
                                       const uint spread_multiplier=1);
   double               CorrectPricePending(const ENUM_ORDER_TYPE order_type,
                                       const double price_set,
                                       const double price,
                                       const CSymbol *symbol_obj,
                                       const uint spread_multiplier=1);
//--- Возвращает объём, при котором возможно открыть позицию
   double               CorrectVolume(const double price,
                                       const ENUM_ORDER_TYPE order_type,
                                       const CSymbol *symbol_obj,
                                       const string source_method);

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

public:

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

//--- Проверка ограничений и ошибок
   ENUM_ERROR_CODE_PROCESSING_METHOD CheckErrors(const double volume,
                                                 const double price,
                                                 const ENUM_ACTION_TYPE action,
                                                 const ENUM_ORDER_TYPE order_type,
                                                 CSymbol *symbol_obj,
                                                 const CTradeObj *trade_obj,
                                                 const string source_method,
                                                 const double limit=0,
                                                 double sl=0,
                                                 double tp=0);

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

Объявим метод установки множителя спреда:

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

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

//--- Устанавливает/возвращает флаг разрешения использования звуков
   void                 SetUseSounds(const bool flag);
   bool                 IsUseSounds(void)                            const { return this.m_use_sound;       }

//--- Устанавливает/возвращает флаг разрешения торговли
   void                 SetTradingDisableFlag(const bool flag)             { this.m_is_trade_disable=flag;  }
   bool                 IsTradingDisable(void)                       const { return this.m_is_trade_disable;}

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

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

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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CTrading::CTrading()
  {
   this.m_list_errors.Clear();
   this.m_list_errors.Sort();
   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);
  }
//+------------------------------------------------------------------+

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

Реализация метода обработки кодов возврата торгового сервера в данном исполнении возвращает лишь флаг успеха:

//+------------------------------------------------------------------+
//| Возвращает метод обработки ошибки                                |
//+------------------------------------------------------------------+
ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::ResultProccessingMethod(void)
  {
   return ERROR_CODE_PROCESSING_METHOD_OK;
  }
//+------------------------------------------------------------------+

Почему так? Просто в этой статье мы не рассматриваем этот метод — обработку кодов возврата торгового сервера будем делать в следующей статье. Но метод уже описан и реализован в минимальном исполнении.

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

//+------------------------------------------------------------------+
//| Корректировка ошибок                                             |
//+------------------------------------------------------------------+
ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::RequestErrorsCorrecting(MqlTradeRequest &request,
                                                                    const ENUM_ORDER_TYPE order_type,
                                                                    const uint spread_multiplier,
                                                                    CSymbol *symbol_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)       || // Для текущего счёта запрещена торговля
      this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED)    || // Для советников на текущем счёте запрещена торговля на стороне торгового сервера
      this.IsPresentErorCode(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED)      || // В терминале нет разрешения на проведение торговых операций
      this.IsPresentErorCode(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED)            || // Для советника нет разрешения на проведение торговых пераций
      this.IsPresentErorCode(MSG_SYM_TRADE_MODE_DISABLED)                  || // Торговля по символу запрещена
      this.IsPresentErorCode(MSG_SYM_TRADE_MODE_CLOSEONLY)                 || // Разрешены только операции закрытия позиций
      this.IsPresentErorCode(MSG_SYM_MARKET_ORDER_DISABLED)                || // Торговля рыночными ордерами запрещена
      this.IsPresentErorCode(MSG_SYM_LIMIT_ORDER_DISABLED)                 || // Установка Limit-ордеров запрещена
      this.IsPresentErorCode(MSG_SYM_STOP_ORDER_DISABLED)                  || // Установка Stop-ордеров запрещена
      this.IsPresentErorCode(MSG_SYM_STOP_LIMIT_ORDER_DISABLED)            || // Установка StopLimit-ордеров запрещена
      this.IsPresentErorCode(MSG_SYM_TRADE_MODE_SHORTONLY)                 || // Разрешены только продажи
      this.IsPresentErorCode(MSG_SYM_TRADE_MODE_LONGONLY)                  || // Разрешены только покупки
      this.IsPresentErorCode(MSG_SYM_CLOSE_BY_ORDER_DISABLED)              || // Установка CloseBy-ордеров запрещена
      this.IsPresentErorCode(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED)       || // Превышен максимальный совокупный объём ордеров и позиций в одном направлении
      this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED)        || // Закрытие встречным запрещено
      this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL)        || // Символы встречных позиций не равны
      this.IsPresentErorCode(MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ)   || // Неподдерживаемый тип параметра цены в запросе
      this.IsPresentErorCode(MSG_LIB_TEXT_TRADING_DISABLE)                 || // Торговля отключена для эксперта до устранения причины запрета
      this.IsPresentErorCode(10006)                                        || // Запрос отклонен
      this.IsPresentErorCode(10011)                                        || // Ошибка обработки запроса
      this.IsPresentErorCode(10012)                                        || // Запрос отклонен по истечению времени
      this.IsPresentErorCode(10013)                                        || // Неправильный запрос
      this.IsPresentErorCode(10017)                                        || // Торговля запрещена
      this.IsPresentErorCode(10018)                                        || // Рынок закрыт
      this.IsPresentErorCode(10023)                                        || // Состояние ордера изменилось
      this.IsPresentErorCode(10025)                                        || // В запросе нет изменений
      this.IsPresentErorCode(10026)                                        || // Автотрейдинг запрещен сервером
      this.IsPresentErorCode(10027)                                        || // Автотрейдинг запрещен клиентским терминалом
      this.IsPresentErorCode(10032)                                        || // Операция разрешена только для реальных счетов
      this.IsPresentErorCode(10033)                                        || // Достигнут лимит на количество отложенных ордеров
      this.IsPresentErorCode(10034)                                           // Достигнут лимит на объем ордеров и позиций для данного символа
     ) return ERROR_CODE_PROCESSING_METHOD_EXIT;
//--- Просмотрим полный список ошибок и скорректируем параметры торгового запроса
   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_PR_LESS_STOP_LEVEL      :  request.price=this.CorrectPricePending(order_type,request.price,0,symbol_obj,spread_multiplier);      break;
         case MSG_LIB_TEXT_SL_LESS_STOP_LEVEL      :  request.sl=this.CorrectStopLoss(order_type,request.price,request.sl,symbol_obj,spread_multiplier);    break;
         case MSG_LIB_TEXT_TP_LESS_STOP_LEVEL      :  request.tp=this.CorrectTakeProfit(order_type,request.price,request.tp,symbol_obj,spread_multiplier);  break;
         //--- Если не получится подобрать лот позиции, то вернём "прервать торговую попытку", так как денег нет даже на минимальный лот
         case MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR    :  request.volume=this.CorrectVolume(request.volume,request.price,order_type,symbol_obj,DFUN);
                                                      if(request.volume==0)
                                                         return ERROR_CODE_PROCESSING_METHOD_EXIT;                                                                                      break;
         //--- Близость к уровню срабатывания ордера обрабатываем пятисекундным ожиданием - цена может выйти из зоны заморозки за это время
         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;
        }
     }
   return ERROR_CODE_PROCESSING_METHOD_OK;
  }
//+------------------------------------------------------------------+

Логика метода описана в комментариях к коду. Вкратце: при обнаружении пока необрабатываемых кодов ошибок, возвращаем метод обработки "прервать торговую попытку", при встрече ошибок, которые возможно скорректировать — корректируем значения параметров и возвращаем ОК.

Доработка метода, проверяющего ограничения для торговли и ошибки торгового запроса:

//+------------------------------------------------------------------+
//| Проверка ограничений и ошибок                                    |
//+------------------------------------------------------------------+
ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::CheckErrors(const double volume,
                                                        const double price,
                                                        const ENUM_ACTION_TYPE action,
                                                        const ENUM_ORDER_TYPE order_type,
                                                        CSymbol *symbol_obj,
                                                        const CTradeObj *trade_obj,
                                                        const string source_method,
                                                        const double limit=0,
                                                        double sl=0,
                                                        double tp=0)
  {
//--- Проверка установленного ранее флага запрета торговли для эксперта
   if(this.IsTradingDisable())
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_FATAL_ERROR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE));
      return ERROR_CODE_PROCESSING_METHOD_DISABLE;
     }
//--- результат проведения всех проверок и флаги ошибок
   this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR;
   bool res=true;
//--- Очищаем список ошибок
   this.m_list_errors.Clear();
   this.m_list_errors.Sort();
//--- Проверка ограничений для торговли
   res &=this.CheckTradeConstraints(volume,action,symbol_obj,source_method,sl,tp);
//--- Проверка достаточности средств для открытия позиций/установки ордеров
   if(action<ACTION_TYPE_CLOSE_BY)
      res &=this.CheckMoneyFree(volume,price,order_type,symbol_obj,source_method);
//--- Проверка значений параметров по уровням StopLevel и FreezeLevel
   res &=this.CheckLevels(action,order_type,price,limit,sl,tp,symbol_obj,source_method);
   
//--- Если есть ограничения, выводим заголовок и список ошибок
   if(!res)
     {
      //--- Запрос отклонён до отправки на сервер по причине:
      int total=this.m_list_errors.Total();
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
        {
         //--- Для MQL5 сначала выводится заголовок списка, затем список ошибок
         #ifdef __MQL5__
         ::Print(source_method,CMessage::Text(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK ? MSG_LIB_TEXT_REQUEST_REJECTED_DUE : MSG_LIB_TEXT_INVALID_REQUEST));
         for(int i=0;i<total;i++)
            ::Print((total>1 ? string(i+1)+". " : ""),CMessage::Text(m_list_errors.At(i)));
         //--- Для MQL4, так как в журнал выводится всё в обратном порядке, то сначала выводится список ошибок в обратном цикле, а затем заголовок списка
         #else    
         for(int i=total-1;i>WRONG_VALUE;i--)
            ::Print((total>1 ? string(i+1)+". " : ""),CMessage::Text(m_list_errors.At(i)));
         ::Print(source_method,CMessage::Text(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK ? MSG_LIB_TEXT_REQUEST_REJECTED_DUE : MSG_LIB_TEXT_INVALID_REQUEST));
         #endif 
        }
      //--- Если действие при ошибке "прервать торговую операцию"
      if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK)
         return ERROR_CODE_PROCESSING_METHOD_EXIT;
      //--- Если действие при ошибке "создать отложенный запрос"
      if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST)
         return ERROR_CODE_PROCESSING_METHOD_PENDING;
      //--- Если действие при ошибке "скорректировать параметры"
      if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_CORRECT)
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_CORRECTED_TRADE_REQUEST));
         //--- Возвращаем результат попытки скорректировать параметры запроса
         return this.RequestErrorsCorrecting(this.m_request,order_type,trade_obj.SpreadMultiplier(),symbol_obj);
        }
     }
//--- Нет ограничений
   return ERROR_CODE_PROCESSING_METHOD_OK;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Проверка ограничений для торговли                                |
//+------------------------------------------------------------------+
bool CTrading::CheckTradeConstraints(const double volume,
                                     const ENUM_ACTION_TYPE action_type,
                                     const CSymbol *symbol_obj,
                                     const string source_method,
                                     double sl=0,
                                     double tp=0)
  {
//--- результат проведения всех проверок
   bool res=true;
//--- Проверяем связь с торговым сервером (не в режиме тестирования)
   if(!::TerminalInfoInteger(TERMINAL_CONNECTED))
     {
      if(!::MQLInfoInteger(MQL_TESTER))
        {
         //--- Записываем код ошибки в список и возвращаем false - нет смысла проверять далее
         this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(10031);
         return false;
        }
     }
//--- Проверяем разрешение торговли для данного счета (при наличии связи с торговым сервером)
   else if(!this.m_account.TradeAllowed())
     {
      //--- Записываем код ошибки в список и возвращаем false - нет смысла проверять далее
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
      this.AddErrorCodeToList(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED);
      return false;
     }
//--- Проверяем разрешение на торговлю любым экспертам/скриптам для данного счета
   if(!this.m_account.TradeExpert())
     {
      //--- Записываем код ошибки в список и возвращаем false - нет смысла проверять далее
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
      this.AddErrorCodeToList(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED);
      return false;
     }
//--- Проверяем разрешение автоторговли в терминале.
//--- Кнопка "Авто-торговля" (Настройки --> Советники --> Флажок "Разрешить автоматическую торговлю")
   if(!::TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
     {
      //--- Записываем код ошибки в список и возвращаем false - нет смысла проверять далее
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
      this.AddErrorCodeToList(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED);
      return false;
     }
//--- Проверяем разрешение автоторговли для текущего эксперта.
//--- (F7 --> Общие --> Флажок "Разрешить автоматическую торговлю")
   if(!::MQLInfoInteger(MQL_TRADE_ALLOWED))
     {
      //--- Записываем код ошибки в список и возвращаем false - нет смысла проверять далее
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
      this.AddErrorCodeToList(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED);
      return false;
     }
//--- Проверяем разрешение торговли на символе.
//--- Если торговля запрещена - записываем код ошибки в список и возвращаем false - нет смысла проверять далее
   if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
      this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_DISABLED);
      return false;
     }

//--- Если не закрытие/удаление/модификация
   if(action_type<ACTION_TYPE_CLOSE_BY)
     {
      //--- Если разрешены только операции закрытия позиций - записываем код ошибки в список и возвращаем false - нет смысла проверять далее
      if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)
        {
         this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_CLOSEONLY);
         return false;
        }
      //--- Проверка минимального объёма
      if(volume<symbol_obj.LotsMin())
        {
         //--- Объём в запросе меньше минимально-допустимого.
         //--- добавляем код ошибки в список
         this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME);
         //--- Если поведение советника при торговой ошибке "прервать торговую операцию", то
         //--- возвращаем false - нет смысла проверять далее
         if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK)
            return false;
         //--- Если поведение советника при торговой ошибке указано как
         //--- "скорректировать параметры" или "создать отложенный запрос", то
         //--- записываем в результат false
         else res &=false;
        }
      //--- Проверка максимального объёма
      else if(volume>symbol_obj.LotsMax())
        {
         //--- Объём в запросе больше максимально-допустимого.
         //--- добавляем код ошибки в список
         this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME);
         //--- Если поведение советника при торговой ошибке "прервать торговую операцию", то
         //--- возвращаем false - нет смысла проверять далее
         if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK)
            return false;
         //--- Если поведение советника при торговой ошибке указано как
         //--- "скорректировать параметры" или "создать отложенный запрос", то
         //--- записываем в результат false
         else res &=false;
        }
      //--- Проверка минимальной градации объема
      double step=symbol_obj.LotsStep();
      if(fabs((int)round(volume/step)*step-volume)>0.0000001)
        {
         //--- Объём в запросе не кратен минимальной градации шага изменения лота
         //--- добавляем код ошибки в список
         this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_LIB_TEXT_INVALID_VOLUME_STEP);
         //--- Если поведение советника при торговой ошибке "прервать торговую операцию", то
         //--- возвращаем false - нет смысла проверять далее
         if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK)
            return false;
         //--- Если поведение советника при торговой ошибке указано как
         //--- "скорректировать параметры" или "создать отложенный запрос", то
         //--- записываем в результат false
         else res &=false;
        }
     }

//--- Если открытие позиции
   if(action_type<ACTION_TYPE_BUY_LIMIT)
     {
      //--- Проверяем разрешение отправки маркет-ордеров на символе.
      //--- Если торговля маркет-ордерами запрещена - записываем код ошибки в список и возвращаем false - нет смысла проверять далее
      if(!symbol_obj.IsMarketOrdersAllowed())
        {
         this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_SYM_MARKET_ORDER_DISABLED);
         return false;
        }
     }
//--- Если установка отложенного ордера
   else if(action_type>ACTION_TYPE_SELL && action_type<ACTION_TYPE_CLOSE_BY)
     {
      //--- Если есть ограничение на количество отложенных ордеров на счёте, и выставление нового ордера превысит допустимое количество
      if(this.m_account.LimitOrders()>0 && this.OrdersTotalAll()+1 > this.m_account.LimitOrders())
        {
         //--- Достигнут лимит на количество отложенных ордеров - записываем код ошибки в список и возвращаем false - нет смысла проверять далее
         this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(10033);
         return false;
        }  
      //--- Проверяем разрешение установки лимит-ордеров на символе.
      if(action_type==ACTION_TYPE_BUY_LIMIT || action_type==ACTION_TYPE_SELL_LIMIT)
        {
         //--- Если установка лимит-ордеров запрещена - записываем код ошибки в список и возвращаем false - нет смысла проверять далее
         if(!symbol_obj.IsLimitOrdersAllowed())
           {
            this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
            this.AddErrorCodeToList(MSG_SYM_LIMIT_ORDER_DISABLED);
            return false;
           }
        }
      //--- Проверяем разрешение установки стоп-ордеров на символе.
      else if(action_type==ACTION_TYPE_BUY_STOP || action_type==ACTION_TYPE_SELL_STOP)
        {
         //--- Если установка стоп-ордеров запрещена - записываем код ошибки в список и возвращаем false - нет смысла проверять далее
         if(!symbol_obj.IsStopOrdersAllowed())
           {
            this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
            this.AddErrorCodeToList(MSG_SYM_STOP_ORDER_DISABLED);
            return false;
           }
        }
      //--- Для MQL5 Проверяем разрешение установки стоп-лимит-ордеров на символе.
      #ifdef __MQL5__
      else if(action_type==ACTION_TYPE_BUY_STOP_LIMIT || action_type==ACTION_TYPE_SELL_STOP_LIMIT)
        {
         //--- Если установка стоп-лимит-ордеров запрещена - записываем код ошибки в список и возвращаем false - нет смысла проверять далее
         if(!symbol_obj.IsStopLimitOrdersAllowed())
           {
            this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
            this.AddErrorCodeToList(MSG_SYM_STOP_LIMIT_ORDER_DISABLED);
            return false;
           }
        }
      #endif 
     }

//--- Если открытие/установка/модификация
   if(action_type!=ACTION_TYPE_CLOSE_BY)
     {
      //--- Если не модификация
      if(action_type!=ACTION_TYPE_MODIFY)
        {
         //--- Если покупка: проверяем разрешение торговли в long на символе
         if(this.DirectionByActionType(action_type)==ORDER_TYPE_BUY)
           {
            //--- Если разрешены только продажи - записываем код ошибки в список и возвращаем false - нет смысла проверять далее
            if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)
              {
               this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
               this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_SHORTONLY);
               return false;
              }
            //--- Если для символа есть ограничение на совокупный объем открытой позиции и отложенных ордеров в одном направлении   
            if(symbol_obj.VolumeLimit()>0)
              {
               //--- (Если совокупный объём установленных long-ордеров и открытых long-позиций)+открываемый объём превышают максимальный
               if(this.OrdersTotalVolumeLong()+this.PositionsTotalVolumeLong()+volume > symbol_obj.VolumeLimit())
                 {
                  //--- Превышен максимальный совокупный объём ордеров и позиций в одном направлении
                  //--- записываем код ошибки в список и возвращаем false - нет смысла проверять далее
                  this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
                  this.AddErrorCodeToList(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED);
                  return false;
                 }
              }
           }
         //--- Если продажа: проверяем разрешение торговли в short на символе
         else if(this.DirectionByActionType(action_type)==ORDER_TYPE_SELL)
           {
            //--- Если разрешены только покупки - записываем код ошибки в список и возвращаем false - нет смысла проверять далее
            if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)
              {
               this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
               this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_LONGONLY);
               return false;
              }
            //--- Если для символа есть ограничение на совокупный объем открытой позиции и отложенных ордеров в одном направлении   
            if(symbol_obj.VolumeLimit()>0)
              {
               //--- (Если совокупный объём установленных short-ордеров и открытых short-позиций)+открываемый объём превышают максимальный
               if(this.OrdersTotalVolumeShort()+this.PositionsTotalVolumeShort()+volume > symbol_obj.VolumeLimit())
                 {
                  //--- Превышен максимальный совокупный объём ордеров и позиций в одном направлении
                  //--- записываем код ошибки в список и возвращаем false - нет смысла проверять далее
                  this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
                  this.AddErrorCodeToList(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED);
                  return false;
                 }
              }
           }
        }
      //--- Если в запросе есть StopLoss и не разрешена его установка
      if(sl>0 && !symbol_obj.IsStopLossOrdersAllowed())
        {
         //--- добавляем код ошибки в список
         this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_SYM_SL_ORDER_DISABLED);
         //--- Если поведение советника при торговой ошибке "прервать торговую операцию", то
         //--- возвращаем false - нет смысла проверять далее
         if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK)
            return false;
         //--- Если поведение советника при торговой ошибке указано как
         //--- "скорректировать параметры" или "создать отложенный запрос", то
         //--- записываем в результат false
         else res &=false;
        }
      //--- Если в запросе есть TakeProfit и не разрешена его установка
      if(tp>0 && !symbol_obj.IsTakeProfitOrdersAllowed())
        {
         //--- добавляем код ошибки в список
         this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_SYM_TP_ORDER_DISABLED);
         //--- Если поведение советника при торговой ошибке "прервать торговую операцию", то
         //--- возвращаем false - нет смысла проверять далее
         if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK)
            return false;
         //--- Если поведение советника при торговой ошибке указано как
         //--- "скорректировать параметры" или "создать отложенный запрос", то
         //--- записываем в результат false
         else res &=false;
        }
     }

//--- Если закрытие встречным
   else if(action_type==ACTION_TYPE_CLOSE_BY)
     {
      //--- Если закрытие встречным не разрешено
      if(!symbol_obj.IsCloseByOrdersAllowed())
        {
         //--- записываем код ошибки в список и возвращаем false
         this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED);
         return false;
        }
     }
   return res;
  }
//+------------------------------------------------------------------+

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

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

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

//+------------------------------------------------------------------+
//| Устанавливает цены торгового запроса                             |
//+------------------------------------------------------------------+
template <typename PR,typename SL,typename TP,typename PL> 
bool CTrading::SetPrices(const ENUM_ORDER_TYPE action,const PR price,const SL sl,const TP tp,const PL limit,const string source_method,CSymbol *symbol_obj)
  {
//--- Обнуляем цены
   ::ZeroMemory(this.m_request);
//--- Обновляем все данные по символу
   if(!symbol_obj.RefreshRates())
     {
      this.AddErrorCodeToList(10021);
      return false;
     }

//--- Цена открытия/установки

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

         //--- Рассчитываем цену установки ордера
         switch((int)action)
           {
            //--- Отложенный ордер
            case ORDER_TYPE_BUY_LIMIT       :  this.m_request.price=::NormalizeDouble(symbol_obj.Ask()-price*symbol_obj.Point(),symbol_obj.Digits());      break;
            case ORDER_TYPE_BUY_STOP        :
            case ORDER_TYPE_BUY_STOP_LIMIT  :  this.m_request.price=::NormalizeDouble(symbol_obj.Ask()+price*symbol_obj.Point(),symbol_obj.Digits());      break;
            
            case ORDER_TYPE_SELL_LIMIT      :  this.m_request.price=::NormalizeDouble(symbol_obj.BidLast()+price*symbol_obj.Point(),symbol_obj.Digits());  break;
            case ORDER_TYPE_SELL_STOP       :
            case ORDER_TYPE_SELL_STOP_LIMIT :  this.m_request.price=::NormalizeDouble(symbol_obj.BidLast()-price*symbol_obj.Point(),symbol_obj.Digits());  break;
            //--- По умолчанию - текущие цены открытия позиции
            default  :  this.m_request.price=
              (
               this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY ? ::NormalizeDouble(symbol_obj.Ask(),symbol_obj.Digits()) : 
               ::NormalizeDouble(symbol_obj.BidLast(),symbol_obj.Digits())
              ); break;
           }

Теперь метод класса объекта-символа Bid() заменён на метод BidLast(), возвращающий либо цену Bid, либо цену Last в зависимости от режима построения графика.

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

//+------------------------------------------------------------------+
//| Устанавливает множитель спреда                                   |
//| для торговых объектов всех символов                              |
//+------------------------------------------------------------------+
void CTrading::SetSpreadMultiplier(const uint value=1,const string symbol=NULL)
  {
   CSymbol *symbol_obj=NULL;
   if(symbol==NULL)
     {
      CArrayObj *list=this.m_symbols.GetList();
      if(list==NULL || list.Total()==0)
         return;
      int total=list.Total();
      for(int i=0;i<total;i++)
        {
         symbol_obj=list.At(i);
         if(symbol_obj==NULL)
            continue;
         CTradeObj *trade_obj=symbol_obj.GetTradeObj();
         if(trade_obj==NULL)
            continue;
         trade_obj.SetSpreadMultiplier(value);
        }
     }
   else
     {
      CTradeObj *trade_obj=this.GetTradeObjBySymbol(symbol,DFUN);
      if(trade_obj==NULL)
         return;
      trade_obj.SetSpreadMultiplier(value);
     }
  }
//+------------------------------------------------------------------+

В метод передаётся значение множителя (по умолчанию 1) и наименование символа (по умолчанию NULL).

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

В связи с новой обработкой ошибок, все торговые методы были доработаны.
Рассмотрим код метода открытия позиции Buy:

//+------------------------------------------------------------------+
//| Открывает позицию Buy                                            |
//+------------------------------------------------------------------+
template<typename SL,typename TP> 
bool CTrading::OpenBuy(const double volume,
                       const string symbol,
                       const ulong magic=ULONG_MAX,
                       const SL sl=0,
                       const TP tp=0,
                       const string comment=NULL,
                       const ulong deviation=ULONG_MAX)
  {
//--- Устанавливаем результат торгового запроса как true и флаг ошибки как "нет ошибок"
   bool res=true;
   this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR;
   ENUM_ACTION_TYPE action=ACTION_TYPE_BUY;
   ENUM_ORDER_TYPE order_type=ORDER_TYPE_BUY;
//--- Получаем объект-символ по имени символа. Если получить не удалось
   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;
      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)
        {
         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)
        {
         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)
        {
         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));
        }
     }
   
//--- Отсылаем запрос
   res=trade_obj.OpenPosition(POSITION_TYPE_BUY,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation);
//--- Если запрос выполнен успешно - проиграем звук успеха, установленный торговому объекту символа для данного типа торговой операции
   if(res)
     {
      if(this.IsUseSounds())
         trade_obj.PlaySoundSuccess(action,order_type);
     }
//--- Если запрос не выполнен - выведем сообщение и проиграем звук ошибки, установленный торговому объекту символа для данного типа торговой операции
   else
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode()));
      if(this.IsUseSounds())
         trade_obj.PlaySoundError(action,order_type);
     }
//--- Возвращаем результат отправки торгового запроса в торговом объекте символа
   return res;
  }
//+------------------------------------------------------------------+

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

Методы, возвращающие рассчитанные корректные цены установки стоп-приказов и отложенных ордеров:

//+------------------------------------------------------------------+
//| Возвращает корректный StopLoss относительно StopLevel            |
//+------------------------------------------------------------------+
double CTrading::CorrectStopLoss(const ENUM_ORDER_TYPE order_type,const double price_set,const double stop_loss,const CSymbol *symbol_obj,const uint spread_multiplier=1)
  {
   if(stop_loss==0) return 0;
   uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel());
   double price=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : order_type==ORDER_TYPE_SELL ? symbol_obj.Ask() : price_set);
   return
     (this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY            ?
      ::NormalizeDouble(fmin(price-lv*symbol_obj.Point(),stop_loss),symbol_obj.Digits())  :
      ::NormalizeDouble(fmax(price+lv*symbol_obj.Point(),stop_loss),symbol_obj.Digits())
     );
  }
//+------------------------------------------------------------------+
//| Возвращает корректный TakeProfit относительно StopLevel          |
//+------------------------------------------------------------------+
double CTrading::CorrectTakeProfit(const ENUM_ORDER_TYPE order_type,const double price_set,const double take_profit,const CSymbol *symbol_obj,const uint spread_multiplier=1)
  {
   if(take_profit==0) return 0;
   uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel());
   double price=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : order_type==ORDER_TYPE_SELL ? symbol_obj.Ask() : price_set);
   return
     (this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY             ?
      ::NormalizeDouble(fmax(price+lv*symbol_obj.Point(),take_profit),symbol_obj.Digits()) :
      ::NormalizeDouble(fmin(price-lv*symbol_obj.Point(),take_profit),symbol_obj.Digits())
     );
  }
//+------------------------------------------------------------------+
//| Возвращает корректную цену постановки ордера                     |
//| относительно StopLevel                                           |
//+------------------------------------------------------------------+
double CTrading::CorrectPricePending(const ENUM_ORDER_TYPE order_type,const double price_set,const double price,const CSymbol *symbol_obj,const uint spread_multiplier=1)
  {
   uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel());
   double pp=0;
   switch((int)order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ? symbol_obj.Ask()     : price); return ::NormalizeDouble(fmin(pp-lv*symbol_obj.Point(),price_set),symbol_obj.Digits());
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ? symbol_obj.Ask()     : price); return ::NormalizeDouble(fmax(pp+lv*symbol_obj.Point(),price_set),symbol_obj.Digits());
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ? symbol_obj.BidLast() : price); return ::NormalizeDouble(fmax(pp+lv*symbol_obj.Point(),price_set),symbol_obj.Digits());
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ? symbol_obj.BidLast() : price); return ::NormalizeDouble(fmin(pp-lv*symbol_obj.Point(),price_set),symbol_obj.Digits());
      default                          :  if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_INVALID_ORDER_TYPE),::EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+

Здесь всё должно быть понятно даже без комментариев к коду — просто сравниваются цены, переданные в методы, с ценой, полученной как отступ размера StopLevel от цены открытия. Корректная (б ольшая/меньшая, в зависимости от типа ордера) цена возвращается в вызывающую программу.

Метод, возвращающий объём, при котором возможно открыть позицию:

//+------------------------------------------------------------------+
//| Возвращает объём, при котором возможно открыть позицию           |
//+------------------------------------------------------------------+
double CTrading::CorrectVolume(const double price,const ENUM_ORDER_TYPE order_type,CSymbol *symbol_obj,const string source_method)
  {
//--- Если не хватает средств на минимальный лот - сообщаем и возвращаем ноль
   if(!this.CheckMoneyFree(symbol_obj.LotsMin(),price,order_type,symbol_obj,source_method))
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(CMessage::Text(MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT));
      return 0;
     }

//--- Обновим данные счёта и символа
   this.m_account.Refresh();
   symbol_obj.RefreshRates();
//--- Рассчитаем лот, ближайший к допустимому
   double vol=symbol_obj.NormalizedLot(this.m_account.Equity()*this.m_account.Leverage()/symbol_obj.TradeContractSize()/(symbol_obj.CurrencyBase()=="USD" ? 1.0 : symbol_obj.BidLast()));
//--- Рассчитаем достаточный лот
   double margin=this.m_account.MarginForAction(order_type,symbol_obj.Name(),1.0,price);
   if(margin!=EMPTY_VALUE)
      vol=symbol_obj.NormalizedLot(this.m_account.MarginFree()/margin);

//--- Если рассчитанный лот ошибочен или расчёт необходимой маржи вернул ошибку
   if(!this.CheckMoneyFree(vol,price,order_type,symbol_obj,source_method))
     {
      //--- В цикле do-while, пока рассчитанный корректный объём больше минимального лота
      do
        {
         //--- Вычитаем из корректного значения лота величину минимального изменения лота
         vol-=symbol_obj.LotsStep();
         //--- Если при рассчитанном лоте возможно открыть позицию/поставить ордер - возвращаем значение этого лота
         if(this.CheckMoneyFree(symbol_obj.NormalizedLot(vol),price,order_type,symbol_obj,source_method))
            return vol;
        }
      while(vol>symbol_obj.LotsMin() && !::IsStopped());
     }
//--- Если лот рассчитан верно - возвращаем рассчитанный лот
   else
      return vol;
//--- Если дошли до сюда - не хватает средств - сообщаем и возвращаем ноль
   if(this.m_log_level>LOG_LEVEL_NO_MSG)
      ::Print(CMessage::Text(MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT));
   return 0;
  }
//+------------------------------------------------------------------+

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

В этом расчёте используется функция OrderCalcMargin(), которая при ошибке может вернуть false, а метод MarginForAction() класса CAccount, использующий эту функцию, в такой ситуации возвращает EMPTY_VALUE, что соответствует значению константы DBL_MAX (максимальное значение, которое может быть представлено типом double). И если мы получили это значение, то значит была ошибка, илот у нас не рассчитан.

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

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

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

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

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

В список глобальных переменных добавим переменную-флаг работы в тестере стратегий:

//--- global variables
CEngine        engine;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ulong          magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           slippage;
bool           trailing_on;
double         trailing_stop;
double         trailing_step;
uint           trailing_start;
uint           stoploss_to_modify;
uint           takeprofit_to_modify;
int            used_symbols_mode;
string         used_symbols;
string         array_used_symbols[];
bool           testing;
//+------------------------------------------------------------------+

В обработчике OnInit() установим значение переменной-флага работы в тестере стратегий:

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

//--- Установка глобальных переменных советника
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   testing=engine.IsTester();
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }

Чтобы отправлять события в обработчик событий библиотеки OnDoEasyEvent() при работе в тестере, у нас есть специальная функция EventsHandling().
Она претерпела небольшую доработку:

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

Здесь всё понятно по комментариям к коду.

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

//+------------------------------------------------------------------+
//| Обработка событий библиотеки DoEasy                              |
//+------------------------------------------------------------------+
void OnDoEasyEvent(const int id,
                   const long &lparam,
                   const double &dparam,
                   const string &sparam)
  {
   int idx=id-CHARTEVENT_CUSTOM;
//--- Извлекаем из lparam (1) милисекунды времени события, (2) причину, (3) источник события и (4) устанавливаем точное время события
   ushort msc=engine.EventMSC(lparam);
   ushort reason=engine.EventReason(lparam);
   ushort source=engine.EventSource(lparam);
   long time=TimeCurrent()*1000+msc;
   
//--- Обработка событий символов
   if(source==COLLECTION_SYMBOLS_ID)
     {
      CSymbol *symbol=engine.GetSymbolObjByName(sparam);
      if(symbol==NULL)
         return;
      //--- Количество знаков после запятой в значении события - если long-событие, то 0, иначе - Digits() символа
      int digits=(idx<SYMBOL_PROP_INTEGER_TOTAL ? 0 : symbol.Digits());
      //--- Текстовое описание события
      string id_descr=(idx<SYMBOL_PROP_INTEGER_TOTAL ? symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_INTEGER)idx) : symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_DOUBLE)idx));
      //--- Текстовое значение величины изменения свойства
      string value=DoubleToString(dparam,digits);
      
      //--- Проверка причин события и просто вывод в журнал его описания
      if(reason==BASE_EVENT_REASON_INC)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_DEC)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_MORE_THEN)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_LESS_THEN)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_EQUALS)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
     }   
     
//--- Обработка событий аккаунта
   else if(source==COLLECTION_ACCOUNT_ID)
     {
      CAccount *account=engine.GetAccountCurrent();
      if(account==NULL)
         return;
      //--- Количество знаков после запятой в значении события - если long-событие, то 0, иначе - Digits() символа
      int digits=int(idx<ACCOUNT_PROP_INTEGER_TOTAL ? 0 : account.CurrencyDigits());
      //--- Текстовое описание события
      string id_descr=(idx<ACCOUNT_PROP_INTEGER_TOTAL ? account.GetPropertyDescription((ENUM_ACCOUNT_PROP_INTEGER)idx) : account.GetPropertyDescription((ENUM_ACCOUNT_PROP_DOUBLE)idx));
      //--- Текстовое значение величины изменения свойства
      string value=DoubleToString(dparam,digits);
      
      //--- Проверка причин события и обработка увеличения средств на заданную величину,
      
      //--- Если это увеличение значения свойства
      if(reason==BASE_EVENT_REASON_INC)
        {
         //--- Распечатаем событие в журнал
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
         //--- если это увеличение средств
         if(idx==ACCOUNT_PROP_EQUITY)
           {
            //--- Получаем список всех открытых позиций
            CArrayObj* list_positions=engine.GetListMarketPosition();
            //--- Выбираем позиции с прибылью болше нуля
            list_positions=CSelect::ByOrderProperty(list_positions,ORDER_PROP_PROFIT_FULL,0,MORE);
            if(list_positions!=NULL)
              {
               //--- Сортируем список по прибыли с учётом комиссии и свопа
               list_positions.Sort(SORT_BY_ORDER_PROFIT_FULL);
               //--- Получаем индекс позиции с наибольшей прибылью
               int index=CSelect::FindOrderMax(list_positions,ORDER_PROP_PROFIT_FULL);
               if(index>WRONG_VALUE)
                 {
                  COrder* position=list_positions.At(index);
                  if(position!=NULL)
                    {
                     //--- Получаем тикет позиции с наибольшей прибылью и закрываем позицию по тикету
                     engine.ClosePosition(position.Ticket());
                    }
                 }
              }
           }
        }
      //--- Остальные события просто выводим в журнал
      if(reason==BASE_EVENT_REASON_DEC)
        {
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_MORE_THEN)
        {
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_LESS_THEN)
        {
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_EQUALS)
        {
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
     } 
     
//--- Обработка событий окна обзор рынка
   else if(idx>MARKET_WATCH_EVENT_NO_EVENT && idx<SYMBOL_EVENTS_NEXT_CODE)
     {
      //--- Событие окна "Обзор рынка"
      string descr=engine.GetMWEventDescription((ENUM_MW_EVENT)idx);
      string name=(idx==MARKET_WATCH_EVENT_SYMBOL_SORT ? "" : ": "+sparam);
      Print(TimeMSCtoString(lparam)," ",descr,name);
     }
//--- Обработка торговых событий
   else if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE)
     {
      //--- Получим список всех торговых событий
      CArrayObj *list=engine.GetListAllOrdersEvents();
      if(list==NULL)
         return;
      //--- получим смещение индекса события относительно конца списка
      //--- в тестере смещение передаётся параметром lparam в обработчик событий
      //--- не в тестере - каждое событие отправляется по одному и обрабатывается в OnChartEvent()
      int shift=(testing ? (int)lparam : 0);
      CEvent *event=list.At(list.Total()-1-shift);
      if(event==NULL)
      return;
      //--- Начисление кредита
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CREDIT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Дополнительные сборы
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CHARGE)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Корректирующая запись
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CORRECTION)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Перечисление бонусов
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BONUS)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Дополнительные комиссии
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Комиссия, начисляемая в конце торгового дня
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_DAILY)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Комиссия, начисляемая в конце месяца
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Агентская комиссия, начисляемая в конце торгового дня
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Агентская комиссия, начисляемая в конце месяца
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Начисления процентов на свободные средства
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_INTEREST)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Отмененная сделка покупки
      if(event.TypeEvent()==TRADE_EVENT_BUY_CANCELLED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Отмененная сделка продажи
      if(event.TypeEvent()==TRADE_EVENT_SELL_CANCELLED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Начисление дивиденда
      if(event.TypeEvent()==TRADE_EVENT_DIVIDENT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Начисление франкированного дивиденда
      if(event.TypeEvent()==TRADE_EVENT_DIVIDENT_FRANKED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Начисление налога
      if(event.TypeEvent()==TRADE_EVENT_TAX)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Пополнение средств на балансе
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_REFILL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Снятие средств с баланса
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      
      //--- Отложенный ордер установлен
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_PLASED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Отложенный ордер удалён
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_REMOVED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Отложенный ордер активирован ценой
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Отложенный ордер активирован ценой частично
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция открыта
      if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция открыта частично
      if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED_PARTIAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта встречной
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_POS)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта по StopLoss
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_SL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта по TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_TP)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Разворот позиции новой сделкой (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Разворот позиции активацией отложенного ордера (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Разворот позиции частичным исполнением маркет-ордера (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Разворот позиции частичной активацией отложенного ордера (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Добавлен объём к позиции новой сделкой (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Добавлен объём к позиции частичным исполнением маркет-ордера (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Добавлен объём к позиции активацией отложенного ордера (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Добавлен объём к позиции частичной активацией отложенного ордера (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта частично
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта частично встречной
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта частично по StopLoss
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта частично по TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Срабатывание StopLimit ордера
      if(event.TypeEvent()==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение цены установки ордера
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение цены установки ордера и StopLoss
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение цены установки ордера и TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение цены установки ордера, StopLoss и TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение StopLoss и TakeProfit ордера
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение StopLoss ордера
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение TakeProfit ордера
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение StopLoss и TakeProfit позиции
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение StopLoss позиции
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение TakeProfit позиции
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
     }
  }
//+------------------------------------------------------------------+

Здесь, для простоты, мы просто получаем событие из списка по его индексу (для тестера индекс передаётся в параметре lparam функцей EventsHandling(), тогда как на демо и реале индекс всегда равен нулю так как каждое событие отправляется в OnChartEvent() самостоятельным событием, а не из списка), и выводим в журнал описание полученного события.

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

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

Скомпилируем советник и запустим его в тестере. Установим несколько отложенных ордеров, а затем удалим их все в одном цикле:


Советник вывел в журнал четыре события, которые произошли при удалении четырёх отложенных ордеров в одном цикле после нажатия кнопки "Delete pending".

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

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

Что дальше

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

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

К содержанию

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

Часть 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. Торговые классы - Основной торговый класс, контроль допустимых параметров
Прикрепленные файлы |
MQL5.zip (3617.22 KB)
MQL4.zip (3617.14 KB)
Конструктор стратегий на основе технических фигур Меррилла Конструктор стратегий на основе технических фигур Меррилла

В предыдущей статье была рассмотрена модель применения технических фигур Меррилла к различным данным, таким как ценовое значение на графике валютного инструмента и значениям различных индикаторов из стандартного набора терминала MetaTrader 5: ATR, WPR, CCI, RSI и других.Теперь мы попробуем созданить конструктор стратегий на основе идеи использования технических фигур Меррилла.

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

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

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

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

Разработка Pivot Mean Oscillator: новый осциллятор на кумулятивном скользящем среднем Разработка Pivot Mean Oscillator: новый осциллятор на кумулятивном скользящем среднем

В статье описывается осциллятор Pivot Mean Oscillator (PMO), который представляет собой реализацию торговых сигналов на основе индикатора кумулятивного скользящего среднего для платформ MetaTrader. В частности, сначала будет рассмотрено понятие Pivot Mean (PM) — индекс нормализации временных рядов, который вычисляет соотношение между любой точкой данных и скользящей CMA. Затем построим осциллятор PMO как разницу между скользящими средними, построенными по двум сигналам PM. Также в статье будут показаны эксперименты на символе EURUSD, которые проводились для проверки эффективности индикатора.