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

Artyom Trishkin | 19 сентября, 2019

Содержание

С этой статьи мы начинаем новую достаточно обширную тему — торговые классы.

Концепция

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

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

Так как вся торговля напрямую завязана на символ, то базовый торговый объект будет входить в состав объекта-символа, рассмотренного нами в статье 14. А доступ к торговым объектам символов мы организуем позже — в основном торговом классе. Сегодня же мы сделаем временный доступ к торговым объектам символов из базового класса библиотеки CEngine, рассмотренного нами в статье 3 — ведь именно в базовом классе аккумулируются все данные окружения, и именно там нам доступны любые свойства аккаунта и символа, необходимые для работы с торговыми классами.

Создаём базовый торговый объект

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

//+------------------------------------------------------------------+
//| Данные для работы с торговыми классами                           |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Уровень логирования                                             |
//+------------------------------------------------------------------+
enum ENUM_LOG_LEVEL
  {
   LOG_LEVEL_NO_MSG,                                        // Логирование торговли отключено
   LOG_LEVEL_ERROR_MSG,                                     // Только торговые ошибки
   LOG_LEVEL_ALL_MSG                                        // Полное логирование
  };
//+------------------------------------------------------------------+

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

   MSG_LIB_SYS_NOT_SYMBOL_ON_SERVER,                  // Ошибка. Такого символа нет на сервере
   MSG_LIB_SYS_NOT_SYMBOL_ON_LIST,                    // Ошибка. Такого символа нет в списке используемых символов: 
   MSG_LIB_SYS_FAILED_PUT_SYMBOL,                     // Не удалось поместить в обзор рынка. Ошибка: 
   MSG_LIB_SYS_ERROR_NOT_POSITION,                    // Ошибка. Не позиция:
   MSG_LIB_SYS_ERROR_NO_OPEN_POSITION_WITH_TICKET,    // Ошибка. Нет открытой позиции с тикетом #
   MSG_LIB_SYS_ERROR_NO_PLACED_ORDER_WITH_TICKET,     // Ошибка. Нет установленного ордера с тикетом #
   MSG_LIB_SYS_ERROR_FAILED_CLOSE_POS,                // Не удалось закрыть позицию. Ошибка 
   MSG_LIB_SYS_ERROR_FAILED_MODIFY_ORD,               // Не удалось модифицировать ордер. Ошибка
   MSG_LIB_SYS_ERROR_UNABLE_PLACE_WITHOUT_TIME_SPEC,  // Ошибка: невозможно разместить ордер без явно заданного его времени истечения
   MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ,            // Ошибка. Не удалось получить торговый объект
   MSG_LIB_SYS_ERROR_FAILED_GET_POS_OBJ,              // Ошибка. Не удалось получить объект-позицию
   MSG_LIB_SYS_ERROR_FAILED_GET_ORD_OBJ,              // Ошибка. Не удалось получить объект-ордер
   MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ,              // Ошибка. Не удалось получить объект-символ
   MSG_LIB_SYS_ERROR_CODE_OUT_OF_RANGE,               // Код возврата вне заданного диапазона кодов ошибок
   MSG_LIB_TEXT_FAILED_ADD_TO_LIST,                   // не удалось добавить в список
   MSG_LIB_TEXT_TIME_UNTIL_THE_END_DAY,               // Будет использоваться время действия ордера до конца текущего дня
   MSG_LIB_TEXT_SUNDAY,                               // Воскресение
   MSG_ACC_MARGIN_MODE_RETAIL_EXCHANGE,               // Биржевой рынок
   MSG_ACC_UNABLE_CLOSE_BY,                           // Закрытие встречным доступно только на счетах с типом "Хеджинг"
   MSG_ACC_SAME_TYPE_CLOSE_BY,                        // Ошибка. Позиции для встречного закрытия имеют один и тот же тип
   
//--- CEngine
   MSG_ENG_NO_TRADE_EVENTS,                           // С момента последнего запуска ЕА торговых событий не было
   MSG_ENG_FAILED_GET_LAST_TRADE_EVENT_DESCR,         // Не удалось получить описание последнего торгового события
   MSG_ENG_FAILED_GET_MARKET_POS_LIST,                // Не удалось получить список открытых позиций
   MSG_ENG_FAILED_GET_PENDING_ORD_LIST,               // Не удалось получить список установленных ордеров
   MSG_ENG_NO_OPEN_POSITIONS,                         // Нет открытых позиций
   MSG_ENG_NO_PLACED_ORDERS,                          // Нет установленных ордеров

  };

Здесь показаны только части файла с "привязкой к местности", куда необходимо вписать константы перечислений индексов.

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

   {"Ошибка. Такого символа нет на сервере","Error. There is no such symbol on the server"},
   {"Ошибка. Такого символа нет в списке используемых символов: ","Error. This symbol is not in the list of the symbols used: "},
   {"Не удалось поместить в обзор рынка. Ошибка: ","Failed to put in the market watch. Error: "},
   {"Ошибка. Не позиция: ","Error. Not position: "},
   {"Ошибка. Нет открытой позиции с тикетом #","Error. No open position with ticket #"},
   {"Ошибка. Нет установленного ордера с тикетом #","Error. No placed order with ticket #"},
   {"Не удалось закрыть позицию. Ошибка ","Could not close position. Error "},
   {"Не удалось модифицировать ордер. Ошибка ","Failed to order modify. Error "},
   {"Ошибка: невозможно разместить ордер без явно заданного его времени истечения","Error: Unable to place order without explicitly specified expiration time"},
   {"Ошибка. Не удалось получить торговый объект","Error. Failed to get a trade object"},
   {"Ошибка. Не удалось получить объект-позицию","Error. Failed to get position object"},
   {"Ошибка. Не удалось получить объект-ордер","Error. Failed to get order object"},
   {"Ошибка. Не удалось получить объект-символ","Error. Failed to get symbol object"},
   {"Код возврата вне заданного диапазона кодов ошибок","Return code out of range of error codes"},
   {"не удалось добавить в список","failed to add to the list"},
   {"Будет использоваться время действия ордера до конца текущего дня","The order validity time until the end of the current day will be used"},
   
   {"Воскресение","Sunday"},
   {"Биржевой рынок","Exchange market mode"},
   {"Закрытие встречным доступно только на счетах с типом \"Хеджинг\"","Close by opposite position is available only on accounts with the type \"Hedging\""},
   {"Ошибка. Позиции для встречного закрытия имеют один и тот же тип","Error. Positions of the same type in a counterclosure request"},
   
//--- CEngine
   {"С момента последнего запуска ЕА торговых событий не было","There have been no trade events since the last launch of EA"},
   {"Не удалось получить описание последнего торгового события","Failed to get the description of the last trading event"},
   {"Не удалось получить список открытых позиций","Failed to get open positions list"},
   {"Не удалось получить список установленных ордеров","Failed to get pending orders list"},
   {"Нет открытых позиций","No open positions"},
   {"Нет установленных ордеров","No placed orders"},

  };

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

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

//+------------------------------------------------------------------+
//| Возвращает тип ордера по типу позиции                            |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE OrderTypeByPositionType(ENUM_POSITION_TYPE type_position)
  {
   return(type_position==POSITION_TYPE_BUY ? ORDER_TYPE_BUY :  ORDER_TYPE_SELL);
  }
//+------------------------------------------------------------------+
//| Возвращает обратный тип ордера по типу позиции                   |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE OrderTypeOppositeByPositionType(ENUM_POSITION_TYPE type_position)
  {
   return(type_position==POSITION_TYPE_BUY ? ORDER_TYPE_SELL :  ORDER_TYPE_BUY);
  }
//+------------------------------------------------------------------+

Все данные подготовили, теперь займёмся непосредственно классом торгового объекта.

В папке объектов библиотеки \MQL5\Include\DoEasy\Objects\ создадим подпапку Trade\, а в ней — новый класс CTradeObj в файле TradeObj.mqh.
Сразу же подключим к вновь созданному файлу файл сервисных функций:

//+------------------------------------------------------------------+
//|                                                     TradeObj.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\..\Services\DELib.mqh"
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//|                                                     TradeObj.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\..\Services\DELib.mqh"
//+------------------------------------------------------------------+
//| Класс торгового объекта                                          |
//+------------------------------------------------------------------+
class CTradeObj
  {
private:
   MqlTick                    m_tick;                                            // Структура тика для получения цен
   MqlTradeRequest            m_request;                                         // Структура торгового запроса
   MqlTradeResult             m_result;                                          // Структура результата исполнения торгового запроса
   ENUM_ACCOUNT_MARGIN_MODE   m_margin_mode;                                     // Режим расчёта маржи
   ENUM_ORDER_TYPE_FILLING    m_type_filling;                                    // Политика исполнения
   ENUM_ORDER_TYPE_TIME       m_type_expiration;                                 // Тип истечения ордера
   int                        m_symbol_expiration_flags;                         // Флаги режимов истечения ордера для символа торгового объекта
   ulong                      m_magic;                                           // Магик
   string                     m_symbol;                                          // Символ
   string                     m_comment;                                         // Комментарий
   ulong                      m_deviation;                                       // Размер проскальзывания в пунктах
   double                     m_volume;                                          // Объём
   datetime                   m_expiration;                                      // Срок истечения ордера (для ордеров типа ORDER_TIME_SPECIFIED)
   bool                       m_async_mode;                                      // Флаг асинхронной отправки торгового запроса
   ENUM_LOG_LEVEL             m_log_level;                                       // Уровень логирования
   int                        m_stop_limit;                                      // Дистанция установки StopLimit ордера в пунктах
public:
//--- Конструктор
                              CTradeObj();;

//--- Устанавливает значения по умолчанию
   void                       Init(const string symbol,
                                   const ulong magic,
                                   const double volume,
                                   const ulong deviation,
                                   const int stoplimit,
                                   const datetime expiration,
                                   const bool async_mode,
                                   const ENUM_ORDER_TYPE_FILLING type_filling,
                                   const ENUM_ORDER_TYPE_TIME type_expiration,
                                   ENUM_LOG_LEVEL log_level);
                                   
//--- (1) Возвращает режим расчёта маржи (2) флаг счёта хедж
   ENUM_ACCOUNT_MARGIN_MODE   GetMarginMode(void)                                const { return this.m_margin_mode;           }
   bool                       IsHedge(void) const { return this.GetMarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING;          }
//--- (1) Устанавливает, (2) возвращает уровень логирования ошибок
   void                       SetLogLevel(const ENUM_LOG_LEVEL level)                  { this.m_log_level=level;              }
   ENUM_LOG_LEVEL             GetLogLevel(void)                                  const { return this.m_log_level;             }
//--- (1) Устанавливает, (2) возвращает политику исполнения
   void                       SetTypeFilling(const ENUM_ORDER_TYPE_FILLING type)       { this.m_type_filling=type;            }
   ENUM_ORDER_TYPE_FILLING    GetTypeFilling(void)                               const { return this.m_type_filling;          }
//--- (1) Устанавливает, (2) возвращает тип истечения ордера
   void                       SetTypeExpiration(const ENUM_ORDER_TYPE_TIME type)       { this.m_type_expiration=type;         }
   ENUM_ORDER_TYPE_TIME       GetTypeExpiration(void)                            const { return this.m_type_expiration;       }
//--- (1) Устанавливает, (2) возвращает магик
   void                       SetMagic(const ulong magic)                              { this.m_magic=magic;                  }
   ulong                      GetMagic(void)                                     const { return this.m_magic;                 }
//--- (1) Устанавливает, (2) возвращает символ
   void                       SetSymbol(const string symbol)                           { this.m_symbol=symbol;                }
   string                     GetSymbol(void)                                    const { return this.m_symbol;                }
//--- (1) Устанавливает, (2) возвращает комментарий
   void                       SetComment(const string comment)                         { this.m_comment=comment;              }
   string                     GetComment(void)                                   const { return this.m_comment;               }
//--- (1) Устанавливает, (2) возвращает размер проскальзывания
   void                       SetDeviation(const ulong deviation)                      { this.m_deviation=deviation;          }
   ulong                      GetDeviation(void)                                 const { return this.m_deviation;             }
//--- (1) Устанавливает, (2) возвращает объём
   void                       SetVolume(const double volume)                           { this.m_volume=volume;                }
   double                     GetVolume(void)                                    const { return this.m_volume;                }
//--- (1) Устанавливает, (2) возвращает срок истечения ордера
   void                       SetExpiration(const datetime time)                       { this.m_expiration=time;              }
   datetime                   GetExpiration(void)                                const { return this.m_expiration;            }
//--- (1) Устанавливает, (2) возвращает флаг асинхронной отправки торгового запроса
   void                       SetAsyncMode(const bool async)                           { this.m_async_mode=async;             }
   bool                       GetAsyncMode(void)                                 const { return this.m_async_mode;            }
   
//--- Данные последнего запроса:
//--- Возвращает (1) Тип выполненного действия, (2) магик, (3) тикет ордера, (4) объём,
//--- цены (5) открытия, (6) StopLimit-ордера, (7) StopLoss, (8) TakeProfit, (9) отклонение,
//--- тип (10) ордера, (11) исполнения, (12) времени действия, (13) срок истечения ордера,
//--- (14) комментарий, (15) тикет позиции, (16) тикет встречной позиции
   ENUM_TRADE_REQUEST_ACTIONS GetLastRequestAction(void)                         const { return this.m_request.action;        }
   ulong                      GetLastRequestMagic(void)                          const { return this.m_request.magic;         }
   ulong                      GetLastRequestOrder(void)                          const { return this.m_request.order;         }
   double                     GetLastRequestVolume(void)                         const { return this.m_request.volume;        }
   double                     GetLastRequestPrice(void)                          const { return this.m_request.price;         }
   double                     GetLastRequestStopLimit(void)                      const { return this.m_request.stoplimit;     }
   double                     GetLastRequestStopLoss(void)                       const { return this.m_request.sl;            }
   double                     GetLastRequestTakeProfit(void)                     const { return this.m_request.tp;            }
   ulong                      GetLastRequestDeviation(void)                      const { return this.m_request.deviation;     }
   ENUM_ORDER_TYPE            GetLastRequestType(void)                           const { return this.m_request.type;          }
   ENUM_ORDER_TYPE_FILLING    GetLastRequestTypeFilling(void)                    const { return this.m_request.type_filling;  }
   ENUM_ORDER_TYPE_TIME       GetLastRequestTypeTime(void)                       const { return this.m_request.type_time;     }
   datetime                   GetLastRequestExpiration(void)                     const { return this.m_request.expiration;    }
   string                     GetLastRequestComment(void)                        const { return this.m_request.comment;       }
   ulong                      GetLastRequestPosition(void)                       const { return this.m_request.position;      }
   ulong                      GetLastRequestPositionBy(void)                     const { return this.m_request.position_by;   }

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

//--- Открывает позицию
   bool                       OpenPosition(const ENUM_POSITION_TYPE type,
                                           const double volume,
                                           const double sl=0,
                                           const double tp=0,
                                           const ulong magic=ULONG_MAX,
                                           const ulong deviation=ULONG_MAX,
                                           const string comment=NULL);
//--- Закрывает позицию
   bool                       ClosePosition(const ulong ticket,
                                            const ulong deviation=ULONG_MAX,
                                            const string comment=NULL);
//--- Частично закрывает позицию
   bool                       ClosePositionPartially(const ulong ticket,
                                                     const double volume,
                                                     const ulong deviation=ULONG_MAX,
                                                     const string comment=NULL);
//--- Закрывает позицию встречной
   bool                       ClosePositionBy(const ulong ticket,const ulong ticket_by);
//--- Модифицирует позицию
   bool                       ModifyPosition(const ulong ticket,const double sl=WRONG_VALUE,const double tp=WRONG_VALUE);
//--- Устанавливает ордер
   bool                       SetOrder(const ENUM_ORDER_TYPE type,
                                       const double volume,
                                       const double price,
                                       const double sl=0,
                                       const double tp=0,
                                       const double price_stoplimit=0,
                                       const ulong magic=ULONG_MAX,
                                       const datetime expiration=0,
                                       const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,
                                       const string comment=NULL);
//--- Удаляет ордер
   bool                       DeleteOrder(const ulong ticket);
//--- Модифицирует ордер
   bool                       ModifyOrder(const ulong ticket,
                                          const double price=WRONG_VALUE,
                                          const double sl=WRONG_VALUE,
                                          const double tp=WRONG_VALUE,
                                          const double price_stoplimit=WRONG_VALUE,
                                          const datetime expiration=WRONG_VALUE,
                                          const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE);
   
  };
