English 中文 Español Deutsch 日本語 Português
Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXVI): Работа с отложенными торговыми запросами - первая реализация (открытие позиций)

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

MetaTrader 5Примеры | 27 ноября 2019, 14:55
4 598 3
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

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

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

Это один из вариантов применения отложенных запросов, дающий нам возможность не прерывать выполнение программы на время ожидания, особенно длительного.
Ещё одним вариантом применения отложенных запросов является реализация StopLimit-ордеров на MQL4. Ведь что такое StopLimit-ордер? Это двойной отложенный ордер, содержащий в себе две цены — цену Stop-ордера и цену Limit-ордера. Лимитный отложенный ордер устанавливается при достижении ценой уровня, заданного для Stop-ордера. Таким образом, при помощи отложенного запроса легко реализуется логика работы StopLimit-ордера: мы просто создаём отложенный запрос на установку Limit-ордера, а момент установки Limit-ордера — цену, на которой необходимо послать запрос — записываем в параметрах отложенного запроса. Как только цена достигнет заданного значения, будет отправлен запрос на сервер на выставление Limit-ордера. Такая логика полностью повторяет логику работы StopLimit-ордеров.

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

Магический номер как хранилище данных

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

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

  • идентификатор магика (тот, что задаётся во входных параметрах советника)
  • идентификатор первой группы (с номерами подгрупп от 0 до 15, ноль — нет принадлежности к группе)
  • идентификатор второй группы (с номерами подгрупп от 0 до 15, ноль — нет принадлежности к группе)
  • идентификатор отложенного запроса (с возможными номерами от 0 до 255, ноль — нет идентификатора)

Таким образом, у нас будет возможность задать номер первой и второй групп ордеров. В каждой группе ордеров может быть до пятнадцати подгрупп. Что нам это даст? Ну допустим, у нас есть 20 ордеров/позиций, которые мы хотим собрать по какому-то критерию в одну подгруппу. Мы им задаём в первой группе номер подгруппы 1. И ещё у нас есть 20 ордеров/позиций, которые мы хотим собрать по иному критерию в другую подгруппу этой же первой группы. Мы им задаём номер подгруппы 2. И третьей двадцатке ордеров/позиций задаём подгруппу 3. Теперь у нас есть три группы ордеров/позиций, которые мы легко можем обрабатывать все одновременно своим обработчиком для каждой из подгрупп первой группы. И если нам потребуется две группы из трёх как-то ещё обрабатывать/анализировать, но уже совсем иным обработчиком, при этом не теряя их принадлежности к уже установленным группам, то мы их сможем определить во вторую группу (до пятнадцати подгрупп). Это даёт нам большую гибкость объединения ордеров/позиций в разные группы, чем при наличии только одной группы, пусть и с б ольшим количеством номеров подгрупп.

Итак, мы видим, что запланировали много, но где же тут подвох? А подвох вот в чём: размер целочисленного значения, в котором хранится магический номер для MQL4 — всего 4 байта (int). Поэтому здесь нам приходится жертвовать величиной значения магика, который мы можем задавать в своих программах. Для MQL5 размер для магика задаётся типом ulong, и там мы можем задавать большие числа и хранить гораздо больше информации. Но всё упирается в совместимость... А значит — придётся чем-то пожертвовать. А жертвовать придётся размером значения магического номера — он у нас будет всего два байта (ushort), а освободившиеся два байта отведём под идентификаторы двух групп (uchar) и идентификатор отложенного запроса (uchar).

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

-------------------------------------------------------------------------
| bit   |31           24|23   20|19   16|15            8|7             0|
-------------------------------------------------------------------------
| byte  |       3       |       2       |       1       |       0       |
-------------------------------------------------------------------------
| type  |     uchar     |     uchar     |            ushort             |
-------------------------------------------------------------------------
| descr |  request id   |  id2  |  id1  |             magic             |
-------------------------------------------------------------------------

Как видно из таблички,

  • значение магического номера занимает размер в два байта, и хранится в двух младших байтах 0 и 1 uint-числа (биты 0 — 15), что соответствует типу ushort. Именно такой тип магического номера нам придётся использовать в своих программах с возможными значениями от 0 до 65535.
  • Следующий по старшинству — байт 2 uint-числа служит для хранения двух идентификаторов групп, и имеет размер uchar (биты 16 — 23).
    Идентификатор первой группы хранится в младших четырёх битах uchar-числа (биты 16 — 19), а
    идентификатор второй группы хранится в старших четырёх битах этого uchar-числа (биты 20 — 23).
    Таким образом, мы можем сохранить в однобайтовом uchar-значении две группы, количество номеров каждой из которых может быть от нуля (нет группы) до 15 (максимальное значение, которое мы можем хранить в четырёх битах). 
  • В третьем, и последнем байте uint-числа мы будем хранить uchar-значение идентификатора отложенного запроса (биты 24 — 31), который может иметь значения от нуля (нет идентификатора) до 255. Это означает, что всего мы можем одновременно иметь до 255 активных отложенных запросов.

Для организации хранения данных в значении свойства "магический номер" ордера, доработаем класс абстрактного ордера и классы событий.
Но сначала в файл Defines.mqh впишем новые целочисленные свойства объекта абстрактного ордера:

//+------------------------------------------------------------------+
//| Целочисленные свойства ордера, сделки, позиции                   |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_INTEGER
  {
   ORDER_PROP_TICKET = 0,                                   // Тикет ордера
   ORDER_PROP_MAGIC,                                        // Реальный магик ордера
   ORDER_PROP_TIME_OPEN,                                    // Время открытия в милисекундах (MQL5 Время сделки)
   ORDER_PROP_TIME_CLOSE,                                   // Время закрытия в милисекундах (MQL5 Время исполнения или снятия - ORDER_TIME_DONE)
   ORDER_PROP_TIME_EXP,                                     // Дата экспирации ордера (для отложенных ордеров)
   ORDER_PROP_STATUS,                                       // Статус ордера (из перечисления ENUM_ORDER_STATUS)
   ORDER_PROP_TYPE,                                         // Тип ордера/сделки
   ORDER_PROP_REASON,                                       // Причина или источник сделки/ордера/позиции
   ORDER_PROP_STATE,                                        // Состояние ордера (из перечисления ENUM_ORDER_STATE)
   ORDER_PROP_POSITION_ID,                                  // Идентификатор позиции
   ORDER_PROP_POSITION_BY_ID,                               // Идентификатор встречной позиции
   ORDER_PROP_DEAL_ORDER_TICKET,                            // Тикет ордера, на основании которого выполнена сделка
   ORDER_PROP_DEAL_ENTRY,                                   // Направление сделки – IN, OUT или IN/OUT
   ORDER_PROP_TIME_UPDATE,                                  // Время изменения позиции  в милисекундах
   ORDER_PROP_TICKET_FROM,                                  // Тикет родительского ордера
   ORDER_PROP_TICKET_TO,                                    // Тикет дочернего ордера
   ORDER_PROP_PROFIT_PT,                                    // Профит в пунктах
   ORDER_PROP_CLOSE_BY_SL,                                  // Признак закрытия по StopLoss
   ORDER_PROP_CLOSE_BY_TP,                                  // Признак закрытия по TakeProfit
   ORDER_PROP_MAGIC_ID,                                     // Идентификатор "Магик" ордера
   ORDER_PROP_GROUP_ID1,                                    // Идентификатор 1 группы ордеров/позиций
   ORDER_PROP_GROUP_ID2,                                    // Идентификатор 2 группы ордеров/позиций
   ORDER_PROP_PEND_REQ_ID,                                  // Идентификатор отложенного запроса
   ORDER_PROP_DIRECTION,                                    // Тип по направлению (Buy, Sell)
  }; 
