Понравилась статья?
Поставьте ссылку на нее -
пусть другие почитают
Используй новые возможности MetaTrader 5

Почти конструктор для создания советника

16 сентября 2021, 16:52
Vladimir Karputov
7
3 485

Оглавление


Введение

С самого начала у меня была цель использовать Стандартную библиотеку. Помню свою первую задачу — это реализовать самый простейший функционал: подключить торговый класс CTrade и выполнить метод Buy или Sell. Стандартная библиотека была выбрана из-за короткого и лаконичного полученного кода. Короткий код ниже, выполненный в виде скрипта, открывает позицию BUY объёмом 1.0 лот:

//+------------------------------------------------------------------+
//|                                                     Open Buy.mq5 |
//|                         Copyright © 2018-2021, Vladimir Karputov |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2018-2021, Vladimir Karputov"
#property version   "1.001"
//---
#include <Trade\Trade.mqh>
CTrade         m_trade;                      // trading object
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   m_trade.Buy(1.0); // open Buy position, volume 1.0 lot
  }

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

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

ПОМНИТЕ: MQL5 стиль подразумевает, что хендл индикатора создаётся ОДИН РАЗ и делается это, как правило, в OnInit

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

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

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

Каждый входной параметр тянул за собой создание блоков кода и новых функций.

Для ежедневного использования решено было собрать все самые популярные функции и полный набор входных параметров в советник 'Trading engine 3.mq5' по сути, это готовый советник, который заменяет много рутинной работы. Остаётся под каждый конкретный случай добавить или удалить функции или изменить взаимодействие между блоками кода.


1. Функционал советника после конструктора

Советник созданный конструктором имеет сразу очень много настроек, компонуя которые можно создавать уникальные стратегии. В версии 4.XXX применяются такие правила: 

  • используется текущий символ (символ на графике которого запущен советник)
  • Take Profit, Stop Loss и Trailing во входных параметрах задаются в Points. Points — размер пункта текущего инструмента в валюте котировки, например, для пары 'EURSD' 1.00055-1.00045=10 points.

Что такое 'points' всегда можно увидеть на графике символа, если перетащить инструмент Перекрестие:

points

Рис. 1. Points

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

  • "Trading settings" торговые параметры
    • Working timeframe— рабочий таймфрейм. Может отличаться от таймфрейма графика, на котором запущен эксперт. Является таймфреймом, на котором создаётся индикатор (если в индикаторе явно не указан другой таймфрейм). Также используется для отслеживания момента рождения нового бара (для случаев, когда нужно искать торговый сигнал только в момент рождения нового бара или когда нужно запустить Трейлинг только в момент рождения нового бара).
    • Stop Loss   Стоп лосс, если задать 0, то это означает выключить параметр.
    • Take Profit  Тейк профит, если задать 0 , это означает выключить параметр.
    • Trailing on ...  два варианта, когда проверять возможность Трейлинга: 'bar #0 (at every tick)' - на каждом тике или 'bar #1 (on a new bar)' — только в момент рождения нового бара.
    • Search signals on ...  два варианта, когда проводить поиск торгового сигнала: 'bar #0 (at every tick)' - на каждом тике или 'bar #1 (on a new bar)' - только в момент рождения нового бара.
    • Trailing Stop (min distance from price to Stop Loss)  Трейлинг стоп, минимальное расстояние между ценой и Стоп лосс позиции. Трейлинг начинает работать только если позиция прибыльная и цена отошла от цены открытия на расстояние 'Trailing Stop' + 'Trailing Step'. Как работает трейлинг показано в картинках в коде TrailingStop.
    • Trailing Step  шаг трейлинга. 
  • "Position size management (lot calculation) расчет лота
    • Money management lot: Lot OR Risk  выбор системы расчета лота. Лот может быть как постоянный (' Money management' установить в 'Constant lot' и задать размер лота в ' The value for "Money management"') и динамический - в процентах риска на сделку (' Money management' установить в 'Risk in percent for a deal' и задать процент риска в  ' The value for "Money management"'). Также можно задать постоянный лот равный минимальному лоту — ' Money management' установить в 'Lots Min'.
    • The value for "Money management"  значение для ' Money management
  • "Trade mode"  режим торговли
    • Trade mode:  выбор режима торговли. Может быть 'Allowed only BUY positions' (разрешается открывать только позиции BUY), 'Allowed only SELL positions' (разрешается открывать только позиции SELL) и 'Allowed BUY and SELL positions' (разрешается открывать и позиции BUY и позиции SELL)
  • "DEMA" параметры пользовательского индикатора. В это место вы потом вставляете свой индикатор и его параметры
    • DEMA: averaging period
    • DEMA: horizontal shift
    • DEMA: type of price
  • "Time control" рабочий временной отрезок. Задаётся промежуток времени, в который можно проводить поиск торговых сигналов
    • Use time control  флаг, вкл/выкл Time control
    • Start Hour  часы начала периода
    • Start Minute  минуты начала периода
    • End Hour  часы окончания периода
    • End Minute  минуты окончания периода
  • "Pending Order Parameters параметры, относящиеся к отложенным ордерам
    • Pending: Expiration, in minutes ('0' -> OFF)  время жизни отложенного ордера ('0' означает, что параметр выключен). 
    • Pending: Indent  отступ отложенного ордера от текущей цены (используется в случае, когда цена отложенного ордера не задаётся явно)
    • Pending: Maximum spread ('0' -> OFF)  максимальный спред ( '0' означает, что параметр выключен). Если текущий спред больше заданного, то отложенный ордер не выставляется (советник ждёт кога спред уменьшится)
    • Pending: Only one pending  флаг, вкл/выкл, в рынке разрешён только один отложенный ордер
    • Pending: Reverse pending type   флаг, вкл/выкл, реверс отложенного ордера
    • Pending: New pending -> delete previous ones  если есть приказ на выставление отложенного ордера, то предварительно удаляются все остальные отложенные ордера
  • "Additional features дополнительные параметры
    • Positions: Only one   флаг, вкл/выкл, в рынке разрешается иметь только одну позицию
    • Positions: Reverse  флаг, вкл/выкл, реверс торгового приказа
    • Positions: Close opposite  флаг, вкл/выкл, если есть торговый приказ, предварительно закрываются все позиции и только потом исполняется приказ
    • Print log  флаг, вкл/выкл, выводить расширенную информацию по операциям и ошибкам
    • Coefficient (if Freeze==0 Or StopsLevels==0)  коэффициент, учитывающий Стоп левел 
    • Deviation  заданное проскальзывание
    • Magic number  уникальный идентификатор советника

