preview
DoEasy. Сервисные функции (Часть 3): Паттерн "Внешний бар"

DoEasy. Сервисные функции (Часть 3): Паттерн "Внешний бар"

MetaTrader 5Примеры | 26 сентября 2024, 13:26
550 0
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

Продолжаем раздел библиотеки DoEasy по работе с ценовыми паттернами.

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

Но есть и отличия. Если внутренний бар является двунаправленным паттерном, и вход может осуществляться по любой из сторон паттерна, то паттерн "Внешний Бар" делится на два направления — Бычий и Медвежий:

  • BUOVB (Bullish Outside Vertical Bar) – бычий внешний вертикальный бар. Сигнальный бар полностью перекрывает предыдущий, цена его закрытия выше максимума предыдущего бара. Вход осуществляется при пробое максимума сигнального бара+фильтр (5-10 пунктов).
  • BEOVB (Bearish Outside Vertical Bar) – медвежий внешний вертикальный бар. Сигнальный бар полностью перекрывает предыдущий, цена его закрытия ниже минимума предыдущего бара. Вход осуществляется при пробое минимума сигнального бара + фильтр (5-10 пунктов).

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

И во-вторых, за длительное время, пока не было работы над библиотекой, в языке MQL5 произошли некоторые изменения и добавления (не все анонсированные изменения ещё добавлены в язык), и мы эти изменения добавим в библиотеку. Также была проведена работа над найденными ошибками за всё время тестирования библиотеки — опишем все проведённые доработки.



Доработка классов библиотеки

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

В файле библиотеки \MQL5\Include\DoEasy\Defines.mqh в перечисление вещественных свойств паттерна добавим новые свойства, а общее количество вещественных свойств увеличим с 10 до 12:

//+------------------------------------------------------------------+
//| Вещественные свойства паттерна                                   |
//+------------------------------------------------------------------+
enum ENUM_PATTERN_PROP_DOUBLE
  {
//--- данные бара
   PATTERN_PROP_BAR_PRICE_OPEN = PATTERN_PROP_INTEGER_TOTAL,// Цена Open определяющего бара паттерна
   PATTERN_PROP_BAR_PRICE_HIGH,                             // Цена High определяющего бара паттерна
   PATTERN_PROP_BAR_PRICE_LOW,                              // Цена Low определяющего бара паттерна
   PATTERN_PROP_BAR_PRICE_CLOSE,                            // Цена Close определяющего бара паттерна
   PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE,                  // Отношение тела свечи к полному размеру свечи в %
   PATTERN_PROP_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE,          // Отношение размера верхней тени к размеру свечи в %
   PATTERN_PROP_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE,          // Отношение размера нижней тени к размеру свечи в %
   PATTERN_PROP_RATIO_CANDLE_SIZES,                         // Отношение размеров свечей паттерна
   
   PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION,           // Установленный критерий отношения тела свечи к полному размеру свечи в %
   PATTERN_PROP_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRITERION,  // Установленный критерий отношения размера наибольшей тени к размеру свечи в %
   PATTERN_PROP_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRITERION, // Установленный критерий отношения размера наименьшей тени к размеру свечи в %
   PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION,               // Установленный критерий отношения размеров свечей паттерна
  }; 
#define PATTERN_PROP_DOUBLE_TOTAL   (12)                    // Общее количество вещественных свойств паттерна
#define PATTERN_PROP_DOUBLE_SKIP    (0)                     // Количество неиспользуемых в сортировке свойств паттерна

Начиная с бета-версии 4540 клиентского терминала MetaTrader 5 в перечисление ENUM_SYMBOL_SWAP_MODE добавлено значение SYMBOL_SWAP_MODE_CURRENCY_PROFIT.

Если функция SymbolInfoInteger()возвращает такое значение, значит свопы на счете начисляются в деньгах в валюте расчета прибыли.

Добавим это значение в файлы библиотеки. В файле \MQL5\Include\DoEasy\Data.mqh впишем индексы новых сообщений библиотеки:

   MSG_SYM_SWAP_MODE_CURRENCY_MARGIN,                 // Свопы начисляются в деньгах в маржинальной валюте символа
   MSG_SYM_SWAP_MODE_CURRENCY_DEPOSIT,                // Свопы начисляются в деньгах в валюте депозита клиента
   MSG_SYM_SWAP_MODE_CURRENCY_PROFIT,                 // Свопы начисляются в деньгах в валюте расчета прибыли
   MSG_SYM_SWAP_MODE_INTEREST_CURRENT,                // Свопы начисляются в годовых процентах от цены инструмента на момент расчета свопа
   MSG_SYM_SWAP_MODE_INTEREST_OPEN,                   // Свопы начисляются в годовых процентах от цены открытия позиции по символу

...

   MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE,          // Отношение тела свечи к полному размеру свечи в %
   MSG_LIB_TEXT_PATTERN_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE,  // Отношение размера верхней тени к размеру свечи в %
   MSG_LIB_TEXT_PATTERN_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE,  // Отношение размера нижней тени к размеру свечи в %
   MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES,                 // Отношение размеров свечей паттерна
   
   MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE_CRIT,           // Установленный критерий отношения тела свечи к полному размеру свечи в %
   MSG_LIB_TEXT_PATTERN_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRIT,  // Установленный критерий отношения размера наибольшей тени к размеру свечи в %
   MSG_LIB_TEXT_PATTERN_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRIT, // Установленный критерий отношения размера наименьшей тени к размеру свечи в %
   MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES_CRITERION,             // Установленный критерий отношения размеров свечей паттерна
   
   MSG_LIB_TEXT_PATTERN_NAME,                         // Наименование

и тестовые сообщения, соответствующие вновь добавленным индексам:

   {"Свопы начисляются в деньгах в маржинальной валюте символа","Swaps are charged in money in margin currency of the symbol"},
   {"Свопы начисляются в деньгах в валюте депозита клиента","Swaps are charged in money, in client deposit currency"},
   {"Свопы начисляются в деньгах в валюте расчета прибыли","Swaps are charged in money, in profit calculation currency"},
   {
    "Свопы начисляются в годовых процентах от цены инструмента на момент расчета свопа",
    "Swaps are charged as the specified annual interest from the instrument price at calculation of swap"
   },
   {"Свопы начисляются в годовых процентах от цены открытия позиции по символу","Swaps are charged as the specified annual interest from the open price of position"},

...

   {"Отношение размера верхней тени к размеру свечи в %","Ratio of the size of the upper shadow to the size of the candle in %"},
   {"Отношение размера нижней тени к размеру свечи в %","Ratio of the size of the lower shadow to the size of the candle in %"},
   {"Отношение размеров свечей паттерна","Ratio of pattern candle sizes"},
   
   {"Установленный критерий отношения тела свечи к полному размеру свечи в %","Criterion for the Ratio of candle body to full candle size in %"},
   {"Установленный критерий отношения размера наибольшей тени к размеру свечи в %","Criterion for the Ratio of the size of the larger shadow to the size of the candle in %"},
   {"Установленный критерий отношения размера наименьшей тени к размеру свечи в %","Criterion for the Ratio of the size of the smaller shadow to the size of the candle in %"},
   {"Установленный критерий отношения размеров свечей паттерна","Criterion for the Ratio of pattern candle sizes"},
   
   {"Наименование","Name"},

В массив сообщений ошибок времени выполнения добавим описание двух новых ошибок времени выполнения с кодами 4306 и 4307:

//+------------------------------------------------------------------+
//| Массив сообщений ошибок времени выполнения  (4301 - 4307)        |
//| (MarketInfo)                                                     |
//| (1) на языке страны пользователя                                 |
//| (2) на международном языке                                       |
//+------------------------------------------------------------------+
string messages_runtime_market[][TOTAL_LANG]=
  {
   {"Неизвестный символ","Unknown symbol"},                                                                                                        // 4301
   {"Символ не выбран в MarketWatch","Symbol is not selected in MarketWatch"},                                                                     // 4302
   {"Ошибочный идентификатор свойства символа","Wrong identifier of a symbol property"},                                                           // 4303
   {"Время последнего тика неизвестно (тиков не было)","Time of the last tick is not known (no ticks)"},                                           // 4304
   {"Ошибка добавления или удаления символа в MarketWatch","Error adding or deleting a symbol in MarketWatch"},                                    // 4305
   {"Превышен лимит выбранных символов в MarketWatch","Exceeded the limit of selected symbols in MarketWatch"},                                    // 4306
   {
    "Неправильный индекс сессии при вызове функции SymbolInfoSessionQuote/SymbolInfoSessionTrade",                                                 // 4307
    "Wrong session ID when calling the SymbolInfoSessionQuote/SymbolInfoSessionTrade function"    
   },                                                                                             
  };

В файле \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh в метод, возвращающий количество знаков после запятой в зависимости от метода расчёта свопа, впишем обработку нового значения перечисления:

//+------------------------------------------------------------------+
//| Возвращает количество знаков после запятой                       |
//| в зависимости от метода расчёта свопа                            |
//+------------------------------------------------------------------+
int CSymbol::SymbolDigitsBySwap(void)
  {
   return
     (
      this.SwapMode()==SYMBOL_SWAP_MODE_POINTS           || 
      this.SwapMode()==SYMBOL_SWAP_MODE_REOPEN_CURRENT   || 
      this.SwapMode()==SYMBOL_SWAP_MODE_REOPEN_BID       ?  this.Digits() :
      this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_SYMBOL  || 
      this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_MARGIN  || 
      this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_PROFIT  || 
      this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT ?  this.DigitsCurrency():
      this.SwapMode()==SYMBOL_SWAP_MODE_INTEREST_CURRENT || 
      this.SwapMode()==SYMBOL_SWAP_MODE_INTEREST_OPEN    ?  1  :  0
     );
  }

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

//+------------------------------------------------------------------+
//| Возвращает описание модели расчета свопа                         |
//+------------------------------------------------------------------+
string CSymbol::GetSwapModeDescription(void) const
  {
   return
     (
      this.SwapMode()==SYMBOL_SWAP_MODE_DISABLED         ?  CMessage::Text(MSG_SYM_SWAP_MODE_DISABLED)         :
      this.SwapMode()==SYMBOL_SWAP_MODE_POINTS           ?  CMessage::Text(MSG_SYM_SWAP_MODE_POINTS)           :
      this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_SYMBOL  ?  CMessage::Text(MSG_SYM_SWAP_MODE_CURRENCY_SYMBOL)  :
      this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_MARGIN  ?  CMessage::Text(MSG_SYM_SWAP_MODE_CURRENCY_MARGIN)  :
      this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT ?  CMessage::Text(MSG_SYM_SWAP_MODE_CURRENCY_DEPOSIT) :
      this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_PROFIT  ?  CMessage::Text(MSG_SYM_SWAP_MODE_CURRENCY_PROFIT)  :
      this.SwapMode()==SYMBOL_SWAP_MODE_INTEREST_CURRENT ?  CMessage::Text(MSG_SYM_SWAP_MODE_INTEREST_CURRENT) :
      this.SwapMode()==SYMBOL_SWAP_MODE_INTEREST_OPEN    ?  CMessage::Text(MSG_SYM_SWAP_MODE_INTEREST_OPEN)    :
      this.SwapMode()==SYMBOL_SWAP_MODE_REOPEN_CURRENT   ?  CMessage::Text(MSG_SYM_SWAP_MODE_REOPEN_CURRENT)   :
      this.SwapMode()==SYMBOL_SWAP_MODE_REOPEN_BID       ?  CMessage::Text(MSG_SYM_SWAP_MODE_REOPEN_BID)       :
      CMessage::Text(MSG_SYM_MODE_UNKNOWN)
     );
  }

В файле определений для языка MQL4 \MQL5\Include\DoEasy\ToMQL4.mqh в перечисление способов начисления свопов при переносе позиции добавим новое свойство:

//+------------------------------------------------------------------+
//| Способы начисления свопов при переносе позиции                   |
//+------------------------------------------------------------------+
enum ENUM_SYMBOL_SWAP_MODE
  {
   SYMBOL_SWAP_MODE_POINTS,                        // (MQL5 - 1, MQL4 - 0) Свопы начисляются в пунктах
   SYMBOL_SWAP_MODE_CURRENCY_SYMBOL,               // (MQL5 - 2, MQL4 - 1) Свопы начисляются в деньгах в базовой валюте символа
   SYMBOL_SWAP_MODE_INTEREST_OPEN,                 // (MQL5 - 6, MQL4 - 2) Свопы начисляются в годовых процентах от цены открытия позиции по символу
   SYMBOL_SWAP_MODE_CURRENCY_MARGIN,               // (MQL5 - 3, MQL4 - 3) Свопы начисляются в деньгах в маржинальной валюте символа
   SYMBOL_SWAP_MODE_DISABLED,                      // (MQL5 - 0, MQL4 - N) Нет свопов
   SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT,              // Свопы начисляются в деньгах в валюте депозита клиента
   SYMBOL_SWAP_MODE_INTEREST_CURRENT,              // Свопы начисляются в годовых процентах от цены инструмента на момент расчета свопа
   SYMBOL_SWAP_MODE_REOPEN_CURRENT,                // Свопы начисляются переоткрытием позиции по цене закрытия
   SYMBOL_SWAP_MODE_REOPEN_BID,                    // Свопы начисляются переоткрытием позиции по текущей цене Bid
   SYMBOL_SWAP_MODE_CURRENCY_PROFIT                // Свопы начисляются в деньгах в валюте расчета прибыли
  };

В файле класса базового объекта библиотеки \MQL5\Include\DoEasy\Objects\BaseObj.mqh исправим строки с потенциальными утечками памяти:

//+------------------------------------------------------------------+
//| Добавляет событие объекта в список                               |
//+------------------------------------------------------------------+
bool CBaseObjExt::EventAdd(const ushort event_id,const long lparam,const double dparam,const string sparam)
  {
   CEventBaseObj *event=new CEventBaseObj(event_id,lparam,dparam,sparam);
   if(event==NULL)
      return false;
   this.m_list_events.Sort();
   if(this.m_list_events.Search(event)>WRONG_VALUE)
     {
      delete event;
      return false;
     }
   return this.m_list_events.Add(event);
  }
//+------------------------------------------------------------------+
//| Добавляет базовое событие объекта в список                       |
//+------------------------------------------------------------------+
bool CBaseObjExt::EventBaseAdd(const int event_id,const ENUM_BASE_EVENT_REASON reason,const double value)
  {
   CBaseEvent* event=new CBaseEvent(event_id,reason,value);
   if(event==NULL)
      return false;
   this.m_list_events_base.Sort();
   if(this.m_list_events_base.Search(event)>WRONG_VALUE)
     {
      delete event;
      return false;
     }
   return this.m_list_events_base.Add(event);
  }

Здесь методы возвращают результат добавления объекта в список (true при удачном добавлении, и false — при ошибке). Если произойдёт ошибка добавления созданного посредством оператора newобъекта, то этот объект останется где-то в памяти без сохранения на него указателя в списке, что приведёт к утечкам памяти. Исправим:

//+------------------------------------------------------------------+
//| Добавляет событие объекта в список                               |
//+------------------------------------------------------------------+
bool CBaseObjExt::EventAdd(const ushort event_id,const long lparam,const double dparam,const string sparam)
  {
   CEventBaseObj *event=new CEventBaseObj(event_id,lparam,dparam,sparam);
   if(event==NULL)
      return false;
   this.m_list_events.Sort();
   if(this.m_list_events.Search(event)>WRONG_VALUE || !this.m_list_events.Add(event))
     {
      delete event;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Добавляет базовое событие объекта в список                       |
//+------------------------------------------------------------------+
bool CBaseObjExt::EventBaseAdd(const int event_id,const ENUM_BASE_EVENT_REASON reason,const double value)
  {
   CBaseEvent* event=new CBaseEvent(event_id,reason,value);
   if(event==NULL)
      return false;
   this.m_list_events_base.Sort();
   if(this.m_list_events_base.Search(event)>WRONG_VALUE || !this.m_list_events_base.Add(event))
     {
      delete event;
      return false;
     }
   return true;
  }

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

В файле \MT5\MQL5\Include\DoEasy\Objects\Orders\Order.mqh при подсчёте количества пунктов необходимо добавить округление полученного результата, иначе, при значениях меньше 1, но близких к единице, получаем нулевое количество пунктов при приведении вещественного числа к целочисленному:

//+------------------------------------------------------------------+
//| Возвращает профит ордера в пунктах                               |
//+------------------------------------------------------------------+
int COrder::ProfitInPoints(void) const
  {
   MqlTick tick={0};
   string symbol=this.Symbol();
   if(!::SymbolInfoTick(symbol,tick))
      return 0;
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)this.TypeOrder();
   double point=::SymbolInfoDouble(symbol,SYMBOL_POINT);
   if(type==ORDER_TYPE_CLOSE_BY || point==0) return 0;
   if(this.Status()==ORDER_STATUS_HISTORY_ORDER)
      return int(type==ORDER_TYPE_BUY ? (::round(this.PriceClose()-this.PriceOpen())/point) : type==ORDER_TYPE_SELL ? ::round((this.PriceOpen()-this.PriceClose())/point) : 0);
   else if(this.Status()==ORDER_STATUS_MARKET_POSITION)
     {
      if(type==ORDER_TYPE_BUY)
         return (int)::round((tick.bid-this.PriceOpen())/point);
      else if(type==ORDER_TYPE_SELL)
         return (int)::round((this.PriceOpen()-tick.ask)/point);
     }
   else if(this.Status()==ORDER_STATUS_MARKET_PENDING)
     {
      if(type==ORDER_TYPE_BUY_LIMIT || type==ORDER_TYPE_BUY_STOP || type==ORDER_TYPE_BUY_STOP_LIMIT)
         return (int)fabs(::round((tick.bid-this.PriceOpen())/point));
      else if(type==ORDER_TYPE_SELL_LIMIT || type==ORDER_TYPE_SELL_STOP || type==ORDER_TYPE_SELL_STOP_LIMIT)
         return (int)fabs(::round((this.PriceOpen()-tick.ask)/point));
     }
   return 0;
  }

В файле \MQL5\Include\DoEasy\Objects\Events\Event.mqh изначально была допущена ошибка при возврате long-значения как значения перечисления:

//--- При смене направления позиции возвращает (1) тип ордера предыдущей позиции, (2) тикет ордера предыдущей позиции
//--- (3) тип ордера текущей позиции, (4) тикет ордера текущей позиции
//--- (5) тип и (6) тикет позиции до смены направления, (7) тип и (8) тикет позиции после смены направления
   ENUM_ORDER_TYPE   TypeOrderPosPrevious(void)                         const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE);   }
   long              TicketOrderPosPrevious(void)                       const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE); }
   ENUM_ORDER_TYPE   TypeOrderPosCurrent(void)                          const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT);  }
   long              TicketOrderPosCurrent(void)                        const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT);}
   ENUM_POSITION_TYPE TypePositionPrevious(void)                        const { return PositionTypeByOrderType(this.TypeOrderPosPrevious());                }
   ulong             TicketPositionPrevious(void)                       const { return this.TicketOrderPosPrevious();                                       }
   ENUM_POSITION_TYPE TypePositionCurrent(void)                         const { return PositionTypeByOrderType(this.TypeOrderPosCurrent());                 }
   ulong             TicketPositionCurrent(void)                        const { return this.TicketOrderPosCurrent();                                        }