//+------------------------------------------------------------------+

Рассмотрим что мы тут понаписали.

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

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

Остальные переменные-члены класса, думаю, в пояснениях не нуждаются.

Рассмотрим реализацию методов класса.

Методы установки и получения свойств (Set- и Get-методы) торгового приказа прописаны в теле класса. Всё, что они делают — это записывают в соответствующую переменную переданное в метод значение, либо возвращают значение соответствующей переменной. Работают эти методы только с переменными, хранящими значения по умолчанию. Т.е. при помощи этих методов можно установить требуемое свойство торгового запроса, и далее оно будет иметь по умолчанию установленное значение. Если же нужно разово использовать иное значение для торгового приказа — отличное от заданного по умолчанию, то в методах отсылки торговых приказов предусмотрена передача требуемых значений для разового использования переданного в метод значения.

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

Методы, возвращающие результат торгового запроса, служат для получения информации о результате обработки торгового запроса. Если запрос оказался ошибочным, то в retcode мы сможем увидеть уточняющую информацию по коду ошибки. Либо же структура будет заполнена данными об открытой позиции или установленного отложенного ордера, а в request_id будет записан код запроса, который впоследствии можно анализировать в обработчике OnTradeTransaction() чтобы можно было связать торговый запрос, отосланный на сервер посредством OrderSendAsync() с результатом этого запроса.
В данной библиотеке мы не используем OnTradeTransaction() по причине его отсутствия в MQL4, и анализ асинхронной отправки приказов и их результатов будем проводить самостоятельно.

Конструктор класса:

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

В его списке инициализации задаём инициализирующие значения:

В теле класса вписываем в переменную m_margin_mode режим расчёта маржи, установленный для аккаунта.
Для MQL5 — получаем требуемое значение посредством функции AccountInfoInteger() с идентификатором свойства ACCOUNT_MARGIN_MODE.
А для MQL4 сразу же вписываем хеджевый режим расчёта маржи ( ACCOUNT_MARGIN_MODE_RETAIL_HEDGING).

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

Напишем метод инициализации параметров по умолчанию торгового запроса:

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

В метод передаются требуемые значения параметров торгового запроса, и в теле метода переданные значения устанавливаются соответствующим переменным при помощи методов их установки, рассмотренные выше. Флаги разрешённых режимов истечения ордера устанавливаем при помощи функции SymbolInfoInteger() с идентификатором свойства SYMBOL_EXPIRATION_MODE. А объём устанавливаем минимально разрешённый для символа при помощи функции SymbolInfoDouble() с идентификатором свойства SYMBOL_VOLUME_MIN.

Метод открытия позиции:

//+------------------------------------------------------------------+
//| Открывает позицию                                                |
//+------------------------------------------------------------------+
bool CTradeObj::OpenPosition(const ENUM_POSITION_TYPE type,
                             const double volume,
                             const double sl=0,
                             const double tp=0,
                             const ulong magic=ULONG_MAX,
                             const ulong deviation=ULONG_MAX,
                             const string comment=NULL)
  {
   //--- Если не удалось получить текущие цены - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
   if(!::SymbolInfoTick(this.m_symbol,this.m_tick))
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode));
      return false;
     }
   //--- Очищаем структуры
   ::ZeroMemory(this.m_request);
   ::ZeroMemory(this.m_result);
   //--- Заполняем структуру запроса
   this.m_request.action   =  TRADE_ACTION_DEAL;
   this.m_request.symbol   =  this.m_symbol;
   this.m_request.magic    =  (magic==ULONG_MAX ? this.m_magic : magic);
   this.m_request.type     =  OrderTypeByPositionType(type);
   this.m_request.price    =  (type==POSITION_TYPE_BUY ? this.m_tick.ask : this.m_tick.bid);
   this.m_request.volume   =  volume;
   this.m_request.sl       =  sl;
   this.m_request.tp       =  tp;
   this.m_request.deviation=  (deviation==ULONG_MAX ? this.m_deviation : deviation);
   this.m_request.comment  =  (comment==NULL ? this.m_comment : comment);
   //--- Возвращаем результат отсылки запроса на сервер
   return
     (
      #ifdef __MQL5__
         !this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)
      #else 
         (::OrderSend(m_request.symbol,m_request.type,m_request.volume,m_request.price,(int)m_request.deviation,m_request.sl,m_request.tp,m_request.comment,(int)m_request.magic,m_request.expiration,clrNONE)!=WRONG_VALUE)
      #endif 
     );
  }
//+------------------------------------------------------------------+

В метод передаётся тип позиции, её объём, цены StopLoss и TakeProfit, магик позиции, величина проскальзывания и комментарий.
По умолчанию для StopLoss, TakeProfit, магика, проскальзывания и комментария заданы значения. Если эти значения оставить без изменения при вызове метода, то для этих значений будут использованы значения, установленные по умолчанию в методе Init(), или заданные непосредственно из программы методами установки умолчательных значений, которые мы рассмотрели выше. Вся логика метода прописана в комментариях к коду.
Единственное, что тут можно отметить — в поле структуры торгового запроса, хранящего тип ордера, отсылаем результат, возвращаемый функцией OrderTypeByPositionType(), которую мы написали в DELib.mqh для получения типа ордера по типу позиции. И ещё: метод никак не проверяет верность переданных в него параметров, и считает, что они уже проверены и корректны.
Для MQL4 мы пока также ничего не проверяем при возврате результата отправки запроса на сервер, и не заполняем структуру результата торгового запроса — на данный момент нам необходимо быстро собрать торговые методы для тестирования. А в последующих статьях будем всё приводить в порядок.

Метод закрытия позиции:

//+------------------------------------------------------------------+
//| Закрывает позицию                                                |
//+------------------------------------------------------------------+
bool CTradeObj::ClosePosition(const ulong ticket,
                              const ulong deviation=ULONG_MAX,
                              const string comment=NULL)
  {
   //--- Если не удалось выбрать позицию. Записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
   if(!::PositionSelectByTicket(ticket))
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         Print(DFUN,"#",(string)ticket,": ",CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_SELECT_POS),CMessage::Text(this.m_result.retcode));
      return false;
     }
   //--- Получаем символ позиции
   string symbol=::PositionGetString(POSITION_SYMBOL);
   //--- Если не удалось получить текущие цены - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
   if(!::SymbolInfoTick(symbol,this.m_tick))
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode));
      return false;
     }
   //--- Получаем тип позиции и тип ордера, обратный типу позиции
   ENUM_POSITION_TYPE position_type=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE);
   ENUM_ORDER_TYPE type=OrderTypeOppositeByPositionType(position_type);
   //--- Получаем объём и магик позиции
   double position_volume=::PositionGetDouble(POSITION_VOLUME);
   ulong magic=::PositionGetInteger(POSITION_MAGIC);
   //--- Очищаем структуры
   ::ZeroMemory(this.m_request);
   ::ZeroMemory(this.m_result);
   //--- Заполняем структуру запроса
   this.m_request.action   =  TRADE_ACTION_DEAL;
   this.m_request.symbol   =  symbol;
   this.m_request.magic    =  magic;
   this.m_request.type     =  type;
   this.m_request.price    =  (position_type==POSITION_TYPE_SELL ? this.m_tick.ask : this.m_tick.bid);
   this.m_request.volume   =  position_volume;
   this.m_request.deviation=  (deviation==ULONG_MAX ? this.m_deviation : deviation);
   this.m_request.comment  =  (comment==NULL ? this.m_comment : comment);
   //--- Если счёт хеджевый, то в запишем в структуру тикет закрываемой позиции
   if(this.IsHedge())
      this.m_request.position=::PositionGetInteger(POSITION_TICKET);
   //--- Возвращаем результат отсылки запроса на сервер
   return
     (
      #ifdef __MQL5__
         !this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)
      #else 
         ::OrderClose((int)m_request.position,m_request.volume,m_request.price,(int)m_request.deviation,clrNONE)
      #endif 
     );
  }
//+------------------------------------------------------------------+

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

Метод для частичного закрытия позиции:

//+------------------------------------------------------------------+
//| Частично закрывает позицию                                       |
//+------------------------------------------------------------------+
bool CTradeObj::ClosePositionPartially(const ulong ticket,
                                       const double volume,
                                       const ulong deviation=ULONG_MAX,
                                       const string comment=NULL)
  {
   //--- Если не удалось выбрать позицию. Записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
   if(!::PositionSelectByTicket(ticket))
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         Print(DFUN,"#",(string)ticket,": ",CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_SELECT_POS),CMessage::Text(this.m_result.retcode));
      return false;
     }
   //--- Получаем символ позиции
   string symbol=::PositionGetString(POSITION_SYMBOL);
   //--- Если не удалось получить текущие цены - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
   if(!::SymbolInfoTick(symbol,this.m_tick))
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode));
      return false;
     }
   //--- Получаем тип позиции и тип ордера, обратный типу позиции
   ENUM_POSITION_TYPE position_type=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE);
   ENUM_ORDER_TYPE type=OrderTypeOppositeByPositionType(position_type);
   //--- Получаем объём и магик позиции
   double position_volume=::PositionGetDouble(POSITION_VOLUME);
   ulong magic=::PositionGetInteger(POSITION_MAGIC);
   //--- Очищаем структуры
   ::ZeroMemory(this.m_request);
   ::ZeroMemory(this.m_result);
   //--- Заполняем структуру запроса
   this.m_request.action   =  TRADE_ACTION_DEAL;
   this.m_request.position =  ticket;
   this.m_request.symbol   =  symbol;
   this.m_request.magic    =  magic;
   this.m_request.type     =  type;
   this.m_request.price    =  (position_type==POSITION_TYPE_SELL ? this.m_tick.ask : this.m_tick.bid);
   this.m_request.volume   =  (volume<position_volume ? volume : position_volume);
   this.m_request.deviation=  (deviation==ULONG_MAX ? this.m_deviation : deviation);
   this.m_request.comment  =  (comment==NULL ? this.m_comment : comment);
   //--- Если счёт хеджевый, то в запишем в структуру тикет закрываемой позиции
   if(this.IsHedge())
      this.m_request.position=::PositionGetInteger(POSITION_TICKET);
   //--- Возвращаем результат отсылки запроса на сервер
   return
     (
      #ifdef __MQL5__
         !this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)
      #else 
         ::OrderClose((int)m_request.position,m_request.volume,m_request.price,(int)m_request.deviation,clrNONE)
      #endif 
     );
  }
//+------------------------------------------------------------------+

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

Метод закрытия позиции встречной:

//+------------------------------------------------------------------+
//| Закрывает позицию встречной                                      |
//+------------------------------------------------------------------+
bool CTradeObj::ClosePositionBy(const ulong ticket,const ulong ticket_by)
  {
   #ifdef __MQL5__
   //--- Если счёт не хеджевый. 
   if(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
     {
      //--- Закрытие встречным доступно только на счетах с типом "Хеджинг".
      //--- Записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
      this.m_result.retcode=MSG_ACC_UNABLE_CLOSE_BY;
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_ACC_UNABLE_CLOSE_BY));
      return false;
     }
   #endif 
//--- Закрываемая позиция
   //--- Если не удалось выбрать позицию - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
   if(!::PositionSelectByTicket(ticket))
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         Print(DFUN,"#",(string)ticket,": ",CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_SELECT_POS),CMessage::Text(this.m_result.retcode));
      return false;
     }
   //--- Получаем тип и магик закрываемой позиции
   ENUM_POSITION_TYPE position_type=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE);
   ulong magic=::PositionGetInteger(POSITION_MAGIC);
   
//--- Встречная позиция
   //--- Если не удалось выбрать позицию - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
   if(!::PositionSelectByTicket(ticket_by))
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         Print(DFUN,"#",(string)ticket_by,": ",CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_SELECT_POS),CMessage::Text(this.m_result.retcode));
      return false;
     }
   //--- Получаем тип встречной позиции
   ENUM_POSITION_TYPE position_type_by=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE);
   //--- Если типы закрываемой и встречной позиции одинаковы - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
   if(position_type==position_type_by)
     {
      this.m_result.retcode=MSG_ACC_SAME_TYPE_CLOSE_BY;
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         Print(DFUN,CMessage::Text(MSG_ACC_SAME_TYPE_CLOSE_BY));
      return false;
     }
   //--- Очищаем структуры
   ::ZeroMemory(this.m_request);
   ::ZeroMemory(this.m_result);
   //--- Заполняем структуру запроса
   this.m_request.action      =  TRADE_ACTION_CLOSE_BY;
   this.m_request.position    =  ticket;
   this.m_request.position_by =  ticket_by;
   this.m_request.magic       =  magic;
   //--- Возвращаем результат отсылки запроса на сервер
   return
     (
      #ifdef __MQL5__
         !this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)
      #else 
         ::OrderCloseBy((int)m_request.position,(int)m_request.position_by,clrNONE)
      #endif 
     );
  }