2. Общий алгоритм конструктора

На глобальном программном уровне (в "шапке" советника) объявляется массив 'SPosition' ('SPosition' — имя массива), состоящий из структур 'STRUCT_POSITION'. При старте этот массив имеет нулевой размер, после отработки торгового сигнала массив также возвращается в нулевой размер.

Из OnTick вызывается функция 'SearchTradingSignals' — при наличии сигнала (сигнал получаем, если в рынке нет открытых позиций) эта функция формирует торговый приказ (создаёт для каждого торгового приказа одну структуру 'STRUCT_POSITION' в массиве. Также в OnTick проверяется наличие торгового приказа — проверяется размер массива 'SPosition:  если он больше нуля, значит есть торговый приказ и этот торговый приказ пересылается на выполнение в 'OpenBuy' или в 'OpenSell'. Контроль за исполнением торгового приказа осуществляется в OnTradeTransaction:

general algorithm (simple)

Рис. 2. Общий алгоритм (простой)

Всегда подразумевается, что советник работает по текущему символу — то есть по символу на чей график установлен советник. Пример: советник размещён на графике 'USDPLN', значит, советник работает по символу 'USDPLN'.

2.1. Структура 'STRUCT_POSITION'

Данная структура это сердце советника, она выполняет сразу две роли: в структуре есть поля, в которые записывается торговый приказ (запись проводится в 'SearchTradingSignals') и в структуре есть поля для контроля за выполнением торгового приказа (контроль проводится в OnTradeTransaction). 

//+------------------------------------------------------------------+
//| Structure Positions                                              |
//+------------------------------------------------------------------+
struct STRUCT_POSITION
  {
   ENUM_POSITION_TYPE pos_type;              // position type
   double            volume;                 // position volume (if "0.0" -> the lot is "Money management")
   double            lot_coefficient;        // lot coefficient
   bool              waiting_transaction;    // waiting transaction, "true" -> it's forbidden to trade, we expect a transaction
   ulong             waiting_order_ticket;   // waiting order ticket, ticket of the expected order
   bool              transaction_confirmed;  // transaction confirmed, "true" -> transaction confirmed
   //--- Constructor
                     STRUCT_POSITION()
     {
      pos_type                   = WRONG_VALUE;
      volume                     = 0.0;
      lot_coefficient            = 0.0;
      waiting_transaction        = false;
      waiting_order_ticket       = 0;
      transaction_confirmed      = false;
     }
  };

Одни поля отвечают за торговый приказ, а другие поля за контроль над исполнением торгового приказа.  Cтруктура содержит конструктор  специальную функцию 'STRUCT_POSITION()', которая вызывается при создании объекта структуры и инициализирует элементы структуры.

Поля торгового приказа:

  • pos_type     тип позиции, которую нужно открыть (может быть 'POSITION_TYPE_BUY' или 'POSITION_TYPE_SELL')
  • volume объем позиции. Если указан объём '0.0', значит объём позиции будет браться из группы входных параметров 'Position size management (lot calculation)'
  • lot_coefficient  если этот коэффициент больше '0.0', значит, объём позиции будет умножен на этот коэффициент

Поля контроля над исполнением торгового приказа

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

После поднятия флага в поле 'transaction_confirmed' торговый приказ удаляется из структуры. Таким образом, если в работе советника нет торгового приказа, структура имеет нулевой размер. Подробнее про поля структуры и про контроль в главе 'Ловим транзакцию упрощенный код We catch the transaction'.

Почему именно такой алгоритм

Казалось бы, достаточно проверить метод Buy на возврат 'true' или 'false' и в случае 'true' решить, что торговый приказ выполнен. И во многих случаях такой подход сработает, но встречаются моменты, когда возврат 'true' не дает гарантии результата, о чём, кстати, говорится в документации к методам Buy и Sell:

Примечание

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

А окончательное и точное подтверждение об выполнении торговой операции может служить наличие записи в торговой истории. Поэтому и был выбран такой алгоритм: при успешном выполнении метода проверяем ResultDeal (тикет сделки), проверяем ResultRetcode (код результата выполнения запроса) и запоминаем ResultOrder (тикет ордера). А тикет ордера отлавливаем в OnTradeTransaction).


3. Добавляем стандартный индикатор — работа с файлом 'Indicators Code.mq5'

Для удобства работы готовые блоки кода (объявление переменных для хранения хендлов, входные параметры, создание хендлов) собраны в советнике 'Indicators Code.mq5'. Входные параметры индикаторов и переменные для хранения хендлов расположены в "шапке" советника, создание хендлов, как и полагается, прописано в OnInit. Нюанс: имена переменных для хранения хендлов формируются по следующему шаблону: 'handle_' + 'индикатор', например 'handle_iStdDev'. Вся работа с 'Indicators Code.mq5' сводится к операциям 'copy-paste'. 

ПОМНИТЕ: MQL5 стиль подразумевает, что хендл индикатора создаётся ОДИН РАЗ и делается это (как правило) в OnInit

3.1. Пример добавления индикатора 'iRVI' (Relative Vigor Index, RVI)