#define ORDER_PROP_INTEGER_TOTAL    (24)                    // Общее количество целочисленных свойств
#define ORDER_PROP_INTEGER_SKIP     (0)                     // Количество неиспользуемых в сортировке свойств ордера
//+------------------------------------------------------------------+

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

Также допишем в перечисление возможных критериев сортировки ордеров и сделок сортировку по этим свойствам:

//+------------------------------------------------------------------+
//| Возможные критерии сортировки ордеров и сделок                   |
//+------------------------------------------------------------------+
#define FIRST_ORD_DBL_PROP          (ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_INTEGER_SKIP)
#define FIRST_ORD_STR_PROP          (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL-ORDER_PROP_INTEGER_SKIP)
enum ENUM_SORT_ORDERS_MODE
  {
//--- Сортировка по целочисленным свойствам
   SORT_BY_ORDER_TICKET = 0,                                // Сортировать по тикету ордера
   SORT_BY_ORDER_MAGIC,                                     // Сортировать по магику ордера
   SORT_BY_ORDER_TIME_OPEN,                                 // Сортировать по времени открытия ордера в милисекундах
   SORT_BY_ORDER_TIME_CLOSE,                                // Сортировать по времени закрытия ордера в милисекундах
   SORT_BY_ORDER_TIME_EXP,                                  // Сортировать по дате экспирации ордера
   SORT_BY_ORDER_STATUS,                                    // Сортировать по статусу ордера (маркет-ордер/отложенный ордер/сделка/балансовая,кредитная операция)
   SORT_BY_ORDER_TYPE,                                      // Сортировать по типу ордера
   SORT_BY_ORDER_REASON,                                    // Сортировать по причине/источнику сделки/ордера/позиции
   SORT_BY_ORDER_STATE,                                     // Сортировать по состоянию ордера
   SORT_BY_ORDER_POSITION_ID,                               // Сортировать по идентификатору позиции
   SORT_BY_ORDER_POSITION_BY_ID,                            // Сортировать по идентификатору встречной позиции
   SORT_BY_ORDER_DEAL_ORDER,                                // Сортировать по ордеру, на основание которого выполнена сделка
   SORT_BY_ORDER_DEAL_ENTRY,                                // Сортировать по направлению сделки – IN, OUT или IN/OUT
   SORT_BY_ORDER_TIME_UPDATE,                               // Сортировать по времени изменения позиции в секундах
   SORT_BY_ORDER_TICKET_FROM,                               // Сортировать по тикету родительского ордера
   SORT_BY_ORDER_TICKET_TO,                                 // Сортировать по тикету дочернего ордера
   SORT_BY_ORDER_PROFIT_PT,                                 // Сортировать по профиту ордера в пунктах
   SORT_BY_ORDER_CLOSE_BY_SL,                               // Сортировать по признаку закрытия ордера по StopLoss
   SORT_BY_ORDER_CLOSE_BY_TP,                               // Сортировать по признаку закрытия ордера по TakeProfit
   SORT_BY_ORDER_MAGIC_ID,                                  // Сортировать по идентификатору "магик" ордера/позиции
   SORT_BY_ORDER_GROUP_ID1,                                 // Сортировать по идентификатору 1 группы ордеров/позиций
   SORT_BY_ORDER_GROUP_ID2,                                 // Сортировать по идентификатору 2 группы ордеров/позиций
   SORT_BY_ORDER_PEND_REQ_ID,                               // Сортировать по идентификатору отложенного запроса
   SORT_BY_ORDER_DIRECTION,                                 // Сортировать по направлению (Buy, Sell)
//--- Сортировка по вещественным свойствам
   SORT_BY_ORDER_PRICE_OPEN = FIRST_ORD_DBL_PROP,           // Сортировать по цене открытия
   SORT_BY_ORDER_PRICE_CLOSE,                               // Сортировать по цене закрытия
   SORT_BY_ORDER_SL,                                        // Сортировать по цене StopLoss
   SORT_BY_ORDER_TP,                                        // Сортировать по цене TaleProfit
   SORT_BY_ORDER_PROFIT,                                    // Сортировать по профиту
   SORT_BY_ORDER_COMMISSION,                                // Сортировать по комиссии
   SORT_BY_ORDER_SWAP,                                      // Сортировать по свопу
   SORT_BY_ORDER_VOLUME,                                    // Сортировать по объёму
   SORT_BY_ORDER_VOLUME_CURRENT,                            // Сортировать по невыполненному объему
   SORT_BY_ORDER_PROFIT_FULL,                               // Сортировать по критерию профит+комиссия+своп
   SORT_BY_ORDER_PRICE_STOP_LIMIT,                          // Сортировать по цене постановки Limit ордера при срабатывании StopLimit ордера
//--- Сортировка по строковым свойствам
   SORT_BY_ORDER_SYMBOL = FIRST_ORD_STR_PROP,               // Сортировать по символу
   SORT_BY_ORDER_COMMENT,                                   // Сортировать по комментарию
   SORT_BY_ORDER_COMMENT_EXT,                               // Сортировать по пользовательскому комментарию
   SORT_BY_ORDER_EXT_ID                                     // Сортировать по идентификатору ордера во внешней торговой системе
  };
//+------------------------------------------------------------------+

Для корректного вывода свойств ордера в журнал,
допишем в файле Datas.mqh индексы новых свойств и соответствующие индексам сообщения:

   MSG_ORD_PROFIT_PT,                                 // Прибыль в пунктах
   MSG_ORD_MAGIC_ID,                                  // Идентификатор магического номера
   MSG_ORD_GROUP_ID1,                                 // Идентификатор первой группы
   MSG_ORD_GROUP_ID2,                                 // Идентификатор второй группы
   MSG_ORD_PEND_REQ_ID,                               // Идентификатор отложенного запроса
   MSG_ORD_PRICE_OPEN,                                // Цена открытия
   {"Прибыль в пунктах","Profit in points"},
   {"Идентификатор магического номера","Magic number's identifier"},
   {"Идентификатор первой группы","First group's identifier"},
   {"Идентификатор второй группы","Second group's identifier"},
   {"Идентификатор отложенного запроса","Pending request's identifier"},
   {"Цена открытия","Price open"},

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

В приватную секцию класса впишем четыре метода, извлекающие из значения свойства ордера "магический номер" и возвращающие идентификатор магического номера (магик, задаваемый в настройках программы), идентификаторы группы 1 и группы 2, и идентификатор отложенного запроса:

//+------------------------------------------------------------------+
//| Класс абстрактного ордера                                        |
//+------------------------------------------------------------------+
class COrder : public CObject
  {
private:
   ulong             m_ticket;                                    // Тикет выбранного ордера/сделки (MQL5)
   long              m_long_prop[ORDER_PROP_INTEGER_TOTAL];       // Целочисленные свойства
   double            m_double_prop[ORDER_PROP_DOUBLE_TOTAL];      // Вещественные свойства
   string            m_string_prop[ORDER_PROP_STRING_TOTAL];      // Строковые свойства

//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство ордера
   int               IndexProp(ENUM_ORDER_PROP_DOUBLE property)      const { return(int)property-ORDER_PROP_INTEGER_TOTAL;                         }
   int               IndexProp(ENUM_ORDER_PROP_STRING property)      const { return(int)property-ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_DOUBLE_TOTAL; }

//--- Расположение данных в int-значении магика
      //-----------------------------------------------------------
      //  bit   32|31       24|23       16|15        8|7         0|
      //-----------------------------------------------------------
      //  byte    |     3     |     2     |     1     |     0     |
      //-----------------------------------------------------------
      //  data    |   uchar   |   uchar   |         ushort        |
      //-----------------------------------------------------------
      //  descr   |pend req id| id2 | id1 |          magic        |
      //-----------------------------------------------------------
//--- Возвращает (1) заданный магический номер, идентификатор (2) первой группы, (3) второй группы, (4) отложенного запроса из значения магика
   ushort            GetMagicID(void)                                const { return ushort(this.Magic() & 0xFFFF);                                 }
   uchar             GetGroupID1(void)                               const { return uchar(this.Magic()>>16) & 0x0F;                                }
   uchar             GetGroupID2(void)                               const { return uchar((this.Magic()>>16) & 0xF0)>>4;                           }
   uchar             GetPendReqID(void)                              const { return uchar(this.Magic()>>24) & 0xFF;                                }

public:
//--- Конструктор по умолчанию
                     COrder(void){;}

Для возврата заданного в настройках ushort-значения магика из uint-значения магического номера ордера, достаточно наложить маску (0xFFFF), оставляющую неизменными в значении uint-числа только два младших байта, а два старших байта uint-числа при этом заполняются нулями. Впрочем, при преобразовании типа uint в тип ushort старшие два байта отбрасываются автоматически.

Для извлечения идентификатора первой группы нам необходимо сначала сместить вправо на 16 бит значение свойства магического номера (чтобы uchar-значение идентификаторов групп попало на нулевой байт uint-числа), а затем на полученное число наложить маску 0x0F, которая оставит только младшие четыре бита из полученного при смещении значения. Преобразование типа uint в тип uchar отбросит все старшие байты числа, оставив только один младший, на который наложена маска. Таким образом мы получаем четырёхбитное значение от 0 до 15.

Извлечение идентификатора второй группы немного отличается от извлечения идентификатора первой группы так как здесь нужное нам значение находится в старших четырёх битах uchar-значения. Поэтому мы сначала делаем точно так же, как и при извлечении идентификатора первой группы — смещаем вправо на 16 бит значение свойства магического номера (чтобы uchar-значение идентификаторов групп попало на нулевой байт uint-числа), на полученное число накладываем маску 0xF0, которая оставляет только старшие четыре бита из полученного при смещении значения, а затем полученное значение смещаем ещё на четыре бита вправо — чтобы старшие биты, хранящие номер идентификатора привести к значениям от 0 до 15.

Для извлечения идентификатора отложенного запроса, нам необходимо самый старший байт uint-числа сместить вправо на 24 бита — чтобы это однобайтное uchar-значение попало на нулевой байт uint-числа, а затем наложить на него маску 0xFF (что впрочем необязательно, так как при преобразовании uint-числа в тип uchar и так останется только один младший байт)

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

//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта-ордера            |
//+------------------------------------------------------------------+
//--- Возвращает (1) тикет, (2) тикет родительского ордера, (3) тикет дочернего ордера, (4) магик, (5) причину выставления ордера,
//--- (6) идентификатор позиции, (7) идентификатор встречной позиции, (8) идентификатор первой группы, (9) идентификатор второй группы,
//--- (10) идентификатор отложенного запроса, (11) идентификатор магического номера, (12) тип, (13) флаг закрытия по StopLoss,
//--- (14) флаг закрытия по TakeProfit (15) время открытия, (16) время закрытия,
//--- (17) дату экспирации, (18) состояние, (19) статус (20) тип ордера по направлению
   long              Ticket(void)                                       const { return this.GetProperty(ORDER_PROP_TICKET);                     }
   long              TicketFrom(void)                                   const { return this.GetProperty(ORDER_PROP_TICKET_FROM);                }
   long              TicketTo(void)                                     const { return this.GetProperty(ORDER_PROP_TICKET_TO);                  }
   long              Magic(void)                                        const { return this.GetProperty(ORDER_PROP_MAGIC);                      }
   long              Reason(void)                                       const { return this.GetProperty(ORDER_PROP_REASON);                     }
   long              PositionID(void)                                   const { return this.GetProperty(ORDER_PROP_POSITION_ID);                }
   long              PositionByID(void)                                 const { return this.GetProperty(ORDER_PROP_POSITION_BY_ID);             }
   long              MagicID(void)                                      const { return this.GetProperty(ORDER_PROP_MAGIC_ID);                   }
   long              GroupID1(void)                                     const { return this.GetProperty(ORDER_PROP_GROUP_ID1);                  }
   long              GroupID2(void)                                     const { return this.GetProperty(ORDER_PROP_GROUP_ID2);                  }
   long              PendReqID(void)                                    const { return this.GetProperty(ORDER_PROP_PEND_REQ_ID);                }
   long              TypeOrder(void)                                    const { return this.GetProperty(ORDER_PROP_TYPE);                       }
   bool              IsCloseByStopLoss(void)                            const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_SL);          }
   bool              IsCloseByTakeProfit(void)                          const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_TP);          }
   long              TimeOpen(void)                                     const { return this.GetProperty(ORDER_PROP_TIME_OPEN);                  }
   long              TimeClose(void)                                    const { return this.GetProperty(ORDER_PROP_TIME_CLOSE);                 }
   datetime          TimeExpiration(void)                               const { return (datetime)this.GetProperty(ORDER_PROP_TIME_EXP);         }
   ENUM_ORDER_STATE  State(void)                                        const { return (ENUM_ORDER_STATE)this.GetProperty(ORDER_PROP_STATE);    }
   ENUM_ORDER_STATUS Status(void)                                       const { return (ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS);  }
   ENUM_ORDER_TYPE   TypeByDirection(void)                              const { return (ENUM_ORDER_TYPE)this.GetProperty(ORDER_PROP_DIRECTION); }

И там же добавим три метода для установки в свойства абстрактного ордера новых свойств:

//--- Возвращает полный профит ордера
   double            ProfitFull(void)                                   const { return this.Profit()+this.Comission()+this.Swap();              }
//--- Возвращает профит ордера в пунктах
   int               ProfitInPoints(void) const;