Пока значение тикета ордера, имеющего тип long, не превышало значения INT_MAX (тип данных для перечисления — int), тикеты ордеров возвращались корректно. Но стоило значению тикета превысить INT_MAX, как происходило переполнение, и возвращалось отрицательное число. Теперь всё исправлено:

//--- При смене направления позиции возвращает (1) тип ордера предыдущей позиции, (2) тикет ордера предыдущей позиции
//--- (3) тип ордера текущей позиции, (4) тикет ордера текущей позиции
//--- (5) тип и (6) тикет позиции до смены направления, (7) тип и (8) тикет позиции после смены направления
   ENUM_ORDER_TYPE   TypeOrderPosPrevious(void)                         const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE);   }
   long              TicketOrderPosPrevious(void)                       const { return this.GetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE);                  }
   ENUM_ORDER_TYPE   TypeOrderPosCurrent(void)                          const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT);  }
   long              TicketOrderPosCurrent(void)                        const { return this.GetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT);                 }
   ENUM_POSITION_TYPE TypePositionPrevious(void)                        const { return PositionTypeByOrderType(this.TypeOrderPosPrevious());                }
   ulong             TicketPositionPrevious(void)                       const { return this.TicketOrderPosPrevious();                                       }
   ENUM_POSITION_TYPE TypePositionCurrent(void)                         const { return PositionTypeByOrderType(this.TypeOrderPosCurrent());                 }
   ulong             TicketPositionCurrent(void)                        const { return this.TicketOrderPosCurrent();                                        }

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

//+------------------------------------------------------------------+
//| Возвращает наименование статуса события                          |
//+------------------------------------------------------------------+
string CEvent::StatusDescription(void) const
  {
   ENUM_EVENT_STATUS status=(ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT);
   return
     (
      status==EVENT_STATUS_MARKET_PENDING    ?  CMessage::Text(MSG_EVN_STATUS_MARKET_PENDING)   :
      status==EVENT_STATUS_MARKET_POSITION   ?  CMessage::Text(MSG_EVN_STATUS_MARKET_POSITION)  :
      status==EVENT_STATUS_HISTORY_PENDING   ?  CMessage::Text(MSG_EVN_STATUS_HISTORY_PENDING)  :
      status==EVENT_STATUS_HISTORY_POSITION  ?  CMessage::Text(MSG_EVN_STATUS_HISTORY_POSITION) :
      status==EVENT_STATUS_BALANCE           ?  CMessage::Text(MSG_LIB_PROP_BALANCE)            :
      status==EVENT_STATUS_MODIFY            ?  CMessage::Text(MSG_EVN_REASON_MODIFY)           :
      CMessage::Text(MSG_EVN_STATUS_UNKNOWN)
     );
  }

В классе торгового объекта поправим одно упущение: политика заполнения объёма (из перечисления ENUM_ORDER_TYPE_FILLING) неправильно передавалась в метод открытия позиции.

Внесём в торговые методы в файле MQL5\Include\DoEasy\Objects\Trade\TradeObj.mqh необходимые доработки. В методе открытия позиции в блоке заполнения структуры торгового запроса добавим установку политики заполнения объёма:

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

В данном классе значение политики заполнения устанавливается в переменную m_type_filling изначально при инициализации библиотеки разрешёнными для ордеров значениями (метод CEngine::TradingSetCorrectTypeFilling). Если в метод открытия передать отрицательное значение для политики заполнения, то будет использовано значение из переменной m_type_filling, установленное при инициализации библиотеки. Если же необходимо указать иной тип заполнения объёма, то его нужно передать в параметре метода type_filling, и будет использоваться именно переданное значение.

Ранее добавленной строки не было, и политика заполнения, если нужно было указать иную политику, не по умолчанию, всегда имела значение Return (ORDER_FILLING_RETURN), так как поле type_filling структуры MqlTradeRequest не заполнялось и всегда имело нулевое значение. Теперь это исправлено.

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

//+------------------------------------------------------------------+
//| Закрывает позицию                                                |
//+------------------------------------------------------------------+
bool CTradeObj::ClosePosition(const ulong ticket,
                              const string comment=NULL,
                              const ulong deviation=ULONG_MAX)
  {
   if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE)
      return true;
   ::ResetLastError();
   //--- Если не удалось выбрать позицию. Записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем 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;
     }
   //--- Если не удалось получить текущие цены - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем 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;
     }
   //--- Получаем тип позиции и тип ордера, обратный типу позиции
   ENUM_POSITION_TYPE position_type=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE);
   ENUM_ORDER_TYPE type=OrderTypeOppositeByPositionType(position_type);
   //--- Очищаем структуры
   ::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.type        =  type;
   this.m_request.magic       =  ::PositionGetInteger(POSITION_MAGIC);
   this.m_request.price       =  (position_type==POSITION_TYPE_SELL ? this.m_tick.ask : this.m_tick.bid);
   this.m_request.volume      =  ::PositionGetDouble(POSITION_VOLUME);
   this.m_request.deviation   =  (deviation==ULONG_MAX ? this.m_deviation : deviation);
   this.m_request.comment     =  (comment==NULL ? this.m_comment : comment);
   this.m_request.type_filling=  this.m_type_filling;
   //--- Если счёт хеджевый, то в запишем в структуру тикет закрываемой позиции
   if(this.IsHedge())
      this.m_request.position=::PositionGetInteger(POSITION_TICKET);
   //--- Возвращаем результат отсылки запроса на сервер
#ifdef __MQL5__
   return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result));
#else 
   ::SymbolInfoTick(this.m_symbol,this.m_tick);
   this.m_result.ask=this.m_tick.ask;
   this.m_result.bid=this.m_tick.bid;
   ::ResetLastError();
   if(::OrderClose((int)this.m_request.position,this.m_request.volume,this.m_request.price,(int)this.m_request.deviation,clrNONE))
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.deal=ticket;
      this.m_result.price=(::OrderSelect((int)ticket,SELECT_BY_TICKET) ? ::OrderClosePrice() : this.m_request.price);
      this.m_result.volume=(::OrderSelect((int)ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume);
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      return true;
     }
   else
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.ask=this.m_tick.ask;
      this.m_result.bid=this.m_tick.bid;
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      return false;
     }
#endif 
  }
//+------------------------------------------------------------------+
//| Частично закрывает позицию                                       |
//+------------------------------------------------------------------+
bool CTradeObj::ClosePositionPartially(const ulong ticket,
                                       const double volume,
                                       const string comment=NULL,
                                       const ulong deviation=ULONG_MAX)
  {
   if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE)
      return true;
   ::ResetLastError();
   //--- Если не удалось выбрать позицию. Записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем 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;
     }
   //--- Если не удалось получить текущие цены - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем 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;
     }
   //--- Получаем тип позиции и тип ордера, обратный типу позиции
   ENUM_POSITION_TYPE position_type=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE);
   ENUM_ORDER_TYPE type=OrderTypeOppositeByPositionType(position_type);
   //--- Получаем объём позиции
   double position_volume=::PositionGetDouble(POSITION_VOLUME);
   //--- Очищаем структуры
   ::ZeroMemory(this.m_request);
   ::ZeroMemory(this.m_result);
   //--- Заполняем структуру запроса
   this.m_request.action      =  TRADE_ACTION_DEAL;
   this.m_request.position    =  ticket;
   this.m_request.symbol      =  this.m_symbol;
   this.m_request.magic       =  ::PositionGetInteger(POSITION_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);
   this.m_request.type_filling=  this.m_type_filling;
   //--- Если счёт хеджевый, то в запишем в структуру тикет закрываемой позиции
   if(this.IsHedge())
      this.m_request.position=::PositionGetInteger(POSITION_TICKET);
   //--- Возвращаем результат отсылки запроса на сервер
#ifdef __MQL5__
   return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result));
#else 
   ::SymbolInfoTick(this.m_symbol,this.m_tick);
   this.m_result.ask=this.m_tick.ask;
   this.m_result.bid=this.m_tick.bid;
   ::ResetLastError();
   if(::OrderClose((int)this.m_request.position,this.m_request.volume,this.m_request.price,(int)this.m_request.deviation,clrNONE))
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.deal=ticket;
      this.m_result.price=(::OrderSelect((int)ticket,SELECT_BY_TICKET) ? ::OrderClosePrice() : this.m_request.price);
      this.m_result.volume=(::OrderSelect((int)ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume);
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      return true;
     }
   else
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      return false;
     }
#endif 
  }

Теперь поправим аналогичные упущения в торговом классе библиотеки в файле \MQL5\Include\DoEasy\Trading.mqh.

В методе открытия позиции OpenPosition() структура торгового запроса заполняется значениями на основании значений, переданных в метод:

//--- Записываем объём, отклонение, комментарий и тип заливки в структуру запроса
   this.m_request.volume=volume;
   this.m_request.deviation=(deviation==ULONG_MAX ? trade_obj.GetDeviation() : deviation);
   this.m_request.comment=(comment==NULL ? trade_obj.GetComment() : comment);
   this.m_request.type_filling=(type_filling>WRONG_VALUE ? type_filling : trade_obj.GetTypeFilling());

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

//--- В цикле по количеству попыток
   for(int i=0;i<this.m_total_try;i++)
     {                
      //--- Отсылаем запрос
      res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation,type_filling);
      //... ... ...

Исправим это:

      //--- Отсылаем запрос
      res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,this.m_request.comment,this.m_request.deviation,this.m_request.type_filling);

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

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

В файле \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh в методе, создающем индикатор контроля событий, добавим проверку наличия символа на сервере:

//+------------------------------------------------------------------+
//| CChartObjectsControl: Создаёт индикатор контроля событий         |
//+------------------------------------------------------------------+
bool CChartObjectsControl::CreateEventControlInd(const long chart_id_main)
  {
//--- Если симола нет на сервере - возвращаем false
   bool is_custom=false;
   if(!::SymbolExist(this.Symbol(), is_custom))
     {
      CMessage::ToLog(DFUN+" "+this.Symbol()+": ",MSG_LIB_SYS_NOT_SYMBOL_ON_SERVER);
      return false;
     }
//--- Создаём индикатор
   this.m_chart_id_main=chart_id_main;
   string name="::"+PATH_TO_EVENT_CTRL_IND;
   ::ResetLastError();
   this.m_handle_ind=::iCustom(this.Symbol(),this.Timeframe(),name,this.ChartID(),this.m_chart_id_main);
   if(this.m_handle_ind==INVALID_HANDLE)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR);
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   
   this.m_name_ind="EventSend_From#"+(string)this.ChartID()+"_To#"+(string)this.m_chart_id_main;
   ::Print
     (
      DFUN,this.Symbol()," ",TimeframeDescription(this.Timeframe()),": ",
      CMessage::Text(MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR)," \"",this.m_name_ind,"\""
     );
   return true;
  }

Теперь при отсутствии символа на сервере, будет выведено сообщение об этом в журнал, и метод вернёт false.

В файле базового графического объекта библиотеки \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh в методе, возвращающем описание типа графического элемента, добавим один пропущенный тип — графический объект "Bitmap":

//+------------------------------------------------------------------+
//| Возвращает описание типа графического элемента                   |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return
     (
      type==GRAPH_ELEMENT_TYPE_STANDARD                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD)                 :
      type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)        :
      type==GRAPH_ELEMENT_TYPE_ELEMENT                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT)                  :
      type==GRAPH_ELEMENT_TYPE_BITMAP                    ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_BITMAP)                   :
      type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ                ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ)               :
      type==GRAPH_ELEMENT_TYPE_FORM                      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM)                     :
      type==GRAPH_ELEMENT_TYPE_WINDOW                    ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW)                   :
      //--- WinForms
      type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY)              :
      type==GRAPH_ELEMENT_TYPE_WF_BASE                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE)                  :
      //--- Контейнеры
      type==GRAPH_ELEMENT_TYPE_WF_CONTAINER              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER)             :
      type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_PANEL                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL)                 :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)           :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER)       :
      //--- Стандартные элементы управления
      type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE)           :
      type==GRAPH_ELEMENT_TYPE_WF_LABEL                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL)                 :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON)           :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON                 ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON)                :
      type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX)     :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM          ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM)         :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX)      :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX)       :
      type==GRAPH_ELEMENT_TYPE_WF_TOOLTIP                ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TOOLTIP)               :
      type==GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR)          :
      //--- Вспомогательные объекты элементов управления
      type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER)            :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD)             :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON)          :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP)       :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT     ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT)    :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) :
      type==GRAPH_ELEMENT_TYPE_WF_SPLITTER               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER)              :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_BASE              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE)             :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT)        :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT)       :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP)          :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN)        :
      type==GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR)      :
      type==GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ)             :
      type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR)            :
      type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL    ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL)   :
      type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL) :
      type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_THUMB       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_THUMB)      :
      "Unknown"
     );
  }  

Классы паттернов — их организацию и структуру, тоже немного переработаем. Класс базового абстрактного паттерна находится в файле \MQL5\Include\DoEasy\Objects\Series\Patterns\Pattern.mqh, и в нём же далее пишутся классы паттернов, наследуемые от базового класса.

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

В папке библиотеки \MT5\MQL5\Include\DoEasy\Objects\Series\Patterns\ создадим два новых файла PatternPinBar.mqh и PatternInsideBar.mqh — в них разместим методом Cut and Paste (вырезать и вставить) классы паттернов "Пин-Бар" и "Внутренний Бар", написанные прямо в файле класса базового абстрактного паттерна. Далее внесём в них изменения, а пока продолжим правку класса абстрактного паттерна.

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

protected:
   CForm            *m_form;                                      // Указатель на объект-форму
   CGCnvBitmap      *m_bitmap;                                    // Указатель на объект-рисунок
   int               m_digits;                                    // Значение Digits символа
   ulong             m_symbol_code;                               // Символ в виде числа (сумма кодов символов наименования)
   string            m_name_graph_obj;                            // Имя графического объекта, отображающего паттерн
   double            m_price;                                     // Уровень цены, на который устанавливается графический объект
   color             m_color_bullish;                             // Цвет графического объекта, устанавливаемый значку бычьего паттерна
   color             m_color_bearish;                             // Цвет графического объекта, устанавливаемый значку медвежьего паттерна
   color             m_color_bidirect;                            // Цвет графического объекта, устанавливаемый значку двунаправленного паттерна
   color             m_color;                                     // Цвет графического объекта
   color             m_color_panel_bullish;                       // Цвет панели бычьего паттерна
   color             m_color_panel_bearish;                       // Цвет панели медвежьего паттерна
   color             m_color_panel_bidirect;                      // Цвет панели двунаправленного паттерна
   int               m_bars_formation;                            // Количество баров формации (вложенный паттерн)
   bool              m_draw_dots;                                 // Рисовать на графике точками
   int               m_chart_scale;                               // Масштаб графика
   int               m_chart_height_px;                           // Высота графика в пикселях
   int               m_chart_width_px;                            // Ширина графика в пикселях
   double            m_chart_price_max;                           // Максимум графика
   double            m_chart_price_min;                           // Минимум графика
   
public:

Методы, рассчитывающие ширину и высоту объекта-рисунка

//--- Рассчитывает (1) ширину, (2) высоту объекта-рисунка
   int               GetBitmapWidth(void);
   int               GetBitmapHeight(void);

переименуем в правильные названия и объявим их как виртуальные:

//--- Рассчитывает (1) ширину, (2) высоту объекта-рисунка
   virtual int       CalculatetBitmapWidth(void);
   virtual int       CalculatetBitmapHeight(void);

Всё же Get — это "Получить", а не "Рассчитать". Виртуальные методы позволят в наследуемых классах использовать собственные расчёты ширины и высоты рисунка, в зависимости от типа паттерна и способа его рисования.

В публичной секции класса удалим метод SetDrawAsDots():

public:
//--- Удаляет графический объект
   bool              DeleteGraphObj(bool redraw=false);
//--- Устанавливает цвета графического объекта и цвет отображения паттерна
   void              SetColors(const color color_bullish,const color color_bearish,const color color_bidirect,const bool redraw=false);
//--- Устанавливает флаг рисования меток паттернов как точки
   void              SetDrawAsDots(const bool flag)         { this.m_draw_dots=flag;            }
   
//--- Устанавливает цвет фона панели (1) бычьего, (2) медвежьего, (3) двунаправленного паттерна
   void              SetColorPanelBullish(const color clr)  { this.m_color_panel_bullish=clr;   }
   void              SetColorPanelBearish(const color clr)  { this.m_color_panel_bearish=clr;   }
   void              SetColorPanelBiDirect(const color clr) { this.m_color_panel_bidirect=clr;  }
//--- Устанавливает цвет фона панели (1) бычьего, (2) медвежьего, (3) двунаправленного паттерна установкой значений RGB-компонент цвета
   void              SetColorPanelBullish(const uchar R,const uchar G,const uchar B);
   void              SetColorPanelBearish(const uchar R,const uchar G,const uchar B);
   void              SetColorPanelBiDirect(const uchar R,const uchar G,const uchar B);

//--- Рисует значок паттерна на графике
   virtual void      Draw(const bool redraw);

//--- (1) Отображает, (2) скрывает значок паттерна на графике
   void              Show(const bool redraw=false);
   void              Hide(const bool redraw=false);
//--- (1) Отображает, (2) скрывает инфо-панель на графике
   void              ShowInfoPanel(const int x,const int y,const bool redraw=true);
   void              HideInfoPanel(void);

и объявим виртуальный метод Redraw():

public:
//--- Удаляет графический объект
   bool              DeleteGraphObj(bool redraw=false);
//--- Устанавливает цвета графического объекта и цвет отображения паттерна
   void              SetColors(const color color_bullish,const color color_bearish,const color color_bidirect,const bool redraw=false);

//--- Устанавливает цвет фона панели (1) бычьего, (2) медвежьего, (3) двунаправленного паттерна
   void              SetColorPanelBullish(const color clr)  { this.m_color_panel_bullish=clr;   }
   void              SetColorPanelBearish(const color clr)  { this.m_color_panel_bearish=clr;   }
   void              SetColorPanelBiDirect(const color clr) { this.m_color_panel_bidirect=clr;  }
//--- Устанавливает цвет фона панели (1) бычьего, (2) медвежьего, (3) двунаправленного паттерна установкой значений RGB-компонент цвета
   void              SetColorPanelBullish(const uchar R,const uchar G,const uchar B);
   void              SetColorPanelBearish(const uchar R,const uchar G,const uchar B);
   void              SetColorPanelBiDirect(const uchar R,const uchar G,const uchar B);