Создадим советник 'Add indicator.mq5'. В редакторе MetaEditor вызываем 'MQL Wizard', например, кликом по кнопке  New, и выбираем 'Expert Advisor (template)'

Expert Advisor (template)

Рис. 3. 'MQL Wizard' -> 'Expert Advisor (template)'

На следующем шаге я настоятельно рекомендую добавить хотя бы один входной параметр 

Expert Advisor (template)

Рис. 4. 'Expert Advisor (template)' -> 'Add parameter'

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

//--- input parameters
input int      Input1=9;

'MQL Wizard' создал пустой советник, теперь добавим в него индикатор 'iRVI' (Relative Vigor Index, RVI). В 'Indicators Code.mq5' проводим поиск 'handle_iRVI' (поиск вызывается через 'ctrl' + 'F'). Поиск находит переменную, в которой хранится хендл:

iRVI

Рис. 5. handle RVI

Найденную строку копируем и вставляем в советник 'Add indicator' в шапку:

//--- input parameters
input int      Input1=9;
//---
int      handle_iRVI;                           // variable for storing the handle of the iRVI indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()

Продолжаем поиск и находим блок создания хендла:

iRVI

Рис. 6. handle iRVI

Найденные строки копируем и вставляем в советник 'Add indicator' в OnInit:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create handle of the indicator iRVI
   handle_iRVI=iRVI(m_symbol.Name(),Inp_RVI_period,Inp_RVI_ma_period);
//--- if the handle is not created
   if(handle_iRVI==INVALID_HANDLE)
     {
      //--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iRVI indicator for the symbol %s/%s, error code %d",
                  m_symbol.Name(),
                  EnumToString(Inp_RVI_period),
                  GetLastError());
      //--- the indicator is stopped early
      m_init_error=true;
      return(INIT_SUCCEEDED);
     }
//---
   return(INIT_SUCCEEDED);
  }

Теперь добавим входные параметры индикатора. В 'Indicators Code.mq5' делаем клик средней кнопкой мышки, например, на 'Inp_RVI_period' нас перебросит сразу в блок входных параметров:

iRVI

Рис. 7. handle iRVI

Копируем строки и вставляем во входные параметры:

#property version   "1.00"
//--- input parameters
input group             "RVI"
input ENUM_TIMEFRAMES      Inp_RVI_period                = PERIOD_D1;      // RVI: timeframe
input int                  Inp_RVI_ma_period             = 15;             // RVI: averaging period
//---
int      handle_iRVI;                           // variable for storing the handle of the iRVI indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

Если скомпилировать, то получим ошибки: компилятор ругается на 'm_symbol' и на 'm_init_error'. И это правильно так как эти переменные есть в коде, который получаем после работы конструктора, а советник 'Add indicator' создан исключительно для демонстрации работы с файлом 'Indicators Code.mq5'.


4. Добавляем пользовательский индикатор

Добавим пользовательский индикатор MA on DeMarker. Во-первых это пользовательский индикатор, во-вторых, этот индикатора использует group. По аналогии с предыдущим разделом создадим советник 'Add custom indicator'. После этого нужно из пользовательского индикатора скопировать входные параметры и вставить в советник:

#property version   "1.00"
//--- input parameters
input group             "DeMarker"
input int                  Inp_DeM_ma_period    = 14;             // DeM: averaging period
input double               Inp_DeM_LevelUP      = 0.7;            // DeM: Level UP
input double               Inp_DeM_LevelDOWN    = 0.3;            // DeM: Level DOWN
input group             "MA"
input int                  Inp_MA_ma_period     = 6;              // MA: averaging period
input ENUM_MA_METHOD       Inp_MA_ma_method     = MODE_EMA;       // MA: smoothing type
//---

Найдём в файле 'Indicators Code.mq5' переменную 'handle_iCustom' переменную для хранения хендла пользовательского индикатора и вставим в советник:

//+------------------------------------------------------------------+
//|                                         Add custom indicator.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "1.00"
//--- input parameters
input group             "DeMarker"
input int                  Inp_DeM_ma_period    = 14;             // DeM: averaging period
input double               Inp_DeM_LevelUP      = 0.7;            // DeM: Level UP
input double               Inp_DeM_LevelDOWN    = 0.3;            // DeM: Level DOWN
input group             "MA"
input int                  Inp_MA_ma_period     = 6;              // MA: averaging period
input ENUM_MA_METHOD       Inp_MA_ma_method     = MODE_EMA;       // MA: smoothing type
//---
int      handle_iCustom;                        // variable for storing the handle of the iCustom indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()

Найдём в файле 'Indicators Code.mq5' в OnInit() блок создания хендла пользовательского индикатора и вставим в советник:  

int      handle_iCustom;                        // variable for storing the handle of the iCustom indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create handle of the indicator iCustom
   handle_iCustom=iCustom(m_symbol.Name(),Inp_DEMA_period,"Examples\\DEMA",
                          Inp_DEMA_ma_period,
                          Inp_DEMA_ma_shift,
                          Inp_DEMA_applied_price);
//--- if the handle is not created
   if(handle_iCustom==INVALID_HANDLE)
     {
      //--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iCustom indicator for the symbol %s/%s, error code %d",
                  m_symbol.Name(),
                  EnumToString(Inp_DEMA_period),
                  GetLastError());
      //--- the indicator is stopped early
      m_init_error=true;
      return(INIT_SUCCEEDED);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |

Здесь придётся потрудиться. Нужно прописать таймфрейм ('InpWorkingPeriod'), путь к индикатору (предполагаем, что индикатор лежит в корне папки 'Indicators') и входные параметры:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create handle of the indicator iCustom
   handle_iCustom=iCustom(m_symbol.Name(),InpWorkingPeriod,"MA on DeMarker",
                          "DeMarker",
                          Inp_DeM_ma_period,
                          Inp_DeM_LevelUP,
                          Inp_DeM_LevelDOWN,
                          "MA",
                          Inp_MA_ma_period,
                          Inp_MA_ma_method);