//--- Устанавливает (1) идентификатор первой группы, (2) идентификатор второй группы, (3) идентификатор отложенного запроса, (4) пользовательский комментарий
   void              SetGroupID1(const long group_id)                         { this.SetProperty(ORDER_PROP_GROUP_ID1,group_id);                }
   void              SetGroupID2(const long group_id)                         { this.SetProperty(ORDER_PROP_GROUP_ID2,group_id);                }
   void              SetPendReqID(const long req_id)                          { this.SetProperty(ORDER_PROP_PEND_REQ_ID,req_id);                }
   void              SetCommentExt(const string comment_ext)                  { this.SetProperty(ORDER_PROP_COMMENT_EXT,comment_ext);           }
   
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Закрытый параметрический конструктор                             |
//+------------------------------------------------------------------+
COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket)
  {
//--- Сохранение целочисленных свойств
   this.m_ticket=ticket;
   this.m_long_prop[ORDER_PROP_STATUS]                               = order_status;
   this.m_long_prop[ORDER_PROP_MAGIC]                                = this.OrderMagicNumber();
   this.m_long_prop[ORDER_PROP_TICKET]                               = this.OrderTicket();
   this.m_long_prop[ORDER_PROP_TIME_EXP]                             = this.OrderExpiration();
   this.m_long_prop[ORDER_PROP_TYPE]                                 = this.OrderType();
   this.m_long_prop[ORDER_PROP_STATE]                                = this.OrderState();
   this.m_long_prop[ORDER_PROP_DIRECTION]                            = this.OrderTypeByDirection();
   this.m_long_prop[ORDER_PROP_POSITION_ID]                          = this.OrderPositionID();
   this.m_long_prop[ORDER_PROP_REASON]                               = this.OrderReason();
   this.m_long_prop[ORDER_PROP_DEAL_ORDER_TICKET]                    = this.DealOrderTicket();
   this.m_long_prop[ORDER_PROP_DEAL_ENTRY]                           = this.DealEntry();
   this.m_long_prop[ORDER_PROP_POSITION_BY_ID]                       = this.OrderPositionByID();
   this.m_long_prop[ORDER_PROP_TIME_OPEN]                            = this.OrderOpenTimeMSC();
   this.m_long_prop[ORDER_PROP_TIME_CLOSE]                           = this.OrderCloseTimeMSC();
   this.m_long_prop[ORDER_PROP_TIME_UPDATE]                          = this.PositionTimeUpdateMSC();
   
//--- Сохранение вещественных свойств
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)]         = this.OrderOpenPrice();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)]        = this.OrderClosePrice();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)]             = this.OrderProfit();
   this.m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)]         = this.OrderCommission();
   this.m_double_prop[this.IndexProp(ORDER_PROP_SWAP)]               = this.OrderSwap();
   this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)]             = this.OrderVolume();
   this.m_double_prop[this.IndexProp(ORDER_PROP_SL)]                 = this.OrderStopLoss();
   this.m_double_prop[this.IndexProp(ORDER_PROP_TP)]                 = this.OrderTakeProfit();
   this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)]     = this.OrderVolumeCurrent();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)]   = this.OrderPriceStopLimit();
   
//--- Сохранение строковых свойств
   this.m_string_prop[this.IndexProp(ORDER_PROP_SYMBOL)]             = this.OrderSymbol();
   this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT)]            = this.OrderComment();
   this.m_string_prop[this.IndexProp(ORDER_PROP_EXT_ID)]             = this.OrderExternalID();
   
//--- Сохранение дополнительных целочисленных свойств
   this.m_long_prop[ORDER_PROP_PROFIT_PT]                            = this.ProfitInPoints();
   this.m_long_prop[ORDER_PROP_TICKET_FROM]                          = this.OrderTicketFrom();
   this.m_long_prop[ORDER_PROP_TICKET_TO]                            = this.OrderTicketTo();
   this.m_long_prop[ORDER_PROP_CLOSE_BY_SL]                          = this.OrderCloseByStopLoss();
   this.m_long_prop[ORDER_PROP_CLOSE_BY_TP]                          = this.OrderCloseByTakeProfit();
   this.m_long_prop[ORDER_PROP_MAGIC_ID]                             = this.GetMagicID();
   this.m_long_prop[ORDER_PROP_GROUP_ID1]                            = this.GetGroupID1();
   this.m_long_prop[ORDER_PROP_GROUP_ID2]                            = this.GetGroupID2();
   this.m_long_prop[ORDER_PROP_PEND_REQ_ID]                          = this.GetPendReqID();
   
//--- Сохранение дополнительных вещественных свойств
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)]        = this.ProfitFull();
   