//--- (1) Рисует, (2) перерисовывает с новым размером значок паттерна на графике
   virtual bool      Draw(const bool redraw);
   virtual bool      Redraw(const bool redraw)              { return true;                      }
//--- (1) Отображает, (2) скрывает значок паттерна на графике
   void              Show(const bool redraw=false);
   void              Hide(const bool redraw=false);
//--- (1) Отображает, (2) скрывает инфо-панель на графике
   void              ShowInfoPanel(const int x,const int y,const bool redraw=true);
   void              HideInfoPanel(void);

Метод Redraw() будет перерисовывать объект-рисунок с новыми размерами. А так, как у каждого типа паттерна могут быть свои типы рисунков, то метод объявлен виртуальным, и здесь просто возвращает true. В наследуемых классах метод будет переопределяться, чтобы перерисовывать именно тот рисунок, который рисуется для данного паттерна.

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

//--- Устанавливает (1) масштаб графика, (2) высоту, (3) ширину графика в пикселях, (4) максимум, (5) минимум графика
   void              SetChartScale(const int scale)            { this.m_chart_scale=scale;      }
   void              SetChartHeightInPixels(const int height)  { this.m_chart_height_px=height; }
   void              SetChartWidthInPixels(const int width)    { this.m_chart_width_px=width;   }
   void              SetChartPriceMax(const double price)      { this.m_chart_price_max=price;  }
   void              SetChartPriceMin(const double price)      { this.m_chart_price_min=price;  }
//--- Возвращает (1) масштаб графика, (2) высоту, (3) ширину графика в пикселях, (4) максимум, (5) минимум графика
   int               ChartScale(void)                    const { return this.m_chart_scale;     }
   int               ChartHeightInPixels(void)           const { return this.m_chart_height_px; }
   int               ChartWidthInPixels(void)            const { return this.m_chart_width_px;  }
   double            ChartPriceMax(void)                 const { return this.m_chart_price_max; }
   double            ChartPriceMin(void)                 const { return this.m_chart_price_min; }

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

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

//--- Устанавливаем базовые цвета информационных панелей паттерна
   this.m_color_panel_bullish=clrLightGray;
   this.m_color_panel_bearish=clrLightGray;
   this.m_color_panel_bidirect=clrLightGray;
   this.m_form=NULL;
   this.m_bitmap=NULL;
   this.m_draw_dots=true;
   this.m_bars_formation=1;
  }

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

//+------------------------------------------------------------------+
//| Возвращает описание вещественного свойства паттерна              |
//+------------------------------------------------------------------+
string CPattern::GetPropertyDescription(ENUM_PATTERN_PROP_DOUBLE property)
  {
   int dg=(this.m_digits>0 ? this.m_digits : 1);
   return
     (
      property==PATTERN_PROP_BAR_PRICE_OPEN  ?  CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_OPEN)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==PATTERN_PROP_BAR_PRICE_HIGH  ?  CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_HIGH)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==PATTERN_PROP_BAR_PRICE_LOW   ?  CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_LOW)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==PATTERN_PROP_BAR_PRICE_CLOSE ?  CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_CLOSE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE         ?  CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),2)
         )  :
      property==PATTERN_PROP_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE ?  CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),2)
         )  :
      property==PATTERN_PROP_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE ?  CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),2)
         )  :
      property==PATTERN_PROP_RATIO_CANDLE_SIZES ?  CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),2)
         )  :
      property==PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION           ?  CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE_CRIT)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),2)
         )  :
      property==PATTERN_PROP_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRITERION  ?  CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRIT)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),2)
         )  :
      property==PATTERN_PROP_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRITERION ?  CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRIT)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),2)
         )  :
      property==PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION   ?  CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES_CRITERION)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),2)
         )  :
      ""
     );
  }

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

Удалим из метода строки получения свойств графика:

//+------------------------------------------------------------------+
//| Отображает инфо-панель на графике                                |
//+------------------------------------------------------------------+
void CPattern::ShowInfoPanel(const int x,const int y,const bool redraw=true)
  {
//--- Если объекта панели ещё нету - создаём его
   if(this.m_form==NULL)
      if(!this.CreateInfoPanel())
         return;
//--- Получаем ширину и высоту графика
   int chart_w=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS);
   int chart_h=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS);
//--- Рассчитываем координаты X и Y панели так, чтобы она не выходила за пределы графика

Теперь вместо ранее получаемых свойств графика используем эти свойства, предустановленные при создании объекта:

//+------------------------------------------------------------------+
//| Отображает инфо-панель на графике                                |
//+------------------------------------------------------------------+
void CPattern::ShowInfoPanel(const int x,const int y,const bool redraw=true)
  {
//--- Если объекта панели ещё нету - создаём его
   if(this.m_form==NULL)
      if(!this.CreateInfoPanel())
         return;
//--- Рассчитываем координаты X и Y панели так, чтобы она не выходила за пределы графика
   int cx=(x+this.m_form.Width() >this.m_chart_width_px-1 ? this.m_chart_width_px-1-this.m_form.Width()  : x);
   int cy=(y+this.m_form.Height()>this.m_chart_height_px-1 ? this.m_chart_height_px-1-this.m_form.Height() : y);
//--- Устанавливаем панели рассчитанные координаты и отображаем панель
   if(this.m_form.SetCoordX(cx) && this.m_form.SetCoordY(cy))
      this.m_form.Show();
   if(redraw)
      ::ChartRedraw(this.m_chart_id);
  }

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

//+------------------------------------------------------------------+
//| Рисует значок паттерна на графике                                |
//+------------------------------------------------------------------+
void CPattern::Draw(const bool redraw)
  {
//--- Если графический объект ещё не создавался - создаём его
   if(::ObjectFind(this.m_chart_id,this.m_name_graph_obj)<0)
      this.CreateTrendLine(this.m_chart_id,this.m_name_graph_obj,0,this.Time(),this.m_price,this.Time(),this.m_price,this.m_color,5);
//--- Иначе - отображаем
   else
      this.Show(redraw);
  }
//+------------------------------------------------------------------+
//| Отображает значок паттерна на графике                            |
//+------------------------------------------------------------------+
void CPattern::Show(const bool redraw=false)
  {
   if(this.m_draw_dots)
     {
      ::ObjectSetInteger(this.m_chart_id,this.m_name_graph_obj,OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS);
      return;
     }
   if(this.m_bitmap!=NULL)
      this.m_bitmap.Show();
   if(redraw)
      ::ChartRedraw(this.m_chart_id);
  }
//+------------------------------------------------------------------+
//| Скрывает значок паттерна на графике                              |
//+------------------------------------------------------------------+
void CPattern::Hide(const bool redraw=false)
  {
   if(this.m_draw_dots)
     {
      ::ObjectSetInteger(this.m_chart_id,this.m_name_graph_obj,OBJPROP_TIMEFRAMES,OBJ_NO_PERIODS);
      return;
     }
   if(this.m_bitmap!=NULL)
      this.m_bitmap.Hide();
   if(redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Теперь эти методы выглядят проще:

//+------------------------------------------------------------------+
//| Рисует значок паттерна на графике                                |
//+------------------------------------------------------------------+
bool CPattern::Draw(const bool redraw)
  {
   this.Show(redraw);
   return true;
  }
//+------------------------------------------------------------------+
//| Отображает значок паттерна на графике                            |
//+------------------------------------------------------------------+
void CPattern::Show(const bool redraw=false)
  {
   if(this.m_bitmap==NULL)
      return;
   this.m_bitmap.Show();
   if(redraw)
      ::ChartRedraw(this.m_chart_id);
  }
//+------------------------------------------------------------------+
//| Скрывает значок паттерна на графике                              |
//+------------------------------------------------------------------+
void CPattern::Hide(const bool redraw=false)
  {
   if(this.m_bitmap==NULL)
      return;
   this.m_bitmap.Hide();
   if(redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Далее доработаем классы паттернов, коды которых были перенесены из файла абстрактного паттерна.

Откроем файл класса паттерна "Пин-Бар" \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternPinBar.mqh и внесём изменения.

Ранее значки этого паттерна рисовались только точками при помощи стандартных графических объектов. Теперь же нам нужно добавить методы рисования точек на графическом объекте "Bitmap".

Добавим объявление новых методов в тело класса:

//+------------------------------------------------------------------+
//|                                                PatternPinBar.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Pattern.mqh"
//+------------------------------------------------------------------+
//| Класс паттерна "Пин-Бар"                                         |
//+------------------------------------------------------------------+
class CPatternPinBar : public CPattern 
  {
protected:
//--- Создаёт (1) объект-рисунок, внешний вид (2) инфо-панели, (3) объекта-рисунка
   virtual bool      CreateBitmap(void);
   virtual void      CreateInfoPanelView(void);
   void              CreateBitmapView(void);
   
//--- Рассчитывает (1) ширину, (2) высоту объекта-рисунка
   virtual int       CalculatetBitmapWidth(void)                           { return(20);  }
   virtual int       CalculatetBitmapHeight(void)                          { return(40);  }

public:
//--- Возвращает флаг поддержания паттерном указанного свойства
   virtual bool      SupportProperty(ENUM_PATTERN_PROP_INTEGER property)   { return true; }
   virtual bool      SupportProperty(ENUM_PATTERN_PROP_DOUBLE property)    { return true; }
   virtual bool      SupportProperty(ENUM_PATTERN_PROP_STRING property)    { return true; }
   
//--- Возвращает описание (1) статуса, (2) типа паттерна
   virtual string    StatusDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_STATUS_PA);    }
   virtual string    TypeDescription(void)   const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_TYPE_PIN_BAR); }

//--- Рисует значок паттерна на графике
   virtual bool      Draw(const bool redraw);

//--- Конструктор
                     CPatternPinBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct);
  };

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

В реализации метода, создающего внешний вид инфо-панели CreateInfoPanelView(), удалим строки получения свойств графика:

//--- В зависимости от размеров и координат графика, рассчитываем координаты панели так, чтобы она не выходила за пределы графика
   int chart_w=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS);
   int chart_h=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS);
   int cx=(this.m_form.RightEdge() >chart_w-1 ? chart_w-1-this.m_form.Width()  : this.m_form.CoordX());
   int cy=(this.m_form.BottomEdge()>chart_h-1 ? chart_h-1-this.m_form.Height() : this.m_form.CoordY());

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

//--- В зависимости от размеров и координат графика, рассчитываем координаты панели так, чтобы она не выходила за пределы графика
   int cx=(this.m_form.RightEdge() >this.m_chart_width_px-1 ? this.m_chart_width_px-1-this.m_form.Width()  : this.m_form.CoordX());
   int cy=(this.m_form.BottomEdge()>this.m_chart_height_px-1 ? this.m_chart_height_px-1-this.m_form.Height() : this.m_form.CoordY());

Напишем реализацию методов рисования значка паттерна.

Метод, рисующий значок паттерна на графике:

//+------------------------------------------------------------------+
//| Рисует значок паттерна на графике                                |
//+------------------------------------------------------------------+
bool CPatternPinBar::Draw(const bool redraw)
  {
//--- Если объект-рисунок ещё не создавался - создаём его
   if(this.m_bitmap==NULL)
     {
      if(!this.CreateBitmap())
         return false;
     }
//--- отображаем
   this.Show(redraw);
   return true;
  }

В случае, если физически объекта-рисунка ещё нет — создаём его, и далее отображаем на графике.

Метод, создающий объект-рисунок:

//+------------------------------------------------------------------+
//| Создаёт объект-рисунок                                           |
//+------------------------------------------------------------------+
bool CPatternPinBar::CreateBitmap(void)
  {
//--- Если объект-рисунок уже создан ранее - возвращаем true
   if(this.m_bitmap!=NULL)
      return true;
//--- Рассчитываем координаты и размеры объекта
   datetime time=this.MotherBarTime();
   double   price=(this.Direction()==PATTERN_DIRECTION_BULLISH ? this.MotherBarLow() : this.MotherBarHigh());
   int      w=this.CalculatetBitmapWidth();
   int      h=this.CalculatetBitmapHeight();
//--- Создаём объект Bitmap
   this.m_bitmap=this.CreateBitmap(this.ID(),this.GetChartID(),0,this.Name(),time,price,w,h,this.m_color_bidirect);
   if(this.m_bitmap==NULL)
      return false;
//--- Устанавливаем начало координат объекта по его центру и убираем всплывающую подсказку
   ::ObjectSetInteger(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_ANCHOR,ANCHOR_CENTER);
   ::ObjectSetString(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_TOOLTIP,"\n");
   
//--- Рисуем внешний вид объекта-рисунка
   this.CreateBitmapView();
   return true;
  }

Логика метода прокомментирована в коде. При установке координат объекта на графике учитываем направление паттерна. Если паттерн бычий, то координатой будет цена Low бара паттерна, если медвежий — координатой будет цена High.

Метод, создающий внешний вид объекта-рисунка:

//+------------------------------------------------------------------+
//| Создаёт внешний вид объекта-рисунка                              |
//+------------------------------------------------------------------+
void CPatternPinBar::CreateBitmapView(void)
  {
   this.m_bitmap.Erase(CLR_CANV_NULL,0);
   int y=(this.Direction()==PATTERN_DIRECTION_BULLISH ? this.m_bitmap.Height()/2+6 : this.m_bitmap.Height()/2-6);
   int x=this.m_bitmap.Width()/2;
   int r=2;
   this.m_bitmap.DrawCircleFill(x,y,r,this.Direction()==PATTERN_DIRECTION_BULLISH ? this.m_color_bullish : this.m_color_bearish);
   this.m_bitmap.Update(false);
  }

Сначала очищаем холст полностью прозрачным цветом. Затем определяем локальные координаты рисуемой точки. Для бычьего паттерна координатой Y будет половина высоты рисунка плюс 6 пикселей (ниже центра рисунка на 6 пикселей). Для медвежьего паттерна 6 пикселей вычитаем из координаты центра рисунка. Координатой X будет половина ширины рисунка. Радиус рисуемой окружности задан равным 2, что делает нормально видимую точку на любых таймфреймах графика.

Теперь аналогичные доработки сделаем в файле паттерна "Внутренний Бар" \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternInsideBar.mqh.

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

//+------------------------------------------------------------------+
//|                                             PatternInsideBar.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Pattern.mqh"
//+------------------------------------------------------------------+
//| Класс паттерна "Внутренний бар"                                  |
//+------------------------------------------------------------------+
class CPatternInsideBar : public CPattern
  {
protected:
//--- Создаёт (1) объект-рисунок, внешний вид (2) инфо-панели, (3) объекта-рисунка
   virtual bool      CreateBitmap(void);
   virtual void      CreateInfoPanelView(void);
   void              CreateBitmapView(void);
public:
//--- Возвращает флаг поддержания паттерном указанного свойства
   virtual bool      SupportProperty(ENUM_PATTERN_PROP_INTEGER property)   { return true; }
   virtual bool      SupportProperty(ENUM_PATTERN_PROP_DOUBLE property)    { return true; }
   virtual bool      SupportProperty(ENUM_PATTERN_PROP_STRING property)    { return true; }
   
//--- Возвращает описание (1) статуса, (2) типа паттерна
   virtual string    StatusDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_STATUS_PA);       }
   virtual string    TypeDescription(void)   const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_TYPE_INSIDE_BAR); }

//--- (1) Рисует, (2) перерисовывает с новым размером значок паттерна на графике
   virtual bool      Draw(const bool redraw);
   virtual bool      Redraw(const bool redraw);

//--- Конструктор
                     CPatternInsideBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct);
  };

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

//+------------------------------------------------------------------+
//| Перерисовывает с новым размером значок паттерна на графике       |
//+------------------------------------------------------------------+
bool CPatternInsideBar::Redraw(const bool redraw)
  {
//--- Если объект-рисунок ещё не создавался - создаём и отображаем его в методе Draw()
   if(this.m_bitmap==NULL)
      return CPatternInsideBar::Draw(redraw);
//--- Рассчитываем новые размеры объекта
   int w=this.CalculatetBitmapWidth();
   int h=this.CalculatetBitmapHeight();
//--- Если не удалось изменить размеры холста, возвращаем false
   if(!this.m_bitmap.SetWidth(w) || !this.m_bitmap.SetHeight(h))
      return false;
//--- Рисуем новый внешний вид объекта-рисунка с новыми размерами
   this.CreateBitmapView();
//--- отображаем и возвращаем true
   this.Show(redraw);
   return true;
  }

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

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

//--- Создаём строки для описания паттерна, его параметров и критериев его поиска
   string name=::StringFormat("Inside Bar (%lu bars)",int(this.Time()-this.MotherBarTime())/::PeriodSeconds(this.Timeframe())+1);
   string param=this.DirectDescription();

Такой расчёт имеет право на существование, но только в том случае, если смежные бары паттерна не разделены выходными днями. Если же между левым и правым баром паттерна есть выходные дни, то они будут добавлены к количеству баров, и вместо 2 получим значение 4.

Поправим расчёт:

//--- Создаём строки для описания паттерна, его параметров и критериев его поиска
   string name=::StringFormat("Inside Bar (%lu bars)",::Bars(this.Symbol(),this.Timeframe(),this.MotherBarTime(),this.Time()));
   string param=this.DirectDescription();

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

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

//--- В зависимости от размеров и координат графика, рассчитываем координаты панели так, чтобы она не выходила за пределы графика
   int chart_w=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS);
   int chart_h=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS);
   int cx=(this.m_form.RightEdge() >chart_w-1 ? chart_w-1-this.m_form.Width()  : this.m_form.CoordX());
   int cy=(this.m_form.BottomEdge()>chart_h-1 ? chart_h-1-this.m_form.Height() : this.m_form.CoordY());

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

//--- В зависимости от размеров и координат графика, рассчитываем координаты панели так, чтобы она не выходила за пределы графика
   int cx=(this.m_form.RightEdge() >this.m_chart_width_px-1 ? this.m_chart_width_px-1-this.m_form.Width()  : this.m_form.CoordX());
   int cy=(this.m_form.BottomEdge()>this.m_chart_height_px-1 ? this.m_chart_height_px-1-this.m_form.Height() : this.m_form.CoordY());

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

//+------------------------------------------------------------------+
//| Рисует значок паттерна на графике                                |
//+------------------------------------------------------------------+
void CPatternInsideBar::Draw(const bool redraw)
  {
//--- Если установлен флаг рисования точками - вызываем метод родительского класса и уходим
   if(this.m_draw_dots)
     {
      CPattern::Draw(redraw);
      return;
     }
//--- Если объект-рисунок ещё не создавался - создаём его
   if(this.m_bitmap==NULL)
     {
      if(!this.CreateBitmap())
         return;
     }
//--- отображаем
   this.Show(redraw);
  }

Теперь метод выглядит проще:

//+------------------------------------------------------------------+
//| Рисует значок паттерна на графике                                |
//+------------------------------------------------------------------+
bool CPatternInsideBar::Draw(const bool redraw)
  {
//--- Если объект-рисунок ещё не создавался - создаём его
   if(this.m_bitmap==NULL)
     {
      if(!this.CreateBitmap())
         return false;
     }
//--- отображаем
   this.Show(redraw);
   return true;
  }

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


Класс паттерна "Внешний бар"

В папке библиотеки \MQL5\Include\DoEasy\Objects\Series\Patterns\ создадим новый файл PatternOutsideBar.mqh класса CPatternOutsideBar.

Класс должен быть унаследован от базового класса объекта-паттерна, а его файл должен быть подключен к созданному файлу класса паттерна:

//+------------------------------------------------------------------+
//|                                            PatternOutsideBar.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Pattern.mqh"
//+------------------------------------------------------------------+
//| Класс паттерна "Внешний бар"                                     |
//+------------------------------------------------------------------+
class CPatternOutsideBar : public CPattern
  {
  }

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

//+------------------------------------------------------------------+
//| Класс паттерна "Внешний бар"                                     |
//+------------------------------------------------------------------+
class CPatternOutsideBar : public CPattern
  {
protected:
//--- Создаёт (1) объект-рисунок, внешний вид (2) инфо-панели, (3) объекта-рисунка
   virtual bool      CreateBitmap(void);
   virtual void      CreateInfoPanelView(void);
   void              CreateBitmapView(void);
//--- Рассчитывает высоту объекта-рисунка
   virtual int       CalculatetBitmapHeight(void);
public:
//--- Возвращает флаг поддержания паттерном указанного свойства
   virtual bool      SupportProperty(ENUM_PATTERN_PROP_INTEGER property)   { return true; }
   virtual bool      SupportProperty(ENUM_PATTERN_PROP_DOUBLE property)    { return true; }
   virtual bool      SupportProperty(ENUM_PATTERN_PROP_STRING property)    { return true; }
   
//--- Возвращает описание (1) статуса, (2) типа паттерна
   virtual string    StatusDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_STATUS_PA);       }
   virtual string    TypeDescription(void)   const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_TYPE_OUTSIDE_BAR);}

//--- (1) Рисует, (2) перерисовывает с новым размером значок паттерна на графике
   virtual bool      Draw(const bool redraw);
   virtual bool      Redraw(const bool redraw);

//--- Конструктор
                     CPatternOutsideBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct);
  };

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

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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CPatternOutsideBar::CPatternOutsideBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct) : 
   CPattern(PATTERN_STATUS_PA,PATTERN_TYPE_OUTSIDE_BAR,id,direct,symbol,timeframe,rates)
  {
   this.SetProperty(PATTERN_PROP_NAME,"Outside Bar");
   this.SetProperty(PATTERN_PROP_CANDLES,2);
   this.m_bars_formation=(int)this.GetProperty(PATTERN_PROP_CANDLES);
  }

Метод, создающий внешний вид инфо-панели:

//+------------------------------------------------------------------+
//| Создаёт внешний вид инфо-панели                                  |
//+------------------------------------------------------------------+
void CPatternOutsideBar::CreateInfoPanelView(void)
  {
//--- Если объект-форма не создана - уходим
   if(this.m_form==NULL)
      return;
//--- Изменяем тональность цветовой окраски для бычьих и медвежьих паттернов: бычий будет иметь синий оттенок, медвежий - красный
   color color_bullish=this.m_form.ChangeRGBComponents(this.m_form.ChangeColorLightness(this.m_color_panel_bullish,5),0,0,100);
   color color_bearish=this.m_form.ChangeRGBComponents(this.m_form.ChangeColorLightness(this.m_color_panel_bearish,5),100,0,0);
   color color_bidirect=this.m_color_panel_bidirect;
//--- Объявляем массив для начального и конечного цветов градиентной заливки
   color clr[2]={};
//--- В зависимости от направления паттерна меняем светлоту соответствующих цветов - начальный чуть темнее, конечный - чуть светлее
   switch(this.Direction())
     {
      case PATTERN_DIRECTION_BULLISH : 
        clr[0]=this.m_form.ChangeColorLightness(color_bullish,-2.5);
        clr[1]=this.m_form.ChangeColorLightness(color_bullish,2.5);
        break;
      case PATTERN_DIRECTION_BEARISH : 
        clr[0]=this.m_form.ChangeColorLightness(color_bearish,-2.5);
        clr[1]=this.m_form.ChangeColorLightness(color_bearish,2.5);
        break;
      default:
        clr[0]=this.m_form.ChangeColorLightness(color_bidirect,-2.5);
        clr[1]=this.m_form.ChangeColorLightness(color_bidirect,2.5);
        break;
     }
   
//--- Устанавливаем цвета фона и рамки формы
   this.m_form.SetBackgroundColor(this.Direction()==PATTERN_DIRECTION_BULLISH ? color_bullish : this.Direction()==PATTERN_DIRECTION_BEARISH ? color_bearish : color_bidirect,true);
   this.m_form.SetBorderColor(clrGray,true);
//--- Создаём строки для описания паттерна, его параметров и критериев его поиска
   string name=::StringFormat("Outside Bar (%.2f/%.2f)",this.GetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES),this.GetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE));
   string param=::StringFormat("%s (%.2f/%.2f)",this.DirectDescription(),this.GetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION),this.GetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION));
//--- Устанавливаем координаты панели и рассчитываем её ширину и высоту в зависимости от размеров текстов, размещаемых на панели
   int x=3;
   int y=20;
   int w=4+(::fmax(20+this.m_form.TextWidth(name),::fmax(x+this.m_form.TextWidth(param),x+this.m_form.TextWidth(::TimeToString(this.Time())))));
   int h=2+(20+this.m_form.TextHeight(this.DirectDescription())+this.m_form.TextHeight(::TimeToString(this.Time())));
//--- Устанавливаем ширину и высоту панели по рассчитанным значениям
   this.m_form.SetWidth(w);
   this.m_form.SetHeight(h);
//--- В зависимости от размеров и координат графика, рассчитываем координаты панели так, чтобы она не выходила за пределы графика
   int cx=(this.m_form.RightEdge() >this.m_chart_width_px-1 ? this.m_chart_width_px-1-this.m_form.Width()  : this.m_form.CoordX());
   int cy=(this.m_form.BottomEdge()>this.m_chart_height_px-1 ? this.m_chart_height_px-1-this.m_form.Height() : this.m_form.CoordY());
   this.m_form.SetCoordX(cx);
   this.m_form.SetCoordY(cy);
//--- Заливаем фон градиентным цветом
   this.m_form.Erase(clr,200,true,false);
//--- Рисуем рамку панели, иконку с (i), рисуем текст заголовка с пропорциями свечи и отделяем заголовок горизонтальной линией
   this.m_form.DrawFrameSimple(0,0,this.m_form.Width(),this.m_form.Height(),1,1,1,1,this.m_form.BorderColor(),200);
   this.m_form.DrawIconInfo(1,1,200);
   this.m_form.Text(20,3,name,clrBlack,200);
   this.m_form.DrawLine(1,18,this.m_form.Width()-1,18,clrDarkGray,250);
//--- Под горизонтальной линией вписываем описание паттерна с критериями его поиска и датой определяющего паттерн бара
   y=20;
   this.m_form.Text(x,y,param,clrBlack,200);
   y+=this.m_form.TextHeight(::TimeToString(this.Time()));
   this.m_form.Text(x,y,::TimeToString(this.Time()),clrBlack,200);
//--- Обновляем панель с перерисовкой графика
   this.m_form.Update(true);
  }

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

Метод, рисующий значок паттерна на графике:

//+------------------------------------------------------------------+
//| Рисует значок паттерна на графике                                |
//+------------------------------------------------------------------+
bool CPatternOutsideBar::Draw(const bool redraw)
  {
//--- Если объект-рисунок ещё не создавался - создаём его
   if(this.m_bitmap==NULL)
     {
      if(!this.CreateBitmap())
         return false;
     }
//--- отображаем
   this.Show(redraw);
   return true;
  }

Метод, перерисовывающий с новым размером значок паттерна на графике:

//+------------------------------------------------------------------+
//| Перерисовывает с новым размером значок паттерна на графике       |
//+------------------------------------------------------------------+
bool CPatternOutsideBar::Redraw(const bool redraw)
  {
//--- Если объект-рисунок ещё не создавался - создаём и отображаем его в методе Draw()
   if(this.m_bitmap==NULL)
      return CPatternOutsideBar::Draw(redraw);
//--- Рассчитываем новые размеры объекта
   int w=this.CalculatetBitmapWidth();
   int h=this.CalculatetBitmapHeight();
//--- Если не удалось изменить размеры холста, возвращаем false
   if(!this.m_bitmap.SetWidth(w) || !this.m_bitmap.SetHeight(h))
      return false;
//--- Рисуем новый внешний вид объекта-рисунка с новыми размерами
   this.CreateBitmapView();
//--- отображаем и возвращаем true
   this.Show(redraw);
   return true;
  }

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

Метод, создающий объект-рисунок:

//+------------------------------------------------------------------+
//| Создаёт объект-рисунок                                           |
//+------------------------------------------------------------------+
bool CPatternOutsideBar::CreateBitmap(void)
  {
//--- Если объект-рисунок уже создан ранее - возвращаем true
   if(this.m_bitmap!=NULL)
      return true;
//--- Рассчитываем координаты и размеры объекта
   datetime time=this.MotherBarTime();
   double   price=(this.BarPriceHigh()+this.BarPriceLow())/2;
   int      w=this.CalculatetBitmapWidth();
   int      h=this.CalculatetBitmapHeight();
   
//--- Создаём объект Bitmap
   this.m_bitmap=this.CreateBitmap(this.ID(),this.GetChartID(),0,this.Name(),time,price,w,h,this.m_color_bidirect);
   if(this.m_bitmap==NULL)
      return false;
//--- Устанавливаем начало координат объекта по его центру и убираем всплывающую подсказку
   ::ObjectSetInteger(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_ANCHOR,ANCHOR_CENTER);
   ::ObjectSetString(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_TOOLTIP,"\n");
   
//--- Рисуем внешний вид объекта-рисунка
   this.CreateBitmapView();
   return true;
  }

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

Метод, создающий внешний вид объекта-рисунка:

//+------------------------------------------------------------------+
//| Создаёт внешний вид объекта-рисунка                              |
//+------------------------------------------------------------------+
void CPatternOutsideBar::CreateBitmapView(void)
  {
   this.m_bitmap.Erase(CLR_CANV_NULL,0);
   int x=this.m_bitmap.Width()/2-int(1<<this.m_chart_scale)/2;
   this.m_bitmap.DrawRectangleFill(x,0,this.m_bitmap.Width()-1,this.m_bitmap.Height()-1,(this.Direction()==PATTERN_DIRECTION_BULLISH ? this.m_color_bullish : this.m_color_bearish),80);
   this.m_bitmap.DrawRectangle(x,0,this.m_bitmap.Width()-1,this.m_bitmap.Height()-1,clrGray);
   this.m_bitmap.Update(false);
  }

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

Метод, рассчитывающий высоту объекта-рисунка:

//+------------------------------------------------------------------+
//| Рассчитывает высоту объекта-рисунка                              |
//+------------------------------------------------------------------+
int CPatternOutsideBar::CalculatetBitmapHeight(void)
  {
//--- Рассчитаем диапазон цен графика и диапазон цен паттерна
   double chart_price_range=this.m_chart_price_max-this.m_chart_price_min;
   double patt_price_range=this.BarPriceHigh()-this.BarPriceLow();
//--- По рассчитанным диапазонам цен рассчитаем и вернём высоту объекта-рисунка
   return (int)ceil(patt_price_range*this.m_chart_height_px/chart_price_range)+8;
  }

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

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

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

Пока названия этих переменных, в которых указываются различные соотношения свечей паттернов, менять не будем. Но при необходимости далее их переименуем в "безликие" переменные, типа "param1", "param2" и т.д. А в конструкторах классов уже будем назначать каждой такой переменной требуемый параметр паттерна. Но это только при необходимости. Пока же обойдёмся уже названными переменными, одинаковыми для всех паттернов.

В классе объекта-бара, расположенном в файле \MQL5\Include\DoEasy\Objects\Series\Bar.mqh, поменяем наименование метода AddPattern() на AddPatternType():

//--- Возвращает себя
   CBar             *GetObject(void)                                    { return &this;}
//--- Устанавливает (1) символ, таймфрейм и время бара, (2) параметры объекта-бар
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   void              SetProperties(const MqlRates &rates);
//--- Добавляет тип паттерна на баре
   void              AddPatternType(const ENUM_PATTERN_TYPE pattern_type){ this.m_long_prop[BAR_PROP_PATTERNS_TYPE] |=pattern_type;          }

//--- Сравнивает объекты CBar между собой по всем возможным свойствам (для сортировки списков по указанному свойству объекта-бара)

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

Для того, чтобы можно было выбирать и получать из списков паттернов требуемые, нужно в файле \MQL5\Include\DoEasy\Services\Select.mqh класса CSelect подключить все файлы паттернов:

//+------------------------------------------------------------------+
//|                                                       Select.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\Event.mqh"
#include "..\Objects\Accounts\Account.mqh"
#include "..\Objects\Symbols\Symbol.mqh"
#include "..\Objects\PendRequest\PendRequest.mqh"
#include "..\Objects\Series\\Patterns\PatternPinBar.mqh"
#include "..\Objects\Series\\Patterns\PatternInsideBar.mqh"
#include "..\Objects\Series\\Patterns\PatternOutsideBar.mqh"
#include "..\Objects\Series\SeriesDE.mqh"
#include "..\Objects\Indicators\Buffer.mqh"
#include "..\Objects\Indicators\IndicatorDE.mqh"
#include "..\Objects\Indicators\DataInd.mqh"
#include "..\Objects\Ticks\DataTick.mqh"
#include "..\Objects\Book\MarketBookOrd.mqh"
#include "..\Objects\MQLSignalBase\MQLSignal.mqh"
#include "..\Objects\Chart\ChartObj.mqh"
#include "..\Objects\Graph\GCnvElement.mqh"
#include "..\Objects\Graph\Standard\GStdGraphObj.mqh"

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

Нам потребуется сравнивать на равенство массивы структур MqlParam. Напишем функции для сравнения полей структур и массива структур в файле \MQL5\Include\DoEasy\Services\DELib.mqh:

//+------------------------------------------------------------------+
//| Сравнивает структуры MqlParam между собой                        |
//+------------------------------------------------------------------+
bool IsEqualMqlParams(MqlParam &struct1,MqlParam &struct2)
  {
   if(struct1.type!=struct2.type)
      return false;
   switch(struct1.type)
     {
      //--- целочисленные типы
      case TYPE_BOOL    :  case TYPE_CHAR : case TYPE_UCHAR : case TYPE_SHORT    :  case TYPE_USHORT  :  
      case TYPE_COLOR   :  case TYPE_INT  : case TYPE_UINT  : case TYPE_DATETIME :  case TYPE_LONG    :
      case TYPE_ULONG   :  return(struct1.integer_value==struct2.integer_value);
      //--- вещественные типы
      case TYPE_FLOAT   :
      case TYPE_DOUBLE  :  return(NormalizeDouble(struct1.double_value-struct2.double_value,DBL_DIG)==0);
      //--- строковый тип
      case TYPE_STRING  :  return(struct1.string_value==struct2.string_value);
      default           :  return false;
     }
  }
//+------------------------------------------------------------------+
//| Сравнивает массив структур MqlParam между собой                  |
//+------------------------------------------------------------------+
bool IsEqualMqlParamArrays(MqlParam &array1[],MqlParam &array2[])
  {
   int total=ArraySize(array1);
   int size=ArraySize(array2);
   if(total!=size || total==0 || size==0)
      return false;
   for(int i=0;i<total;i++)
     {
      if(!IsEqualMqlParams(array1[i],array2[i]))
         return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Классы управления паттернами находятся в файле класса таймсерии \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh. В классе управления абстрактным паттерном удалим теперь не нужную и добавим новые переменные:

//+------------------------------------------------------------------+
//| Класс управления абстрактным паттерном                           |
//+------------------------------------------------------------------+
class CPatternControl : public CBaseObjExt
  {
private:
   ENUM_TIMEFRAMES   m_timeframe;                                                // Период графика таймсерии паттерна
   string            m_symbol;                                                   // Символ таймсерии паттерна
   double            m_point;                                                    // Point символа
   bool              m_used;                                                     // Флаг использования паттерна
   bool              m_drawing;                                                  // Флаг отрисовки значка паттерна на графике
   bool              m_draw_dots;                                                // Флаг отрисовки значка паттерна на графике точками
//--- Обрабатываемый паттерн
   ENUM_PATTERN_TYPE m_type_pattern;                                             // Тип паттерна
protected:
//--- Пропорции свечи
   double            m_ratio_body_to_candle_size;                                // Процентное отношение тела свечи к полному размеру свечи
   double            m_ratio_larger_shadow_to_candle_size;                       // Процентное отношение размера большей тени к размеру свечи
   double            m_ratio_smaller_shadow_to_candle_size;                      // Процентное отношение размера меньшей тени к размеру свечи
   double            m_ratio_candle_sizes;                                       // Процентное отношение размеров свечей
   uint              m_min_body_size;                                            // Минимальный размер тела свечи
   ulong             m_object_id;                                                // Уникальный код объекта на основе критериев поиска паттерна
//--- Списки
   CArrayObj        *m_list_series;                                              // Указатель на список таймсерии
   CArrayObj        *m_list_all_patterns;                                        // Указатель на список всех паттернов
   CPattern          m_pattern_instance;                                         // Объект-паттерн для поиска по свойству
//--- График
   ulong             m_symbol_code;                                              // Наименование символа графика в виде числа
   int               m_chart_scale;                                              // Масштаб графика
   int               m_chart_height_px;                                          // Высота графика в пикселях
   int               m_chart_width_px;                                           // Ширина графика в пикселях
   double            m_chart_price_max;                                          // Максимум графика
   double            m_chart_price_min;                                          // Минимум графика

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

   virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,const uint min_body_size,MqlRates &mother_bar_data) const
                                    { return WRONG_VALUE;   }

Теперь же этот размер будем передавать посредством MqlParam-переменной, поэтому сейчас данная переменная удалена из всех методов поиска паттерна во всех классах управления паттернами:

//--- (1) Ищет паттерн, возвращает направление или -1, если паттерн не найден,
//--- (2) создаёт паттерн с указанным направлением,
//--- (3) создаёт и возвращает уникальный код паттерна,
//--- (4) возвращает список паттернов, управляемых этим объектом
   virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const { return WRONG_VALUE;    }
   virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar){ return NULL;                       }
   virtual ulong     GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const datetime time) const { return 0;                      }
   virtual CArrayObj*GetListPatterns(void)                                       { return NULL;                                        }

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