//--- if the handle is not created
   if(handle_iCustom==INVALID_HANDLE)
     {
      //--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iCustom indicator for the symbol %s/%s, error code %d",
                  m_symbol.Name(),
                  EnumToString(InpWorkingPeriod),
                  GetLastError());
      //--- the indicator is stopped early
      m_init_error=true;
      return(INIT_SUCCEEDED);
     }
//---
   return(INIT_SUCCEEDED);
  }

5. Ловим транзакцию упрощенный код We catch the transaction

ВНИМАНИЕ: Это советник - упрощенная версия. Многие функции имеет сокращенный код по сравнению с полноценным конструктором

Если в рынке нет позиций открытых данным советником, то записываем торговый приказ на открытие позиции BUY. Подтверждение, что позиция открыта, распечатываем из OnTradeTransaction и из OnTick. Поиск и запись торгового приказа в функции 'SearchTradingSignals':

//+------------------------------------------------------------------+
//| Search trading signals                                           |
//+------------------------------------------------------------------+
bool SearchTradingSignals(void)
  {
   if(IsPositionExists())
      return(true);
//---
   int size_need_position=ArraySize(SPosition);
   if(size_need_position>0)
      return(true);
   ArrayResize(SPosition,size_need_position+1);
   SPosition[size_need_position].pos_type=POSITION_TYPE_BUY;
   if(InpPrintLog)
      Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY");
   return(true);
  }

Если в рынке нет позиций открытых советником и если размер массива 'SPosition' равен нулю, увеличиваем размер массива на единицу таким образом создаём один объект структуры 'STRUCT_POSITION', что в свою очередь вызывает конструктор 'STRUCT_POSITION()'. После вызова конструктора элементы структуры инициализированы (например, volume  объем позиции стоит в '0.0' значит объём позиции будет браться из группы входных параметров 'Position size management (lot calculation)'). Остаётся в структуру записать только тип торгового приказа  в данном случае этот приказ можно прочитать как: "Открыть позицию BUY".

После записи торгового приказа массив 'SPosition' состоит из одной структуры и элементы этой структуры имеют такие значения:

Элемент Значение Примечание 
 pos_type  POSITION_TYPE_BUY  записан в 'SearchTradingSignals'
 volume  0.0  проинициализирован в конструкторе структуры
 lot_coefficient  0.0  проинициализирован в конструкторе структуры
 waiting_transaction  false  проинициализирован в конструкторе структуры
 waiting_order_ticket  0  проинициализирован в конструкторе структуры
 transaction_confirmed  false  проинициализирован в конструкторе структуры

5.1. На новом тике попадаем в OnTick

Общий принцип действия в OnTick:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int size_need_position=ArraySize(SPosition);
   if(size_need_position>0)
     {
      for(int i=size_need_position-1; i>=0; i--)
        {
         if(SPosition[i].waiting_transaction)
           {
            if(!SPosition[i].transaction_confirmed)
              {
               if(InpPrintLog)
                  Print(__FILE__," ",__FUNCTION__,", OK: ","transaction_confirmed: ",SPosition[i].transaction_confirmed);
               return;
              }
            else
               if(SPosition[i].transaction_confirmed)
                 {
                  ArrayRemove(SPosition,i,1);
                  return;
                 }
           }
         if(SPosition[i].pos_type==POSITION_TYPE_BUY)
           {
            SPosition[i].waiting_transaction=true;
            OpenPosition(i);
            return;
           }
         if(SPosition[i].pos_type==POSITION_TYPE_SELL)
           {
            SPosition[i].waiting_transaction=true;
            OpenPosition(i);
            return;
           }
        }
     }
//--- search for trading signals only at the time of the birth of new bar
   if(!RefreshRates())
      return;
//--- search for trading signals
   if(!SearchTradingSignals())
      return;
//---
  }

В начале OnTick проверяем размер массива 'SPosition' (это массив структур 'STRUCT_POSITION'). Если размер массива больше нуля  начинаем обход к нулю, при этом доступно два сценария: 

  • если у структуры флаг 'waiting_transaction' в 'true' (что означает была выполнена подготовка торгового приказа и необходимо ждать подтверждения), проверяем флаг 'transaction_confirmed
    • если он 'false', значит, транзакция ещё не подтверждена (например, такое может быть, если торговый приказ отдан, поступил новый тик, а в OnTradeTransaction ещё нет подтверждения). Тогда мы распечатывает сообщение об этом и выходим по return  ждём нового тика в надежде, что на новом тике информация обновится
    • если он 'true', значит, транзакция подтверждена — тогда мы удаляем из массива эту структуру и выходим по return
  • если у структуры флаг 'waiting_transaction' в 'false' (что означает этот торговый приказ только-что был записан и ещё не выполнялся) взводим флаг 'waiting_transaction' и переправляем приказ в 'OpenPosition'. Заметьте, что блок кода можно было бы упростить до вида

             SPosition[i].waiting_transaction=true;
             OpenPosition(i);
             return;
    но я оставил именно так, чтобы было легче понять полную форму конструктора советников:
             if(SPosition[i].pos_type==POSITION_TYPE_BUY)
               {
                if(InpCloseOpposite)
                  {
                   if(count_sells>0)
                     {
                      ClosePositions(POSITION_TYPE_SELL);
                      return;
                     }
                  }
                if(InpOnlyOne)
                  {
                   if(count_buys+count_sells==0)
                     {
                      SPosition[i].waiting_transaction=true;
                      OpenPosition(i);
                      return;
                     }
                   else
                     {
                      ArrayRemove(SPosition,i,1);
                      return;
                     }
                  }
                SPosition[i].waiting_transaction=true;
                OpenPosition(i);
                return;
               }
             if(SPosition[i].pos_type==POSITION_TYPE_SELL)
               {
                if(InpCloseOpposite)
                  {
                   if(count_buys>0)
                     {
                      ClosePositions(POSITION_TYPE_BUY);
                      return;
                     }
                  }
                if(InpOnlyOne)
                  {
                   if(count_buys+count_sells==0)
                     {
                      SPosition[i].waiting_transaction=true;
                      OpenPosition(i);
                      return;
                     }
                   else
                     {
                      ArrayRemove(SPosition,i,1);
                      return;
                     }
                  }
                SPosition[i].waiting_transaction=true;
                OpenPosition(i);
                return;
               }