//+------------------------------------------------------------------+

В метод передаётся тикет закрываемой позиции и тикет встречной.

Метод модификации стоп-уровней позиции:

//+------------------------------------------------------------------+
//| Модифицирует позицию                                             |
//+------------------------------------------------------------------+
bool CTradeObj::ModifyPosition(const ulong ticket,const double sl=WRONG_VALUE,const double tp=WRONG_VALUE)
  {
   //--- Если переданы все значения по умолчанию - значит модифицировать нечего
   if(sl==WRONG_VALUE && tp==WRONG_VALUE)
     {
      //--- В запросе нет изменений - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
      this.m_result.retcode= #ifdef __MQL5__ TRADE_RETCODE_NO_CHANGES #else 10025 #endif ;
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         Print(DFUN,CMessage::Text(this.m_result.retcode),CMessage::Retcode(this.m_result.retcode));
      return false;
     }
   //--- Если не удалось выбрать позицию - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
   if(!::PositionSelectByTicket(ticket))
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         Print(DFUN,"#",(string)ticket,": ",CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_SELECT_POS),CMessage::Text(this.m_result.retcode));
      return false;
     }
   //--- Очищаем структуры
   ::ZeroMemory(this.m_request);
   ::ZeroMemory(this.m_result);
   //--- Заполняем структуру запроса
   m_request.action  =  TRADE_ACTION_SLTP;
   m_request.position=  ticket;
   m_request.symbol  =  ::PositionGetString(POSITION_SYMBOL);
   m_request.magic   =  ::PositionGetInteger(POSITION_MAGIC);
   m_request.sl      =  (sl==WRONG_VALUE ? ::PositionGetDouble(POSITION_SL) : sl);
   m_request.tp      =  (tp==WRONG_VALUE ? ::PositionGetDouble(POSITION_TP) : tp);
   //--- Возвращаем результат отсылки запроса на сервер
   return
     (
      #ifdef __MQL5__
         !this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)
      #else 
         ::OrderModify((int)m_request.position,::OrderOpenPrice(),m_request.sl,m_request.tp,::OrderExpiration(),clrNONE)
      #endif 
     );
  }
//+------------------------------------------------------------------+

В метод передаются тикет модифицируемой позиции и новые уровни StopLoss и TakeProfit.

Метод для установки отложенного ордера:

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

В метод передаются тип отложенного ордера, его объём, цена установки, цены StopLoss, TakeProfit и StopLimit-ордера, магик, время жизни ордера, тип истечения ордера и комментарий.

Метод для удаления отложенного ордера:

//+------------------------------------------------------------------+
//| Удаляет ордер                                                    |
//+------------------------------------------------------------------+
bool CTradeObj::DeleteOrder(const ulong ticket)
  {
   //--- Очищаем структуры
   ::ZeroMemory(this.m_request);
   ::ZeroMemory(this.m_result);
   //--- Заполняем структуру запроса
   m_request.action  =  TRADE_ACTION_REMOVE;
   m_request.order   =  ticket;
   //--- Возвращаем результат отсылки запроса на сервер
   return
     (
      #ifdef __MQL5__
         !this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)
      #else 
         ::OrderDelete((int)m_request.order,clrNONE)
      #endif 
     );
  }
//+------------------------------------------------------------------+

В метод передаётся тикет удаляемого ордера.

Метод для модификации отложенного ордера:

//+------------------------------------------------------------------+
//| Модифицирует ордер                                               |
//+------------------------------------------------------------------+
bool CTradeObj::ModifyOrder(const ulong ticket,
                            const double price=WRONG_VALUE,
                            const double sl=WRONG_VALUE,
                            const double tp=WRONG_VALUE,
                            const double price_stoplimit=WRONG_VALUE,
                            const datetime expiration=WRONG_VALUE,
                            const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE)
  {
   //--- Если не удалось выбрать ордер - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
   #ifdef __MQL5__
   if(!::OrderSelect(ticket))
   #else 
   if(!::OrderSelect((int)ticket,SELECT_BY_TICKET))
   #endif 
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         Print(DFUN,"#",(string)ticket,": ",CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_SELECT_ORD),CMessage::Text(this.m_result.retcode));
      return false;
     }
   double order_price=::OrderGetDouble(ORDER_PRICE_OPEN);
   double order_sl=::OrderGetDouble(ORDER_SL);
   double order_tp=::OrderGetDouble(ORDER_TP);
   double order_stoplimit=::OrderGetDouble(ORDER_PRICE_STOPLIMIT);
   ENUM_ORDER_TYPE_TIME order_type_time=(ENUM_ORDER_TYPE_TIME)::OrderGetInteger(ORDER_TYPE_TIME);
   datetime order_expiration=(datetime)::OrderGetInteger(ORDER_TIME_EXPIRATION);
   //--- Если переданы значения по умолчанию и цена равна цене, записанной в ордере - значит в запросе нет изменений
   //--- Записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false
   if(price==order_price && sl==WRONG_VALUE && tp==WRONG_VALUE && price_stoplimit==WRONG_VALUE && type_time==WRONG_VALUE && expiration==WRONG_VALUE)
     {
      this.m_result.retcode = #ifdef __MQL5__ TRADE_RETCODE_NO_CHANGES #else 10025 #endif ;
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         Print(DFUN,CMessage::Text(this.m_result.retcode),CMessage::Retcode(this.m_result.retcode));
      return false;
     }
   //--- Очищаем структуры
   ::ZeroMemory(this.m_request);
   ::ZeroMemory(this.m_result);
   //--- Заполняем структуру запроса
   m_request.action     =  TRADE_ACTION_MODIFY;
   m_request.order      =  ticket;
   m_request.price      =  (price==WRONG_VALUE ? order_price : price);
   m_request.sl         =  (sl==WRONG_VALUE ? order_sl : sl);
   m_request.tp         =  (tp==WRONG_VALUE ? order_tp : tp);
   m_request.stoplimit  =  (price_stoplimit==WRONG_VALUE ? order_stoplimit : price_stoplimit);
   m_request.type_time  =  (type_time==WRONG_VALUE ? order_type_time : type_time);
   m_request.expiration =  (expiration==WRONG_VALUE ? order_expiration : expiration);
   //--- Возвращаем результат модификации ордера
   return
     (
      #ifdef __MQL5__
         !this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)
      #else 
         ::OrderModify((int)m_request.order,m_request.price,m_request.sl,m_request.tp,m_request.expiration,clrNONE)
      #endif 
     );
   Print(DFUN);
  }
//+------------------------------------------------------------------+

В метод передаются тикет модифицируемого ордера, новые значения цены, уровней StopLoss, TakeProfit и StopLimit-ордера, время жизни и тип истечения ордера.

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

На этом создание минимального функционала базового торгового класса завершено.

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

Откроем файл объекта-символа \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh и
подключим к нему файл торгового объекта TradeObj.mqh:

//+------------------------------------------------------------------+
//|                                                       Symbol.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\BaseObj.mqh"
#include "..\Trade\TradeObj.mqh"
//+------------------------------------------------------------------+

В приватной секции объявим переменную-объект торгового класса:

//+------------------------------------------------------------------+
//| Класс абстрактного символа                                       |
//+------------------------------------------------------------------+
class CSymbol : public CBaseObj
  {
private:
   struct MqlMarginRate
     {
      double         Initial;                                  // коэффициент взимания начальной маржи
      double         Maintenance;                              // коэффициент взимания поддерживающей маржи
     };
   struct MqlMarginRateMode
     {
      MqlMarginRate  Long;                                     // MarginRate длинных позиций
      MqlMarginRate  Short;                                    // MarginRate коротких позиций
      MqlMarginRate  BuyStop;                                  // MarginRate BuyStop-ордеров
      MqlMarginRate  BuyLimit;                                 // MarginRate BuyLimit-ордеров
      MqlMarginRate  BuyStopLimit;                             // MarginRate BuyStopLimit-ордеров
      MqlMarginRate  SellStop;                                 // MarginRate SellStop-ордеров
      MqlMarginRate  SellLimit;                                // MarginRate SellLimit-ордеров
      MqlMarginRate  SellStopLimit;                            // MarginRate SellStopLimit-ордеров
     };
   MqlMarginRateMode m_margin_rate;                            // Структура коэффициентов взимания маржи
   MqlBookInfo       m_book_info_array[];                      // Массив структур данных стакана

   long              m_long_prop[SYMBOL_PROP_INTEGER_TOTAL];   // Целочисленные свойства
   double            m_double_prop[SYMBOL_PROP_DOUBLE_TOTAL];  // Вещественные свойства
   string            m_string_prop[SYMBOL_PROP_STRING_TOTAL];  // Строковые свойства
   bool              m_is_change_trade_mode;                   // Флаг изменения режима торговли для символа
   CTradeObj         m_trade;                                  // Объект торгового класса
//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство символа
   int               IndexProp(ENUM_SYMBOL_PROP_DOUBLE property)  const { return(int)property-SYMBOL_PROP_INTEGER_TOTAL;                                    }
   int               IndexProp(ENUM_SYMBOL_PROP_STRING property)  const { return(int)property-SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_DOUBLE_TOTAL;           }
//--- (1) Заполняет все свойства символа "коэффициент взимания маржи", (2) инициализирует коэффициенты
   bool              MarginRates(void);
   void              InitMarginRates(void);
//--- Обнуляет все данные объекта-символа
   void              Reset(void);
//--- Возвращает текущий день недели
   ENUM_DAY_OF_WEEK  CurrentDayOfWeek(void)              const;

public:
//--- Конструктор по умолчанию

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

public:
//--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство символа
   void              SetProperty(ENUM_SYMBOL_PROP_INTEGER property,long value)   { this.m_long_prop[property]=value;                                        }
   void              SetProperty(ENUM_SYMBOL_PROP_DOUBLE property,double value)  { this.m_double_prop[this.IndexProp(property)]=value;                      }
   void              SetProperty(ENUM_SYMBOL_PROP_STRING property,string value)  { this.m_string_prop[this.IndexProp(property)]=value;                      }
//--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство символа
   long              GetProperty(ENUM_SYMBOL_PROP_INTEGER property)        const { return this.m_long_prop[property];                                       }
   double            GetProperty(ENUM_SYMBOL_PROP_DOUBLE property)         const { return this.m_double_prop[this.IndexProp(property)];                     }
   string            GetProperty(ENUM_SYMBOL_PROP_STRING property)         const { return this.m_string_prop[this.IndexProp(property)];                     }

//--- Возвращает флаг поддержания символом данного свойства
   virtual bool      SupportProperty(ENUM_SYMBOL_PROP_INTEGER property)          { return true; }
   virtual bool      SupportProperty(ENUM_SYMBOL_PROP_DOUBLE property)           { return true; }
   virtual bool      SupportProperty(ENUM_SYMBOL_PROP_STRING property)           { return true; }
   
//--- Возвращает флаг разрешения (1) рыночных, (2) лимитных, (3) стоп-, (4) стоп-лимит ордеров,
//--- флаг разрешения установки (5) StopLoss, (6) TakeProfit приказов, (7) закрытия встречной
   bool              IsMarketOrdersAllowed(void)            const { return((this.OrderModeFlags() & SYMBOL_ORDER_MARKET)==SYMBOL_ORDER_MARKET);             }
   bool              IsLimitOrdersAllowed(void)             const { return((this.OrderModeFlags() & SYMBOL_ORDER_LIMIT)==SYMBOL_ORDER_LIMIT);               }
   bool              IsStopOrdersAllowed(void)              const { return((this.OrderModeFlags() & SYMBOL_ORDER_STOP)==SYMBOL_ORDER_STOP);                 }
   bool              IsStopLimitOrdersAllowed(void)         const { return((this.OrderModeFlags() & SYMBOL_ORDER_STOP_LIMIT)==SYMBOL_ORDER_STOP_LIMIT);     }
   bool              IsStopLossOrdersAllowed(void)          const { return((this.OrderModeFlags() & SYMBOL_ORDER_SL)==SYMBOL_ORDER_SL);                     }
   bool              IsTakeProfitOrdersAllowed(void)        const { return((this.OrderModeFlags() & SYMBOL_ORDER_TP)==SYMBOL_ORDER_TP);                     }
   bool              IsCloseByOrdersAllowed(void)           const;

//--- Возвращает флаг заливки (1) FOK, (2) IOC
   bool              IsFillingModeFOK(void)                 const { return((this.FillingModeFlags() & SYMBOL_FILLING_FOK)==SYMBOL_FILLING_FOK);             }
   bool              IsFillingModeIOC(void)                 const { return((this.FillingModeFlags() & SYMBOL_FILLING_IOC)==SYMBOL_FILLING_IOC);             }

//--- Возвращает флаг истечения действия ордера (1) GTC, (2) DAY, (3) Specified, (4) Specified Day
   bool              IsExpirationModeGTC(void)              const { return((this.ExpirationModeFlags() & SYMBOL_EXPIRATION_GTC)==SYMBOL_EXPIRATION_GTC);    }
   bool              IsExpirationModeDAY(void)              const { return((this.ExpirationModeFlags() & SYMBOL_EXPIRATION_DAY)==SYMBOL_EXPIRATION_DAY);    }
   bool              IsExpirationModeSpecified(void)        const { return((this.ExpirationModeFlags() & SYMBOL_EXPIRATION_SPECIFIED)==SYMBOL_EXPIRATION_SPECIFIED);          }
   bool              IsExpirationModeSpecifiedDay(void)     const { return((this.ExpirationModeFlags() & SYMBOL_EXPIRATION_SPECIFIED_DAY)==SYMBOL_EXPIRATION_SPECIFIED_DAY);  }

//--- Возвращает описание разрешения (1) рыночных, (2) лимитных, (3) стоп- и (4) стоп-лимит ордеров,
//--- описание разрешения установки (5) StopLoss и (6) TakeProfit приказов, (7) закрытия встречной
   string            GetMarketOrdersAllowedDescription(void)      const;
   string            GetLimitOrdersAllowedDescription(void)       const;
   string            GetStopOrdersAllowedDescription(void)        const;
   string            GetStopLimitOrdersAllowedDescription(void)   const;
   string            GetStopLossOrdersAllowedDescription(void)    const;
   string            GetTakeProfitOrdersAllowedDescription(void)  const;
   string            GetCloseByOrdersAllowedDescription(void)     const;

//--- Возвращает описание разрешения типа заливки (1) FOK и (2) IOC, (3) разрешенных режимов истечения ордера
   string            GetFillingModeFOKAllowedDescrioption(void)   const;
   string            GetFillingModeIOCAllowedDescrioption(void)   const;

//--- Возвращает описание истечения действия ордера (1) GTC, (2) DAY, (3) Specified, (4) Specified Day
   string            GetExpirationModeGTCDescription(void)        const;
   string            GetExpirationModeDAYDescription(void)        const;
   string            GetExpirationModeSpecifiedDescription(void)  const;
   string            GetExpirationModeSpecDayDescription(void)    const;

//--- Возвращает описание (1) статуса, (2) типа цены для построения баров, 
//--- (3) способа вычисления залоговых средств, (4) режима торговли по инструменту,
//--- (5) режима заключения сделок по инструменту, (6) модели расчета свопа,
//--- (7) срока действия StopLoss и TakeProfit ордеров, (8) типа опциона, (9) права опциона
//--- флагов (10) разрешенных типов ордеров, (11) разрешённых типов заливки,
//--- (12) разрешенных режимов истечения ордера
   string            GetStatusDescription(void)                   const;
   string            GetChartModeDescription(void)                const;
   string            GetCalcModeDescription(void)                 const;
   string            GetTradeModeDescription(void)                const;
   string            GetTradeExecDescription(void)                const;
   string            GetSwapModeDescription(void)                 const;
   string            GetOrderGTCModeDescription(void)             const;
   string            GetOptionTypeDescription(void)               const;
   string            GetOptionRightDescription(void)              const;
   string            GetOrderModeFlagsDescription(void)           const;
   string            GetFillingModeFlagsDescription(void)         const;
   string            GetExpirationModeFlagsDescription(void)      const;
   
//--- Возвращает (1) тип исполнения, (2) тип истечения ордера, равный type, если он доступен на символе, иначе - корректный вариант
   ENUM_ORDER_TYPE_FILLING GetCorrectTypeFilling(const uint type=ORDER_FILLING_RETURN);
   ENUM_ORDER_TYPE_TIME    GetCorrectTypeExpiration(uint expiration=ORDER_TIME_GTC);
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возвращает тип исполнения ордера, равный type,                   |
//| если он доступен на символе, иначе - корректный вариант          |
//| https://www.mql5.com/ru/forum/170952/page4#comment_4128864       |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE_FILLING CSymbol::GetCorrectTypeFilling(const uint type=ORDER_FILLING_RETURN)
  {
  const ENUM_SYMBOL_TRADE_EXECUTION exe_mode=this.TradeExecutionMode();
  const int filling_mode=this.FillingModeFlags();

  return(
         (filling_mode == 0 || (type >= ORDER_FILLING_RETURN) || ((filling_mode & (type + 1)) != type + 1)) ?
         (((exe_mode == SYMBOL_TRADE_EXECUTION_EXCHANGE) || (exe_mode == SYMBOL_TRADE_EXECUTION_INSTANT)) ?
           ORDER_FILLING_RETURN : ((filling_mode == SYMBOL_FILLING_IOC) ? ORDER_FILLING_IOC : ORDER_FILLING_FOK)) :
         (ENUM_ORDER_TYPE_FILLING)type
        );
  }
//+------------------------------------------------------------------+
//| Возвращает тип истечения ордера, равный expiration,              |
//| если он доступен на символе Symb, иначе - корректный вариант     |
//| https://www.mql5.com/ru/forum/170952/page4#comment_4128871       |
//| Применение:                                                      |
//| Request.type_time = GetExpirationType((uint)Expiration);         |
//| Expiration может быть datetime-временем                          |
//| if(Expiration > ORDER_TIME_DAY) Request.expiration = Expiration; |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE_TIME CSymbol::GetCorrectTypeExpiration(uint expiration=ORDER_TIME_GTC)
  {
#ifdef __MQL5__
  const int expiration_mode=this.ExpirationModeFlags();
  if((expiration > ORDER_TIME_SPECIFIED_DAY) || (((expiration_mode >> expiration) & 1) == 0))
    {
      if((expiration < ORDER_TIME_SPECIFIED) || (expiration_mode < SYMBOL_EXPIRATION_SPECIFIED))
         expiration=ORDER_TIME_GTC;
      else if(expiration > ORDER_TIME_DAY)
         expiration=ORDER_TIME_SPECIFIED;

      uint i=1 << expiration;
      while((expiration <= ORDER_TIME_SPECIFIED_DAY) && ((expiration_mode & i) != i))
        {
         i <<= 1;
         expiration++;
        }
     }
#endif 
  return (ENUM_ORDER_TYPE_TIME)expiration;
  }
//+------------------------------------------------------------------+

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

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

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

//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта-символа           |
//+------------------------------------------------------------------+
//--- Целочисленные свойства
   long              Status(void)                                 const { return this.GetProperty(SYMBOL_PROP_STATUS);                                      }
   int               IndexInMarketWatch(void)                     const { return (int)this.GetProperty(SYMBOL_PROP_INDEX_MW);                               }
   bool              IsCustom(void)                               const { return (bool)this.GetProperty(SYMBOL_PROP_CUSTOM);                                }
   color             ColorBackground(void)                        const { return (color)this.GetProperty(SYMBOL_PROP_BACKGROUND_COLOR);                     }
   ENUM_SYMBOL_CHART_MODE ChartMode(void)                         const { return (ENUM_SYMBOL_CHART_MODE)this.GetProperty(SYMBOL_PROP_CHART_MODE);          }
   bool              IsExist(void)                                const { return (bool)this.GetProperty(SYMBOL_PROP_EXIST);                                 }
   bool              IsExist(const string name)                   const { return this.SymbolExists(name);                                                   }
   bool              IsSelect(void)                               const { return (bool)this.GetProperty(SYMBOL_PROP_SELECT);                                }
   bool              IsVisible(void)                              const { return (bool)this.GetProperty(SYMBOL_PROP_VISIBLE);                               }
   long              SessionDeals(void)                           const { return this.GetProperty(SYMBOL_PROP_SESSION_DEALS);                               }
   long              SessionBuyOrders(void)                       const { return this.GetProperty(SYMBOL_PROP_SESSION_BUY_ORDERS);                          }
   long              SessionSellOrders(void)                      const { return this.GetProperty(SYMBOL_PROP_SESSION_SELL_ORDERS);                         }
   long              Volume(void)                                 const { return this.GetProperty(SYMBOL_PROP_VOLUME);                                      }
   long              VolumeHigh(void)                             const { return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH);                                  }
   long              VolumeLow(void)                              const { return this.GetProperty(SYMBOL_PROP_VOLUMELOW);                                   }
   datetime          Time(void)                                   const { return (datetime)this.GetProperty(SYMBOL_PROP_TIME);                              }
   int               Digits(void)                                 const { return (int)this.GetProperty(SYMBOL_PROP_DIGITS);                                 }
   int               DigitsLot(void)                              const { return (int)this.GetProperty(SYMBOL_PROP_DIGITS_LOTS);                            }
   int               Spread(void)                                 const { return (int)this.GetProperty(SYMBOL_PROP_SPREAD);                                 }
   bool              IsSpreadFloat(void)                          const { return (bool)this.GetProperty(SYMBOL_PROP_SPREAD_FLOAT);                          }
   int               TicksBookdepth(void)                         const { return (int)this.GetProperty(SYMBOL_PROP_TICKS_BOOKDEPTH);                        }
   ENUM_SYMBOL_CALC_MODE TradeCalcMode(void)                      const { return (ENUM_SYMBOL_CALC_MODE)this.GetProperty(SYMBOL_PROP_TRADE_CALC_MODE);      }
   ENUM_SYMBOL_TRADE_MODE TradeMode(void)                         const { return (ENUM_SYMBOL_TRADE_MODE)this.GetProperty(SYMBOL_PROP_TRADE_MODE);          }
   datetime          StartTime(void)                              const { return (datetime)this.GetProperty(SYMBOL_PROP_START_TIME);                        }
   datetime          ExpirationTime(void)                         const { return (datetime)this.GetProperty(SYMBOL_PROP_EXPIRATION_TIME);                   }
   int               TradeStopLevel(void)                         const { return (int)this.GetProperty(SYMBOL_PROP_TRADE_STOPS_LEVEL);                      }
   int               TradeFreezeLevel(void)                       const { return (int)this.GetProperty(SYMBOL_PROP_TRADE_FREEZE_LEVEL);                     }
   ENUM_SYMBOL_TRADE_EXECUTION TradeExecutionMode(void)           const { return (ENUM_SYMBOL_TRADE_EXECUTION)this.GetProperty(SYMBOL_PROP_TRADE_EXEMODE);  }
   ENUM_SYMBOL_SWAP_MODE SwapMode(void)                           const { return (ENUM_SYMBOL_SWAP_MODE)this.GetProperty(SYMBOL_PROP_SWAP_MODE);            }
   ENUM_DAY_OF_WEEK  SwapRollover3Days(void)                      const { return (ENUM_DAY_OF_WEEK)this.GetProperty(SYMBOL_PROP_SWAP_ROLLOVER3DAYS);        }
   bool              IsMarginHedgedUseLeg(void)                   const { return (bool)this.GetProperty(SYMBOL_PROP_MARGIN_HEDGED_USE_LEG);                 }
   int               ExpirationModeFlags(void)                    const { return (int)this.GetProperty(SYMBOL_PROP_EXPIRATION_MODE);                        }
   int               FillingModeFlags(void)                       const { return (int)this.GetProperty(SYMBOL_PROP_FILLING_MODE);                           }
   int               OrderModeFlags(void)                         const { return (int)this.GetProperty(SYMBOL_PROP_ORDER_MODE);                             }
   ENUM_SYMBOL_ORDER_GTC_MODE OrderModeGTC(void)                  const { return (ENUM_SYMBOL_ORDER_GTC_MODE)this.GetProperty(SYMBOL_PROP_ORDER_GTC_MODE);  }
   ENUM_SYMBOL_OPTION_MODE OptionMode(void)                       const { return (ENUM_SYMBOL_OPTION_MODE)this.GetProperty(SYMBOL_PROP_OPTION_MODE);        }
   ENUM_SYMBOL_OPTION_RIGHT OptionRight(void)                     const { return (ENUM_SYMBOL_OPTION_RIGHT)this.GetProperty(SYMBOL_PROP_OPTION_RIGHT);      }
//--- Вещественные свойства
   double            Bid(void)                                    const { return this.GetProperty(SYMBOL_PROP_BID);                                         }
   double            BidHigh(void)                                const { return this.GetProperty(SYMBOL_PROP_BIDHIGH);                                     }
   double            BidLow(void)                                 const { return this.GetProperty(SYMBOL_PROP_BIDLOW);                                      }
   double            Ask(void)                                    const { return this.GetProperty(SYMBOL_PROP_ASK);                                         }
   double            AskHigh(void)                                const { return this.GetProperty(SYMBOL_PROP_ASKHIGH);                                     }
   double            AskLow(void)                                 const { return this.GetProperty(SYMBOL_PROP_ASKLOW);                                      }
   double            Last(void)                                   const { return this.GetProperty(SYMBOL_PROP_LAST);                                        }
   double            LastHigh(void)                               const { return this.GetProperty(SYMBOL_PROP_LASTHIGH);                                    }
   double            LastLow(void)                                const { return this.GetProperty(SYMBOL_PROP_LASTLOW);                                     }
   double            VolumeReal(void)                             const { return this.GetProperty(SYMBOL_PROP_VOLUME_REAL);                                 }
   double            VolumeHighReal(void)                         const { return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH_REAL);                             }
   double            VolumeLowReal(void)                          const { return this.GetProperty(SYMBOL_PROP_VOLUMELOW_REAL);                              }
   double            OptionStrike(void)                           const { return this.GetProperty(SYMBOL_PROP_OPTION_STRIKE);                               }
   double            Point(void)                                  const { return this.GetProperty(SYMBOL_PROP_POINT);                                       }
   double            TradeTickValue(void)                         const { return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE);                            }
   double            TradeTickValueProfit(void)                   const { return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT);                     }
   double            TradeTickValueLoss(void)                     const { return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS);                       }
   double            TradeTickSize(void)                          const { return this.GetProperty(SYMBOL_PROP_TRADE_TICK_SIZE);                             }
   double            TradeContractSize(void)                      const { return this.GetProperty(SYMBOL_PROP_TRADE_CONTRACT_SIZE);                         }
   double            TradeAccuredInterest(void)                   const { return this.GetProperty(SYMBOL_PROP_TRADE_ACCRUED_INTEREST);                      }
   double            TradeFaceValue(void)                         const { return this.GetProperty(SYMBOL_PROP_TRADE_FACE_VALUE);                            }
   double            TradeLiquidityRate(void)                     const { return this.GetProperty(SYMBOL_PROP_TRADE_LIQUIDITY_RATE);                        }
   double            LotsMin(void)                                const { return this.GetProperty(SYMBOL_PROP_VOLUME_MIN);                                  }
   double            LotsMax(void)                                const { return this.GetProperty(SYMBOL_PROP_VOLUME_MAX);                                  }
   double            LotsStep(void)                               const { return this.GetProperty(SYMBOL_PROP_VOLUME_STEP);                                 }
   double            VolumeLimit(void)                            const { return this.GetProperty(SYMBOL_PROP_VOLUME_LIMIT);                                }
   double            SwapLong(void)                               const { return this.GetProperty(SYMBOL_PROP_SWAP_LONG);                                   }
   double            SwapShort(void)                              const { return this.GetProperty(SYMBOL_PROP_SWAP_SHORT);                                  }
   double            MarginInitial(void)                          const { return this.GetProperty(SYMBOL_PROP_MARGIN_INITIAL);                              }
   double            MarginMaintenance(void)                      const { return this.GetProperty(SYMBOL_PROP_MARGIN_MAINTENANCE);                          }
   double            MarginLongInitial(void)                      const { return this.GetProperty(SYMBOL_PROP_MARGIN_LONG_INITIAL);                         }
   double            MarginBuyStopInitial(void)                   const { return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL);                     }
   double            MarginBuyLimitInitial(void)                  const { return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL);                    }
   double            MarginBuyStopLimitInitial(void)              const { return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_INITIAL);                }
   double            MarginLongMaintenance(void)                  const { return this.GetProperty(SYMBOL_PROP_MARGIN_LONG_MAINTENANCE);                     }
   double            MarginBuyStopMaintenance(void)               const { return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOP_MAINTENANCE);                 }
   double            MarginBuyLimitMaintenance(void)              const { return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_LIMIT_MAINTENANCE);                }
   double            MarginBuyStopLimitMaintenance(void)          const { return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_MAINTENANCE);            }
   double            MarginShortInitial(void)                     const { return this.GetProperty(SYMBOL_PROP_MARGIN_SHORT_INITIAL);                        }
   double            MarginSellStopInitial(void)                  const { return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL);                    }
   double            MarginSellLimitInitial(void)                 const { return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL);                   }
   double            MarginSellStopLimitInitial(void)             const { return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL);               }
   double            MarginShortMaintenance(void)                 const { return this.GetProperty(SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE);                    }
   double            MarginSellStopMaintenance(void)              const { return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOP_MAINTENANCE);                }
   double            MarginSellLimitMaintenance(void)             const { return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_LIMIT_MAINTENANCE);               }
   double            MarginSellStopLimitMaintenance(void)         const { return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE);           }
   double            SessionVolume(void)                          const { return this.GetProperty(SYMBOL_PROP_SESSION_VOLUME);                              }
   double            SessionTurnover(void)                        const { return this.GetProperty(SYMBOL_PROP_SESSION_TURNOVER);                            }
   double            SessionInterest(void)                        const { return this.GetProperty(SYMBOL_PROP_SESSION_INTEREST);                            }
   double            SessionBuyOrdersVolume(void)                 const { return this.GetProperty(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME);                   }
   double            SessionSellOrdersVolume(void)                const { return this.GetProperty(SYMBOL_PROP_SESSION_SELL_ORDERS_VOLUME);                  }
   double            SessionOpen(void)                            const { return this.GetProperty(SYMBOL_PROP_SESSION_OPEN);                                }
   double            SessionClose(void)                           const { return this.GetProperty(SYMBOL_PROP_SESSION_CLOSE);                               }
   double            SessionAW(void)                              const { return this.GetProperty(SYMBOL_PROP_SESSION_AW);                                  }
   double            SessionPriceSettlement(void)                 const { return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_SETTLEMENT);                    }
   double            SessionPriceLimitMin(void)                   const { return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_LIMIT_MIN);                     }
   double            SessionPriceLimitMax(void)                   const { return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_LIMIT_MAX);                     }
   double            MarginHedged(void)                           const { return this.GetProperty(SYMBOL_PROP_MARGIN_HEDGED);                               }
   double            NormalizedPrice(const double price)          const;
   double            NormalizedLot(const double volume)           const;
   double            BidLast(void)                                const;
   double            BidLastHigh(void)                            const;
   double            BidLastLow(void)                             const;