//--- (1) Устанавливает, (2) возвращает флаг рисования значков паттерна в виде точек
   void              SetDrawingAsDots(const bool flag,const bool redraw);
   bool              IsDrawingAsDots(void)                                 const { return this.m_draw_dots;                            }

Объявим массив параметров паттерна, добавим методы для работы с новыми переменными, из виртуального метода CreateAndRefreshPatternList() удалим передачу параметра min_body_size, а в параметрическом конструкторе добавим передачу массива свойств паттерна посредством массива структур MqlParam. Объявим новый метод для перерисовки всех имеющихся паттернов на графике:

public:
   MqlParam          PatternParams[];                                            // Массив параметров паттерна
//--- Возвращает себя
   CPatternControl  *GetObject(void)                                             { return &this;                                       }
//--- (1) Устанавливает, (2) возвращает флаг использования паттерна
   void              SetUsed(const bool flag)                                    { this.m_used=flag;                                   }
   bool              IsUsed(void)                                          const { return this.m_used;                                 }
//--- (1) Устанавливает, (2) возвращает флаг рисования паттерна
   void              SetDrawing(const bool flag)                                 { this.m_drawing=flag;                                }
   bool              IsDrawing(void)                                       const { return this.m_drawing;                              }

//--- Устанавливает искомое процентное отношение (1) тела свечи к полному размеру свечи,
//--- размера (2) верхней, (3) нижней тени к размеру свечи, (4) размеров свечей, (5) минимальный размер тела свечи
   void              SetRatioBodyToCandleSizeValue(const double value)           { this.m_ratio_body_to_candle_size=value;             }
   void              SetRatioLargerShadowToCandleSizeValue(const double value)   { this.m_ratio_larger_shadow_to_candle_size=value;    }
   void              SetRatioSmallerShadowToCandleSizeValue(const double value)  { this.m_ratio_smaller_shadow_to_candle_size=value;   }
   void              SetRatioCandleSizeValue(const double value)                 { this.m_ratio_candle_sizes=value;                    }
   void              SetMinBodySize(const uint value)                            { this.m_min_body_size=value;                         }
//--- Возвращает искомое процентное отношение (1) тела свечи к полному размеру свечи,
//--- размера (2) верхней, (3) нижней тени к размеру свечи, (4) размеров свечей, (5) минимальный размер тела свечи
   double            RatioBodyToCandleSizeValue(void)                      const { return this.m_ratio_body_to_candle_size;            }
   double            RatioLargerShadowToCandleSizeValue(void)              const { return this.m_ratio_larger_shadow_to_candle_size;   }
   double            RatioSmallerShadowToCandleSizeValue(void)             const { return this.m_ratio_smaller_shadow_to_candle_size;  }
   double            RatioCandleSizeValue(void)                            const { return this.m_ratio_candle_sizes;                   }
   int               MinBodySize(void)                                     const { return (int)this.m_min_body_size;                   }
   
//--- Возвращает идентификатор объекта на основе критериев поиска паттерна
   virtual ulong     ObjectID(void)                                        const { return this.m_object_id;                            }

//--- Возвращает (1) тип, (2) таймфрейм, (3) символ, (4) Point символа, (5) код символа паттерна
   ENUM_PATTERN_TYPE TypePattern(void)                                     const { return this.m_type_pattern;                         }
   ENUM_TIMEFRAMES   Timeframe(void)                                       const { return this.m_timeframe;                            }
   string            Symbol(void)                                          const { return this.m_symbol;                               }
   double            Point(void)                                           const { return this.m_point;                                }
   ulong             SymbolCode(void)                                      const { return this.m_symbol_code;                          }
   
//--- Устанавливает (1) масштаб графика, (2) высоту, (3) ширину графика в пикселях, (4) максимум, (5) минимум графика
   void              SetChartScale(const int scale)                              { this.m_chart_scale=scale;                           }
   void              SetChartHeightInPixels(const int height)                    { this.m_chart_height_px=height;                      }
   void              SetChartWidthInPixels(const int width)                      { this.m_chart_width_px=width;                        }
   void              SetChartPriceMax(const double price)                        { this.m_chart_price_max=price;                       }
   void              SetChartPriceMin(const double price)                        { this.m_chart_price_min=price;                       }
//--- Возвращает (1) масштаб графика, (2) высоту, (3) ширину графика в пикселях, (4) максимум, (5) минимум графика
   int               ChartScale(void)                                      const { return this.m_chart_scale;                          }
   int               ChartHeightInPixels(void)                             const { return this.m_chart_height_px;                      }
   int               ChartWidthInPixels(void)                              const { return this.m_chart_width_px;                       }
   double            ChartPriceMax(void)                                   const { return this.m_chart_price_max;                      }
   double            ChartPriceMin(void)                                   const { return this.m_chart_price_min;                      }

//--- Сравнивает объекты CPatternControl между собой по всем возможным свойствам
   virtual int       Compare(const CObject *node,const int mode=0) const;

//--- Ищет паттерны и добавляет найденные в список всех паттернов
   virtual int       CreateAndRefreshPatternList(void);
//--- Выводит паттерны на график
   void              DrawPatterns(const bool redraw=false);
//--- Перерисовывает паттерны на графике с новым размером
   void              RedrawPatterns(const bool redraw=false);
   
//--- Защищённый параметрический конструктор
protected:
                     CPatternControl(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_PATTERN_STATUS status,const ENUM_PATTERN_TYPE type,CArrayObj *list_series,CArrayObj *list_patterns,const MqlParam &param[]);
  };

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

//+------------------------------------------------------------------+
//| CPatternControl::Защищённый параметрический конструктор          |
//+------------------------------------------------------------------+
CPatternControl::CPatternControl(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_PATTERN_STATUS status,const ENUM_PATTERN_TYPE type,CArrayObj *list_series,CArrayObj *list_patterns,const MqlParam &param[]) :
  m_used(true),m_drawing(true)
  {
   this.m_type=OBJECT_DE_TYPE_SERIES_PATTERN_CONTROL;
   this.m_type_pattern=type;
   this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol);
   this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
   this.m_point=::SymbolInfoDouble(this.m_symbol,SYMBOL_POINT);
   this.m_object_id=0;
   this.m_list_series=list_series;
   this.m_list_all_patterns=list_patterns;
   for(int i=0;i<(int)this.m_symbol.Length();i++)
      this.m_symbol_code+=this.m_symbol.GetChar(i);
   this.m_chart_scale=(int)::ChartGetInteger(this.m_chart_id,CHART_SCALE);
   this.m_chart_height_px=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS);
   this.m_chart_width_px=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS);
   this.m_chart_price_max=::ChartGetDouble(this.m_chart_id,CHART_PRICE_MAX);
   this.m_chart_price_min=::ChartGetDouble(this.m_chart_id,CHART_PRICE_MIN);

//--- заполняем массив параметров данными из переданного в конструктор массива
   int count=::ArrayResize(this.PatternParams,::ArraySize(param));
   for(int i=0;i<count;i++)
     {
      this.PatternParams[i].type         = param[i].type;
      this.PatternParams[i].double_value = param[i].double_value;
      this.PatternParams[i].integer_value= param[i].integer_value;
      this.PatternParams[i].string_value = param[i].string_value;
     }
  }

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

//+------------------------------------------------------------------+
//| CPatternControl::Ищет паттерны и добавляет                       |
//| найденные в список всех паттернов                                |
//+------------------------------------------------------------------+
int CPatternControl::CreateAndRefreshPatternList(void)
  {
//--- Если не используется - уходим
   if(!this.m_used)
      return 0;
//--- Сбрасываем флаг события таймсерии и очищаем список всех событий паттернов таймсерии
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- Получаем дату открытия последнего (текущего) бара
   datetime time_open=0;
   if(!::SeriesInfoInteger(this.Symbol(),this.Timeframe(),SERIES_LASTBAR_DATE,time_open))
      return 0;
      
//--- Получаем список всех баров таймсерии кроме текущего
   CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,time_open,LESS);
   if(list==NULL || list.Total()==0)
      return 0;
//--- Структура данных "материнского" бара
   MqlRates pattern_mother_bar_data={};
//--- Сортируем полученный список по времени открытия баров
   list.Sort(SORT_BY_BAR_TIME);
//--- В цикле от самого позднего бара
   for(int i=list.Total()-1;i>=0;i--)
     {
      //--- получаем очередной объект-бар из списка
      CBar *bar=list.At(i);
      if(bar==NULL)
         continue;
      //--- ищем паттерн относительно полученного бара
      ENUM_PATTERN_DIRECTION direction=this.FindPattern(bar.Time(),pattern_mother_bar_data);
      //--- Если паттерна нет - идём к следующему бару
      if(direction==WRONG_VALUE)
         continue;
         
      //--- Паттерн на текущем баре цикла найден
      //--- уникальный код паттерна = время открытия свечи + тип + статус + направление паттерна + таймфрейм + символ таймсерии
      ulong code=this.GetPatternCode(direction,bar.Time());
      //--- Устанавливаем в образец код паттерна
      this.m_pattern_instance.SetProperty(PATTERN_PROP_CODE,code);
      //--- Сортируем список всех паттернов по уникальному коду паттерна
      this.m_list_all_patterns.Sort(SORT_BY_PATTERN_CODE);
      //--- ищем паттерн в списке по уникальному коду
      int index=this.m_list_all_patterns.Search(&this.m_pattern_instance);
      //--- Если в списке всех паттернов нет паттерна, равного образцу
      if(index==WRONG_VALUE)
        {
         //--- Создаём объект-паттерн
         CPattern *pattern=this.CreatePattern(direction,this.m_list_all_patterns.Total(),bar);
         if(pattern==NULL)
            continue;
         //--- Сортируем список всех паттернов по времени и вставляем паттерн в список по его времени
         this.m_list_all_patterns.Sort(SORT_BY_PATTERN_TIME);
         if(!this.m_list_all_patterns.InsertSort(pattern))
           {
            delete pattern;
            continue;
           }
         //--- Добавляем тип паттерна в список типов паттернов объекта-бара
         bar.AddPatternType(pattern.TypePattern());
         //--- В объект-паттерн добавляем указатель на бар, на котором он найден, и данные материнского бара
         pattern.SetPatternBar(bar);
         pattern.SetMotherBarData(pattern_mother_bar_data);
         
         //--- устанавливаем в объект-паттерн данные графика
         pattern.SetChartHeightInPixels(this.m_chart_height_px);
         pattern.SetChartWidthInPixels(this.m_chart_width_px);
         pattern.SetChartScale(this.m_chart_scale);
         pattern.SetChartPriceMax(this.m_chart_price_max);
         pattern.SetChartPriceMin(this.m_chart_price_min);
         //--- Если стоит флаг рисования - рисуем метку паттерна на графике
         if(this.m_drawing)
            pattern.Draw(false);
         //--- Получаем время предпоследнего бара в списке-коллекции (бар таймсерии с индексом 1)
         datetime time_prev=time_open-::PeriodSeconds(this.Timeframe());
         //--- Если текущий бар в цикле - это бар с индексом 1 в таймсерии - создаёи и отправляем сообщение
         if(bar.Time()==time_prev)
           {
            // Здесь создание и отправка сообщения
           }
        }
     }
//--- Сортируем список всех паттернов по времени и возвращаем общее количество паттернов в списке
   this.m_list_all_patterns.Sort(SORT_BY_PATTERN_TIME);
   return m_list_all_patterns.Total();
  }

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

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

//+-------------------------------------------------------------------+
//|Перерисовывает паттерны на графике с новым размером объекта-рисунка|
//+-------------------------------------------------------------------+
void CPatternControl::RedrawPatterns(const bool redraw=false)
  {
//--- Получаем список паттернов, управляемых данным объектом управления
   CArrayObj *list=this.GetListPatterns();
   if(list==NULL || list.Total()==0)
      return;
//--- Сортируем полученный список по времени паттернов
   list.Sort(SORT_BY_PATTERN_TIME);
//--- В цикле от самого позднего паттерна
   for(int i=list.Total()-1;i>=0;i--)
     {
      //--- получаем очередной объект-паттерн
      CPattern *obj=list.At(i);
      if(obj==NULL)
         continue;
      //--- Перерисовываем объект-рисунок паттерна на графике
      obj.Redraw(false);
     }
//--- По окончании цикла, если установлен флаг, перерисовываем график
   if(redraw)
      ::ChartRedraw(this.m_chart_id);
  }

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

Теперь доработаем классы управления паттернами. Они расположены далее в этом же файле. Найдём все вхождения строки  —

const uint min_body_size

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

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

В классе управления паттерном "Пин бар":

//+------------------------------------------------------------------+
//| Класс управления паттерном "Пин бар"                             |
//+------------------------------------------------------------------+
class CPatternControlPinBar : public CPatternControl
  {
protected:
//--- (1) Ищет паттерн, возвращает направление (или -1),
//--- (2) создаёт паттерн с указанным направлением,
//--- (3) создаёт и возвращает уникальный код паттерна
//--- (4) возвращает список паттернов, управляемых этим объектом
   virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const;
   virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar);
   virtual ulong     GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const datetime time) const
                       {
                        //--- уникальный код паттерна = время открытия свечи + тип + статус + направление паттерна + таймфрейм + символ таймсерии
                        return(time+PATTERN_TYPE_PIN_BAR+PATTERN_STATUS_PA+direction+this.Timeframe()+this.m_symbol_code);
                       }
   virtual CArrayObj*GetListPatterns(void);
//--- Создаёт идентификатор объекта на основе критериев поиска паттерна
   virtual ulong     CreateObjectID(void);

public:
//--- Параметрический конструктор
                     CPatternControlPinBar(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                           CArrayObj *list_series,CArrayObj *list_patterns,
                                           const MqlParam &param[]) :
                        CPatternControl(symbol,timeframe,PATTERN_STATUS_PA,PATTERN_TYPE_PIN_BAR,list_series,list_patterns,param)
                       {
                        this.m_min_body_size=(uint)this.PatternParams[0].integer_value;
                        this.m_ratio_body_to_candle_size=this.PatternParams[1].double_value;
                        this.m_ratio_larger_shadow_to_candle_size=this.PatternParams[2].double_value;
                        this.m_ratio_smaller_shadow_to_candle_size=this.PatternParams[3].double_value;
                        this.m_ratio_candle_sizes=0;
                        this.m_object_id=this.CreateObjectID();
                       }
  };

...

//+------------------------------------------------------------------+
//| CPatternControlPinBar::Ищет паттерн                              |
//+------------------------------------------------------------------+
ENUM_PATTERN_DIRECTION CPatternControlPinBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const
  {
//--- Получаем данные одного бара по времени
   CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,series_bar_time,EQUAL);
//--- Если список пустой - возвращаем -1
   if(list==NULL || list.Total()==0)
      return WRONG_VALUE;
//--- Размер тела свечи должен быть меньше, либо равен RatioBodyToCandleSizeValue() (по умолчанию 30%) размера всей свечи,
//--- при этом размер тела не должен быть меньше min_body_size
   list=CSelect::ByBarProperty(list,BAR_PROP_RATIO_BODY_TO_CANDLE_SIZE,this.RatioBodyToCandleSizeValue(),EQUAL_OR_LESS);
   list=CSelect::ByBarProperty(list,BAR_PROP_RATIO_BODY_TO_CANDLE_SIZE,this.m_min_body_size,EQUAL_OR_MORE);
//--- Если список пустой - паттернов нет, возвращаем -1
   if(list==NULL || list.Total()==0)
      return WRONG_VALUE;
      
//--- Определяем бычий паттерн

В классе управления паттерном "Внутренний бар":

//+------------------------------------------------------------------+
//| Класс управления паттерном "Внутренний бар"                      |
//+------------------------------------------------------------------+
class CPatternControlInsideBar : public CPatternControl
  {
private:
//--- Проверяет и возвращает факт наличия паттерна на двух соседних барах
   bool              CheckInsideBar(const CBar *bar1,const CBar *bar0) const
                       {
                        //--- Если переданы пустые объекты-бары - возвращаем false
                        if(bar0==NULL || bar1==NULL)
                           return false;
                        //--- Возвращаем факт того, что бар справа полностью находится внутри размеров бара слева
                        return(bar0.High()<bar1.High() && bar0.Low()>bar1.Low());
                       }
   bool              FindMotherBar(CArrayObj *list,MqlRates &rates) const
                       {
                        bool res=false;
                        if(list==NULL)
                           return false;
                        //--- В цикле по списку, начиная с бара слева от определяющего
                        for(int i=list.Total()-2;i>0;i--)
                          {
                           //--- Получаем указатели на два подряд идущих бара
                           CBar *bar0=list.At(i);
                           CBar *bar1=list.At(i-1);
                           if(bar0==NULL || bar1==NULL)
                              return false;
                           //--- Если полученные бары представляют паттерн
                           if(CheckInsideBar(bar1,bar0))
                             {
                              //--- записываем в переменную MqlRates данные материнского бара и записываем в res значение true
                              this.SetBarData(bar1,rates);
                              res=true;
                             }
                           //--- Если паттерна нет - прерываем цикл
                           else
                              break;
                          }
                        //--- возвращаем результат
                        return res;
                       }
protected:
//--- (1) Ищет паттерн, возвращает направление (или -1),
//--- (2) создаёт паттерн с указанным направлением,
//--- (3) создаёт и возвращает уникальный код паттерна
//--- (4) возвращает список паттернов, управляемых этим объектом
   virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const;
   virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar);
   virtual ulong     GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const datetime time) const
                       {
                        //--- уникальный код паттерна = время открытия свечи + тип + статус + направление паттерна + таймфрейм + символ таймсерии
                        return(time+PATTERN_TYPE_INSIDE_BAR+PATTERN_STATUS_PA+PATTERN_DIRECTION_BOTH+this.Timeframe()+this.m_symbol_code);
                       }
   virtual CArrayObj*GetListPatterns(void);
//--- Создаёт идентификатор объекта на основе критериев поиска паттерна
   virtual ulong     CreateObjectID(void);

public:
//--- Параметрический конструктор
                     CPatternControlInsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                              CArrayObj *list_series,CArrayObj *list_patterns,const MqlParam &param[]) :
                        CPatternControl(symbol,timeframe,PATTERN_STATUS_PA,PATTERN_TYPE_INSIDE_BAR,list_series,list_patterns,param)
                       {
                        this.m_min_body_size=(uint)this.PatternParams[0].integer_value;
                        this.m_ratio_body_to_candle_size=0;
                        this.m_ratio_larger_shadow_to_candle_size=0;
                        this.m_ratio_smaller_shadow_to_candle_size=0;
                        this.m_ratio_candle_sizes=0;
                        this.m_object_id=this.CreateObjectID();
                       }
  };

...