//--- Сохранение дополнительных строковых свойств
   this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT_EXT)]        = "";
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства ордера               |
//+------------------------------------------------------------------+
string COrder::GetPropertyDescription(ENUM_ORDER_PROP_INTEGER property)
  {
   return
     (
   //--- Общие свойства
      property==ORDER_PROP_MAGIC             ?  CMessage::Text(MSG_ORD_MAGIC)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET            ?  CMessage::Text(MSG_ORD_TICKET)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET_FROM       ?  CMessage::Text(MSG_ORD_TICKET_FROM)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET_TO         ?  CMessage::Text(MSG_ORD_TICKET_TO)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TIME_EXP          ?  CMessage::Text(MSG_ORD_TIME_EXP)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          (this.GetProperty(property)==0     ?  CMessage::Text(MSG_LIB_PROP_NOT_SET)+": "+CMessage::Text(MSG_LIB_PROP_NOT_SET)  :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS))
         )  :
      property==ORDER_PROP_TYPE              ?  CMessage::Text(MSG_ORD_TYPE)+": "+this.TypeDescription()   :
      property==ORDER_PROP_DIRECTION         ?  CMessage::Text(MSG_ORD_TYPE_BY_DIRECTION)+": "+this.DirectionDescription() :
      
      property==ORDER_PROP_REASON            ?  CMessage::Text(MSG_ORD_REASON)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetReasonDescription(this.GetProperty(property))
         )  :
      property==ORDER_PROP_POSITION_ID       ?  CMessage::Text(MSG_ORD_POSITION_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_DEAL_ORDER_TICKET ?  CMessage::Text(MSG_ORD_DEAL_ORDER_TICKET)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_DEAL_ENTRY        ?  CMessage::Text(MSG_ORD_DEAL_ENTRY)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetEntryDescription(this.GetProperty(property))
         )  :
      property==ORDER_PROP_POSITION_BY_ID    ?  CMessage::Text(MSG_ORD_POSITION_BY_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TIME_OPEN         ?  CMessage::Text(MSG_ORD_TIME_OPEN)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")"
         )  :
      property==ORDER_PROP_TIME_CLOSE        ?  CMessage::Text(MSG_ORD_TIME_CLOSE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")"
         )  :
      property==ORDER_PROP_TIME_UPDATE       ?  CMessage::Text(MSG_ORD_TIME_UPDATE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)!=0 ? TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" : "0")
         )  :
      property==ORDER_PROP_STATE             ?  CMessage::Text(MSG_ORD_STATE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": \""+this.StateDescription()+"\""
         )  :
   //--- Дополнительное свойство
      property==ORDER_PROP_STATUS            ?  CMessage::Text(MSG_ORD_STATUS)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": \""+this.StatusDescription()+"\""
         )  :
      property==ORDER_PROP_PROFIT_PT         ?  (
                                                 this.Status()==ORDER_STATUS_MARKET_PENDING ? 
                                                 CMessage::Text(MSG_ORD_DISTANCE_PT)  : 
                                                 CMessage::Text(MSG_ORD_PROFIT_PT)
                                                )+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_CLOSE_BY_SL       ?  CMessage::Text(MSG_LIB_PROP_CLOSE_BY_SL)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)   ?  CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==ORDER_PROP_CLOSE_BY_TP       ?  CMessage::Text(MSG_LIB_PROP_CLOSE_BY_TP)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)   ?  CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==ORDER_PROP_MAGIC_ID          ?  CMessage::Text(MSG_ORD_MAGIC_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_GROUP_ID1         ?  CMessage::Text(MSG_ORD_GROUP_ID1)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_GROUP_ID2         ?  CMessage::Text(MSG_ORD_GROUP_ID2)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_PEND_REQ_ID       ?  CMessage::Text(MSG_ORD_PEND_REQ_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

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

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

protected:
   ENUM_TRADE_EVENT  m_trade_event;                                  // Торговое событие
   bool              m_is_hedge;                                     // Флаг хедж-счёта
   long              m_chart_id;                                     // Идентификатор графика управляющей программы
   int               m_digits;                                       // Digits() символа
   int               m_digits_acc;                                   // Количество знаков после запятой для валюты счета
   long              m_long_prop[EVENT_PROP_INTEGER_TOTAL];          // Целочисленные свойства события
   double            m_double_prop[EVENT_PROP_DOUBLE_TOTAL];         // Вещественные свойства события
   string            m_string_prop[EVENT_PROP_STRING_TOTAL];         // Строковые свойства события
//--- возвращает факт наличия флага в торговом событии
   bool              IsPresentEventFlag(const int event_code)  const { return (this.m_event_code & event_code)==event_code;            }
//--- Возвращает (1) заданный магический номер, идентификатор (2) первой группы, (3) второй группы, (4) отложенного запроса из значения магика
   ushort            GetMagicID(void)                          const { return ushort(this.Magic() & 0xFFFF);                           }
   uchar             GetGroupID1(void)                         const { return uchar(this.Magic()>>16) & 0x0F;                          }
   uchar             GetGroupID2(void)                         const { return uchar((this.Magic()>>16) & 0xF0)>>4;                     }
   uchar             GetPendReqID(void)                        const { return uchar(this.Magic()>>24) & 0xFF;                          }
//--- Защищённый параметрический конструктор
                     CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket);

Методы аналогичны вышерассмотренным методам класса абстрактного ордера.

Теперь в пять классов-потомков класса абстрактного события в файлах EventModify.mqh, EventOrderPlaced.mqh, EventOrderRemoved.mqh, EventPositionClose.mqh и EventPositionOpen.mqh в их методы краткого описания события вместо одной строки

//+------------------------------------------------------------------+
//| Создаёт и возвращает краткое сообщение события                   |
//+------------------------------------------------------------------+
string CEventModify::EventsMessage(void)
  {

//--- (1) заголовок, (2) магический номер
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string magic=(this.Magic()!=0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(string)this.Magic() : "");
   string text="";

впишем для каждого класса такие строки:

//+------------------------------------------------------------------+
//| Создаёт и возвращает краткое сообщение события                   |
//+------------------------------------------------------------------+
string CEventModify::EventsMessage(void)
  {
//--- (1) заголовок, (2) магический номер
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string magic_id=((this.GetPendReqID()>0 || this.GetGroupID1()>0 || this.GetGroupID2()>0) ? " ("+(string)this.GetMagicID()+")" : "");
   string group_id1=(this.GetGroupID1()>0 ? ", G1: "+(string)this.GetGroupID1() : "");
   string group_id2=(this.GetGroupID2()>0 ? ", G2: "+(string)this.GetGroupID2() : "");
   string magic=(this.Magic()!=0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(string)this.Magic()+magic_id+group_id1+group_id2 : "");
   string text="";

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

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

Класс отложенного запроса, первая реализация запросов

В файле торгового класса Trading.mqh, непосредственно перед телом торгового класса CTrading, впишем новый класс, описывающий объект-отложенный запрос:

//+------------------------------------------------------------------+
//| Класс объекта-отложенного запроса                                |
//+------------------------------------------------------------------+
class CPendingReq : public CObject
  {
private:
   MqlTradeRequest      m_request;                       // Структура торгового запроса
   uchar                m_id;                            // Идентификатор торгового запроса
   int                  m_retcode;                       // Результат, на основании которого создан запрос
   double               m_price_create;                  // Цена при создании запроса
   ulong                m_time_create;                   // Время создания запроса
   ulong                m_time_activate;                 // Время активации очередной попытки
   ulong                m_waiting_msc;                   // Продолжительность ожидания между запросами
   uchar                m_current_attempt;               // Номер текущей попытки
   uchar                m_total_attempts;                // Количество попыток
//--- Копирует данные торгового запроса
   void                 CopyRequest(const MqlTradeRequest &request)  { this.m_request=request;        }
//--- Сравнивает объекты CPendingReq между собой по идентификаторам
   virtual int          Compare(const CObject *node,const int mode=0) const;

public:
//--- Возвращает (1) структуру запроса, (2) цену при создании запроса,
//--- (3) время создания запроса, (4) время текущей попытки,
//--- (5) продолжительность ожидания между запросами, (6) номер текущей попытки,
//--- (7) количество попыток, (8) идентификатор запроса
   MqlTradeRequest      MqlRequest(void)                    const { return this.m_request;            }
   double               PriceCreate(void)                   const { return this.m_price_create;       }
   ulong                TimeCreate(void)                    const { return this.m_time_create;        }
   ulong                TimeActivate(void)                  const { return this.m_time_activate;      }
   ulong                WaitingMSC(void)                    const { return this.m_waiting_msc;        }
   uchar                CurrentAttempt(void)                const { return this.m_current_attempt;    }
   uchar                TotalAttempts(void)                 const { return this.m_total_attempts;     }
   uchar                ID(void)                            const { return this.m_id;                 }
//--- Устанавливает (1) цену при создании запроса, (2) время создания запроса,
//--- (3) время текущей попытки, (4) продолжительность ожидания между запросами,
//--- (5) номер текущей попытки, (6) количество попыток, (7) идентификатор запроса
   void                 SetPriceCreate(const double price)        { this.m_price_create=price;        }
   void                 SetTimeCreate(const ulong time)           { this.m_time_create=time;          }
   void                 SetTimeActivate(const ulong time)         { this.m_time_activate=time;        }
   void                 SetWaitingMSC(const ulong miliseconds)    { this.m_waiting_msc=miliseconds;   }
   void                 SetCurrentAttempt(const uchar number)     { this.m_current_attempt=number;    }
   void                 SetTotalAttempts(const uchar number)      { this.m_total_attempts=number;     }
   void                 SetID(const uchar id)                     { this.m_id=id;                     }
//--- Конструкторы
                        CPendingReq(void){;}
                        CPendingReq(const uchar id,const double price,const ulong time,const MqlTradeRequest &request,const int retcode);
  };
//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CPendingReq::CPendingReq(const uchar id,const double price,const ulong time,const MqlTradeRequest &request,const int retcode) : m_price_create(price),
                                                                                                                                m_time_create(time),
                                                                                                                                m_id(id),
                                                                                                                                m_retcode(retcode)
  {
   this.CopyRequest(request);
  }
//+------------------------------------------------------------------+
//| Сравнивает объекты CPendingReq между собой по идентификаторам    |
//+------------------------------------------------------------------+
int CPendingReq::Compare(const CObject *node,const int mode=0) const
  {
   const CPendingReq *compared_req=node;
   return(this.ID()>compared_req.ID() ? 1 : this.ID()<compared_req.ID() ? -1 : 0);
   return 0;
  }
//+------------------------------------------------------------------+

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

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

Вкратце логика такова. Приступим к её реализации.

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

//--- Ищет первый свободный идентификатор отложенного запроса
   int                  GetFreeID(void);
                                    
public:
//--- Конструктор
                        CTrading();

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

//+------------------------------------------------------------------+
//| Ищет первый свободный идентификатор отложенного запроса          |
//+------------------------------------------------------------------+
int CTrading::GetFreeID(void)
  {
   int id=WRONG_VALUE;
   CPendingReq *element=new CPendingReq();
   if(element==NULL)
      return 0;
   for(int i=1;i<256;i++)
     {
      element.SetID((uchar)i);
      this.m_list_request.Sort();
      if(this.m_list_request.Search(element)==WRONG_VALUE)
        {
         id=i;
         break;
        }
     }
   delete element;
   return id;
  }
//+------------------------------------------------------------------+

У нас всего может быть 255 самостоятельных отложенных запроса. Каждый из запросов имеет свои свойства, своё время ожидания между очередными торговыми попытками, и отсюда — свой период существования объекта-отложенного запроса. В связи с этим может возникать ситуация, когда для идентификатора запроса номер, например, 255 уже используется, а идентификатор с номером 0 или 1, или любой из меньших , уже освободились, и их можно использовать для новых торговых запросов. Для поиска наименьшего освободившегося номера идентификатора и используется данный метод.

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

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

//--- Создаёт отложенный запрос
   bool                 CreatePendingRequest(const uchar id,const uchar attempts,const ulong wait,const MqlTradeRequest &request,const int retcode,CSymbol *symbol_obj);

//--- Расположение данных в int-значении магика
      //-----------------------------------------------------------
      //  bit   32|31       24|23       16|15        8|7         0|
      //-----------------------------------------------------------
      //  byte    |     3     |     2     |     1     |     0     |
      //-----------------------------------------------------------
      //  data    |   uchar   |   uchar   |         ushort        |
      //-----------------------------------------------------------
      //  descr   |pend req id| id2 | id1 |          magic        |
      //-----------------------------------------------------------

//--- Устанавливает идентификатор (1) первой группы, (2) второй группы, (3) отложенного запроса в значение магика
   void              SetGroupID1(const uchar group,uint &magic)            { magic &=0xFFF0FFFF; magic |= uint(ConvToXX(group,0)<<16);    }
   void              SetGroupID2(const uchar group,uint &magic)            { magic &=0xFF0FFFFF; magic |= uint(ConvToXX(group,1)<<16);    }
   void              SetPendReqID(const uchar id,uint &magic)              { magic &=0x00FFFFFF; magic |= (uint)id<<24;                   }
//--- Конветирует значение 0 - 15 в нужные биты uchar-числа (0 - младшие, 1 - старшие)
   uchar             ConvToXX(const uchar number,const uchar index)  const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index)));}