Продолжаем отслеживание, напомню блок кода:

         if(SPosition[i].pos_type==POSITION_TYPE_BUY)
           {
            SPosition[i].waiting_transaction=true;
            OpenPosition(i);
            return;
           }

Только что торговый приказ был записан в структуру (в функции 'SearchTradingSignals') и флаг 'waiting_transaction' стоит в 'false значит взводим флаг 'waiting_transaction' в 'true' и передаём в функцию 'OpenPosition' параметр 'i'  это порядковый номер структуры в массиве. 'OpenPosition'. Так как тип торгового приказа у нас 'POSITION_TYPE_BUY' передаём порядковый номер структуры в функцию 'OpenBuy'.

5.2. Функция 'OpenBuy'

Задача функции пройти предварительные проверки, отослать торговый запрос на открытие позиции BUY и отследить сразу результат.

Первая проверка - проверка SYMBOL_VOLUME_LIMIT

 SYMBOL_VOLUME_LIMIT  Максимально допустимый для данного символа совокупный объем открытой позиции и отложенных ордеров  в одном направлении (покупка или продажа). Например, при ограничении в 5 лотов можно иметь открытую позицию на покупку объемом 5 лотов и выставить отложенный ордер Sell Limit объемом 5 лотов. Но при этом нельзя выставить отложенный ордер Buy Limit (поскольку совокупный объем в одном направлении превысит ограничение) или выставить Sell Limit объемом более 5 лотов.

Если проверку не прошли, то удаляем элемент структуры из массива 'SPosition'.

Вторая проверка - маржа

Получаем размер свободной маржи, которая останется после торговой операции (FreeMarginCheck), размер маржи, необходимой для торговой операции (MarginCheck). После этого защищаемся всегда страхуемся и оставляем после операции сумму как минимум равной FreeMarginCheck:

   if(free_margin_check>margin_check)

Если проверку не прошли распечатываем переменную 'free_margin_check' и удаляем элемент структуры из массива 'SPosition'.

Проверка номер три - bool результат операции Buy

Если метод Buy возвратил результат 'false распечатываем ошибку и записываем в поле 'waiting_transaction' значение 'false' (самая распространённая причина - ошибка 10004, requote) таким образом на новом тике будет предпринята попытка снова открыть позицию BUY. Если же результат 'true' (ниже показан блок кода, когда метод Buy возвратил результат 'true')

         if(m_trade.ResultDeal()==0)
           {
            if(m_trade.ResultRetcode()==10009) // trade order went to the exchange
              {
               SPosition[index].waiting_transaction=true;
               SPosition[index].waiting_order_ticket=m_trade.ResultOrder();
              }
            else
              {
               SPosition[index].waiting_transaction=false;
               if(InpPrintLog)
                  Print(__FILE__," ",__FUNCTION__,", ERROR: ","#1 Buy -> false. Result Retcode: ",m_trade.ResultRetcode(),
                        ", description of result: ",m_trade.ResultRetcodeDescription());
              }
            if(InpPrintLog)
               PrintResultTrade(m_trade,m_symbol);
           }
         else
           {
            if(m_trade.ResultRetcode()==10009)
              {
               SPosition[index].waiting_transaction=true;
               SPosition[index].waiting_order_ticket=m_trade.ResultOrder();
              }
            else
              {
               SPosition[index].waiting_transaction=false;
               if(InpPrintLog)
                  Print(__FILE__," ",__FUNCTION__,", OK: ","#2 Buy -> true. Result Retcode: ",m_trade.ResultRetcode(),
                        ", description of result: ",m_trade.ResultRetcodeDescription());
              }
            if(InpPrintLog)
               PrintResultTrade(m_trade,m_symbol);
           }

то проверяем ResultDeal (тикет сделки).

Если тикет сделки равен нулю  проверяем ResultRetcode (код результата выполнения запроса). Получили код возврата '10009' (например торговый приказ был отослан во внешнюю торговую систему, например на Биржу и поэтому тикет сделки равен нулю) значит в поле 'waiting_transaction' записываем 'true', а в поле 'waiting_order_ticket' записываем ResultOrder (тикет ордера), иначе (код возврата не равен '10009') в поле 'waiting_transaction' записываем 'false' и распечатываем сообщение об ошибке.

Если же тикет сделки не равен нулю (например исполнение происходит на этом торговом сервере), то проводим аналогичные проверки кода возврата и аналогично записываем значения в поля 'waiting_transactionи 'waiting_order_ticket'.

5.3. OnTradeTransaction

В случае, когда торговый приказ был отослан удачно, необходимо дождаться подтверждения о том, что сделка прошла и она записана в торговую историю. В OnTradeTransaction работаем с переменной 'trans'  (структура имеющая тип MqlTradeTransaction). В структуре нас интересует только два поля — 'deal' и 'type':