//--- Строковые свойства

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

   //--- Средневзвешенная цена сессии
   //--- установка контролируемой величины (1) прироста, (2) уменьшения, (3) контрольного уровня средневзвешенной цены сессии
   //--- получение (4) величины изменения средневзвешенной цены сессии,
   //--- получение флага изменения средневзвешенной цены сессии больше, чем на величину (5) прироста, (6) уменьшения
   void              SetControlSessionPriceAWInc(const double value)             { this.SetControlledValueINC(SYMBOL_PROP_SESSION_AW,::fabs(value));                 }
   void              SetControlSessionPriceAWDec(const double value)             { this.SetControlledValueDEC(SYMBOL_PROP_SESSION_AW,::fabs(value));                 }
   void              SetControlSessionPriceAWLevel(const double value)           { this.SetControlledValueLEVEL(SYMBOL_PROP_SESSION_AW,::fabs(value));               }
   double            GetValueChangedSessionPriceAW(void)                   const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SESSION_AW);                    }
   bool              IsIncreasedSessionPriceAW(void)                       const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SESSION_AW);                   }
   bool              IsDecreasedSessionPriceAW(void)                       const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SESSION_AW);                   }

//--- Возвращает торговый объект
   CTradeObj        *GetTradeObj(void)                                           { return &this.m_trade; }

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

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

   
//--- Заполнение текущих данных символа
   for(int i=0;i<SYMBOL_PROP_INTEGER_TOTAL;i++)
      this.m_long_prop_event[i][3]=this.m_long_prop[i];
   for(int i=0;i<SYMBOL_PROP_DOUBLE_TOTAL;i++)
      this.m_double_prop_event[i][3]=this.m_double_prop[i];
   
//--- Обновление данных в базовом объекте и поиск изменений
   CBaseObj::Refresh();
//---
   if(!select)
      this.RemoveFromMarketWatch();

//--- Инициализация умолчательных значений торгового объекта
   this.m_trade.Init(this.Name(),0,this.LotsMin(),5,0,0,false,this.GetCorrectTypeFilling(),this.GetCorrectTypeExpiration(),LOG_LEVEL_ERROR_MSG);
  }
//+------------------------------------------------------------------+

При вызове метода в торговый объект передаём:

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

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

//+------------------------------------------------------------------+
//| Возвращает нормализованный лот с учетом свойств символа          |
//+------------------------------------------------------------------+
double CSymbol::NormalizedLot(const double volume) const
  {
   double ml=this.LotsMin();
   double mx=this.LotsMax();
   double ln=::NormalizeDouble(volume,this.DigitsLot());
   return(ln<ml ? ml : ln>mx ? mx : ln);
  }
//+------------------------------------------------------------------+

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

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

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

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

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

//--- Устанавливает для торговых классов:
//--- (1) корректное значение политики исполнения, (2) значение политики исполнения,
//--- (3) корректный тип истечения ордера, (4) тип истечения ордера,
//--- (5) магик, (6) Комментарий, (7) размер проскальзывания, (8) объём, (9) срок истечения ордера,
//--- (10) флаг асинхронной отправки торгового запроса, (11) уровень логирования
   void                 SetTradeCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL);
   void                 SetTradeTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL);
   void                 SetTradeCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL);
   void                 SetTradeTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL);
   void                 SetTradeMagic(const ulong magic,const string symbol_name=NULL);
   void                 SetTradeComment(const string comment,const string symbol_name=NULL);
   void                 SetTradeDeviation(const ulong deviation,const string symbol_name=NULL);
   void                 SetTradeVolume(const double volume=0,const string symbol_name=NULL);
   void                 SetTradeExpiration(const datetime expiration=0,const string symbol_name=NULL);
   void                 SetTradeAsyncMode(const bool mode=false,const string symbol_name=NULL);
   void                 SetTradeLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol_name=NULL);
   
//--- Возвращает торговый объект символа по тикету (1) позиции, (2) ордера
   CTradeObj           *GetTradeObjByPosition(const ulong ticket);
   CTradeObj           *GetTradeObjByOrder(const ulong ticket);
   
//--- Открывает позицию (1) Buy, (2) Sell
   bool                 OpenBuy(const double volume,const string symbol,const ulong magic=ULONG_MAX,double sl=0,double tp=0,const string comment=NULL);
   bool                 OpenSell(const double volume,const string symbol,const ulong magic=ULONG_MAX,double sl=0,double tp=0,const string comment=NULL);
//--- Модифицирует позицию
   bool                 ModifyPosition(const ulong ticket,const double sl=WRONG_VALUE,const double tp=WRONG_VALUE);
//--- Закрывает позицию (1) полностью, (2) частично, (3) встречной
   bool                 ClosePosition(const ulong ticket);
   bool                 ClosePositionPartially(const ulong ticket,const double volume);
   bool                 ClosePositionBy(const ulong ticket,const ulong ticket_by);