//--- Возвращает (1) заданный магический номер, идентификатор (2) первой группы, (3) второй группы, (4) отложенного запроса из значения магика
   ushort            GetMagicID(const uint magic)                    const { return ushort(magic & 0xFFFF);                               }
   uchar             GetGroupID1(const uint magic)                   const { return uchar(magic>>16) & 0x0F;                              }
   uchar             GetGroupID2(const uint magic)                   const { return uchar((magic>>16) & 0xF0)>>4;                         }
   uchar             GetPendReqID(const uint magic)                  const { return uchar(magic>>24) & 0xFF;                              }

Методы возврата значений мы уже рассматривали выше — здесь они идентичны. Рассмотрим методы для установки различных идентификаторов.

Так как у нас два идентификатора групп хранятся всего в одном байте, и числовое значение идентификатора может быть только от 0 до 15 (4 байта), то для установки идентификатора второй группы нам нужно его значение сместить на 4 бита влево — чтобы хранить его в четырёх старших битах однобайтного числа. Для этого создан метод ConvToXX(), который в зависимости от индекса группы (0 или 1) либо смещает переданное в него число (от 0 до 15) на 4 бита влево (вторая группа, индекс 1), либо не смещает (первая группа, индекс 0)

Для установки значения идентификатора первой группы, нам сначала необходимо обнулить четыре младших бита того байта, в который будем сохранять значение идентификатора. Это можно сделать, наложив на значение магика маску, в которой для каждого полубайта (4 бита) использовать значение F.
Т.е, на те биты, в которых значение нужно оставить без изменения, накладываем шестнадцатеричное значение десятичного числа 15 (F), а на те биты, которые нужно стереть, накладываем ноль. Таким образом, маска, накладываемая на значение магического номера, будет такой: 0x FFF0FFFF.
Здесь:

  • FFFF — оставляем идентификатор магика (магик, задаваемый в настройках советника),
  • F0 — стираем (0) младшие четыре бита в байте, хранящем идентификаторы групп, старшие остаются (F) — там хранится идентификатор второй группы,
  • FF — оставляем значение идентификатора отложенного запроса

Далее помещаем в подготовленный байт для хранения идентификаторов групп номер группы, полученный из метода ConvToXX() с индексом 0, и смещённым на 16 бит влево — чтобы полученное число попало в требуемый байт, в котором хранятся идентификаторы групп.

Для установки значения идентификатора второй группы, обнуляем четыре старших бита того байта, в который будем сохранять значение идентификатора. Делаем это, накладывая на значение магика маску 0x FF0FFFFF.
Здесь:

  • FFFF — оставляем идентификатор магика (магик, задаваемый в настройках советника),
  • 0F — стираем (0) старшие четыре бита в байте, хранящем идентификаторы групп, младшие остаются (F) — там хранится идентификатор первой группы,
  • FF — оставляем значение идентификатора отложенного запроса

Далее в подготовленный байт для хранения идентификаторов групп помещаем номер группы, полученный из метода ConvToXX() с индексом 1, и смещённым на 16 бит влево — чтобы полученное число попало в требуемый байт, в котором хранятся идентификаторы групп.

Для установки значения идентификатора отложенного запроса, обнуляем значение байта, в который будем сохранять значение идентификатора. Делаем это, накладывая на значение магика маску 0x 00FFFFFF.
Здесь:

  • FFFF — оставляем идентификатор магика (магик, задаваемый в настройках советника),
  • FF — оставляем значения идентификаторов групп,
  • 00 — стираем значение идентификатора отложенного запроса

Далее в подготовленный байт для хранения идентификатора отложенного запроса помещаем uchar-значение идентификатора, смещённое на 24 бита влево — чтобы полученное число попало в требуемый байт, в котором хранится идентификатор отложенного запроса.

За пределами тела класса напишем реализацию метода для создания объекта-отложенного запроса:

//+------------------------------------------------------------------+
//| Создаёт отложенный запрос                                        |
//+------------------------------------------------------------------+
bool CTrading::CreatePendingRequest(const uchar id,const uchar attempts,const ulong wait,const MqlTradeRequest &request,const int retcode,CSymbol *symbol_obj)
  {
   //--- Создаём новый объект-отложенный запрос
   CPendingReq *req_obj=new CPendingReq(id,symbol_obj.Bid(),symbol_obj.Time(),request,retcode);
   if(req_obj==NULL)
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ));
      return false;
     }
   //--- Если не удалось добавить запрос в список - выводим об этом сообщение,
   //--- удаляем созданный объект и возвращаем false
   if(!this.m_list_request.Add(req_obj))
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ));
      delete req_obj;
      return false;
     }
   //--- Заполняем поля успешно созданного объекта переданными в метод значениями
   req_obj.SetTimeActivate(symbol_obj.Time()+wait);
   req_obj.SetWaitingMSC(wait);
   req_obj.SetCurrentAttempt(0);
   req_obj.SetTotalAttempts(attempts);
   return true;
  }
//+------------------------------------------------------------------+

Метод прост — создаётся новый объект-запрос, добавляется в список отложенных запросов, заполняются поля объекта переданными в метод значениями (время активации запроса рассчитывается как время создания запроса + время ожидания) и возвращается true. При ошибках возвращается false.

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