//+------------------------------------------------------------------+
//| CPatternControlInsideBar::Ищет паттерн                           |
//+------------------------------------------------------------------+
ENUM_PATTERN_DIRECTION CPatternControlInsideBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const
  {
//--- Получаем данные баров до указанного времени включительно

По аналогии с классами управления паттернами "Пин-Бар" и "Внутренний Бар" далее по тексту напишем класс управления паттерном "Внешний Бар":

//+------------------------------------------------------------------+
//| Класс управления паттерном "Внешний бар"                         |
//+------------------------------------------------------------------+
class CPatternControlOutsideBar : public CPatternControl
  {
private:
//--- Проверяет и возвращает флаг соблюдения соотношения тела свечи к её размеру относительно заданного значения
   bool              CheckProportions(const CBar *bar) const { return(bar.RatioBodyToCandleSize()>=this.RatioBodyToCandleSizeValue());  }
   
//--- Возвращает соотношение близлежащих свечей по указанному бару
   double            GetRatioCandles(const CBar *bar)  const
                       {
                        //--- Если передан пустой объект - возвращаем 0
                        if(bar==NULL)
                           return 0;
                        //--- Получаем список баров со временем, меньшим, чем у переданного в метод
                        CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,bar.Time(),LESS);
                        if(list==NULL || list.Total()==0)
                           return 0;
                        //--- Устанавливаем списку флаг сортировки по времени баров и
                        list.Sort(SORT_BY_BAR_TIME);
                        //--- получаем указатель на последний бар в списке (ближний к переданному в метод)
                        CBar *bar1=list.At(list.Total()-1);
                        if(bar1==NULL)
                           return 0;
                        //--- Возвращаем соотношение размеров одного бара к другому
                        return(bar.Size()>0 ? bar1.Size()*100.0/bar.Size() : 0.0);
                       }
//--- Проверяет и возвращает факт наличия паттерна на двух соседних барах
   bool              CheckOutsideBar(const CBar *bar1,const CBar *bar0) const
                       {
                        //--- Если переданы пустые объекты-бары, или их типы по направлению не бычий и не мадвежий, либо одинаковы - возвращаем false
                        if(bar0==NULL || bar1==NULL || 
                           bar0.TypeBody()==BAR_BODY_TYPE_NULL || bar1.TypeBody()==BAR_BODY_TYPE_NULL ||
                           bar0.TypeBody()==BAR_BODY_TYPE_CANDLE_ZERO_BODY || bar1.TypeBody()==BAR_BODY_TYPE_CANDLE_ZERO_BODY ||
                           bar0.TypeBody()==bar1.TypeBody())
                           return false;
                        //--- Рассчитываем соотношение указанных свечей и, если оно меньше заданного - возвращаем false
                        double ratio=(bar0.Size()>0 ? bar1.Size()*100.0/bar0.Size() : 0.0);
                        if(ratio<this.RatioCandleSizeValue())
                           return false;
                        //--- Возвращаем факт того, что тело бара справа полностью перекрывает размеры тела бара слева,
                        //--- а тени баров либо равны, либо тени бара справа перекрывают тени бара слева
                        return(bar1.High()<=bar0.High() && bar1.Low()>=bar0.Low() && bar1.TopBody()<bar0.TopBody() && bar1.BottomBody()>bar0.BottomBody());
                       }
   
protected:
//--- (1) Ищет паттерн, возвращает направление (или -1),
//--- (2) создаёт паттерн с указанным направлением,
//--- (3) создаёт и возвращает уникальный код паттерна
//--- (4) возвращает список паттернов, управляемых этим объектом
   virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const;
   virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar);
   virtual ulong     GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const datetime time) const
                       {
                        //--- уникальный код паттерна = время открытия свечи + тип + статус + направление паттерна + таймфрейм + символ таймсерии
                        return(time+PATTERN_TYPE_OUTSIDE_BAR+PATTERN_STATUS_PA+direction+this.Timeframe()+this.m_symbol_code);
                       }
   virtual CArrayObj*GetListPatterns(void);
//--- Создаёт идентификатор объекта на основе критериев поиска паттерна
   virtual ulong     CreateObjectID(void);

public:
//--- Параметрический конструктор
                     CPatternControlOutsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                               CArrayObj *list_series,CArrayObj *list_patterns,
                                               const MqlParam &param[]) :
                        CPatternControl(symbol,timeframe,PATTERN_STATUS_PA,PATTERN_TYPE_OUTSIDE_BAR,list_series,list_patterns,param)
                       {
                        this.m_min_body_size=(uint)this.PatternParams[0].integer_value;      // Минимальный размер свечей паттерна
                        this.m_ratio_candle_sizes=this.PatternParams[1].double_value;        // Процентное отношение размера поглощающей свечи к размеру поглощаемой
                        this.m_ratio_body_to_candle_size=this.PatternParams[2].double_value; // Процентное отношение полного размера к размеру тела свечи
                        this.m_object_id=this.CreateObjectID();
                       }
  };

Необходимые условия для определения паттерна: тело бара справа должно полностью перекрывать размеры тела бара слева, а тени баров либо равны, либо тени бара справа могут перекрывать тени бара слева. Такой поиск осуществляется в методе CheckOutsideBar(). Кроме того, необходимо учитывать соотношение размеров тел свечей, входящих в паттерн, относительно полного размера свечей. Эту проверку выполняет метод CheckProportions().

В конструкторе класса устанавливаем статус паттерна как "Price Action", тип паттерна как "Outside Bar" и устанавливаем все пропорции свечей паттерна из массива структур, переданного в конструктор.

Метод, создающий идентификатор объекта на основе критериев поиска паттерна:

//+------------------------------------------------------------------+
//| Создаёт идентификатор объекта на основе критериев поиска паттерна|
//+------------------------------------------------------------------+
ulong CPatternControlOutsideBar::CreateObjectID(void)
  {
   ushort bodies=(ushort)this.RatioCandleSizeValue()*100;
   ushort body=(ushort)this.RatioBodyToCandleSizeValue()*100;
   ulong  res=0;
   this.UshortToLong(bodies,0,res);
   return this.UshortToLong(body,1,res);
  }

Два критерия (процентные отношения размеров свечей и отношение тела свечи к полному размеру свечи) задаются в вещественных числах (в процентах) и не могут быть больше 100. Поэтому мы их переводим в целочисленные значения умножением на 100, а далее создаём ulong-идентификатор при помощи метода расширенного стандартного объекта библиотеки UshortToLong(), заполняющий указанные биты long-числа ushort-значениями:

//+------------------------------------------------------------------+
//| Упаковывает ushort-число в переданное long-число                 |
//+------------------------------------------------------------------+
long CBaseObjExt::UshortToLong(const ushort ushort_value,const uchar to_byte,long &long_value)
  {
   if(to_byte>3)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_INDEX));
      return 0;
     }
   return(long_value |= this.UshortToByte(ushort_value,to_byte));
  }
//+------------------------------------------------------------------+
//| Преобразует ushort-значение в заданный байт long-числа           |
//+------------------------------------------------------------------+
long CBaseObjExt::UshortToByte(const ushort value,const uchar to_byte) const
  {
   return(long)value<<(16*to_byte);
  }

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

//+------------------------------------------------------------------+
//| Создаёт паттерн с указанным направлением                         |
//+------------------------------------------------------------------+
CPattern *CPatternControlOutsideBar::CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar)
  {
//--- Если передан невалидный указатель на объект-бар - возвращаем NULL
   if(bar==NULL)
      return NULL;
//--- Заполняем структуру MqlRates данными бара
   MqlRates rates={0};
   this.SetBarData(bar,rates);
//--- Создаём новый паттерн Внешний Бар
   CPatternOutsideBar *obj=new CPatternOutsideBar(id,this.Symbol(),this.Timeframe(),rates,direction);
   if(obj==NULL)
      return NULL;
//--- устанавливаем в свойства созданного объекта-паттерна пропорции свечи, на которой найден паттерн
   obj.SetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE,bar.RatioBodyToCandleSize());
   obj.SetProperty(PATTERN_PROP_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE,bar.RatioLowerShadowToCandleSize());
   obj.SetProperty(PATTERN_PROP_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE,bar.RatioUpperShadowToCandleSize());
   obj.SetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES,this.GetRatioCandles(bar));
//--- устанавливаем в свойства созданного объекта-паттерна критерии поиска свечи, на которой найден паттерн
   obj.SetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION,this.RatioBodyToCandleSizeValue());
   obj.SetProperty(PATTERN_PROP_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRITERION,this.RatioLargerShadowToCandleSizeValue());
   obj.SetProperty(PATTERN_PROP_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRITERION,this.RatioSmallerShadowToCandleSizeValue());
   obj.SetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION,this.RatioCandleSizeValue());
//--- Устанавливаем объекту-паттерну идентификатор объекта управления
   obj.SetProperty(PATTERN_PROP_CTRL_OBJ_ID,this.ObjectID());
//--- Возвращаем указатель на созданный объект
   return obj;
  }

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

Метод поиска паттерна:

//+------------------------------------------------------------------+
//| CPatternControlOutsideBar::Ищет паттерн                          |
//+------------------------------------------------------------------+
ENUM_PATTERN_DIRECTION CPatternControlOutsideBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const
  {
//--- Получаем данные баров до указанного времени включительно
   CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,series_bar_time,EQUAL_OR_LESS);
//--- Если список пустой - возвращаем -1
   if(list==NULL || list.Total()==0)
      return WRONG_VALUE;
//--- Сортируем список по времени открытия баров
   list.Sort(SORT_BY_BAR_TIME);
//--- Получаем самый поздний бар из списка (определяющий бар)
   CBar *bar_patt=list.At(list.Total()-1);
   if(bar_patt==NULL)
      return WRONG_VALUE;
//--- В цикле от следующего бара (материнский)
   for(int i=list.Total()-2;i>=0;i--)
     {
      //--- Получаем "материнский" бар
      CBar *bar_prev=list.At(i);
      if(bar_prev==NULL)
         return WRONG_VALUE;
      //--- Если пропорции баров не соответствуют паттерну - возвращаем -1
      if(!this.CheckProportions(bar_patt) || !this.CheckProportions(bar_prev))
         return WRONG_VALUE;
      //--- проверяем, что полученные два бара являются паттерном. Если нет - возвращаем -1
      if(!this.CheckOutsideBar(bar_prev,bar_patt))
         return WRONG_VALUE;
      //--- Записываем данные "материнского" бара
      this.SetBarData(bar_prev,mother_bar_data);
      //--- Если на прошлом шаге паттерн найден - определяем и возвращаем его направление
      if(bar_patt.TypeBody()==BAR_BODY_TYPE_BULLISH)
         return PATTERN_DIRECTION_BULLISH;
      if(bar_patt.TypeBody()==BAR_BODY_TYPE_BEARISH)
         return PATTERN_DIRECTION_BEARISH;
     }
//--- Паттернов не найдено - возвращаем -1
   return WRONG_VALUE;
  }

Логика метода расписана в комментариях. В метод передаётся время открытия текущего бара. Получаем список всех баров кроме текущего (на нулевом баре паттерны не ищем). В цикле по полученному списку баров проверяем каждые близлежащие два бара на предмет их соответствия критериям паттерна. Если их соотношение представляет из себя паттерн — определяем и возвращаем направление паттерна.

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

//+------------------------------------------------------------------+
//| Возвращает список паттернов, управляемых этим объектом           |
//+------------------------------------------------------------------+
CArrayObj *CPatternControlOutsideBar::GetListPatterns(void)
  {
   CArrayObj *list=CSelect::ByPatternProperty(this.m_list_all_patterns,PATTERN_PROP_PERIOD,this.Timeframe(),EQUAL);
   list=CSelect::ByPatternProperty(list,PATTERN_PROP_SYMBOL,this.Symbol(),EQUAL);
   list=CSelect::ByPatternProperty(list,PATTERN_PROP_TYPE,PATTERN_TYPE_OUTSIDE_BAR,EQUAL);
   return CSelect::ByPatternProperty(list,PATTERN_PROP_CTRL_OBJ_ID,this.ObjectID(),EQUAL);
  }

Из общего списка всех паттернов получаем список, отфильтрованный по периоду графика, затем из полученного списка получаем список, отфильтрованный по имени символа. Далее полученный список фильтруется по типу паттерна "Внешний Бар" и полученный список фильтруется по идентификатору данного объекта управления. В итоге метод возвращает список только тех паттернов, которыми управляет данный класс.

Теперь нам нужно полностью переработать класс управления паттернами. Подробнее о нём можно почитать в этой статье.

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

... "Возвращает объект управления паттерном ..." 
     CPatternControl  *GetObjControlPattern ...XXXXX(),

... "Устанавливает флаг использования паттерна ... и создаёт объект управления если его ещё нет" 
     void SetUsedPattern ... XXXXX(const bool flag),

... "Возвращает флаг использования паттерна ..." 
     bool IsUsedPattern ...XXXXX(void),

... "Устанавливает флаг рисования точками паттерна ..." 
     void SetDrawingAsDotsPattern ...XXXXX(const bool flag,const bool redraw),

... "Возвращает флаг рисования точками паттерна ..." 
     bool IsDrawingAsDotsPattern ...XXXXX(void),

... "Ставит метки паттернов ... на графике" 
     void DrawPattern ...XXXXX(const bool redraw=false)

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

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

//+------------------------------------------------------------------+
//| Класс управления паттернами                                      |
//+------------------------------------------------------------------+
class CPatternsControl : public CBaseObjExt
  {
private:
   CArrayObj         m_list_controls;                                   // Список контроллеров управления паттернами
   CArrayObj        *m_list_series;                                     // Указатель на список таймсерии
   CArrayObj        *m_list_all_patterns;                               // Указатель на список всех паттернов
//--- Данные таймсерии
   ENUM_TIMEFRAMES   m_timeframe;                                       // Таймфрейм таймсерии
   string            m_symbol;                                          // Символ таймсерии
   
public:
//--- Возвращает (1) таймфрейм, (2) символ таймсерии, (3) себя
   ENUM_TIMEFRAMES   Timeframe(void)                                       const { return this.m_timeframe; }
   string            Symbol(void)                                          const { return this.m_symbol;    }
   CPatternsControl *GetObject(void)                                             { return &this;            }
   
protected:
//--- Создаёт объект управления указанным паттерном
   CPatternControl  *CreateObjControlPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[])
                       {
                        switch(pattern)
                          {
                           case PATTERN_TYPE_HARAMI               :  return NULL;
                           case PATTERN_TYPE_HARAMI_CROSS         :  return NULL;
                           case PATTERN_TYPE_TWEEZER              :  return NULL;
                           case PATTERN_TYPE_PIERCING_LINE        :  return NULL;
                           case PATTERN_TYPE_DARK_CLOUD_COVER     :  return NULL;
                           case PATTERN_TYPE_THREE_WHITE_SOLDIERS :  return NULL;
                           case PATTERN_TYPE_THREE_BLACK_CROWS    :  return NULL;
                           case PATTERN_TYPE_SHOOTING_STAR        :  return NULL;
                           case PATTERN_TYPE_HAMMER               :  return NULL;
                           case PATTERN_TYPE_INVERTED_HAMMER      :  return NULL;
                           case PATTERN_TYPE_HANGING_MAN          :  return NULL;
                           case PATTERN_TYPE_DOJI                 :  return NULL;
                           case PATTERN_TYPE_DRAGONFLY_DOJI       :  return NULL;
                           case PATTERN_TYPE_GRAVESTONE_DOJI      :  return NULL;
                           case PATTERN_TYPE_MORNING_STAR         :  return NULL;
                           case PATTERN_TYPE_MORNING_DOJI_STAR    :  return NULL;
                           case PATTERN_TYPE_EVENING_STAR         :  return NULL;
                           case PATTERN_TYPE_EVENING_DOJI_STAR    :  return NULL;
                           case PATTERN_TYPE_THREE_STARS          :  return NULL;
                           case PATTERN_TYPE_ABANDONED_BABY       :  return NULL;
                           case PATTERN_TYPE_PIVOT_POINT_REVERSAL :  return NULL;
                           case PATTERN_TYPE_OUTSIDE_BAR          :  return new CPatternControlOutsideBar(this.m_symbol,this.m_timeframe,this.m_list_series,this.m_list_all_patterns,param);
                           case PATTERN_TYPE_INSIDE_BAR           :  return new CPatternControlInsideBar(this.m_symbol,this.m_timeframe,this.m_list_series,this.m_list_all_patterns,param);
                           case PATTERN_TYPE_PIN_BAR              :  return new CPatternControlPinBar(this.m_symbol,this.m_timeframe,this.m_list_series,this.m_list_all_patterns,param);
                           case PATTERN_TYPE_RAILS                :  return NULL;
                           //---PATTERN_TYPE_NONE
                           default                                :  return NULL;
                          }
                       }
//--- Возвращает объект управления указанным паттерном
   CPatternControl  *GetObjControlPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[])
                       {
                        //--- В цикле по списку объектов управления
                        int total=this.m_list_controls.Total();
                        for(int i=0;i<total;i++)
                          {
                           //--- получаем очередной объект и,
                           CPatternControl *obj=this.m_list_controls.At(i);
                           //--- если это не объект управления указанным паттерном, идём к следующему
                           if(obj==NULL || obj.TypePattern()!=pattern)
                              continue;
                           //--- Проверка условий поиска и возврат результата
                           if(IsEqualMqlParamArrays(obj.PatternParams,param))
                              return obj;
                          }
                        //--- Не нашли - возвращаем NULL
                        return NULL;
                       }

public:
//--- Ищет и обновляет все активные паттерны
   void              RefreshAll(void)
                       {
                        //--- В цикле по списку объектов управления паттернами
                        int total=this.m_list_controls.Total();
                        for(int i=0;i<total;i++)
                          {
                           //--- получаем очередной объект управления
                           CPatternControl *obj=this.m_list_controls.At(i);
                           if(obj==NULL)
                              continue;
                           
                           //--- если это тестер и текущие размеры графика не заданы, или не равны текущим - пробуем их получить и установить
                           if(::MQLInfoInteger(MQL_TESTER))
                             {
                              long   int_value=0;
                              double dbl_value=0;
                              if(::ChartGetInteger(this.m_chart_id, CHART_SCALE, 0, int_value))
                                {
                                 if(obj.ChartScale()!=int_value)
                                    obj.SetChartScale((int)int_value);
                                }
                              if(::ChartGetInteger(this.m_chart_id, CHART_HEIGHT_IN_PIXELS, 0, int_value))
                                {
                                 if(obj.ChartHeightInPixels()!=int_value)
                                    obj.SetChartHeightInPixels((int)int_value);
                                }
                              if(::ChartGetInteger(this.m_chart_id, CHART_WIDTH_IN_PIXELS, 0, int_value))
                                {
                                 if(obj.ChartWidthInPixels()!=int_value)
                                    obj.SetChartWidthInPixels((int)int_value);
                                }
                              if(::ChartGetDouble(this.m_chart_id, CHART_PRICE_MAX, 0, dbl_value))
                                {
                                 if(obj.ChartPriceMax()!=dbl_value)
                                    obj.SetChartPriceMax(dbl_value);
                                }
                              if(::ChartGetDouble(this.m_chart_id, CHART_PRICE_MIN, 0, dbl_value))
                                {
                                 if(obj.ChartPriceMin()!=dbl_value)
                                    obj.SetChartPriceMin(dbl_value);
                                }
                             }
                           
                           //--- ищем и создаём новый паттерн
                           obj.CreateAndRefreshPatternList();
                          }
                       }