//--- Устанавливает отложенный ордер (1) BuyStop, (2) BuyLimit, (3) BuyStopLimit
   bool                 PlaceBuyStop(const double volume,
                                     const string symbol,
                                     const double price,
                                     const double sl=0,
                                     const double tp=0,
                                     const ulong magic=ULONG_MAX,
                                     const string comment=NULL,
                                     const datetime expiration=0,
                                     const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
   bool                 PlaceBuyLimit(const double volume,
                                     const string symbol,
                                     const double price,
                                     const double sl=0,
                                     const double tp=0,
                                     const ulong magic=ULONG_MAX,
                                     const string comment=NULL,
                                     const datetime expiration=0,
                                     const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
   bool                 PlaceBuyStopLimit(const double volume,
                                     const string symbol,
                                     const double price_stop,
                                     const double price_limit,
                                     const double sl=0,
                                     const double tp=0,
                                     const ulong magic=ULONG_MAX,
                                     const string comment=NULL,
                                     const datetime expiration=0,
                                     const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
//--- Устанавливает отложенный ордер (1) SellStop, (2) SellLimit, (3) SellStopLimit
   bool                 PlaceSellStop(const double volume,
                                     const string symbol,
                                     const double price,
                                     const double sl=0,
                                     const double tp=0,
                                     const ulong magic=ULONG_MAX,
                                     const string comment=NULL,
                                     const datetime expiration=0,
                                     const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
   bool                 PlaceSellLimit(const double volume,
                                     const string symbol,
                                     const double price,
                                     const double sl=0,
                                     const double tp=0,
                                     const ulong magic=ULONG_MAX,
                                     const string comment=NULL,
                                     const datetime expiration=0,
                                     const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
   bool                 PlaceSellStopLimit(const double volume,
                                     const string symbol,
                                     const double price_stop,
                                     const double price_limit,
                                     const double sl=0,
                                     const double tp=0,
                                     const ulong magic=ULONG_MAX,
                                     const string comment=NULL,
                                     const datetime expiration=0,
                                     const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC);
//--- Модифицирует отложенный ордер
   bool                 ModifyOrder(const ulong ticket,
                                    const double price=WRONG_VALUE,
                                    const double sl=WRONG_VALUE,
                                    const double tp=WRONG_VALUE,
                                    const double stoplimit=WRONG_VALUE,
                                    datetime expiration=WRONG_VALUE,
                                    ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE);
//--- Удаляет отложенный ордер
   bool                 DeleteOrder(const ulong ticket);

//--- Возвращает (1) милисекунды, (2) причину, (3) источник события из его long-значения
   ushort               EventMSC(const long lparam)               const { return this.LongToUshortFromByte(lparam,0);         }
   ushort               EventReason(const long lparam)            const { return this.LongToUshortFromByte(lparam,1);         }
   ushort               EventSource(const long lparam)            const { return this.LongToUshortFromByte(lparam,2);         }

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

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

Метод для открытия позиции Buy:

//+------------------------------------------------------------------+
//| Открывает позицию Buy                                            |
//+------------------------------------------------------------------+
bool CEngine::OpenBuy(const double volume,const string symbol,const ulong magic=ULONG_MAX,double sl=0,double tp=0,const string comment=NULL)
  {
   CSymbol *symbol_obj=this.GetSymbolObjByName(symbol);
   if(symbol_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_SYMBOL_ON_LIST));
      return false;
     }
   CTradeObj *trade_obj=symbol_obj.GetTradeObj();
   if(trade_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
   return trade_obj.OpenPosition(POSITION_TYPE_BUY,volume,sl,tp,magic,trade_obj.GetDeviation(),comment);
  }
//+------------------------------------------------------------------+

В метод передаются:

Получаем объект-символ по имени символа. Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Получаем торговый объект из объекта-символа. Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Возвращаем результат работы метода открытия позиции торгового объекта, рассмотренный нами ранее.

Метод для открытия позиции Sell:

//+------------------------------------------------------------------+
//| Открывает позицию Sell                                           |
//+------------------------------------------------------------------+
bool CEngine::OpenSell(const double volume,const string symbol,const ulong magic=ULONG_MAX,double sl=0,double tp=0,const string comment=NULL)
  {
   CSymbol *symbol_obj=this.GetSymbolObjByName(symbol);
   if(symbol_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_SYMBOL_ON_LIST));
      return false;
     }
   CTradeObj *trade_obj=symbol_obj.GetTradeObj();
   if(trade_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
   return trade_obj.OpenPosition(POSITION_TYPE_SELL,volume,sl,tp,magic,trade_obj.GetDeviation(),comment);
  }
//+------------------------------------------------------------------+

В метод передаются:

Получаем объект-символ по имени символа. Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Получаем торговый объект из объекта-символа. Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Возвращаем результат работы метода открытия позиции торгового объекта, рассмотренный нами ранее.

Метод для модификации цен StopLoss и TakeProfit позиции:

//+------------------------------------------------------------------+
//| Модифицирует позицию                                             |
//+------------------------------------------------------------------+
bool CEngine::ModifyPosition(const ulong ticket,const double sl=WRONG_VALUE,const double tp=WRONG_VALUE)
  {
   CTradeObj *trade_obj=this.GetTradeObjByPosition(ticket);
   if(trade_obj==NULL)
     {
      //--- Ошибка. Не удалось получить торговый объект
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
   return trade_obj.ModifyPosition(ticket,sl,tp);
  }
//+------------------------------------------------------------------+

В метод передаются:

Получаем торговый объект по тикету позиции посредством метода GetTradeObjByPosition(), который рассмотрим ниже.
Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Возвращаем результат работы метода модификации позиции торгового объекта, рассмотренный нами ранее.

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

//+------------------------------------------------------------------+
//| Закрывает позицию полностью                                      |
//+------------------------------------------------------------------+
bool CEngine::ClosePosition(const ulong ticket)
  {
   CTradeObj *trade_obj=this.GetTradeObjByPosition(ticket);
   if(trade_obj==NULL)
     {
      //--- Ошибка. Не удалось получить торговый объект
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
   return trade_obj.ClosePosition(ticket);
  }
//+------------------------------------------------------------------+

В метод передаётся тикет закрываемой позиции.

Получаем торговый объект по тикету позиции посредством метода GetTradeObjByPosition(), который рассмотрим ниже.
Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Возвращаем результат работы метода закрытия позиции торгового объекта, рассмотренный нами ранее.

Метод для частичного закрытия позиции:

//+------------------------------------------------------------------+
//| Закрывает позицию частично                                       |
//+------------------------------------------------------------------+
bool CEngine::ClosePositionPartially(const ulong ticket,const double volume)
  {
   CTradeObj *trade_obj=this.GetTradeObjByPosition(ticket);
   if(trade_obj==NULL)
     {
      //--- Ошибка. Не удалось получить торговый объект
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
   CSymbol *symbol=this.GetSymbolObjByName(trade_obj.GetSymbol());
   return trade_obj.ClosePositionPartially(ticket,symbol.NormalizedLot(volume));
  }
//+------------------------------------------------------------------+

В метод передаётся тикет позиции и закрываемый объём.

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

Метод для закрытия позиции встречной:

//+------------------------------------------------------------------+
//| Закрывает позицию встречной                                      |
//+------------------------------------------------------------------+
bool CEngine::ClosePositionBy(const ulong ticket,const ulong ticket_by)
  {
   CTradeObj *trade_obj_pos=this.GetTradeObjByPosition(ticket);
   if(trade_obj_pos==NULL)
     {
      //--- Ошибка. Не удалось получить торговый объект
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
   CTradeObj *trade_obj_by=this.GetTradeObjByPosition(ticket_by);
   if(trade_obj_by==NULL)
     {
      //--- Ошибка. Не удалось получить торговый объект
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
   return trade_obj_pos.ClosePositionBy(ticket,ticket_by);
  }
//+------------------------------------------------------------------+

В метод передаются:

Получаем торговый объект по тикету закрываемой позиции посредством метода GetTradeObjByPosition(), который рассмотрим ниже.
Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Получаем торговый объект по тикету встречной позиции посредством метода GetTradeObjByPosition(), который рассмотрим ниже.
Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Возвращаем результат работы метода закрытия позиции встречной торгового объекта, рассмотренный нами ранее.

Метод для установки отложенного ордера BuyStop:

//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер BuyStop                           |
//+------------------------------------------------------------------+
bool CEngine::PlaceBuyStop(const double volume,
                           const string symbol,
                           const double price,
                           const double sl=0,
                           const double tp=0,
                           const ulong magic=WRONG_VALUE,
                           const string comment=NULL,
                           const datetime expiration=0,
                           const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC)
  {
   CSymbol *symbol_obj=this.GetSymbolObjByName(symbol);
   if(symbol_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_SYMBOL_ON_LIST));
      return false;
     }
   CTradeObj *trade_obj=symbol_obj.GetTradeObj();
   if(trade_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
   return trade_obj.SetOrder(ORDER_TYPE_BUY_STOP,volume,price,sl,tp,0,magic,expiration,type_time,comment);
  }
//+------------------------------------------------------------------+

В метод передаются:

  • объём устанавливаемого ордера (задаётся обязательно),
  • символ, на котором необходимо выставить ордер (задаётся обязательно),
  • цена, на которую необходимо выставить ордер (задаётся обязательно),
  • цена StopLoss устанавливаемого ордера (по умолчанию — нет),
  • цена TakeProfit устанавливаемого ордера (по умолчанию — нет),
  • магик устанавливаемого ордера (по умолчанию — 0),
  • комментарий устанавливаемого ордера (по умолчанию — имя программы+" by DoEasy"),
  • время жизни устанавливаемого ордера (по умолчанию — неограниченно),
  • тип времени жизни устанавливаемого ордера (по умолчанию — до явной отмены)

Получаем объект-символ по имени символа. Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Получаем торговый объект из объекта-символа. Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Возвращаем результат работы метода установки отложенного ордера торгового объекта, рассмотренный нами ранее.

Метод для установки отложенного ордера BuyLimit:

//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер BuyLimit                          |
//+------------------------------------------------------------------+
bool CEngine::PlaceBuyLimit(const double volume,
                            const string symbol,
                            const double price,
                            const double sl=0,
                            const double tp=0,
                            const ulong magic=WRONG_VALUE,
                            const string comment=NULL,
                            const datetime expiration=0,
                            const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC)
  {
   CSymbol *symbol_obj=this.GetSymbolObjByName(symbol);
   if(symbol_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_SYMBOL_ON_LIST));
      return false;
     }
   CTradeObj *trade_obj=symbol_obj.GetTradeObj();
   if(trade_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
   return trade_obj.SetOrder(ORDER_TYPE_BUY_LIMIT,volume,price,sl,tp,0,magic,expiration,type_time,comment);
  }
//+------------------------------------------------------------------+

В метод передаются:

  • объём устанавливаемого ордера (задаётся обязательно),
  • символ, на котором необходимо выставить ордер (задаётся обязательно),
  • цена, на которую необходимо выставить ордер (задаётся обязательно),
  • цена StopLoss устанавливаемого ордера (по умолчанию — нет),
  • цена TakeProfit устанавливаемого ордера (по умолчанию — нет),
  • магик устанавливаемого ордера (по умолчанию — 0),
  • комментарий устанавливаемого ордера (по умолчанию — имя программы+" by DoEasy"),
  • время жизни устанавливаемого ордера (по умолчанию — неограниченно),
  • тип времени жизни устанавливаемого ордера (по умолчанию — до явной отмены)

Получаем объект-символ по имени символа. Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Получаем торговый объект из объекта-символа. Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Возвращаем результат работы метода установки отложенного ордера торгового объекта, рассмотренный нами ранее.

Метод для установки отложенного ордера BuyStopLimit:

//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер BuyStopLimit                      |
//+------------------------------------------------------------------+
bool CEngine::PlaceBuyStopLimit(const double volume,
                                const string symbol,
                                const double price_stop,
                                const double price_limit,
                                const double sl=0,
                                const double tp=0,
                                const ulong magic=WRONG_VALUE,
                                const string comment=NULL,
                                const datetime expiration=0,
                                const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC)
  {
   #ifdef __MQL5__
      CSymbol *symbol_obj=this.GetSymbolObjByName(symbol);
      if(symbol_obj==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_SYMBOL_ON_LIST));
         return false;
        }
      CTradeObj *trade_obj=symbol_obj.GetTradeObj();
      if(trade_obj==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
         return false;
        }
      return trade_obj.SetOrder(ORDER_TYPE_BUY_STOP_LIMIT,volume,price_stop,sl,tp,price_limit,magic,expiration,type_time,comment);
   //--- MQL4
   #else 
      return true;
   #endif 
  }
//+------------------------------------------------------------------+

В метод передаются:

  • объём устанавливаемого ордера (задаётся обязательно),
  • символ, на котором необходимо выставить ордер (задаётся обязательно),
  • цена, на которую необходимо выставить BuyStop-ордер (задаётся обязательно),
  • цена, на которую необходимо выставить BuyLimit-ордер при срабатывании BuyStop-ордера (задаётся обязательно),
  • цена StopLoss устанавливаемого ордера (по умолчанию — нет),
  • цена TakeProfit устанавливаемого ордера (по умолчанию — нет),
  • магик устанавливаемого ордера (по умолчанию — 0),
  • комментарий устанавливаемого ордера (по умолчанию — имя программы+" by DoEasy"),
  • время жизни устанавливаемого ордера (по умолчанию — неограниченно),
  • тип времени жизни устанавливаемого ордера (по умолчанию — до явной отмены)

Для MQL5:

Получаем объект-символ по имени символа. Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Получаем торговый объект из объекта-символа. Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Возвращаем результат работы метода установки отложенного ордера торгового объекта, рассмотренный нами ранее.

Для MQL4:

Ничего не делаем — возвращаем true.

Методы для установки отложенных ордеров SellStop, SellLimit и SellStopLimit:

//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер SellStop                          |
//+------------------------------------------------------------------+
bool CEngine::PlaceSellStop(const double volume,
                            const string symbol,
                            const double price,
                            const double sl=0,
                            const double tp=0,
                            const ulong magic=WRONG_VALUE,
                            const string comment=NULL,
                            const datetime expiration=0,
                            const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC)
  {
   CSymbol *symbol_obj=this.GetSymbolObjByName(symbol);
   if(symbol_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_SYMBOL_ON_LIST));
      return false;
     }
   CTradeObj *trade_obj=symbol_obj.GetTradeObj();
   if(trade_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
   return trade_obj.SetOrder(ORDER_TYPE_SELL_STOP,volume,price,sl,tp,0,magic,expiration,type_time,comment);
  }
//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер SellLimit                         |
//+------------------------------------------------------------------+
bool CEngine::PlaceSellLimit(const double volume,
                             const string symbol,
                             const double price,
                             const double sl=0,
                             const double tp=0,
                             const ulong magic=WRONG_VALUE,
                             const string comment=NULL,
                             const datetime expiration=0,
                             const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC)
  {
   CSymbol *symbol_obj=this.GetSymbolObjByName(symbol);
   if(symbol_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_SYMBOL_ON_LIST));
      return false;
     }
   CTradeObj *trade_obj=symbol_obj.GetTradeObj();
   if(trade_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
   return trade_obj.SetOrder(ORDER_TYPE_SELL_LIMIT,volume,price,sl,tp,0,magic,expiration,type_time,comment);
  }
//+------------------------------------------------------------------+
//| Устанавливает отложенный ордер SellStopLimit                     |
//+------------------------------------------------------------------+
bool CEngine::PlaceSellStopLimit(const double volume,
                                 const string symbol,
                                 const double price_stop,
                                 const double price_limit,
                                 const double sl=0,
                                 const double tp=0,
                                 const ulong magic=WRONG_VALUE,
                                 const string comment=NULL,
                                 const datetime expiration=0,
                                 const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC)
  {
   #ifdef __MQL5__
      CSymbol *symbol_obj=this.GetSymbolObjByName(symbol);
      if(symbol_obj==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_SYMBOL_ON_LIST));
         return false;
        }
      CTradeObj *trade_obj=symbol_obj.GetTradeObj();
      if(trade_obj==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
         return false;
        }
      return trade_obj.SetOrder(ORDER_TYPE_SELL_STOP_LIMIT,volume,price_stop,sl,tp,price_limit,magic,expiration,type_time,comment);
   //--- MQL4
   #else 
      return true;
   #endif 
  }
//+------------------------------------------------------------------+

В этих методах всё аналогично методам установки отложенных ордеров на покупку.

Метод для модификации отложенного ордера:

//+------------------------------------------------------------------+
//| Модифицирует отложенный ордер                                    |
//+------------------------------------------------------------------+
bool CEngine::ModifyOrder(const ulong ticket,
                          const double price=WRONG_VALUE,
                          const double sl=WRONG_VALUE,
                          const double tp=WRONG_VALUE,
                          const double stoplimit=WRONG_VALUE,
                          datetime expiration=WRONG_VALUE,
                          ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE)
  {
   CTradeObj *trade_obj=this.GetTradeObjByOrder(ticket);
   if(trade_obj==NULL)
     {
      //--- Ошибка. Не удалось получить торговый объект
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
   return trade_obj.ModifyOrder(ticket,price,sl,tp,stoplimit,expiration,type_time);
  }
//+------------------------------------------------------------------+

В метод передаются:

  • тикет модифицируемого ордера (задаётся обязательно),
  • новая цена установки отложенного ордера (по умолчанию — без изменений),
  • новая цена StopLoss отложенного ордера (по умолчанию — без изменений),
  • новая цена TakeProfit отложенного ордера (по умолчанию — без изменений),
  • новая цена StopLimit отложенного ордера (по умолчанию — без изменений),
  • новое время экспирации отложенного ордера (по умолчанию — без изменений),
  • новый режим времени жизни отложенного ордера (по умолчанию — без изменений)

Получаем торговый объект по тикету модифицируемого ордера посредством метода GetTradeObjByOrder(), который рассмотрим ниже.
Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Возвращаем результат работы метода модификации отложенного ордера торгового объекта, рассмотренный нами ранее.

Метод для удаления отложенного ордера:

//+------------------------------------------------------------------+
//| Удаляет отложенный ордер                                         |
//+------------------------------------------------------------------+
bool CEngine::DeleteOrder(const ulong ticket)
  {
   CTradeObj *trade_obj=this.GetTradeObjByOrder(ticket);
   if(trade_obj==NULL)
     {
      //--- Ошибка. Не удалось получить торговый объект
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
   return trade_obj.DeleteOrder(ticket);
  }
//+------------------------------------------------------------------+

В метод передаётся тикет удаляемого ордера.

Получаем торговый объект по тикету ордера посредством метода  GetTradeObjByOrder(), который рассмотрим ниже.
Если объект получить не удалось — выводим об этом сообщение и возвращаем false.
Возвращаем результат работы метода удаления отложенного ордера торгового объекта, рассмотренный нами ранее.

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

//+------------------------------------------------------------------+
//| Возвращает торговый объект символа по тикету позиции             |
//+------------------------------------------------------------------+
CTradeObj *CEngine::GetTradeObjByPosition(const ulong ticket)
  {
   //--- Получаем список открытых позиций
   CArrayObj *list=this.GetListMarketPosition();
   //--- Если не удалось получить список открытых позиций - выводим сообщение и возвращаем NULL
   if(list==NULL)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_ENG_FAILED_GET_MARKET_POS_LIST));
      return NULL;
     }
   //--- Если список пустой (Нет открытых позиций) - выводим сообщение и возвращаем NULL
   if(list.Total()==0)
     {
      ::Print(DFUN,CMessage::Text(MSG_ENG_NO_OPEN_POSITIONS));
      return NULL;
     }
   //--- Фильтруем список по тикету 
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,ticket,EQUAL);
   //--- Если не удалось получить список открытых позиций - выводим сообщение и возвращаем NULL
   if(list==NULL)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_ENG_FAILED_GET_MARKET_POS_LIST));
      return NULL;
     }
   //--- Если список пустой (нет искомого тикета) - выводим сообщение и возвращаем NULL
   if(list.Total()==0)
     {
      //--- Ошибка. Нет открытой позиции с тикетом #ticket
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_NO_OPEN_POSITION_WITH_TICKET),(string)ticket);
      return NULL;
     }
   //--- Получаем позицию с тикетом #ticket из полученного списка
   COrder *pos=list.At(0);
   //--- Если не удалось получить объект-позицию - выводим сообщение и возвращаем NULL
   if(pos==NULL)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_POS_OBJ));
      return NULL;
     }
   //--- Получаем объект-символ по имени
   CSymbol * symbol_obj=this.GetSymbolObjByName(pos.Symbol());
   //--- Если не удалось получить объект-символ - выводим сообщение и возвращаем NULL
   if(symbol_obj==NULL)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ));
      return NULL;
     }
   //--- Получаем и возвращаем торговый объект из объекта-символа
   CTradeObj *obj=symbol_obj.GetTradeObj();
   return obj;
  }