//+------------------------------------------------------------------+
//| Таймер                                                           |
//+------------------------------------------------------------------+
void CTrading::OnTimer(void)
  {
   //--- В цикле по списку отложенных запросов
   int total=this.m_list_request.Total();
   for(int i=total-1;i>WRONG_VALUE;i--)
     {
      //--- получаем очередной объект-запрос
      CPendingReq *req_obj=this.m_list_request.At(i);
      if(req_obj==NULL)
         continue;
      //--- если текущая попытка превысила установленное количество торговых попыток
      //--- удаляем текущий объект-запрос и переходим к следующему
      if(req_obj.CurrentAttempt()>req_obj.TotalAttempts() || req_obj.CurrentAttempt()>=UCHAR_MAX)
        {
         this.m_list_request.Delete(i);
         continue;
        }
      //--- получаем структуру запроса и из неё объект-символ, на котором должна быть проведена торговая операция
      MqlTradeRequest request=req_obj.MqlRequest();
      CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(request.symbol);
      if(symbol_obj==NULL || !symbol_obj.RefreshRates())
         continue;
      
      //--- Устанавливаем время активации запроса в объекте-запросе
      req_obj.SetTimeActivate(req_obj.TimeCreate()+req_obj.WaitingMSC()*(req_obj.CurrentAttempt()+1));
      
      //--- Если текущее время меньше времени активации запроса,
      //--- значит время запроса не наступило - идём к следующему запросу в списке
      if(symbol_obj.Time()<req_obj.TimeActivate())
         continue;
      
      //--- Устанавливаем номер попытки в объекте-запросе
      req_obj.SetCurrentAttempt(uchar(req_obj.CurrentAttempt()+1));
      
      //--- Получаем идентификатор отложенного запроса
      uchar id=this.GetPendReqID((uint)request.magic);
      
      //--- Получаем список ордеров/позиций, содержащий ордер/позицию с таким идентификатором отложенного запроса
      CArrayObj *list=this.m_market.GetList(ORDER_PROP_PEND_REQ_ID,id,EQUAL);
      if(::CheckPointer(list)==POINTER_INVALID)
         continue;
      //--- В зависимости от типа выполняемого действия в торговом запросе 
      switch(request.action)
        {
         //--- Открытие позиции
         case TRADE_ACTION_DEAL :
            //--- если нет позиции/ордера с полученным идентификатором отложенного запроса (список пустой) - отсылаем торговый запрос
            if(list.Total()==0)
              {
               this.OpenPosition((ENUM_POSITION_TYPE)request.type,request.volume,request.symbol,request.magic,request.sl,request.tp,request.comment,request.deviation);
              }
            //--- если позиция/ордер с идентификатором отложенного запроса уже есть (список не пустой) - запрос отработан - удаляем его 
            else
               this.m_list_request.Delete(i);
            break;
         //---
         default:
            break;
        }
     }
  }
//+------------------------------------------------------------------+

Логика работы подробно расписана в комментариях к коду, и думаю, в пояснениях не нуждается. Единственное, что можно отметить — это расчёт времени активации очередного торгового запроса. Время рассчитывается как "время создания объекта-запроса" + время ожидания в милисекундах * номер очередной попытки. Таким образом, мы время запроса привязываем ко времени создания первого запроса и номеру попытки — чем больше номер попытки, тем больше времени должно пройти от создания объекта до его активации. И это время увеличивается дискретно: при ожидании в 10 секунд, первая попытка должна произойти через 10 секунд, вторая — через 20, третья — через 30, и т.д. Таким образом, интервал между очередными торговыми попытками всегда будет не меньше заданного времени ожидания между ними.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Внесём небольшие доработки в файл Engine.mqh класса основного объекта библиотеки CEngine.

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

//--- Устанавливает для торговых классов:
//--- (1) корректное значение политики исполнения, (2) значение политики исполнения,
//--- (3) корректный тип истечения ордера, (4) тип истечения ордера,
//--- (5) магик, (6) Комментарий, (7) размер проскальзывания, (8) объём, (9) срок истечения ордера,
//--- (10) флаг асинхронной отправки торгового запроса, (11) уровень логирования, (12) количество торговых попыток
   void                 TradingSetCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL);
   void                 TradingSetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL);
   void                 TradingSetCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL);
   void                 TradingSetTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL);
   void                 TradingSetMagic(const uint magic,const string symbol_name=NULL);
   void                 TradingSetComment(const string comment,const string symbol_name=NULL);
   void                 TradingSetDeviation(const ulong deviation,const string symbol_name=NULL);
   void                 TradingSetVolume(const double volume=0,const string symbol_name=NULL);
   void                 TradingSetExpiration(const datetime expiration=0,const string symbol_name=NULL);
   void                 TradingSetAsyncMode(const bool async_mode=false,const string symbol_name=NULL);
   void                 TradingSetLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol_name=NULL);
   void                 TradingSetTotalTry(const uchar attempts)                          { this.m_trading.SetTotalTry(attempts);               }
   
//--- Устанавливает стандартные звуки (symbol==NULL) торговому объекту символа, (symbol!=NULL) торговым объектам всех символов

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

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

//--- Конструктор/Деструктор
                        CEngine();
                       ~CEngine();

private:
//--- Конветирует значение 0 - 15 в нужные биты uchar-числа (0 - младшие, 1 - старшие)
   uchar                ConvToXX(const uchar number,const uchar index)  const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index)));   }

public:
//--- Создаёт и возвращает составной магик из заданного значения магика, идентификаторов первой и второй групп и идентификатора отложенного запроса
   uint                 SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0);

  };
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Создаёт и возвращает составной магик                             |
//| из заданного значения магика,                                    |
//| идентификаторов первой и второй групп и                          |
//| идентификатора отложенного запроса                               |
//+------------------------------------------------------------------+
uint CEngine::SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0)
  {
   uint magic=magic_id;
   this.m_trading.SetGroupID1(group_id1,magic);
   this.m_trading.SetGroupID2(group_id2,magic);
   this.m_trading.SetPendReqID(pending_req_id,magic);
   return magic;
  }
//+------------------------------------------------------------------+

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

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

Чтобы протестировать создание и обработку отложенного запроса, нам необходимо смоделировать ошибочную ситуацию, требующую повторного запроса после ожидания. Если помните, то мы сделали обработку ошибки "отсутствует связь с торговым сервером" именно как ожидание в 20 секунд. По умолчанию у нас задано пять торговых попыток. Значит, нам нужно просто запустить советник, отключить интернет (разорвать связь с торговым сервером) и попытаться открыть позицию (кнопка Buy или Sell на торговой панели тестового советника). После получения ошибки у нас будет 20 * 5 = 100 секунд чтобы включить обратно интернет и поглядеть обработку советником созданного отложенного запроса. По истечении 100 секунд — времени на совершение пяти повторных попыток, отложенный запрос должен автоматически удалиться из списка запросов (уже после восстановления связи с сервером — так как время можно получить только при установленной связи). Пока эта возможность не реализована так как во-первых это всего-лишь тест работы отложенного запроса, а во-вторых — функционал будет развиваться и потребует внесения изменений — тогда и будут реализованы остальные задуманные возможности. Т.е., после восстановления связи с торговым сервером, советник в любом случае начнёт отсылать торговые запросы, прописанные в объекте-запросе. После первой из повторных попыток позиция должна быть открыта и объект-отложенный запрос удалён из списка запросов.

Наряду с отложенным запросом мы реализовали хранение нескольких идентификаторов в значении магического номера. Для тестирования установки этих идентификаторов в магик отсылаемого запроса, сделаем случайный выбор номеров первой и второй подгрупп в группах 1 и 2 и запись их в свойство ордера "магический номер". При открытии позиции, в журнале будем видеть как реальное значение магика открытой позиции или установленного ордера, так и идентификатор магика, задаваемый в настройках (в скобках после истинного значения магического номера), и идентификаторы подгрупп в первой и второй группе (обозначаются как G1 и G2)

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

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

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

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

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