//--- Устанавливает параметры чарта для всех объектов управления паттернами
   void              SetChartPropertiesToPattCtrl(const double price_max,const double price_min,const int scale,const int height_px,const int width_px)
                       {
                        //--- В цикле по списку объектов управления паттернами
                        int total=this.m_list_controls.Total();
                        for(int i=0;i<total;i++)
                          {
                           //--- получаем очередной объект управления
                           CPatternControl *obj=this.m_list_controls.At(i);
                           if(obj==NULL)
                              continue;
                           //--- Если объект получен - устанавливаем параметры чарта
                           obj.SetChartHeightInPixels(height_px);
                           obj.SetChartWidthInPixels(width_px);
                           obj.SetChartScale(scale);
                           obj.SetChartPriceMax(price_max);
                           obj.SetChartPriceMin(price_min);
                          }
                       }
                       
//--- Устанавливает флаг использования указанного паттерна и создаёт объект управления если его ещё нет
   void              SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const bool flag)
                       {
                        CPatternControl *obj=NULL;
                        //--- Получаем указатель на объект управления указанным паттерном
                        obj=this.GetObjControlPattern(pattern,param);
                        //--- Если указатель получен (объект существует) - устанавливаем флаг использования
                        if(obj!=NULL)
                           obj.SetUsed(flag);
                        //--- Если объекта нет, а флаг передан как true
                        else if(flag)
                          {
                           //--- Создаём новый объект управления паттернами
                           obj=this.CreateObjControlPattern(pattern,param);
                           if(obj==NULL)
                              return;
                           //--- Добавляем указатель на созданный объект в список
                           if(!this.m_list_controls.Add(obj))
                             {
                              delete obj;
                              return;
                             }
                           //--- Устанавливаем флаг использования и параметры паттерна в объект управления
                           obj.SetUsed(flag);
                           obj.CreateAndRefreshPatternList();
                          }
                       }
//--- Возвращает флаг использования указанного паттерна
   bool              IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[])
                       {
                        CPatternControl *obj=this.GetObjControlPattern(pattern,param);
                        return(obj!=NULL ? obj.IsUsed() : false);
                       }

//--- Ставит метки указанного паттерна на графике
   void              DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const bool redraw=false)
                       {
                        CPatternControl *obj=GetObjControlPattern(pattern,param);
                        if(obj!=NULL)
                           obj.DrawPatterns(redraw);
                       }
//--- Перерисовывает объекты-рисунки указанного паттерна на графике
   void              RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const bool redraw=false)
                       {
                        CPatternControl *obj=GetObjControlPattern(pattern,param);
                        if(obj!=NULL)
                           obj.RedrawPatterns(redraw);
                       }
//--- Конструктор
                     CPatternsControl(const string symbol,const ENUM_TIMEFRAMES timeframe,CArrayObj *list_timeseries,CArrayObj *list_all_patterns);
  };
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CPatternsControl::CPatternsControl(const string symbol,const ENUM_TIMEFRAMES timeframe,CArrayObj *list_timeseries,CArrayObj *list_all_patterns)
  {
   this.m_type=OBJECT_DE_TYPE_SERIES_PATTERNS_CONTROLLERS;
   this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol);
   this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
   this.m_list_series=list_timeseries;
   this.m_list_all_patterns=list_all_patterns;
  }
//+------------------------------------------------------------------+

Логика методов прокомментирована в коде. Хочется пояснить по выделенному блоку кода: при работе в визуальном режиме тестера обработчик OnChartEvent() практически бесполезен. А именно в нём мы отслеживаем изменение размеров графика по событию CHARTEVENT_CHART_CHANGE и записываем новые размеры графика в объект управления паттернами, откуда эти данные поступают в объекты паттернов. Но в тестере это не работает. Поэтому в выделенном блоке кода в тестере отслеживается изменение размеров графика и, при его изменении, новые данные заносятся в объекты управления паттернами.

Далее по коду в этом же файле \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh у нас написан класс таймсерии CSeriesDE. Доработаем этот класс.

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

//+------------------------------------------------------------------+
//| Возвращает объект-бар по времени в таймсерии                     |
//+------------------------------------------------------------------+
CBar *CSeriesDE::GetBar(const datetime time)
  {
   CBar *obj=new CBar(); 
   if(obj==NULL)
      return NULL;
   obj.SetSymbolPeriod(this.m_symbol,this.m_timeframe,time);
   this.m_list_series.Sort(SORT_BY_BAR_TIME);
   int index=this.m_list_series.Search(obj);
   delete obj;
   return this.m_list_series.At(index);
  }

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

Объявим экземпляр объекта-бара для поиска:

//+------------------------------------------------------------------+
//| Класс "Таймсерия"                                                |
//+------------------------------------------------------------------+
class CSeriesDE : public CBaseObj
  {
private:
   CBar              m_bar_tmp;                                         // Объект-бар для поиска
   ENUM_TIMEFRAMES   m_timeframe;                                       // Таймфрейм
   string            m_symbol;                                          // Символ

и перепишем заново метод, возвращающий объект-бар по времени в таймсерии:

//+------------------------------------------------------------------+
//| Возвращает объект-бар по времени в таймсерии                     |
//+------------------------------------------------------------------+
CBar *CSeriesDE::GetBar(const datetime time)
  {
   this.m_bar_tmp.SetSymbolPeriod(this.m_symbol,this.m_timeframe,time);
   this.m_list_series.Sort(SORT_BY_BAR_TIME);
   int index=this.m_list_series.Search(&this.m_bar_tmp);
   return this.m_list_series.At(index);
  }

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

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

//+------------------------------------------------------------------+
//| Работа с паттернами                                              |
//+------------------------------------------------------------------+
//--- Устанавливает флаг использования указанного паттерна и создаёт объект управления если его ещё нет
   void              SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const bool flag)
                       {
                        if(this.m_patterns_control!=NULL)
                           this.m_patterns_control.SetUsedPattern(pattern,param,flag);
                       }
//--- Возвращает флаг использования указанного паттерна
   bool              IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[])
                       {
                        return(this.m_patterns_control!=NULL ? this.m_patterns_control.IsUsedPattern(pattern,param) : false);
                       }
//--- Ставит метки указанного паттерна на графике
   void              DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const bool redraw=false)
                       {
                        if(this.m_patterns_control!=NULL)
                           this.m_patterns_control.DrawPattern(pattern,param,redraw);
                       }
//--- Перерисовывает объекты-рисунки указанного паттерна на графике
   void              RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const bool redraw=false)
                       {
                        if(this.m_patterns_control!=NULL)
                           this.m_patterns_control.RedrawPattern(pattern,param,redraw);
                       }
//--- Устанавливает параметры чарта для объектов управления паттернами
   void              SetChartPropertiesToPattCtrl(const double price_max,const double price_min,const int scale,const int height_px,const int width_px)
                       {
                        if(this.m_patterns_control!=NULL)
                           this.m_patterns_control.SetChartPropertiesToPattCtrl(price_max,price_min,scale,height_px,width_px);
                       }

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

В классе таймсерии символа в файле D:\MetaQuotes\MT5\MQL5\Include\DoEasy\Objects\Series\TimeSeriesDE.mqh точно так же изменим методы для работы с паттернами.

В теле класса, в самом конце, под заголовком

//--- Конструкторы
                     CTimeSeriesDE(CArrayObj *list_all_patterns)
                       {
                        this.m_type=OBJECT_DE_TYPE_SERIES_SYMBOL;
                        this.m_list_all_patterns=list_all_patterns;
                       }
                     CTimeSeriesDE(CArrayObj *list_all_patterns,const string symbol);
                     
//+------------------------------------------------------------------+
//| Методы работы с паттернами                                       |
//+------------------------------------------------------------------+

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

//--- Конструкторы
                     CTimeSeriesDE(CArrayObj *list_all_patterns)
                       {
                        this.m_type=OBJECT_DE_TYPE_SERIES_SYMBOL;
                        this.m_list_all_patterns=list_all_patterns;
                       }
                     CTimeSeriesDE(CArrayObj *list_all_patterns,const string symbol);
                     
//+------------------------------------------------------------------+
//| Методы работы с паттернами                                       |
//+------------------------------------------------------------------+
//--- Устанавливает флаг использования указанного паттерна и создаёт объект управления если его ещё нет
   void              SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const ENUM_TIMEFRAMES timeframe,const bool flag);
//--- Возвращает флаг использования указанного паттерна Харами
   bool              IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const ENUM_TIMEFRAMES timeframe);
//--- Рисует метки указанного паттерна на графике
   void              DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false);
//--- Перерисовывает объекты-рисунки указанного паттерна на графике
   void              RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false);
//--- Устанавливает параметры чарта для объектов управления паттернами на указанном таймфрейме
   void              SetChartPropertiesToPattCtrl(const ENUM_TIMEFRAMES timeframe,const double price_max,const double price_min,const int scale,const int height_px,const int width_px);
  };

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

//+------------------------------------------------------------------+
//| Работа с паттернами таймсерий                                    |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Устанавливает флаг использования указанного паттерна             |
//| и создаёт объект управления если его ещё нет                     |
//+------------------------------------------------------------------+
void CTimeSeriesDE::SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const ENUM_TIMEFRAMES timeframe,const bool flag)
  {
   CSeriesDE *series=this.GetSeries(timeframe);
   if(series!=NULL)
      series.SetUsedPattern(pattern,param,flag);
  }
//+------------------------------------------------------------------+
//| Возвращает флаг использования указанного паттерна                |
//+------------------------------------------------------------------+
bool CTimeSeriesDE::IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const ENUM_TIMEFRAMES timeframe)
  {
   CSeriesDE *series=this.GetSeries(timeframe);
   return(series!=NULL ? series.IsUsedPattern(pattern,param) : false);
  }
//+------------------------------------------------------------------+
//| Рисует метки указанного паттерна на графике                      |
//+------------------------------------------------------------------+
void CTimeSeriesDE::DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false)
  {
   CSeriesDE *series=this.GetSeries(timeframe);
   if(series!=NULL)
      series.DrawPattern(pattern,param,redraw);
  }
//+------------------------------------------------------------------+
//| Перерисовывает объекты-рисунки указанного паттерна на графике    |
//+------------------------------------------------------------------+
void CTimeSeriesDE::RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false)
  {
   CSeriesDE *series=this.GetSeries(timeframe);
   if(series!=NULL)
      series.RedrawPattern(pattern,param,redraw);
  }
//+------------------------------------------------------------------+
//| Устанавливает параметры чарта для объектов управления паттернами |
//| на указанном таймфрейме                                          |
//+------------------------------------------------------------------+
void CTimeSeriesDE::SetChartPropertiesToPattCtrl(const ENUM_TIMEFRAMES timeframe,const double price_max,const double price_min,const int scale,const int height_px,const int width_px)
  {
   CSeriesDE *series=this.GetSeries(timeframe);
   if(series!=NULL)
      series.SetChartPropertiesToPattCtrl(price_max,price_min,scale,height_px,width_px);
  }

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

Сделаем доработки и в файле класса коллекции таймсерий \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh.

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

//+------------------------------------------------------------------+
//| Коллекция таймсерий символов                                     |
//+------------------------------------------------------------------+
class CTimeSeriesCollection : public CBaseObjExt
  {
private:
   CListObj                m_list;                    // Список используемых таймсерий символов
   CListObj                m_list_all_patterns;       // Список всех паттернов всех используемых таймсерий символов
   CChartObjCollection    *m_charts;                  // Указатель на коллекцию чартов
   double                  m_chart_max;               // Максимум графика
   double                  m_chart_min;               // Минимум графика
   int                     m_chart_scale;             // Масштаб графика
   int                     m_chart_wpx;               // Ширина графика в пикселях
   int                     m_chart_hpx;               // Высота графика в пикселях
   
//--- Возвращает индекс таймсерии по имени символа
   int                     IndexTimeSeries(const string symbol);
public:

Под заголовком  —

//+------------------------------------------------------------------+
//| Работа с паттернами таймсерий                                    |
//+------------------------------------------------------------------+

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

//--- Копирует в массив указанное double-свойство указанной таймсерии указанного символа
//--- Независимо от направления индексации массива, копирование производится как в массив-таймсерию
   bool                    CopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                const ENUM_BAR_PROP_DOUBLE property,
                                                double &array[],
                                                const double empty=EMPTY_VALUE);
   
//+------------------------------------------------------------------+
//| Работа с паттернами таймсерий                                    |
//+------------------------------------------------------------------+
//--- Устанавливает флаг использования указанного паттерна
   void                    SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag);
//--- Возвращает флаг использования указанного паттерна
   bool                    IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const string symbol,const ENUM_TIMEFRAMES timeframe);
//--- Рисует метки указанного паттерна на графике
   void                    DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false);
//--- Перерисовывает объекты-рисунки указанного паттерна на графике
   void                    RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false);
//--- Устанавливает параметры чарта для объектов управления паттернами на указанных символе и таймфрейме
   void                    SetChartPropertiesToPattCtrl(const string symbol,const ENUM_TIMEFRAMES timeframe,const double price_max,const double price_min,const int scale,const int height_px,const int width_px);

В конце тела класса объявим обработчик событий:

//--- Инициализация
   void                    OnInit(CChartObjCollection *charts) { this.m_charts=charts; }

//--- Обработчик событий
   virtual void            OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Конструктор
                           CTimeSeriesCollection(void);
  };

В конструкторе класса запишем размеры графика в новые переменные:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CTimeSeriesCollection::CTimeSeriesCollection(void)
  {
   this.m_type=COLLECTION_SERIES_ID;
   this.m_list.Clear();
   this.m_list.Sort();
   this.m_list.Type(COLLECTION_SERIES_ID);
   this.m_list_all_patterns.Clear();
   this.m_list_all_patterns.Sort();
   this.m_list_all_patterns.Type(COLLECTION_SERIES_PATTERNS_ID);
   this.m_chart_scale=(int)::ChartGetInteger(this.m_chart_id,CHART_SCALE);//-1;
   this.m_chart_max=::ChartGetDouble(this.m_chart_id, CHART_PRICE_MAX);
   this.m_chart_min=::ChartGetDouble(this.m_chart_id, CHART_PRICE_MIN);
   this.m_chart_wpx=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS);
   this.m_chart_hpx=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS);
  }

За пределами тела класса под заголовком  —

//+------------------------------------------------------------------+
//| Работа с паттернами таймсерий                                    |
//+------------------------------------------------------------------+

удалим все методы для работы с каждым конкретным паттерном. Вместо удалённых методов напишем новые — с указанием типа паттерна:

//+------------------------------------------------------------------+
//| Работа с паттернами таймсерий                                    |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Устанавливает флаг использования указанного паттерна             |
//| и создаёт объект управления если его ещё нет                     |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag)
  {
   CTimeSeriesDE *timeseries=this.GetTimeseries(symbol);
   if(timeseries!=NULL)
      timeseries.SetUsedPattern(pattern,param,timeframe,flag);
  }
//+------------------------------------------------------------------+
//| Возвращает флаг использования указанного паттерна                |

//+------------------------------------------------------------------+
bool CTimeSeriesCollection::IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   CTimeSeriesDE *timeseries=this.GetTimeseries(symbol);
   return(timeseries!=NULL ? timeseries.IsUsedPattern(pattern,param,timeframe) : false);
  }
//+------------------------------------------------------------------+
//| Рисует метки указанного паттерна на графике                      |

//+------------------------------------------------------------------+
void CTimeSeriesCollection::DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false)
  {
   CTimeSeriesDE *timeseries=this.GetTimeseries(symbol);
   if(timeseries!=NULL)
      timeseries.DrawPattern(pattern,param,timeframe,redraw);
  }
//+------------------------------------------------------------------+
//| Перерисовывает объекты-рисунки указанного паттерна на графике    |

//+------------------------------------------------------------------+
void CTimeSeriesCollection::RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false)
  {
   CTimeSeriesDE *timeseries=this.GetTimeseries(symbol);
   if(timeseries!=NULL)
      timeseries.RedrawPattern(pattern,param,timeframe,redraw);
  }
//+------------------------------------------------------------------+
//| Устанавливает параметры чарта для объектов управления паттернами |
//| на указанных символе и таймфрейме                                |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::SetChartPropertiesToPattCtrl(const string symbol,const ENUM_TIMEFRAMES timeframe,const double price_max,const double price_min,const int scale,const int height_px,const int width_px)
  {
   CTimeSeriesDE *timeseries=this.GetTimeseries(symbol);
   if(timeseries!=NULL)
      timeseries.SetChartPropertiesToPattCtrl(timeframe,price_max,price_min,scale,height_px,width_px);
  }

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

Ниже напишем обработчик событий:

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   //--- Получаем объект текущего графика
   CChartObj *chart=this.m_charts.GetChart(this.m_charts.GetMainChartID());
   if(chart==NULL)
      return;
   //--- Получаем объект главного окна текущего графика
   CChartWnd *wnd=this.m_charts.GetChartWindow(chart.ID(),0);
   if(wnd==NULL)
      return;
   //--- Если график изменён
   bool res=false;
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- контролируем изменение масштаба графика
      int scale=(int)::ChartGetInteger(chart.ID(),CHART_SCALE);
      if(this.m_chart_scale!=scale)
        {
         this.m_chart_scale=scale;
         res=true;
        }
      //--- контролируем изменение ширины графика в пикселях
      int chart_wpx=(int)::ChartGetInteger(chart.ID(),CHART_WIDTH_IN_PIXELS);
      if(this.m_chart_wpx!=chart_wpx)
        {
         this.m_chart_wpx=chart_wpx;
         res=true;
        }
      //--- контролируем изменение высоты графика в пикселях
      int chart_hpx=(int)::ChartGetInteger(chart.ID(),CHART_HEIGHT_IN_PIXELS);
      if(this.m_chart_hpx!=chart_hpx)
        {
         this.m_chart_hpx=chart_hpx;
         res=true;
        }
      //--- контролируем изменение максимума графика
      double chart_max=::ChartGetDouble(chart.ID(),CHART_PRICE_MAX);
      if(this.m_chart_max!=chart_max)
        {
         this.m_chart_max=chart_max;
         res=true;
        }
      //--- контролируем изменение минимума графика
      double chart_min=::ChartGetDouble(chart.ID(),CHART_PRICE_MIN);
      if(this.m_chart_min!=chart_min)
        {
         this.m_chart_min=chart_min;
         res=true;
        }
      //--- Если есть хоть одно изменение
      if(res)
        {
         //--- Записываем в объекты управления паттернами новые значения свойств графика
         this.SetChartPropertiesToPattCtrl(chart.Symbol(),chart.Timeframe(),chart_max,chart_min,scale,chart_hpx,chart_wpx);
         //--- Получаем список паттернов на текущем графике
         CArrayObj *list=CSelect::ByPatternProperty(this.GetListAllPatterns(),PATTERN_PROP_SYMBOL,chart.Symbol(),EQUAL);
         list=CSelect::ByPatternProperty(list,PATTERN_PROP_PERIOD,chart.Timeframe(),EQUAL);
         //--- Если список паттернов получен
         if(list!=NULL)
           {
            //--- В цикле по списку паттернов
            int total=list.Total();
            for(int i=0;i<total;i++)
              {
               //--- получаем очередной объект-паттерн
               CPattern *pattern=list.At(i);
               if(pattern==NULL)
                  continue;
               //--- Устанавливаем в объект-паттерн новые свойства графика
               pattern.SetChartWidthInPixels(this.m_chart_wpx);
               pattern.SetChartHeightInPixels(this.m_chart_hpx);
               pattern.SetChartScale(this.m_chart_scale);
               pattern.SetChartPriceMax(this.m_chart_max);
               pattern.SetChartPriceMin(this.m_chart_min);
               //--- Перерисовываем объект-рисунок паттерна с новыми размерами
               pattern.Redraw(false);
              }
            //--- Обновляем график
            ::ChartRedraw(chart.ID());
           }
        }  
     }
  }

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