//+------------------------------------------------------------------+
//| Возвращает торговый объект символа по тикету ордера              |
//+------------------------------------------------------------------+
CTradeObj *CEngine::GetTradeObjByOrder(const ulong ticket)
  {
   //--- Получаем список установленных ордеров
   CArrayObj *list=this.GetListMarketPendings();
   //--- Если не удалось получить список установленных ордеров - выводим сообщение и возвращаем NULL
   if(list==NULL)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_ENG_FAILED_GET_PENDING_ORD_LIST));
      return NULL;
     }
   //--- Если список пустой (Нет установленных ордеров) - выводим сообщение и возвращаем NULL
   if(list.Total()==0)
     {
      ::Print(DFUN,CMessage::Text(MSG_ENG_NO_PLACED_ORDERS));
      return NULL;
     }
   //--- Фильтруем список по тикету 
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,ticket,EQUAL);
   //--- Если не удалось получить список установленных ордеров - выводим сообщение и возвращаем NULL
   if(list==NULL)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_ENG_FAILED_GET_PENDING_ORD_LIST));
      return NULL;
     }
   //--- Если список пустой (нет искомого тикета) - выводим сообщение и возвращаем NULL
   if(list.Total()==0)
     {
      //--- Ошибка. Нет установленного ордера с тикетом #ticket
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_NO_PLACED_ORDER_WITH_TICKET),(string)ticket);
      return NULL;
     }
   //--- Получаем ордер с тикетом #ticket из полученного списка
   COrder *ord=list.At(0);
   //--- Если не удалось получить объект-ордер - выводим сообщение и возвращаем NULL
   if(ord==NULL)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_ORD_OBJ));
      return NULL;
     }
   //--- Получаем объект-символ по имени
   CSymbol *symbol_obj=this.GetSymbolObjByName(ord.Symbol());
   //--- Если не удалось получить объект-символ - выводим сообщение и возвращаем NULL
   if(symbol_obj==NULL)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ));
      return NULL;
     }
   //--- Получаем и возвращаем торговый объект из объекта-символа
   CTradeObj *obj=symbol_obj.GetTradeObj();
   return obj;
  }
//+--------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает корректную политику исполнения                     |
//| для торговых объектов всех символов                              |
//+------------------------------------------------------------------+
void CEngine::SetTradeCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL)
  {
   //--- Объявляем пустой указатель на объект-символ
   CSymbol *symbol=NULL;
   //--- Если имя символа, переданного во входных параметрах метода, не задано - значит устанавливаем политику исполнения всем символам
   if(symbol_name==NULL)
     {
      //--- получаем список всех используемых символов
      CArrayObj *list=this.GetListAllUsedSymbols();
      if(list==NULL || list.Total()==0)
         return;
      int total=list.Total();
      //--- В цикле по списку объектов-символов
      for(int i=0;i<total;i++)
        {
         //--- получаем очередной объект-символ
         symbol=list.At(i);
         if(symbol==NULL)
            continue;
         //--- получаем из объекта-символа его торговый объект 
         CTradeObj *obj=symbol.GetTradeObj();
         if(obj==NULL)
            continue;
         //--- устанавливаем торговому объекту корректную политику исполнения (по умолчанию "всё или ничего")
         obj.SetTypeFilling(symbol.GetCorrectTypeFilling(type));
        }
     }
   //--- Если во входных параметрах метода имя символа задано - значит устанавливаем политику исполнения только заданному символу
   else
     {
      //--- Получаем объект-символ по имени символа
      symbol=this.GetSymbolObjByName(symbol_name);
      if(symbol==NULL)
         return;
      //--- получаем из объекта-символа его торговый объект 
      CTradeObj *obj=symbol.GetTradeObj();
      if(obj==NULL)
         return;
      //--- устанавливаем торговому объекту корректную политику исполнения (по умолчанию "всё или ничего")
      obj.SetTypeFilling(symbol.GetCorrectTypeFilling(type));
     }
  }
//+------------------------------------------------------------------+
//| Устанавливает политику исполнения                                |
//| для торговых объектов всех символов                              |
//+------------------------------------------------------------------+
void CEngine::SetTradeTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL)
  {
   //--- Объявляем пустой указатель на объект-символ
   CSymbol *symbol=NULL;
   //--- Если имя символа, переданного во входных параметрах метода, не задано - значит устанавливаем политику исполнения всем символам
   if(symbol_name==NULL)
     {
      //--- получаем список всех используемых символов
      CArrayObj *list=this.GetListAllUsedSymbols();
      if(list==NULL || list.Total()==0)
         return;
      int total=list.Total();
      //--- В цикле по списку объектов-символов
      for(int i=0;i<total;i++)
        {
         //--- получаем очередной объект-символ
         symbol=list.At(i);
         if(symbol==NULL)
            continue;
         //--- получаем из объекта-символа его торговый объект 
         CTradeObj *obj=symbol.GetTradeObj();
         if(obj==NULL)
            continue;
         //--- устанавливаем торговому объекту политику исполнения, переданную в метод во входных параметрах (по умолчанию "всё или ничего")
         obj.SetTypeFilling(type);
        }
     }
   //--- Если во входных параметрах метода имя символа задано - значит устанавливаем политику исполнения только заданному символу
   else
     {
      //--- Получаем объект-символ по имени символа
      symbol=this.GetSymbolObjByName(symbol_name);
      if(symbol==NULL)
         return;
      //--- получаем из объекта-символа его торговый объект 
      CTradeObj *obj=symbol.GetTradeObj();
      if(obj==NULL)
         return;
      //--- устанавливаем торговому объекту политику исполнения, переданную в метод во входных параметрах (по умолчанию "всё или ничего")
      obj.SetTypeFilling(type);
     }
  }
//+------------------------------------------------------------------+

В методы передаются политика исполнения (по умолчанию "всё или ничего") и символ (по умолчанию — все символы из коллекции символов).

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

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

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

//+------------------------------------------------------------------+
//| Устанавливает корректный тип истечения ордера                    |
//| для торговых объектов всех символов                              |
//+------------------------------------------------------------------+
void CEngine::SetTradeCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL)
  {
   CSymbol *symbol=NULL;
   if(symbol_name==NULL)
     {
      CArrayObj *list=this.GetListAllUsedSymbols();
      if(list==NULL || list.Total()==0)
         return;
      int total=list.Total();
      for(int i=0;i<total;i++)
        {
         symbol=list.At(i);
         if(symbol==NULL)
            continue;
         CTradeObj *obj=symbol.GetTradeObj();
         if(obj==NULL)
            continue;
         obj.SetTypeExpiration(symbol.GetCorrectTypeExpiration(type));
        }
     }
   else
     {
      symbol=this.GetSymbolObjByName(symbol_name);
      if(symbol==NULL)
         return;
      CTradeObj *obj=symbol.GetTradeObj();
      if(obj==NULL)
         return;
      obj.SetTypeExpiration(symbol.GetCorrectTypeExpiration(type));
     }
  }
//+------------------------------------------------------------------+
//| Устанавливает тип истечения ордера                               |
//| для торговых объектов всех символов                              |
//+------------------------------------------------------------------+
void CEngine::SetTradeTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL)
  {
   CSymbol *symbol=NULL;
   if(symbol_name==NULL)
     {
      CArrayObj *list=this.GetListAllUsedSymbols();
      if(list==NULL || list.Total()==0)
         return;
      int total=list.Total();
      for(int i=0;i<total;i++)
        {
         symbol=list.At(i);
         if(symbol==NULL)
            continue;
         CTradeObj *obj=symbol.GetTradeObj();
         if(obj==NULL)
            continue;
         obj.SetTypeExpiration(type);
        }
     }
   else
     {
      symbol=this.GetSymbolObjByName(symbol_name);
      if(symbol==NULL)
         return;
      CTradeObj *obj=symbol.GetTradeObj();
      if(obj==NULL)
         return;
      obj.SetTypeExpiration(type);
     }
  }
//+------------------------------------------------------------------+
//| Устанавливает магик для торговых объектов всех символов          |
//+------------------------------------------------------------------+
void CEngine::SetTradeMagic(const ulong magic,const string symbol_name=NULL)
  {
   CSymbol *symbol=NULL;
   if(symbol_name==NULL)
     {
      CArrayObj *list=this.GetListAllUsedSymbols();
      if(list==NULL || list.Total()==0)
         return;
      int total=list.Total();
      for(int i=0;i<total;i++)
        {
         symbol=list.At(i);
         if(symbol==NULL)
            continue;
         CTradeObj *obj=symbol.GetTradeObj();
         if(obj==NULL)
            continue;
         obj.SetMagic(magic);
        }
     }
   else
     {
      symbol=this.GetSymbolObjByName(symbol_name);
      if(symbol==NULL)
         return;
      CTradeObj *obj=symbol.GetTradeObj();
      if(obj==NULL)
         return;
      obj.SetMagic(magic);
     }
  }
//+------------------------------------------------------------------+
//| Устанавливает комментарий для торговых объектов всех символов    |
//+------------------------------------------------------------------+
void CEngine::SetTradeComment(const string comment,const string symbol_name=NULL)
  {
   CSymbol *symbol=NULL;
   if(symbol_name==NULL)
     {
      CArrayObj *list=this.GetListAllUsedSymbols();
      if(list==NULL || list.Total()==0)
         return;
      int total=list.Total();
      for(int i=0;i<total;i++)
        {
         symbol=list.At(i);
         if(symbol==NULL)
            continue;
         CTradeObj *obj=symbol.GetTradeObj();
         if(obj==NULL)
            continue;
         obj.SetComment(comment);
        }
     }
   else
     {
      symbol=this.GetSymbolObjByName(symbol_name);
      if(symbol==NULL)
         return;
      CTradeObj *obj=symbol.GetTradeObj();
      if(obj==NULL)
         return;
      obj.SetComment(comment);
     }
  }
//+------------------------------------------------------------------+
//| Устанавливает размер проскальзывания                             |
//| для торговых объектов всех символов                              |
//+------------------------------------------------------------------+
void CEngine::SetTradeDeviation(const ulong deviation,const string symbol_name=NULL)
  {
   CSymbol *symbol=NULL;
   if(symbol_name==NULL)
     {
      CArrayObj *list=this.GetListAllUsedSymbols();
      if(list==NULL || list.Total()==0)
         return;
      int total=list.Total();
      for(int i=0;i<total;i++)
        {
         symbol=list.At(i);
         if(symbol==NULL)
            continue;
         CTradeObj *obj=symbol.GetTradeObj();
         if(obj==NULL)
            continue;
         obj.SetDeviation(deviation);
        }
     }
   else
     {
      symbol=this.GetSymbolObjByName(symbol_name);
      if(symbol==NULL)
         return;
      CTradeObj *obj=symbol.GetTradeObj();
      if(obj==NULL)
         return;
      obj.SetDeviation(deviation);
     }
  }
//+------------------------------------------------------------------+
//| Устанавливает объём для торговых объектов всех символов          |
//+------------------------------------------------------------------+
void CEngine::SetTradeVolume(const double volume=0,const string symbol_name=NULL)
  {
   CSymbol *symbol=NULL;
   if(symbol_name==NULL)
     {
      CArrayObj *list=this.GetListAllUsedSymbols();
      if(list==NULL || list.Total()==0)
         return;
      int total=list.Total();
      for(int i=0;i<total;i++)
        {
         symbol=list.At(i);
         if(symbol==NULL)
            continue;
         CTradeObj *obj=symbol.GetTradeObj();
         if(obj==NULL)
            continue;
         obj.SetVolume(volume!=0 ? symbol.NormalizedLot(volume) : symbol.LotsMin());
        }
     }
   else
     {
      symbol=this.GetSymbolObjByName(symbol_name);
      if(symbol==NULL)
         return;
      CTradeObj *obj=symbol.GetTradeObj();
      if(obj==NULL)
         return;
      obj.SetVolume(volume!=0 ? symbol.NormalizedLot(volume) : symbol.LotsMin());
     }
  }
//+------------------------------------------------------------------+
//| Устанавливает срок истечения ордера                              |
//| для торговых объектов всех символов                              |
//+------------------------------------------------------------------+
void CEngine::SetTradeExpiration(const datetime expiration=0,const string symbol_name=NULL)
  {
   CSymbol *symbol=NULL;
   if(symbol_name==NULL)
     {
      CArrayObj *list=this.GetListAllUsedSymbols();
      if(list==NULL || list.Total()==0)
         return;
      int total=list.Total();
      for(int i=0;i<total;i++)
        {
         symbol=list.At(i);
         if(symbol==NULL)
            continue;
         CTradeObj *obj=symbol.GetTradeObj();
         if(obj==NULL)
            continue;
         obj.SetExpiration(expiration);
        }
     }
   else
     {
      symbol=this.GetSymbolObjByName(symbol_name);
      if(symbol==NULL)
         return;
      CTradeObj *obj=symbol.GetTradeObj();
      if(obj==NULL)
         return;
      obj.SetExpiration(expiration);
     }
  }
//+------------------------------------------------------------------+
//| Устанавливает флаг асинхронной отправки торговых запросов        |
//| для торговых объектов всех символов                              |
//+------------------------------------------------------------------+
void CEngine::SetTradeAsyncMode(const bool mode=false,const string symbol_name=NULL)
  {
   CSymbol *symbol=NULL;
   if(symbol_name==NULL)
     {
      CArrayObj *list=this.GetListAllUsedSymbols();
      if(list==NULL || list.Total()==0)
         return;
      int total=list.Total();
      for(int i=0;i<total;i++)
        {
         symbol=list.At(i);
         if(symbol==NULL)
            continue;
         CTradeObj *obj=symbol.GetTradeObj();
         if(obj==NULL)
            continue;
         obj.SetAsyncMode(mode);
        }
     }
   else
     {
      symbol=this.GetSymbolObjByName(symbol_name);
      if(symbol==NULL)
         return;
      CTradeObj *obj=symbol.GetTradeObj();
      if(obj==NULL)
         return;
      obj.SetAsyncMode(mode);
     }
  }
//+------------------------------------------------------------------+
//| Устанавливает уровень логирования торговых запросов              |
//| для торговых объектов всех символов                              |
//+------------------------------------------------------------------+
void CEngine::SetTradeLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol_name=NULL)
  {
   CSymbol *symbol=NULL;
   if(symbol_name==NULL)
     {
      CArrayObj *list=this.GetListAllUsedSymbols();
      if(list==NULL || list.Total()==0)
         return;
      int total=list.Total();
      for(int i=0;i<total;i++)
        {
         symbol=list.At(i);
         if(symbol==NULL)
            continue;
         CTradeObj *obj=symbol.GetTradeObj();
         if(obj==NULL)
            continue;
         obj.SetLogLevel(log_level);
        }
     }
   else
     {
      symbol=this.GetSymbolObjByName(symbol_name);
      if(symbol==NULL)
         return;
      CTradeObj *obj=symbol.GetTradeObj();
      if(obj==NULL)
         return;
      obj.SetLogLevel(log_level);
     }
  }