//--- global variables
CEngine        engine;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ushort         magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           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;
uchar          group1;
uchar          group2;
//+------------------------------------------------------------------+

В обработчике 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);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
   trailing_stop=InpTrailingStop*Point();
   trailing_step=InpTrailingStep*Point();
   trailing_start=InpTrailingStart;
   stoploss_to_modify=InpStopLossModify;
   takeprofit_to_modify=InpTakeProfitModify;
   
//--- Инициализация случайных номеров групп
   group1=0;
   group2=0;
   srand(GetTickCount());
   
//--- Инициализация библиотеки DoEasy
   OnInitDoEasy();

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

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

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

//--- Передача в торговый класс всех имеющихся коллекций
   engine.TradingOnInit();

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

Для того, чтобы протестировать магик со случайными значениями идентификаторов групп, введём булеву переменную comp_magic, равную true, и указывающую на использование составного магика в функциях открытия позиций/установки отложенных ордеров. Вместо использования переменной magic_number, ведём новую переменную magic, хранящую значение магика в зависимости от значения переменной comp_magic.
При установке значения в magic (постоянный магик, заданный в настройках или составной магик, состоящий из заданного магика + случайные значения идентификаторов групп 1 и 2) будем проверять значение comp_magic, и если true, то используем составной магик, а если false
то заданный в настройках.

Внесём правки в функцию обработки нажатия кнопок торговой панели советника PressButtonEvents():

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

Во всех строках вызова торговых методов библиотеки заменим переменную magic_number на переменную magic.

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

//+------------------------------------------------------------------+
//| Случайное число в диапазоне                                      |
//+------------------------------------------------------------------+
uint Rand(const uint min=0, const uint max=15)
  {
   return (rand() % (max+1-min))+min;
  }
//+------------------------------------------------------------------+

Скомпилируем и запустим советник. Отключим любым способом интернет и дождёмся такого значка в правом нижнем углу терминала:



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

2019.11.26 15:34:48.661 CTrading::OpenPosition<uint,uint>: Invalid request:
2019.11.26 15:34:48.661 No connection with the trade server
2019.11.26 15:34:48.661 Correction of trade request parameters ...
2019.11.26 15:34:48.661 Trading attempt #1. Error: No connection with the trade server

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

Затем подключаем интернет, тем самым восстанавливая связь с торговым сервером:


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

2019.11.26 15:35:00.853 CTrading::OpenPosition<double,double>: Invalid request:
2019.11.26 15:35:00.853 Trading is prohibited for the current account
2019.11.26 15:35:00.853 Correction of trade request parameters ...
2019.11.26 15:35:00.853 Trading operation aborted
2019.11.26 15:35:01.192 CTrading::OpenPosition<double,double>: Invalid request:
2019.11.26 15:35:01.192 Trading is prohibited for the current account
2019.11.26 15:35:01.192 Correction of trade request parameters ...
2019.11.26 15:35:01.192 Trading operation aborted
2019.11.26 15:35:01.942 - Position is open: 2019.11.26 10:35:01.660 -
2019.11.26 15:35:01.942 EURUSD Opened 0.10 Sell #486405595 [0.10 Market-order Sell #486405595] at price 1.10126, sl 1.10285, tp 1.09985, Magic number 17629307 (123), G1: 13
2019.11.26 15:35:01.942 OnDoEasyEvent: Position is open

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

Также в журнале видим реальный магический номер 17629307, за ним в скобочках видим установленный магик в настройках советника (123), плюс видим одну запись G1: 13, которая сообщает нам, что идентификатор первой группы равен 13, а идентификатора второй группы нет — его значение оказалось равным нулю, поэтому не было выведено второй записи с идентификаторм второй группы G2: XX

Пожалуйста, имейте в виду:

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

Что дальше

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

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

К содержанию

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

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

Прикрепленные файлы |
MQL5.zip (3628.96 KB)
MQL4.zip (3628.96 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (3)
Alexander
Alexander | 30 нояб. 2019 в 04:01
  Здравствуйте! Скачал последнюю версию библиотеки и Експерт Part_26, поставил на тестер визуальный режим и рыночные

ордера открываются, а все отложенные нет.

Биржевые символы, брокер Открытие, version 5.00 build 2190.

Artyom Trishkin
Artyom Trishkin | 30 нояб. 2019 в 05:14
Alexander:
  Здравствуйте! Скачал последнюю версию библиотеки и Експерт Part_26, поставил на тестер визуальный режим и рыночные

ордера открываются, а все отложенные нет.

Биржевые символы, брокер Открытие, version 5.00 build 2190.

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

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

Artyom Trishkin, 2019.11.28 09:39

В этой версии библиотеки, равно как и в следующей, я пропустил проверку типов заливки ордеров и типов их экспирации. В следующей (27) статье будут исправления.

Пока могу предложить при отсылке торгового запроса явно указывать тип экспирации. Например, для выставления отложенного ордера Sell Limit, нужно дописать в тестовом советнике:

//--- Если нажата кнопка BUTT_SELL_LIMIT: Выставить SellLimit
else if(button==EnumToString(BUTT_SELL_LIMIT))
  {
   //--- Устанавливаем ордер SellLimit
   engine.PlaceSellLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellLimit","Pending order SellLimit"),0,ORDER_TIME_DAY);
  }

И так сделать во всех строках, в которых есть вызов методов установки отложенных ордеров.


Alexander
Alexander | 30 нояб. 2019 в 17:21
Да, спасибо, так заработало.
Расширяем функционал Конструктора стратегий Расширяем функционал Конструктора стратегий
В предшествующих двух статьях было рассмотрено использование технических фигур Меррилла применительно к различным типам данных. Разработано приложения для тестирования на базе этой идеи. В данной статье продолжаем работу над Конструктором стратегий, улучшаем его работу, делаем более удобным и расширяем его функционал и возможности.
Непрерывная скользящая оптимизация (Часть 1): Механизм работы с отчетами оптимизации Непрерывная скользящая оптимизация (Часть 1): Механизм работы с отчетами оптимизации
Первая часть статьи посвящена созданию инструментария для работы с отчетностью оптимизации, ее импорта из терминала, а также процессам фильтрации и сортировки полученных данных. MetaTrader 5 позволяет выгружать отчет проходов оптимизаций, но хотелось бы иметь возможность добавления в отчет собственных данных.
Исследование сезонных характеристик финансовых временных рядов при помощи диаграмм Boxplot Исследование сезонных характеристик финансовых временных рядов при помощи диаграмм Boxplot
Исследование сезонных характеристик финансовых временных рядов при помощи диаграмм Boxplot. Каждый отдельный ящик с усами дает хорошее представление о том, как распределены значения в наборе данных. Boxplots не следует путать с графиком японских свечей, хотя они визуально похожи.
Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXV): Обработка ошибок, возвращаемых торговым сервером Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXV): Обработка ошибок, возвращаемых торговым сервером
После того, как мы отправили торговый приказ на сервер, не стоит считать, что "дело сделано". Теперь нам необходимо проверить коды ошибок, ну или отсутствие ошибок. В статье рассмотрим обработку ошибок, возвращаемых торговым сервером, подготовим базу для создания отложенных торговых запросов.