Теперь доработаем главный класс библиотеки CEngine в файле \MQL5\Include\DoEasy\Engine.mqh.

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

//--- Устанавливает флаг использования паттерна Харами и создаёт объект управления если его ещё нет
   void                 SeriesSetUsedPatternHarami(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag)
                          {
                           MqlParam param[]={};
                           if(::ArrayResize(param,1)==1)
                             {
                              param[0].type=TYPE_UINT;
                              param[0].integer_value=0;
                             }
                           this.m_time_series.SetUsedPattern(PATTERN_TYPE_HARAMI,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag);
                          }

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

Рассмотрим методы для работы с уже созданными в библиотеке паттернами.

Метод, устанавливающий флаг использования паттерна Внешний бар (Поглощение) и создающий объект управления если его ещё нет:

//--- Устанавливает флаг использования паттерна Внешний бар (Поглощение) и создаёт объект управления если его ещё нет
   void                 SeriesSetUsedPatternOutsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                       const bool   flag,                       // Флаг использования Price Action Внешний бар
                                                       const double ratio_candles=70,           // Процентное отношение размера поглощающей свечи к размеру поглощаемой
                                                       const double ratio_body_to_shadows=80,   // Процентное отношение размеров теней к размеру тела свечи
                                                       const uint   min_body_size=3)            // Минимальный размер тела свечи
                          {
                           MqlParam param[]={};
                           if(::ArrayResize(param,3)==3)
                             {
                              param[0].type=TYPE_UINT;
                              param[0].integer_value=min_body_size;
                              //--- Процентное отношение размера поглощающей свечи к размеру поглощаемой
                              param[1].type=TYPE_DOUBLE;
                              param[1].double_value=ratio_candles;
                              //--- Процентное отношение размеров теней к размеру тела свечи
                              param[2].type=TYPE_DOUBLE;
                              param[2].double_value=ratio_body_to_shadows;
                             }
                           this.m_time_series.SetUsedPattern(PATTERN_TYPE_OUTSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag);
                          }

Метод, устанавливающий флаг использования паттерна Внутренний бар и создающий объект управления если его ещё нет:

//--- Устанавливает флаг использования паттерна Внутренний бар и создаёт объект управления если его ещё нет
   void                 SeriesSetUsedPatternInsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag,const uint min_body_size=3)
                          {
                           MqlParam param[]={};
                           if(::ArrayResize(param,1)==1)
                             {
                              param[0].type=TYPE_UINT;
                              param[0].integer_value=min_body_size;
                             }
                           this.m_time_series.SetUsedPattern(PATTERN_TYPE_INSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag);
                          }

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

Метод, устанавливающий флаг использования паттерна Пин бар и создающий объект управления если его ещё нет:

//--- Устанавливает флаг использования паттерна Пин бар и создаёт объект управления если его ещё нет
   void                 SeriesSetUsedPatternPinBar(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                   const bool   flag,                     // Флаг использования Price Action Пин бар
                                                   const double ratio_body=30,            // Процентное отношение тела свечи к полному размеру свечи
                                                   const double ratio_larger_shadow=60,   // Процентное отношение размера большей тени к размеру свечи
                                                   const double ratio_smaller_shadow=30,  // Процентное отношение размера меньшей тени к размеру свечи
                                                   const uint   min_body_size=3)          // Минимальный размер тела свечи
                          {
                           MqlParam param[]={};
                           if(::ArrayResize(param,4)==4)
                             {
                              param[0].type=TYPE_UINT;
                              param[0].integer_value=min_body_size;
                              //--- Процентное отношение тела свечи к полному размеру свечи
                              param[1].type=TYPE_DOUBLE;
                              param[1].double_value=ratio_body;
                              //--- Процентное отношение размера большей тени к размеру свечи
                              param[2].type=TYPE_DOUBLE;
                              param[2].double_value=ratio_larger_shadow;
                              //--- Процентное отношение размера меньшей тени к размеру свечи
                              param[3].type=TYPE_DOUBLE;
                              param[3].double_value=ratio_smaller_shadow;
                             }
                           this.m_time_series.SetUsedPattern(PATTERN_TYPE_PIN_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag);
                          }

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

  • для уже реализованных паттернов в библиотеке
  • для пока ещё не созданного паттерна:

//--- Рисует метки паттернов Внешний бар (Поглощение) на графике
   void                 SeriesDrawPatternOutsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                    const double ratio_candles=70,           // Процентное отношение размера поглощающей свечи к размеру поглощаемой
                                                    const double ratio_body_to_shadows=80,   // Процентное отношение размеров теней к размеру тела свечи
                                                    const uint   min_body_size=3,            // Минимальный размер тела свечи
                                                    const bool   redraw=false)               // Флаг перерисовки графика
                          {
                           MqlParam param[]={};
                           if(::ArrayResize(param,3)==3)
                             {
                              param[0].type=TYPE_UINT;
                              param[0].integer_value=min_body_size;
                              //--- Процентное отношение размера поглощающей свечи к размеру поглощаемой
                              param[1].type=TYPE_DOUBLE;
                              param[1].double_value=ratio_candles;
                              //--- Процентное отношение размеров теней к размеру тела свечи
                              param[2].type=TYPE_DOUBLE;
                              param[2].double_value=ratio_body_to_shadows;
                             }
                           this.m_time_series.DrawPattern(PATTERN_TYPE_OUTSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw);
                          }
//--- Рисует метки паттернов Внутренний бар на графике
   void                 SeriesDrawPatternInsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint min_body_size=3,const bool redraw=false)
                          {
                           MqlParam param[]={};
                           if(::ArrayResize(param,1)==1)
                             {
                              param[0].type=TYPE_UINT;
                              param[0].integer_value=min_body_size;
                             }
                           this.m_time_series.DrawPattern(PATTERN_TYPE_INSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw);
                          }
//--- Рисует метки паттернов Пин бар на графике
   void                 SeriesDrawPatternPinBar(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                const double ratio_body=30,               // Процентное отношение тела свечи к полному размеру свечи
                                                const double ratio_larger_shadow=60,      // Процентное отношение размера большей тени к размеру свечи
                                                const double ratio_smaller_shadow=30,     // Процентное отношение размера меньшей тени к размеру свечи
                                                const uint   min_body_size=3,             // Минимальный размер тела свечи
                                                const bool   redraw=false)                // Флаг перерисовки графика
                          {
                           MqlParam param[]={};
                           if(::ArrayResize(param,4)==4)
                             {
                              param[0].type=TYPE_UINT;
                              param[0].integer_value=min_body_size;
                              //--- Процентное отношение тела свечи к полному размеру свечи
                              param[1].type=TYPE_DOUBLE;
                              param[1].double_value=ratio_body;
                              //--- Процентное отношение размера большей тени к размеру свечи
                              param[2].type=TYPE_DOUBLE;
                              param[2].double_value=ratio_larger_shadow;
                              //--- Процентное отношение размера меньшей тени к размеру свечи
                              param[3].type=TYPE_DOUBLE;
                              param[3].double_value=ratio_smaller_shadow;
                             }
                           this.m_time_series.DrawPattern(PATTERN_TYPE_PIN_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw);
                           if(redraw)
                              ::ChartRedraw();
                          }
//--- Рисует метки паттернов Рельсы на графике
   void                 SeriesDrawPatternRails(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false)
                          {
                           MqlParam param[]={};
                           if(::ArrayResize(param,1)==1)
                             {
                              param[0].type=TYPE_UINT;
                              param[0].integer_value=0;
                             }
                           this.m_time_series.DrawPattern(PATTERN_TYPE_RAILS,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw);
                          }

Остальные методы для других паттернов идентичны методу для ещё не реализованного паттерна "Рельсы", и рассматривать их здесь не имеет смысла.

В самом конце тела класса объявим обработчик событий:

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

//--- Обработчик событий
void                    OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Обработка событий библиотеки DoEasy
void                    OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
//--- Работа с событиями в тестере
void                    EventsHandling(void);

  };

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

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CEngine::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   this.m_graph_objects.OnChartEvent(id,lparam,dparam,sparam);
   this.m_time_series.OnChartEvent(id,lparam,dparam,sparam);
  }

Здесь сначала вызывается обработчик событий класса-коллекции графических объектов, и следом — обработчик событий коллекции таймсерий.

В обработчике событий библиотеки CEngine::OnDoEasyEvent() в блоке обработки событий таймсерий добавим блок для будущей обработки событий появления новых паттернов:

//--- Обработка событий таймсерий
   else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE)
     {
      //--- Событие "Новый бар"
      if(idx==SERIES_EVENTS_NEW_BAR)
        {
         ::Print(DFUN,TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam));
         CArrayObj *list=this.m_buffers.GetListBuffersWithID();
         if(list!=NULL)
           {
            int total=list.Total();
            for(int i=0;i<total;i++)
              {
               CBuffer *buff=list.At(i);
               if(buff==NULL)
                  continue;
               string symbol=sparam;
               ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam;
               if(buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE)
                  continue;
               if(buff.Symbol()==symbol && buff.Timeframe()==timeframe )
                 {
                  CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe);
                  if(series==NULL)
                     continue;
                  int count=::fmin((int)series.AvailableUsedData(),::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated()));
                  this.m_buffers.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),count);
                 }
              }
           }
        }
      //--- Событие "Пропущены бары"
      if(idx==SERIES_EVENTS_MISSING_BARS)
        {
         ::Print(DFUN,TextByLanguage("Пропущены бары на ","Missed bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",(string)lparam);
        }
      //--- Событие "Новый паттерн"
      if(idx==SERIES_EVENTS_PATTERN)
        {
         // Здесь обработка события появления нового паттерна
        }
     }

Здесь пока пусто, но, когда в дальнейшем будем делать отсылку событий паттернов, пропишем сюда обработку появления нового паттерна.

На этом доработка библиотеки завершена. Теперь проверим работу по поиску новых паттернов и обработку изменения горизонтального масштаба графика.


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

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

Удалим из настроек флаг рисования паттернов точками:

sinput   double            InpPinBarRatioBody   =  30.0;                            // Pin Bar Ratio Body to Candle size
sinput   double            InpPinBarRatioLarger =  60.0;                            // Pin Bar Ratio Larger shadow to Candle size
sinput   double            InpPinBarRatioSmaller=  30.0;                            // Pin Bar Ratio Smaller shadow to Candle size
sinput   bool              InpDrawPatternsAsDots=  true;                            // Draw Patterns as dots

Добавим в настройки флаги использования паттернов и параметры для поиска паттерна "Внешний Бар":

sinput   ENUM_SYMBOLS_MODE InpModeUsedSymbols   =  SYMBOLS_MODE_CURRENT;            // Mode of used symbols list
sinput   string            InpUsedSymbols       =  "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)
sinput   ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_CURRENT;         // Mode of used timeframes list
sinput   string            InpUsedTFs           =  "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator)

sinput   ENUM_INPUT_YES_NO InpSearchPinBar      =  INPUT_YES;                       // Search for Pin Bar patterns
sinput   double            InpPinBarRatioBody   =  30.0;                            // Pin Bar Ratio Body to Candle size
sinput   double            InpPinBarRatioLarger =  60.0;                            // Pin Bar Ratio Larger shadow to Candle size
sinput   double            InpPinBarRatioSmaller=  30.0;                            // Pin Bar Ratio Smaller shadow to Candle size


sinput   ENUM_INPUT_YES_NO InpSearchInsideBar   =  INPUT_YES;                       // Search for Inside Bar patterns

sinput   ENUM_INPUT_YES_NO InpSearchOutsideBar  =  INPUT_YES;                       // Search for Outside Bar patterns
sinput   double            InpOBRatioCandles    =  50.0;                            // Outside Bar Ratio of sizes of neighboring candles
sinput   double            InpOBRatioBodyToCandle= 50.0;                            // Outside Bar Ratio Body to Candle size

sinput   ENUM_INPUT_YES_NO InpUseBook           =  INPUT_NO;                        // Use Depth of Market
sinput   ENUM_INPUT_YES_NO InpUseMqlSignals     =  INPUT_NO;                        // Use signal service
sinput   ENUM_INPUT_YES_NO InpUseCharts         =  INPUT_NO;                        // Use Charts control
sinput   ENUM_INPUT_YES_NO InpUseSounds         =  INPUT_YES;                       // Use sounds

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

//--- Очистим список всех паттернов
   engine.GetListAllPatterns().Clear();
   
//--- Установим флаг использования паттерна Пин-Бар с параметрами, заданными в настройках
   engine.SeriesSetUsedPatternPinBar(NULL,PERIOD_CURRENT,(bool)InpSearchPinBar,InpPinBarRatioBody,InpPinBarRatioLarger,InpPinBarRatioSmaller);
   
//--- Установим флаг использования паттерна Внутренний бар
   engine.SeriesSetUsedPatternInsideBar(NULL,PERIOD_CURRENT,(bool)InpSearchInsideBar);
 
//--- Установим флаг использования паттерна Внешний бар
   engine.SeriesSetUsedPatternOutsideBar(NULL,PERIOD_CURRENT,(bool)InpSearchOutsideBar,InpOBRatioCandles,InpOBRatioBodyToCandle);

//---
   ChartRedraw();
   return(INIT_SUCCEEDED);
  }

В обработчике OnChartEvent() советника добавим вызов этого обработчика для главного объекта библиотеки Engine:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Если работа в тестере - выход
   if(MQLInfoInteger(MQL_TESTER))
      return;
//--- Обработка событий мыши
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Обработка нажатий кнопок в панели
      if(StringFind(sparam,"BUTT_")>0)
         PressButtonEvents(sparam);
     }
//--- Обработка событий библиотеки DoEasy
   if(id>CHARTEVENT_CUSTOM-1)
     {
      OnDoEasyEvent(id,lparam,dparam,sparam);
     }
   engine.OnChartEvent(id,lparam,dparam,sparam);
//--- Изменение графика
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- При любом изменении графика скроем все информационные панели
      //... ... ...

В обработчике событий библиотеки — функции OnDoEasyEvent()

//+------------------------------------------------------------------+
//| Обработка событий библиотеки DoEasy                              |
//+------------------------------------------------------------------+
void OnDoEasyEvent(const int id,
                   const long &lparam,
                   const double &dparam,
                   const string &sparam)
  {
   int idx=id-CHARTEVENT_CUSTOM;
//--- Извлекаем из lparam (1) миллисекунды времени события, (2) причину, (3) источник события и (4) устанавливаем точное время события

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

//--- Обработка событий таймсерий
   else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE)
     {
      //--- Событие "Новый бар"
      if(idx==SERIES_EVENTS_NEW_BAR)
        {
         Print(TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam));
        }
      //--- Событие "Пропущены бары"
      if(idx==SERIES_EVENTS_MISSING_BARS)
        {
         Print(TextByLanguage("Пропущены бары на ","Missing bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",lparam);
        }
     }
     
//--- Обработка автоматических событий чартов

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

Скомпилируем советник и запустим его, установив для поиска паттерна "Внешний Бар" такие значения:

Такие маленькие значения для пропорций свечей ставим специально, чтобы было найдено как можно большее количество паттернов.
При нормальных значениях пропорций свечей (50% и более), паттерны более правильные, но достаточно редкие.

После запуска будут найдены и отображены паттерны "Внешний Бар":

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


Что дальше

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

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

К содержанию

Прикрепленные файлы |
MQL5.zip (4714.68 KB)
Треугольный арбитраж с прогнозами Треугольный арбитраж с прогнозами
В статье объясняется, как использовать треугольный арбитраж, а также как применять прогнозы и специализированное программное обеспечение для более разумной торговли валютами, даже если вы новичок на рынке. Готовы торговать как профессионалы?
Возможности Мастера MQL5, которые вам нужно знать (Часть 19): Байесовский вывод Возможности Мастера MQL5, которые вам нужно знать (Часть 19): Байесовский вывод
Байесовский вывод — это применение теоремы Байеса для обновления вероятностной гипотезы по мере поступления новой информации. Это намекает на необходимость адаптации в анализе временных рядов, и поэтому мы рассмотрим, как мы могли бы использовать его при создании пользовательских классов не только применительно к сигналам, но и для управления капиталом и трейлинг-стопами.
Нейросети в трейдинге: Сегментация данных на основе уточняющих выражений Нейросети в трейдинге: Сегментация данных на основе уточняющих выражений
В процессе анализа рыночной ситуации мы делим её на отдельные сегменты, выявляя ключевые тенденции. Однако традиционные методы анализа часто фокусируются на одном аспекте, что ограничивает восприятие. В данной статье мы познакомимся с методом, позволяющем выделять несколько объектов, что даёт более полное и многослойное понимание ситуации.
Оптимизация атмосферными облаками — Atmosphere Clouds Model Optimization (ACMO): Практика Оптимизация атмосферными облаками — Atmosphere Clouds Model Optimization (ACMO): Практика
В данной статье мы продолжим погружение в реализацию алгоритма ACMO (Atmospheric Cloud Model Optimization). В частности, обсудим два ключевых аспекта: перемещение облаков в регионы с низким давлением и моделирование процесса дождя, включая инициализацию капель и распределение их между облаками. Мы также разберем другие методы, которые играют важную роль в управлении состоянием облаков и обеспечении их взаимодействия с окружающей средой.