//+------------------------------------------------------------------+

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

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

Тестирование базового торгового объекта

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

Сохраним советник в новой папке \MQL5\Experts\TestDoEasy\Part21\ под новым именем TestDoEasyPart21.mq5.

В первую очередь удалим подключение торгового класса CTrade стандартной библиотеки и объявление торгового объекта с типом класса CTrade:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart20.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#ifdef __MQL5__
#include <Trade\Trade.mqh>
#endif 
//--- enums
enum ENUM_BUTTONS
  {
   BUTT_BUY,
   BUTT_BUY_LIMIT,
   BUTT_BUY_STOP,
   BUTT_BUY_STOP_LIMIT,
   BUTT_CLOSE_BUY,
   BUTT_CLOSE_BUY2,
   BUTT_CLOSE_BUY_BY_SELL,
   BUTT_SELL,
   BUTT_SELL_LIMIT,
   BUTT_SELL_STOP,
   BUTT_SELL_STOP_LIMIT,
   BUTT_CLOSE_SELL,
   BUTT_CLOSE_SELL2,
   BUTT_CLOSE_SELL_BY_BUY,
   BUTT_DELETE_PENDING,
   BUTT_CLOSE_ALL,
   BUTT_PROFIT_WITHDRAWAL,
   BUTT_SET_STOP_LOSS,
   BUTT_SET_TAKE_PROFIT,
   BUTT_TRAILING_ALL
  };
#define TOTAL_BUTT   (20)
//--- structures
struct SDataButt
  {
   string      name;
   string      text;
  };
//--- input variables
input ulong             InpMagic             =  123;  // Magic number
input double            InpLots              =  0.1;  // Lots
input uint              InpStopLoss          =  50;   // StopLoss in points
input uint              InpTakeProfit        =  50;   // TakeProfit in points
input uint              InpDistance          =  50;   // Pending orders distance (points)
input uint              InpDistanceSL        =  50;   // StopLimit orders distance (points)
input uint              InpSlippage          =  0;    // Slippage in points
input double            InpWithdrawal        =  10;   // Withdrawal funds (in tester)
input uint              InpButtShiftX        =  40;   // Buttons X shift 
input 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)
input ENUM_SYMBOLS_MODE InpModeUsedSymbols   =  SYMBOLS_MODE_CURRENT;   // Mode of used symbols list
input string            InpUsedSymbols       =  "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)

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

В обработчике OnInit() удалим установку параметров объекту trade торгового класса CTrade:

//--- Установка состояния кнопки активизации трейлингов
   ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on);

//--- Установка параметров торгового класса CTrade
#ifdef __MQL5__
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
#endif 
//--- Создание и проверка файлов ресурсов

А далее всё просто — при помощи поиска (Ctrl+F) ищем вхождение строки "trade" и заменяем вызов торговых методов стандартной библиотеки на свои.

Например, это:

                  COrder* position=list_positions.At(index);
                  if(position!=NULL)
                    {
                     //--- Получаем тикет позиции с наибольшей прибылью и закрываем позицию по тикету
                     #ifdef __MQL5__
                        trade.PositionClose(position.Ticket());
                     #else 
                        PositionClose(position.Ticket(),position.Volume());
                     #endif 
                    }

меняем на это:

                  COrder* position=list_positions.At(index);
                  if(position!=NULL)
                    {
                     //--- Получаем тикет позиции с наибольшей прибылью и закрываем позицию по тикету
                     engine.ClosePosition(position.Ticket());
                    }

И далее, по ходу нахождения вызовов торговых методов СБ, просто меняем на вызов своих методов.

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

//+------------------------------------------------------------------+
//| Обработка нажатий кнопок                                         |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   string comment="";
   //--- Преобразуем имя кнопки в её строковый идентификатор
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- Если кнопка в нажатом состоянии
   if(ButtonState(button_name))
     {
      //--- Если нажата кнопка BUTT_BUY: Открыть позицию Buy
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- Открываем позицию Buy
         engine.OpenBuy(lot,Symbol(),magic_number,sl,tp);   // Нет комментария - будет установлен комментарий по умолчанию
        }
      //--- Если нажата кнопка BUTT_BUY_LIMIT: Выставить BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Получаем корректную цену установки ордера относительно уровня StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
         //--- Устанавливаем ордер BuyLimit
         engine.PlaceBuyLimit(lot,Symbol(),price_set,sl,tp,magic_number,TextByLanguage("Отложенный BuyLimit","Pending order BuyLimit"));
        }
      //--- Если нажата кнопка BUTT_BUY_STOP: Выставить BuyStop
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- Получаем корректную цену установки ордера относительно уровня StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit);
         //--- Устанавливаем ордер BuyStop
         engine.PlaceBuyStop(lot,Symbol(),price_set,sl,tp,magic_number,TextByLanguage("Отложенный BuyStop","Pending order BuyStop"));
        }
      //--- Если нажата кнопка BUTT_BUY_STOP_LIMIT: Выставить BuyStopLimit
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- Получаем корректную цену установки ордера BuyStop относительно уровня StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Рассчитываем цену установки ордера BuyLimit относительно уровня установки BuyStop с учётом уровня StopLevel
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit);
         //--- Устанавливаем ордер BuyStopLimit
         engine.PlaceBuyStopLimit(lot,Symbol(),price_set_stop,price_set_limit,sl,tp,magic_number,TextByLanguage("Отложенный BuyStopLimit","Pending order BuyStopLimit"));
        }
      //--- Если нажата кнопка BUTT_SELL: Открыть позицию Sell
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit);
         //--- Открываем позицию Sell
         engine.OpenSell(lot,Symbol(),magic_number,sl,tp);  // Нет комментария - будет установлен комментарий по умолчанию
        }
      //--- Если нажата кнопка BUTT_SELL_LIMIT: Выставить SellLimit
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- Получаем корректную цену установки ордера относительно уровня StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit);
         //--- Устанавливаем ордер SellLimit
         engine.PlaceSellLimit(lot,Symbol(),price_set,sl,tp,magic_number,TextByLanguage("Отложенный SellLimit","Pending order SellLimit"));
        }
      //--- Если нажата кнопка BUTT_SELL_STOP: Выставить SellStop
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- Получаем корректную цену установки ордера относительно уровня StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit);
         //--- Устанавливаем ордер SellStop
         engine.PlaceSellStop(lot,Symbol(),price_set,sl,tp,magic_number,TextByLanguage("Отложенный SellStop","Pending order SellStop"));
        }
      //--- Если нажата кнопка BUTT_SELL_STOP_LIMIT: Выставить SellStopLimit
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- Получаем корректную цену установки ордера SellStop относительно уровня StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Рассчитываем цену установки ордера SellLimit относительно уровня установки SellStop с учётом уровня StopLevel
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit);
         //--- Устанавливаем ордер SellStopLimit
         engine.PlaceSellStopLimit(lot,Symbol(),price_set_stop,price_set_limit,sl,tp,magic_number,TextByLanguage("Отложенный SellStopLimit","Pending order SellStopLimit"));
        }
      //--- Если нажата кнопка BUTT_CLOSE_BUY: Закрыть Buy с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_BUY))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Buy
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Buy с наибольшей прибылью
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            //--- Получаем объект-позицию Buy и закрываем позицию по тикету
            COrder* position=list.At(index);
            if(position!=NULL)
               engine.ClosePosition((ulong)position.Ticket());
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_BUY2: Закрыть половину Buy с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_BUY2))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Buy
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Buy с наибольшей прибылью
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Если счёт хеджевый, закрываем половину позиции Buy по тикету
               if(engine.IsHedge())
                  engine.ClosePositionPartially((ulong)position.Ticket(),position.Volume()/2.0);
               //--- Если счёт неттинговый, открываем позицию Sell с половиной объёма позиции Buy
               else
                  engine.OpenSell(NormalizeLot(position.Symbol(),position.Volume()/2.0),Symbol(),magic_number,position.StopLoss(),position.TakeProfit(),"Частичное закрытие Buy #"+(string)position.Ticket());
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_BUY_BY_SELL: Закрыть Buy с максимальной прибылью встречной Sell с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL))
        {
         //--- Если счёт хеджевый
         if(engine.IsHedge())
           {
            //--- Получаем список всех открытых позиций
            CArrayObj* list_buy=engine.GetListMarketPosition();
            //--- Выбираем из списка только позиции Buy
            list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
            //--- Сортируем список по прибыли с учётом комиссии и свопа
            list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
            //--- Получаем индекс позиции Buy с наибольшей прибылью
            int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
            //--- Получаем список всех открытых позиций
            CArrayObj* list_sell=engine.GetListMarketPosition();
            //--- Выбираем из списка только позиции Sell
            list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
            //--- Сортируем список по прибыли с учётом комиссии и свопа
            list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
            //--- Получаем индекс позиции Sell с наибольшей прибылью
            int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
            if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE)
              {
               //--- Выбираем позицию Buy с наибольшей прибылью
               COrder* position_buy=list_buy.At(index_buy);
               //--- Выбираем позицию Sell с наибольшей прибылью
               COrder* position_sell=list_sell.At(index_sell);
               //--- Закрываем позицию Buy встречной позицией Sell
               if(position_buy!=NULL && position_sell!=NULL)
                  engine.ClosePositionBy((ulong)position_buy.Ticket(),(ulong)position_sell.Ticket());
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_SELL: Закрыть Sell с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_SELL))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Sell
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Sell с наибольшей прибылью
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            //--- Получаем объект-позицию Sell и закрываем позицию по тикету
            COrder* position=list.At(index);
            if(position!=NULL)
               engine.ClosePosition((ulong)position.Ticket());
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_SELL2: Закрыть половину Sell с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_SELL2))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Sell
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Sell с наибольшей прибылью
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Если счёт хеджевый, закрываем половину позиции Sell по тикету
               if(engine.IsHedge())
                  engine.ClosePositionPartially((ulong)position.Ticket(),position.Volume()/2.0);
               //--- Если счёт неттинговый, открываем позицию Buy с половиной объёма позиции Sell
               else
                  engine.OpenBuy(NormalizeLot(position.Symbol(),position.Volume()/2.0),Symbol(),position.Magic(),position.StopLoss(),position.TakeProfit(),"Частичное закрытие Buy #"+(string)position.Ticket());
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_SELL_BY_BUY: Закрыть Sell с максимальной прибылью встречной Buy с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Sell
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Sell с наибольшей прибылью
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         //--- Получаем список всех открытых позиций
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Buy
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Buy с наибольшей прибылью
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE)
           {
            //--- Выбираем позицию Sell с наибольшей прибылью
            COrder* position_sell=list_sell.At(index_sell);
            //--- Выбираем позицию Buy с наибольшей прибылью
            COrder* position_buy=list_buy.At(index_buy);
            if(position_sell!=NULL && position_buy!=NULL)
              {
               //--- Закрываем позицию Sell встречной позицией Buy
               engine.ClosePositionBy((ulong)position_sell.Ticket(),(ulong)position_buy.Ticket());
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_ALL: Закрыть все позиции, начиная от позиции с наименьшим профитом
      else if(button==EnumToString(BUTT_CLOSE_ALL))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         if(list!=NULL)
           {
            //--- Сортируем список по прибыли с учётом комиссии и свопа
            list.Sort(SORT_BY_ORDER_PROFIT_FULL);
            int total=list.Total();
            //--- В цикле от позиции с наименьшей прибылью
            for(int i=0;i<total;i++)
              {
               COrder* position=list.At(i);
               if(position==NULL)
                  continue;
               //--- закрываем каждую позицию по её тикету
               engine.ClosePosition((ulong)position.Ticket());
              }
           }
        }
      //--- Если нажата кнопка BUTT_DELETE_PENDING: Удалить первый отложенный ордер
      else if(button==EnumToString(BUTT_DELETE_PENDING))
        {
         //--- Получаем список всех ордеров
         CArrayObj* list=engine.GetListMarketPendings();
         if(list!=NULL)
           {
            //--- Сортируем список по времени установки
            list.Sort(SORT_BY_ORDER_TIME_OPEN);
            int total=list.Total();
            //--- В цикле от позиции с наибольшим временем
            for(int i=total-1;i>=0;i--)
              {
               COrder* order=list.At(i);
               if(order==NULL)
                  continue;
               //--- удаяем ордер по его тикету
               engine.DeleteOrder((ulong)order.Ticket());
              }
           }
        }
      //--- Если нажата кнопка BUTT_PROFIT_WITHDRAWAL: Вывести средства со счёта
      if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL))
        {
         //--- Если программа запущена в тестере
         if(MQLInfoInteger(MQL_TESTER))
           {
            //--- Эмулируем вывод средств
            TesterWithdrawal(withdrawal);
           }
        }
      //--- Если нажата кнопка BUTT_SET_STOP_LOSS: Установить StopLoss всем ордерам и позициям, где его нету
      if(button==EnumToString(BUTT_SET_STOP_LOSS))
        {
         SetStopLoss();
        }
      //--- Если нажата кнопка BUTT_SET_TAKE_PROFIT: Установить TakeProfit всем ордерам и позициям, где его нету
      if(button==EnumToString(BUTT_SET_TAKE_PROFIT))
        {
         SetTakeProfit();
        }
      //--- Подождём 1/10 секунды
      Sleep(100);
      //--- "Отожмём" кнопку (если это не кнопка трейлинга)
      if(button!=EnumToString(BUTT_TRAILING_ALL))
         ButtonState(button_name,false);
      //--- Если нажата кнопка BUTT_TRAILING_ALL
      else
        {
         //--- Поставим цвет активной кнопки
         ButtonState(button_name,true);
         trailing_on=true;
        }
      //--- перерисуем чарт
      ChartRedraw();
     }
   //--- Вернём цвет неактивной кнопки (если это кнопка трейлинга)
   else if(button==EnumToString(BUTT_TRAILING_ALL))
     {
      ButtonState(button_name,false);
      trailing_on=false;
      //--- перерисуем чарт
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+

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

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


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

Что дальше

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

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

К содержанию

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

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