struct MqlTradeTransaction
  {
   ulong                         deal;             // Тикет сделки
   ulong                         order;            // Тикет ордера
   string                        symbol;           // Имя торгового инструмента
   ENUM_TRADE_TRANSACTION_TYPE   type;             // Тип торговой транзакции
   ENUM_ORDER_TYPE               order_type;       // Тип ордера
   ENUM_ORDER_STATE              order_state;      // Состояние ордера
   ENUM_DEAL_TYPE                deal_type;        // Тип сделки
   ENUM_ORDER_TYPE_TIME          time_type;        // Тип ордера по времени действия
   datetime                      time_expiration;  // Срок истечения ордера
   double                        price;            // Цена 
   double                        price_trigger;    // Цена срабатывания стоп-лимитного ордера
   double                        price_sl;         // Уровень Stop Loss
   double                        price_tp;         // Уровень Take Profit
   double                        volume;           // Объем в лотах
   ulong                         position;         // Тикет позиции
   ulong                         position_by;      // Тикет встречной позиции
  };


Как только в OnTradeTransaction словили транзакцию TRADE_TRANSACTION_DEAL_ADD (добавление сделки в историю), осуществляем проверку: пытаемся выбрать сделку в истории через HistoryDealSelect  если выбрать сделку не удалось, печатаем ошибку, если же сделка существует в торговой истории, то начинаем в цикле обход массива 'SPosition'. В цикле смотрим только те структуры, у которых поле 'waiting_transaction' стоит в 'true' и поле 'waiting_order_ticket' равно тикету ордера выбранной нами сделки. Если обнаружено совпадение, записываем с поле 'transaction_confirmed' значение 'true' это означает, что торговый приказ выполнен и подтвержден.

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
//--- get transaction type as enumeration value
   ENUM_TRADE_TRANSACTION_TYPE type=trans.type;
//--- if transaction is result of addition of the transaction in history
   if(type==TRADE_TRANSACTION_DEAL_ADD)
     {
      ResetLastError();
      if(HistoryDealSelect(trans.deal))
         m_deal.Ticket(trans.deal);
      else
        {
         Print(__FILE__," ",__FUNCTION__,", ERROR: ","HistoryDealSelect(",trans.deal,") error: ",GetLastError());
         return;
        }
      if(m_deal.Symbol()==m_symbol.Name() && m_deal.Magic()==InpMagic)
        {
         if(m_deal.DealType()==DEAL_TYPE_BUY || m_deal.DealType()==DEAL_TYPE_SELL)
           {
            int size_need_position=ArraySize(SPosition);
            if(size_need_position>0)
              {
               for(int i=0; i<size_need_position; i++)
                 {
                  if(SPosition[i].waiting_transaction)
                     if(SPosition[i].waiting_order_ticket==m_deal.Order())
                       {
                        Print(__FUNCTION__," Transaction confirmed");
                        SPosition[i].transaction_confirmed=true;
                        break;
                       }
                 }
              }
           }
        }
     }
  }

На новом тике попадаем в OnTick и там структура, у которой стоит 'true' в поле 'transaction_confirmed', будет удалена из массива 'SPosition'. Таким образом, был выдан торговый приказ и этот торговый приказ был отслежен до момента, когда он появится в торговой истории.


6. Создаём советник (сигналы на открытие позиций) при помощи конструктора

Перед созданием любого советника нужно продумать саму торговую стратегию. Рассмотрим простую стратегию по индикатору iDEMA (Double Exponential Moving Average, DEMA) эта стратегия по умолчанию прописана в конструкторе. Сигнал на открытие позиции ищем только в момент рождения нового бара, а сам торговый сигнал это индикатор, который последовательно повышается или понижается:

DEMA Strategy

Рис. 8. DEMA Strategy

Помните: любую стратегию можно сильно видоизменить, если настраивать параметры. Например можно оставить Тейк профит и Стоп лосс, но выключить Трейлинг. Или наоборот: выключить Тейк профит и Стоп лосс, а Трейлинг оставить. Или ограничить в какую сторону торговать — разрешить только BUY или только SELL. А можно включить 'Time control' и ограничить торговлю в ночные часы или наоборот настроить торговлю только в ночные часы. Также сильно можно изменить торговую систему настраивая параметры из группы 'Additional features'.

В общем костяк торговой стратегии строится в функции 'SearchTradingSignals', а все остальные параметры это "прощупывание" рынка в поисках оптимальных подходов.

Итак, создаём новый файл заготовку советника (выполните шаги обозначенные на рисунках 3 и 4). На шаге 4 необходимо дать уникальное имя советнику пусть это будет 'iDEMA Full EA.mq5'. Получили вот такую заготовку:

//+------------------------------------------------------------------+
//|                                                iDEMA Full EA.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "1.00"
//--- input parameters
input int      Input1=9;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Теперь копируем весь код из файла 'Trading engine 3.mq5' и вставляем вместо строк. Нужно отредактировать "шапку советника". После операции вставки получилась такая "шапка":

//+------------------------------------------------------------------+
//|                                                iDEMA Full EA.mq5 |
//+------------------------------------------------------------------+
//|                                             Trading engine 3.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "4.003"
#property description "barabashkakvn Trading engine 4.003"
#property description "Take Profit, Stop Loss and Trailing - in Points (1.00055-1.00045=10 points)"
/*
   barabashkakvn Trading engine 4.003
*/
#include <Trade\PositionInfo.mqh>

Приводим "шапку" в такой вид:

//+------------------------------------------------------------------+
//|                                                iDEMA Full EA.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "1.001"
#property description "iDEMA EA"
#property description "Take Profit, Stop Loss and Trailing - in Points (1.00055-1.00045=10 points)"
/*
   barabashkakvn Trading engine 4.003
*/
#include <Trade\PositionInfo.mqh>

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

6.1. Функция 'SearchTradingSignals'

Это самая главная функция, которая отвечает за проверку наличия торговых приказов. Рассмотрим эту функцию по блокам.

Не более одной позиции на баре:

   if(iTime(m_symbol.Name(),InpWorkingPeriod,0)==m_last_deal_in) // on one bar - only one deal
      return(true);

Проверка торгового временного диапазона:

   if(!TimeControlHourMinute())
      return(true);

Получение данных с индикатора. Данные с индикатора получаем в массив 'dema', которому при помощи ArraySetAsSeries устанавливается обратный порядок индексации (элемент массива [0] будет соответствовать самому правому бару на графике). Данные получаем через пользовательскую функцию 'iGetArray':

   double dema[];
   ArraySetAsSeries(dema,true);
   int start_pos=0,count=6;
   if(!iGetArray(handle_iCustom,0,start_pos,count,dema))
     {
      return(false);
     }
   int size_need_position=ArraySize(SPosition);
   if(size_need_position>0)
      return(true);

Сигнал на открытие позиции BUY. Если нужно (переменная 'InpReverse' хранит значение входного параметра 'Positions: Reverse') то торговый сигнал будет перевернут. Если есть ограничение в какую сторону торговать (переменная 'InpTradeMode' хранит значение входного параметра 'Trade mode:'), то это ограничение будет учтено:

//--- BUY Signal
   if(dema[m_bar_current]>dema[m_bar_current+1] && dema[m_bar_current+1]>dema[m_bar_current+3])
     {
      if(!InpReverse)
        {
         if(InpTradeMode!=sell)
           {
            ArrayResize(SPosition,size_need_position+1);
            SPosition[size_need_position].pos_type=POSITION_TYPE_BUY;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY");
            return(true);
           }
        }
      else
        {
         if(InpTradeMode!=buy)
           {
            ArrayResize(SPosition,size_need_position+1);
            SPosition[size_need_position].pos_type=POSITION_TYPE_SELL;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL");
            return(true);
           }
        }
     }

Блок кода для сигнала SELL аналогичен. 


7. Создаём советник (сигналы на размещение отложенного ордера) при помощи конструктора

Имя советника будет 'iDEMA Full EA Pending.mq5' для этого откройте советник 'iDEMA Full EA.mq5' и сохраните его под новым именем.

Сначала всегда разрабатывается торговая стратегия и только потом, под эту стратегию, реализуется код. Немного изменим стратегию, которая использовалась в главе 6. Создаём советник (сигналы на открытие позиций) при помощи конструктора  вместо сигнала на открытие позиции BUY будет сигнал на размещение отложенного ордера Buy stop, а вместо сигнала на открытие позиции SELL отложенный ордер Sell stop. Для отложенных ордеров будут использоваться такие параметры:

  • Pending: Expiration, in minutes ('0' -> OFF)  время жизни отложенного ордера ('0' означает, что параметр выключен) -> 600
  • Pending: Indent  отступ отложенного ордера от текущей цены (используется в случае, когда цена отложенного ордера не задаётся явно) -> 50
  • Pending: Maximum spread ('0' -> OFF)  максимальный спред ( '0' означает, что параметр выключен). Если текущий спред больше заданного, то отложенный ордер не выставляется (советник ждёт кога спред уменьшится) -> 12
  • Pending: Only one pending  флаг, вкл/выкл, в рынке разрешён только один отложенный ордер -> true
  • Pending: Reverse pending type   флаг, вкл/выкл, реверс отложенного ордера -> false
  • Pending: New pending -> delete previous ones  если есть приказ на выставление отложенного ордера, предварительно удаляются все остальные отложенные ордера -> true

Функция 'SearchTradingSignals' примет такой вид:

//+------------------------------------------------------------------+
//| Search trading signals                                           |
//+------------------------------------------------------------------+
bool SearchTradingSignals(void)
  {
   if(iTime(m_symbol.Name(),InpWorkingPeriod,0)==m_last_deal_in) // on one bar - only one deal
      return(true);
   if(!TimeControlHourMinute())
      return(true);
   double dema[];
   ArraySetAsSeries(dema,true);
   int start_pos=0,count=6;
   if(!iGetArray(handle_iCustom,0,start_pos,count,dema))
     {
      return(false);
     }
   int size_need_pending=ArraySize(SPending);
   if(size_need_pending>0)
      return(true);
//---
   if(InpPendingOnlyOne)
      if(IsPendingOrdersExists())
         return(true);
   if(InpPendingClosePrevious)
      m_need_delete_all=true;
//--- BUY Signal
   if(dema[m_bar_current]>dema[m_bar_current+1] && dema[m_bar_current+1]>dema[m_bar_current+3])
     {
      if(!InpReverse)
        {
         if(InpTradeMode!=sell)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP");
            return(true);
           }
        }
      else
        {
         if(InpTradeMode!=buy)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL STOP");
            return(true);
           }
        }
     }
//--- SELL Signal
   if(dema[m_bar_current]<dema[m_bar_current+1] && dema[m_bar_current+1]<dema[m_bar_current+3])
     {
      if(!InpReverse)
        {
         if(InpTradeMode!=buy)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL STOP");
            return(true);
           }
        }
      else
        {
         if(InpTradeMode!=sell)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP");
            return(true);
           }
        }
     }
//---
   /*if(InpPendingOnlyOne)
      if(IsPendingOrdersExists())
         return(true);
   if(InpPendingClosePrevious)
      m_need_delete_all=true;
   int size_need_pending=ArraySize(SPending);
   ArrayResize(SPending,size_need_pending+1);
   if(!InpPendingReverse)
      SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP;
   else
      SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP;
   SPending[size_need_pending].indent=m_pending_indent;
   if(InpPendingExpiration>0)
      SPending[size_need_pending].expiration=(long)(InpPendingExpiration*60);
   if(InpPrintLog)
      Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP");*/
//---
   return(true);
  }

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

Мы получили торговый сигнал, но он сработал (отложенный ордер был размещён) только тогда, когда спред стал ниже заданного:

iDEMA Full EA Pending

Рис. 9. iDEMA Full EA Pending


Файлы, прикрепленные к статье:

Название Тип файла Описание
Indicators Code Советник Содержит переменные для хранения хендлов, входные параметры индикаторов, блоки создания индикаторов
Add indicator.mq5 Советник Пример работы с файлом 'Add indicator.mq5' - добавляем стандартный индикатор
Add custom indicator.mq5 Советник
Пример добавления пользовательского индикатора
Trading engine 4.mq5 Советник Конструктор
iDEMA Full EA.mq5
Советник
Советник созданный при помощи конструктора - сигналы на открытие позиций
iDEMA Full EA Pending.mq5
Советник
Советник созданный при помощи конструктора - сигналы на размещение отложенных ордеров

Заключение

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


Прикрепленные файлы |
Indicators_Code.mq5 (88.76 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (7)
Daniil Kurmyshev

Добрый день, спасибо за статью, но есть вопрос...)

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

Vladimir Karputov
Daniil Kurmyshev #:

Добрый день, спасибо за статью, но есть вопрос...)

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

А Вы наверное сами пробовали дополнять CExpert и у Вас даже получилось?

Daniil Kurmyshev
Vladimir Karputov #:

А Вы наверное сами пробовали дополнять CExpert и у Вас даже получилось?

Да, вот пример как можно с помощью наследования от базового класса CExpert, в новом классе CExpertCustom :

1. Реализована возможность дополнять позиции новыми сделками (мультивход)

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

2. Реализована возможность изменить базовый символ эксперта, что дает возможность создавать мультивалютных экспертов

Итого: 

Стандартный класс CExpert имеет высокую скорость работы

Максимально надежный и проверенный

Его легко дополнять и сопровождать

Есть возможность создавать мультивалютного эксперта в одном боте, если унаследоваться от нового класса CExpertCustom

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

Конструктор советников при этом, можно использовать по шаблону из терминала с необходимыми индикаторами и ММ... 

Vladimir Karputov
Daniil Kurmyshev #:

Да, вот пример как можно с помощью наследования от базового класса CExpert, в новом классе CExpertCustom :

1. Реализована возможность дополнять позиции новыми сделками (мультивход)

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

2. Реализована возможность изменить базовый символ эксперта, что дает возможность создавать мультивалютных экспертов

Итого: 

Стандартный класс CExpert имеет высокую скорость работы

Максимально надежный и проверенный

Его легко дополнять и сопровождать

Есть возможность создавать мультивалютного эксперта в одном боте, если унаследоваться от нового класса CExpertCustom

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

Конструктор советников при этом, можно использовать по шаблону из терминала с необходимыми индикаторами и ММ... 

CExpert и всё сгенерированное им уже давно не проходит проверки. Поэтому и был сделан свой набор торговых функций: с множеством проверок. Показанный Вами класс не имеет и доли функционала моего подхода. 

Daniil Kurmyshev
Vladimir Karputov #:

CExpert и всё сгенерированное им уже давно не проходит проверки. Поэтому и был сделан свой набор торговых функций: с множеством проверок. Показанный Вами класс не имеет и доли функционала моего подхода. 

Стандартные библиотек, которые вы использовали: 

#include <Trade\PositionInfo.mqh>

#include <Trade\Trade.mqh>

#include <Trade\SymbolInfo.mqh>

#include <Trade\AccountInfo.mqh>

#include <Trade\DealInfo.mqh>

#include <Trade\OrderInfo.mqh>

#include <Expert\Money\MoneyFixedMargin.mqh> 

В вашем функциональном файле "Trading engine 4" используются точно такие же библиотеки, что в классе CExpert, просто обработку вы писали самостоятельно для класса Trade, а проверки и о подобная механика, которую вы разработали уже реализована в классах CExpertBase, CExpertTrade и других..., про это говорю речь, по этому и говорю про создание очередного велосипеда, ведь можно было переработать класс CExpert, чтобы он прошел проверку, если у вас по какой-то причине не прошел, сделав это 1 раз, больше такой проблемы бы не возникло.

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

Графика в библиотеке DoEasy (Часть 83): Класс абстрактного стандартного графического объекта Графика в библиотеке DoEasy (Часть 83): Класс абстрактного стандартного графического объекта
В статье создадим класс абстрактного графического объекта. Этот объект будет основой для создания классов стандартных графических объектов. Свойств у графических объектов много, и сегодня, прежде чем создать класс абстрактного графического объекта, нам необходимо будет сделать объёмную подготовительную работу — прописать эти свойства в перечислениях библиотеки.
Графика в библиотеке DoEasy (Часть 82): Рефакторинг объектов библиотеки и коллекция графических объектов Графика в библиотеке DoEasy (Часть 82): Рефакторинг объектов библиотеки и коллекция графических объектов
В статье доработаем все объекты библиотеки - присвоим каждому объекту его уникальный тип и продолжим разработку класса-коллекции графических объектов библиотеки.
Стать хорошим программистом (Часть 2): избавляемся еще от пяти привычек на пути к лучшему программированию на MQL5 Стать хорошим программистом (Часть 2): избавляемся еще от пяти привычек на пути к лучшему программированию на MQL5
Статья обязательна к прочтению для всех, кто хочет улучшить свою карьеру программиста. Цель этой серии статей — помочь любому читателю, даже опытному, улучшить навыки программирования. Описанные в статье идеи работают как для начинающих MQL5-программистов, так и для профессионалов.
Стать хорошим программистом (Часть 1): избавляемся от пяти привычек, чтобы лучше программировать на MQL5 Стать хорошим программистом (Часть 1): избавляемся от пяти привычек, чтобы лучше программировать на MQL5
У начинающих и даже у продвинутых программистов есть различные вредные привычки, которые мешают им стать лучше. В этой статье мы обсудим их и посмотрим, что с ними можно сделать. Статья предназначена для всех, кто хочет стать успешным MQL5-программистом.