Русский Español Deutsch 日本語 Português
preview
DoEasy. Service functions (Part 3): Outside Bar pattern

DoEasy. Service functions (Part 3): Outside Bar pattern

MetaTrader 5Examples | 29 April 2025, 14:43
1 819 0
Artyom Trishkin
Artyom Trishkin

Contents


Concept

Let's continue the DoEasy library section on handling price patterns.

In the previous article, we have created a search and display of two-bar Inside Bar Price Action patterns. Here we will create a search for the Outside Bar pattern, which is essentially a mirror image of the inside bar.

But there are also differences. If the inside bar is a bidirectional pattern and entry can be made on either side of the pattern, then the Outside Bar pattern is divided into two directions - Bullish and Bearish:

  • BUOVB (Bullish Outside Vertical Bar) – bullish outside vertical bar. The signal bar completely covers the previous one, its closing price is higher than the maximum of the previous bar. Enter a trade at the breakthrough of the High of the signal bar + filter (5-10 points).
  • BEOVB (Bearish Outside Vertical Bar) – bearish vertical outside bar. The signal bar completely covers the previous one, its closing price is lower than the minimum of the previous bar. Entry is made upon breaking through the minimum of the signal bar - filter (5-10 points).

Before we start making the pattern class, we need to make some modifications to the already existing library classes. Firstly, all the methods in the pattern access classes are not created optimally - this was just a concept test, where everything was implemented "head-on" by many methods, each for its own type of patterns. Now we will make just one method to access work with the specified patterns. For each of the actions, we use its own method, where the required pattern will be indicated by a variable. This will greatly simplify and shorten the class codes.

Secondly, during the long period of time when there was no work on the library, some changes and additions occurred in the MQL5 language (not all announced changes have been added to the language yet), and we will add these changes to the library. I have also fixed a few errors found during the entire period of library tests. I will describe all the improvements made.



Improving library classes

When determining some patterns, the mutual relationship between the sizes of adjacent candles participating in the formation of the pattern figure is important. Let's add a new value to the pattern properties to determine the size ratio. In the properties of each pattern and pattern management class, add a property that will indicate the value, by which the ratio of candles is searched.

In the \MQL5\Include\DoEasy\Defines.mqh library file, namely in the enumeration of the real properties of the pattern, add new properties and increase the total number of real properties from 10 to 12:

//+------------------------------------------------------------------+
//| Pattern real properties                                          |
//+------------------------------------------------------------------+
enum ENUM_PATTERN_PROP_DOUBLE
  {
//--- bar data
   PATTERN_PROP_BAR_PRICE_OPEN = PATTERN_PROP_INTEGER_TOTAL,// Pattern defining bar Open price
   PATTERN_PROP_BAR_PRICE_HIGH,                             // Pattern defining bar High price
   PATTERN_PROP_BAR_PRICE_LOW,                              // Pattern defining bar Low price
   PATTERN_PROP_BAR_PRICE_CLOSE,                            // Pattern defining bar Close price
   PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE,                  // Percentage ratio of the candle body to the full size of the candle
   PATTERN_PROP_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE,          // Percentage ratio of the upper shadow size to the candle size
   PATTERN_PROP_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE,          // Percentage ratio of the lower shadow size to the candle size
   PATTERN_PROP_RATIO_CANDLE_SIZES,                         // Ratio of pattern candle sizes
   
   PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION,           // Defined criterion of the ratio of the candle body to the full candle size in %
   PATTERN_PROP_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRITERION,  // Defined criterion of the ratio of the maximum shadow to the candle size in %
   PATTERN_PROP_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRITERION, // Defined criterion of the ratio of the minimum shadow to the candle size in %
   PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION,               // Defined criterion for the ratio of pattern candle sizes
  }; 
#define PATTERN_PROP_DOUBLE_TOTAL   (12)                    // Total number of real pattern properties
#define PATTERN_PROP_DOUBLE_SKIP    (0)                     // Number of pattern properties not used in sorting

Starting from the beta version 4540 of the MetaTrader 5 client terminal, the ENUM_SYMBOL_SWAP_MODE enumeration features SYMBOL_SWAP_MODE_CURRENCY_PROFIT.

If the SymbolInfoInteger() function returns such a value, swaps on the account are calculated in the profit currency.

Let's add this value to the library files. In \MQL5\Include\DoEasy\Data.mqh, enter the indices of new library messages:

   MSG_SYM_SWAP_MODE_CURRENCY_MARGIN,                 // Swaps charged in money in symbol margin currency
   MSG_SYM_SWAP_MODE_CURRENCY_DEPOSIT,                // Swaps charged in money in client deposit currency
   MSG_SYM_SWAP_MODE_CURRENCY_PROFIT,                 // Swaps charged in money in profit calculation currency
   MSG_SYM_SWAP_MODE_INTEREST_CURRENT,                // Swaps charged as specified annual interest from symbol price at calculation of swap
   MSG_SYM_SWAP_MODE_INTEREST_OPEN,                   // Swaps charged as specified annual interest from position open price

...

   MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE,          // Percentage ratio of the candle body to the full size of the candle
   MSG_LIB_TEXT_PATTERN_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE,  // Percentage ratio of the upper shadow size to the candle size
   MSG_LIB_TEXT_PATTERN_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE,  // Percentage ratio of the lower shadow size to the candle size
   MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES,                 // Ratio of pattern candle sizes
   
   MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE_CRIT,           // Defined criterion of the ratio of the candle body to the full candle size in %
   MSG_LIB_TEXT_PATTERN_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRIT,  // Defined criterion of the ratio of the maximum shadow to the candle size in %
   MSG_LIB_TEXT_PATTERN_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRIT, // Defined criterion of the ratio of the minimum shadow to the candle size in %
   MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES_CRITERION,             // Defined criterion for the ratio of pattern candle sizes
   
   MSG_LIB_TEXT_PATTERN_NAME,                         // Name

and test messages corresponding to newly added indices:

   {"Свопы начисляются в деньгах в маржинальной валюте символа","Swaps charged in money in symbol margin currency"},
   {"Свопы начисляются в деньгах в валюте депозита клиента","Swaps 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 charged as specified annual interest from 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"},

Add the description of two new runtime errors with the codes 4306 and 4307 to the runtime error message array :

//+------------------------------------------------------------------+
//| Array of runtime error messages  (4301 - 4307)                   |
//| (MarketInfo)                                                     |
//| (1) in user's country language                                   |
//| (2) in the international language                                |
//+------------------------------------------------------------------+
string messages_runtime_market[][TOTAL_LANG]=
  {
   {"Неизвестный символ","Unknown symbol"},                                                                                                        // 4301
   {"Символ не выбран в MarketWatch","Symbol not selected in MarketWatch"},                                                                     // 4302
   {"Ошибочный идентификатор свойства символа","Wrong identifier of symbol property"},                                                           // 4303
   {"Время последнего тика неизвестно (тиков не было)","Time of the last tick 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"    
   },                                                                                             
  };

In \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh, add handling a new enumeration value to the method that returns the number of decimal places depending on the swap calculation method:

//+------------------------------------------------------------------+
//| Return the number of decimal places                              |
//| depending on the swap calculation method                         |
//+------------------------------------------------------------------+
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
     );
  }

In the method that returns the description of the swap calculation model, enter the return of a string with a description of the new swap calculation mode:

//+------------------------------------------------------------------+
//| Return the description of a swap calculation model               |
//+------------------------------------------------------------------+
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)
     );
  }

In the MQL4 definitions file \MQL5\Include\DoEasy\ToMQL4.mqh, add a new property to the enumeration of methods for calculating swaps when transferring a position:

//+------------------------------------------------------------------+
//| Swap charging methods during a rollover                          |
//+------------------------------------------------------------------+
enum ENUM_SYMBOL_SWAP_MODE
  {
   SYMBOL_SWAP_MODE_POINTS,                        // (MQL5 - 1, MQL4 - 0) Swaps are charged in points
   SYMBOL_SWAP_MODE_CURRENCY_SYMBOL,               // (MQL5 - 2, MQL4 - 1) Swaps are charged in money in symbol base currency
   SYMBOL_SWAP_MODE_INTEREST_OPEN,                 // (MQL5 - 6, MQL4 - 2) Swaps are charged as the specified annual interest from the open price of position
   SYMBOL_SWAP_MODE_CURRENCY_MARGIN,               // (MQL5 - 3, MQL4 - 3) Swaps are charged in money in margin currency of the symbol
   SYMBOL_SWAP_MODE_DISABLED,                      // (MQL5 - 0, MQL4 - N) No swaps
   SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT,              // Swaps are charged in money, in client deposit currency
   SYMBOL_SWAP_MODE_INTEREST_CURRENT,              // Swaps are charged as the specified annual interest from the instrument price at calculation of swap
   SYMBOL_SWAP_MODE_REOPEN_CURRENT,                // Swaps are charged by reopening positions by the close price
   SYMBOL_SWAP_MODE_REOPEN_BID,                    // Swaps are charged by reopening positions by the current Bid price
   SYMBOL_SWAP_MODE_CURRENCY_PROFIT                // Swaps charged in money in profit calculation currency
  };

In the \MQL5\Include\DoEasy\Objects\BaseObj.mqh library base object class file, fix the strings with potential memory leaks:

//+------------------------------------------------------------------+
//| Add the event object to the list                                 |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Add the object base event to the list                            |
//+------------------------------------------------------------------+
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);
  }

Here the methods return the result of adding the object to the list (true successful adding, false — error). If an error occurs while adding the object created by the new operator, the object will remain somewhere in memory without storing the pointer to it in the list, which will lead to memory leaks. Let's fix this:

//+------------------------------------------------------------------+
//| Add the event object to the list                                 |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Add the object base event to the list                            |
//+------------------------------------------------------------------+
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;
  }

Now, if adding an event object to the list fails, the newly created object will be deleted in the same way as if the same object was already in the list, and the method will return false.

In \MT5\MQL5\Include\DoEasy\Objects\Orders\Order.mqh, when calculating the number of points, it is necessary to add rounding of the obtained result, otherwise, for values less than 1, but close to one, we get a zero number of points when converting a real number to an integer:

//+------------------------------------------------------------------+
//| Get order profit in points                                       |
//+------------------------------------------------------------------+
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;
  }

In \MQL5\Include\DoEasy\Objects\Events\Event.mqh, there was an error in returning a long value as an enumeration one:

//--- When changing position direction, return (1) previous position order type, (2) previous position order ticket,
//--- (3) current position order type, (4) current position order ticket,
//--- (5) position type and (6) ticket before changing direction, (7) position type and (8) ticket after changing direction
   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();                                        }

While the value of the order ticket of long type did not exceed the INT_MAX value (enumeration data type — int), order tickets were returned correctly. But as soon as the ticket value exceeded INT_MAX, the overflow occurred and a negative number was returned. Now all is fixed:

//--- When changing position direction, return (1) previous position order type, (2) previous position order ticket,
//--- (3) current position order type, (4) current position order ticket,
//--- (5) position type and (6) ticket before changing direction, (7) position type and (8) ticket after changing direction
   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();                                        }

The enumeration returning the order system event status name was missing the modification status, and in some cases the trade event description was showing the status as "Unknown". Fix by adding the string:

//+------------------------------------------------------------------+
//| Return the event status name                                     |
//+------------------------------------------------------------------+
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)
     );
  }

Let's fix one omission in the trade object class: the policy of filling the volume (from the ENUM_ORDER_TYPE_FILLING enumeration) was incorrectly passed to the position opening method.

Let's implement the necessary improvements to the trading methods in MQL5\Include\DoEasy\Objects\Trade\TradeObj.mqh. I am going to add setting the volume filling policy to the block of filling the trade request structure in the method of opening a position:

//+------------------------------------------------------------------+
//| Open a position                                                  |
//+------------------------------------------------------------------+
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();
   //--- If failed to get the current prices, write the error code and description, send the message to the journal and return '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;
     }
   //--- Clear the structures
   ::ZeroMemory(this.m_request);
   ::ZeroMemory(this.m_result);
   //--- Fill in the request structure
   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);
   //--- Return the result of sending a request to the server
#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 
  }

In the class, the filling policy value is initially set to the m_type_filling variable when initializing the library with values allowed for orders (the CEngine::TradingSetCorrectTypeFilling method). If a negative value for the filling policy is passed to the open method, the value from the m_type_filling variable, set during library initialization, will be used. If we need to specify a different type of volume filling, then it should be passed in the type_filling method parameter, and the passed value will be used.

Previously, there was no added string. If another (non-default) policy was to be set, the filling policy was always Return (ORDER_FILLING_RETURN), since the type_filling field of the MqlTradeRequest structure was not filled and always had a zero value. Now it has been fixed.

Let's fix similar shortcomings in other methods of the class, where the volume filling policy is needed in one way or another:

//+------------------------------------------------------------------+
//| Close a position                                                 |
//+------------------------------------------------------------------+
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();
   //--- If the position selection failed. Write the error code and error description, send the message to the log and return '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;
     }
   //--- If failed to get the current prices, write the error code and description, send the message to the journal and return '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;
     }
   //--- Get a position type and an order type inverse of the position type
   ENUM_POSITION_TYPE position_type=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE);
   ENUM_ORDER_TYPE type=OrderTypeOppositeByPositionType(position_type);
   //--- Clear the structures
   ::ZeroMemory(this.m_request);
   ::ZeroMemory(this.m_result);
   //--- Fill in the request structure
   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;
   //--- In case of a hedging account, write the ticket of a closed position to the structure
   if(this.IsHedge())
      this.m_request.position=::PositionGetInteger(POSITION_TICKET);
   //--- Return the result of sending a request to the server
#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 
  }
//+------------------------------------------------------------------+
//| Close a position partially                                       |
//+------------------------------------------------------------------+
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();
   //--- If the position selection failed. Write the error code and error description, send the message to the log and return '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;
     }
   //--- If failed to get the current prices, write the error code and description, send the message to the journal and return '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;
     }
   //--- Get a position type and an order type inverse of the position type
   ENUM_POSITION_TYPE position_type=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE);
   ENUM_ORDER_TYPE type=OrderTypeOppositeByPositionType(position_type);
   //--- Get a position volume
   double position_volume=::PositionGetDouble(POSITION_VOLUME);
   //--- Clear the structures
   ::ZeroMemory(this.m_request);
   ::ZeroMemory(this.m_result);
   //--- Fill in the request structure
   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;
   //--- In case of a hedging account, write the ticket of a closed position to the structure
   if(this.IsHedge())
      this.m_request.position=::PositionGetInteger(POSITION_TICKET);
   //--- Return the result of sending a request to the server
#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 
  }

Now let's fix similar omissions in the library's trading class in \MQL5\Include\DoEasy\Trading.mqh.

In the OpenPosition() method, the trade request structure is filled with values based on the values passed to the method:

//--- Write the volume, deviation, comment and filling type to the request structure
   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());


and when calling the method for opening a position of a symbol trading object, its parameters do not specify the values set in the structure of the trading request, but, instead, those that were passed to the position opening method:

//--- In the loop by the number of attempts
   for(int i=0;i<this.m_total_try;i++)
     {                
      //--- Send a request
      res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation,type_filling);
      //... ... ...

Let's fix this:

      //--- Send a request
      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);

In order to track events occurring on all open charts, and not just on the one the program based on the library is attached to, an indicator is placed on each chart being opened (or already opened) that tracks chart events and sends them to the program.

If the client terminal was first connected to one server, and charts of various instruments were opened, and then the terminal was connected to another server, on which there are no instruments with their charts already open, then the program will not be able to place these indicators on such charts, and the journal will contain error entries that indicate an error in creating the indicator, and not the absence of symbols of open charts on the server. To fix these ambiguous log entries, we need to add a check for the presence of the symbol on the server before creating the indicator.

In \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, namely in the method creating the event control indicator, add a check for the presence of a symbol on the server:

//+------------------------------------------------------------------+
//| CChartObjectsControl: Create the event control indicator         |
//+------------------------------------------------------------------+
bool CChartObjectsControl::CreateEventControlInd(const long chart_id_main)
  {
//--- If the symbol is not on the server, return '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;
     }
//--- Create the indicator
   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;
  }

Now, if there is no symbol on the server, a message about this is displayed in the journal, and the method returns false.

In the \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh file of the base graphical object, namely in the method returning the description of the graphical element type, add a missing type — Bitmap graphical object:

//+------------------------------------------------------------------+
//| Return the description of the graphical element type             |
//+------------------------------------------------------------------+
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)                  :
      //--- Containers
      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)       :
      //--- Standard controls
      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)          :
      //--- Auxiliary control objects
      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"
     );
  }  

We will also slightly rework pattern classes - their organization and structure. The base abstract pattern class is located in \MQL5\Include\DoEasy\Objects\Series\Patterns\Pattern.mqh, and then the pattern classes inherited from the base class are written in it.

Let's separate all pattern classes into their own files. Now it is possible to display pattern icons as regular points on the chart, which are drawn using standard Trend line objects with both price and time coordinates on one bar. I decided to abandon this feature to simplify the code. All icons will be drawn on the Bitmap graphical objects. Pattern drawings are created based on the sizes of the bars that make up the pattern. To change the size of drawing objects when changing the horizontal scale of the chart, we need to enter a variable that stores the scale value. Variables for storing the remaining chart sizes are already included in the base pattern object. When resizing the chart, the new values will fit into all created objects of the found patterns, and they will be redrawn according to the new sizes.

In the \MT5\MQL5\Include\DoEasy\Objects\Series\Patterns\ library file, create two new files PatternPinBar.mqh and PatternInsideBar.mqh. There, we place the classes of Pin Bar and Inside Bar patterns (set directly in the base abstract pattern class file) by using Cut and Paste. Next, we will make changes to them, but for now we will continue editing the abstract pattern class.

From the protected section of the class, remove the m_draw_dots flag variable, indicating the way the pattern icons are drawn with dots, and declare the variable to store the chart width in pixels:

protected:
   CForm            *m_form;                                      // Pointer to form object
   CGCnvBitmap      *m_bitmap;                                    // Pointer to the bitmap object
   int               m_digits;                                    // Symbol's digits value
   ulong             m_symbol_code;                               // Symbol as a number (sum of name symbol codes)
   string            m_name_graph_obj;                            // Name of the graphical object displaying the pattern
   double            m_price;                                     // Price level the graphical object is placed at
   color             m_color_bullish;                             // Color of a graphical object set to the bullish pattern icon
   color             m_color_bearish;                             // Color of a graphical object set to the bearish pattern icon
   color             m_color_bidirect;                            // Color of a graphical object set to the bidirectional pattern icon
   color             m_color;                                     // Graphical object color
   color             m_color_panel_bullish;                       // Bullish pattern panel color
   color             m_color_panel_bearish;                       // Bearish pattern panel color
   color             m_color_panel_bidirect;                      // Bidirectional pattern panel color
   int               m_bars_formation;                            // Number of bars in the formation (nested pattern)
   bool              m_draw_dots;                                 // Draw on the chart with dots
   int               m_chart_scale;                               // Chart scale
   int               m_chart_height_px;                           // Height of the chart in pixels
   int               m_chart_width_px;                            // Height of the chart in pixels
   double            m_chart_price_max;                           // Chart maximum
   double            m_chart_price_min;                           // Chart minimum
   
public:

The methods that calculate the width and height of a bitmap object

//--- Calculate the bitmap object (1) width and (2) height
   int               GetBitmapWidth(void);
   int               GetBitmapHeight(void);

rename them to the correct names and declare them as virtual ones:

//--- Calculate the bitmap object (1) width and (2) height
   virtual int       CalculatetBitmapWidth(void);
   virtual int       CalculatetBitmapHeight(void);

Still, Get means "Receive", not "Calculate". Virtual methods will allow inherited classes to use their own calculations of the width and height of the pattern, depending on the type of pattern and the method of drawing it.

In the public section of the class, remove the SetDrawAsDots() method:

public:
//--- Remove a graphical object
   bool              DeleteGraphObj(bool redraw=false);
//--- Set graphical object display colors and pattern display color
   void              SetColors(const color color_bullish,const color color_bearish,const color color_bidirect,const bool redraw=false);
//--- Set the flag for drawing pattern labels as dots
   void              SetDrawAsDots(const bool flag)         { this.m_draw_dots=flag;            }
   
//--- Set the background color for the (1) bullish, (2) bearish and (3) bidirectional pattern panel
   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;  }
//--- Set the background color for the (1) bullish, (2) bearish and (3) bidirectional pattern panel by setting the values of the RGB color components
   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);

//--- Draw the pattern icon on the chart
   virtual void      Draw(const bool redraw);

//--- (1) Display, (2) hide the pattern icon on the chart
   void              Show(const bool redraw=false);
   void              Hide(const bool redraw=false);
//--- (1) Display and (2) hide the info panel on the chart
   void              ShowInfoPanel(const int x,const int y,const bool redraw=true);
   void              HideInfoPanel(void);

and declare the Redraw() virtual method:

public:
//--- Remove a graphical object
   bool              DeleteGraphObj(bool redraw=false);
//--- Set graphical object display colors and pattern display color
   void              SetColors(const color color_bullish,const color color_bearish,const color color_bidirect,const bool redraw=false);

//--- Set the background color for the (1) bullish, (2) bearish and (3) bidirectional pattern panel
   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;  }
//--- Set the background color for the (1) bullish, (2) bearish and (3) bidirectional pattern panel by setting the values of the RGB color components
   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) Draw and (2) resize the pattern icon on the chart
   virtual bool      Draw(const bool redraw);
   virtual bool      Redraw(const bool redraw)              { return true;                      }
//--- (1) Display, (2) hide the pattern icon on the chart
   void              Show(const bool redraw=false);
   void              Hide(const bool redraw=false);
//--- (1) Display and (2) hide the info panel on the chart
   void              ShowInfoPanel(const int x,const int y,const bool redraw=true);
   void              HideInfoPanel(void);

The Redraw() method redraws the bitmap object with the new dimensions. Since each pattern type can have its own types of bitmaps, the method is declared virtual, and here it simply returns true. In inherited classes, the method will be overridden to redraw exactly the bitmap that is drawn for the given pattern.

There, in the public section, we will also set the methods for setting and returning the chart width in pixels:

//--- Set the (1) chart scale, (2) height, (3) width in pixels, (4) high, (5) low
   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;  }
//--- Return the (1) chart scale, (2) height, (3) width in pixels, (4) high, (5) low
   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; }

When changing the width of the chart, the pattern management class will write the new chart sizes to all pattern objects one by one - so that in each of the created pattern objects you do not get the chart properties, but get the new sizes only once when they change, and then write them to all the created pattern objects.

At the very end of the class constructor, remove the string for initializing a variable that is no longer needed:

//--- Set base colors of the pattern information panels
   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;
  }

In the method that returns the description of the real property of the pattern, add two code blocks for displaying descriptions of two new pattern properties:

//+------------------------------------------------------------------+
//| Return the description of the pattern real property              |
//+------------------------------------------------------------------+
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)
         )  :
      ""
     );
  }

In the method that displays the info panel on the chart, there is no longer a need to get the chart properties, since they are already set in the pattern object when it is created, or when the chart is resized.

Remove the strings for receiving chart properties from the method:

//+------------------------------------------------------------------+
//| Display the info panel on the chart                              |
//+------------------------------------------------------------------+
void CPattern::ShowInfoPanel(const int x,const int y,const bool redraw=true)
  {
//--- If there is no panel object yet, create it
   if(this.m_form==NULL)
      if(!this.CreateInfoPanel())
         return;
//--- Get the chart width and height
   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);
//--- Calculate the X and Y coordinates of the panel so that it does not go beyond the chart

The following properties preset when creating the object are now used instead of the previously obtained chart properties:

//+------------------------------------------------------------------+
//| Display the info panel on the chart                              |
//+------------------------------------------------------------------+
void CPattern::ShowInfoPanel(const int x,const int y,const bool redraw=true)
  {
//--- If there is no panel object yet, create it
   if(this.m_form==NULL)
      if(!this.CreateInfoPanel())
         return;
//--- Calculate the X and Y coordinates of the panel so that it does not go beyond the chart
   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);
//--- Set the calculated coordinates and display the panel
   if(this.m_form.SetCoordX(cx) && this.m_form.SetCoordY(cy))
      this.m_form.Show();
   if(redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Remove the strings related to drawing the pattern icons with dots from the method of drawing, displaying and hiding pattern icons:

//+------------------------------------------------------------------+
//| Draw the pattern icon on the chart                               |
//+------------------------------------------------------------------+
void CPattern::Draw(const bool redraw)
  {
//--- If the graphical object has not yet been created, create it
   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);
//--- Otherwise - display
   else
      this.Show(redraw);
  }
//+------------------------------------------------------------------+
//| Display the pattern icon on the chart                            |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Hide the pattern icon on the chart                               |
//+------------------------------------------------------------------+
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);
  }

Now these methods look simpler:

//+------------------------------------------------------------------+
//| Draw the pattern icon on the chart                               |
//+------------------------------------------------------------------+
bool CPattern::Draw(const bool redraw)
  {
   this.Show(redraw);
   return true;
  }
//+------------------------------------------------------------------+
//| Display the pattern icon on the chart                            |
//+------------------------------------------------------------------+
void CPattern::Show(const bool redraw=false)
  {
   if(this.m_bitmap==NULL)
      return;
   this.m_bitmap.Show();
   if(redraw)
      ::ChartRedraw(this.m_chart_id);
  }
//+------------------------------------------------------------------+
//| Hide the pattern icon on the chart                               |
//+------------------------------------------------------------------+
void CPattern::Hide(const bool redraw=false)
  {
   if(this.m_bitmap==NULL)
      return;
   this.m_bitmap.Hide();
   if(redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Next, we will refine the pattern classes whose codes were transferred from the abstract pattern file.

Open \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternPinBar.mqh Pin Bar pattern class file and make the changes.

Previously, the icons of this pattern were drawn only as dots using standard graphical objects. Now we need to add methods for drawing points on the Bitmap graphical object.

Let's add the declaration of new methods to the class body:

//+------------------------------------------------------------------+
//|                                                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    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Pattern.mqh"
//+------------------------------------------------------------------+
//| Pin Bar pattern class                                            |
//+------------------------------------------------------------------+
class CPatternPinBar : public CPattern 
  {
protected:
//--- Create the (1) image object, the appearance of the (2) info panel and (3) the bitmap object
   virtual bool      CreateBitmap(void);
   virtual void      CreateInfoPanelView(void);
   void              CreateBitmapView(void);
   
//--- Calculate the bitmap object (1) width and (2) height
   virtual int       CalculatetBitmapWidth(void)                           { return(20);  }
   virtual int       CalculatetBitmapHeight(void)                          { return(40);  }

public:
//--- Return the flag of the pattern supporting the specified property
   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; }
   
//--- Return description of the pattern (1) status and (2) type
   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); }

//--- Draw the pattern icon on the chart
   virtual bool      Draw(const bool redraw);

//--- Constructor
                     CPatternPinBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct);
  };

The CalculatetBitmapWidth() and CalculatetBitmapHeight() virtual methods always return strictly specified dimensions of the image 20x40 pixels because for this pattern, which is drawn only on one bar, there is no need to calculate either the height or the width of the image - it must always be the same size. The anchor point of the bitmap is set at the center of the object, and the points are always drawn on the canvas in the upper or lower half of the bitmap, depending on the direction of the pattern. For the bullish pattern, the dot is drawn in the lower half of the bitmap, and for a bearish pattern - in the upper half. This makes it possible to display pattern points always at the same distance from the candle shadow, regardless of the vertical scale and chart period, which is quite convenient and practical.

In the implementation of the method that creates the appearance of the CreateInfoPanelView() info panel, remove the strings for getting the chart properties:

//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart
   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());

Now these properties are preset when creating an object, or updated when the chart is resized. Therefore, now we use the values from the variables that contain the chart width and height:

//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart
   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());

Let's implement the methods for drawing the pattern icon.

The method that draws the pattern icon on the chart:

//+------------------------------------------------------------------+
//| Draw the pattern icon on the chart                               |
//+------------------------------------------------------------------+
bool CPatternPinBar::Draw(const bool redraw)
  {
//--- If the bitmap object has not yet been created, create it
   if(this.m_bitmap==NULL)
     {
      if(!this.CreateBitmap())
         return false;
     }
//--- display
   this.Show(redraw);
   return true;
  }

If there is no physical bitmap object yet, we create it and then display it on the chart.

The method that creates the bitmap object:

//+------------------------------------------------------------------+
//| Create the bitmap object                                         |
//+------------------------------------------------------------------+
bool CPatternPinBar::CreateBitmap(void)
  {
//--- If the bitmap object has already been created earlier, return 'true'
   if(this.m_bitmap!=NULL)
      return true;
//--- Calculate the object coordinates and dimensions
   datetime time=this.MotherBarTime();
   double   price=(this.Direction()==PATTERN_DIRECTION_BULLISH ? this.MotherBarLow() : this.MotherBarHigh());
   int      w=this.CalculatetBitmapWidth();
   int      h=this.CalculatetBitmapHeight();
//--- Create the Bitmap object
   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;
//--- Set the object origin to its center and remove the tooltip
   ::ObjectSetInteger(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_ANCHOR,ANCHOR_CENTER);
   ::ObjectSetString(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_TOOLTIP,"\n");
   
//--- Draw the bitmap object appearance
   this.CreateBitmapView();
   return true;
  }

The logic of the method is commented in the code. Take into account the pattern direction when setting the object coordinates. If the pattern is bullish, the coordinate will be the Low price of the pattern bar, if it is bearish, the coordinate will be the High price.

The method creating the bitmap object appearance:

//+------------------------------------------------------------------+
//| Create the bitmap object appearance                              |
//+------------------------------------------------------------------+
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);
  }

First, clear the canvas with a completely transparent color. Then we determine the local coordinates of the point being drawn. For a bullish pattern, the Y coordinate will be half the height of the pattern plus 6 pixels (6 pixels below the center of the pattern). For the bear pattern, subtract 6 pixels from the coordinates of the bitmap center. The X coordinate will be half the bitmap width. The radius of the drawn circle is set to 2, which makes the point normally visible on any chart timeframe.

Now let's implement similar improvements to the \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternInsideBar.mqh Inside Bar pattern file.

Declare the virtual method of redrawing the pattern icon:

//+------------------------------------------------------------------+
//|                                             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    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Pattern.mqh"
//+------------------------------------------------------------------+
//| "Inside Bar" pattern class                                       |
//+------------------------------------------------------------------+
class CPatternInsideBar : public CPattern
  {
protected:
//--- Create the (1) image object, the appearance of the (2) info panel and (3) the bitmap object
   virtual bool      CreateBitmap(void);
   virtual void      CreateInfoPanelView(void);
   void              CreateBitmapView(void);
public:
//--- Return the flag of the pattern supporting the specified property
   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; }
   
//--- Return description of the pattern (1) status and (2) type
   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) Draw and (2) resize the pattern icon on the chart
   virtual bool      Draw(const bool redraw);
   virtual bool      Redraw(const bool redraw);

//--- Constructor
                     CPatternInsideBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct);
  };

Implement the method redrawing the pattern icon with new sizes outside the class body:

//+------------------------------------------------------------------+
//| Redraw the pattern icon on the chart with a new size             |
//+------------------------------------------------------------------+
bool CPatternInsideBar::Redraw(const bool redraw)
  {
//--- If a drawing object has not yet been created, create and display it in the Draw() method
   if(this.m_bitmap==NULL)
      return CPatternInsideBar::Draw(redraw);
//--- Calculate the new object dimensions
   int w=this.CalculatetBitmapWidth();
   int h=this.CalculatetBitmapHeight();
//--- If canvas resizing failed, return 'false'
   if(!this.m_bitmap.SetWidth(w) || !this.m_bitmap.SetHeight(h))
      return false;
//--- Draw the new bitmap object appearance with new dimensions
   this.CreateBitmapView();
//--- display and return 'true'
   this.Show(redraw);
   return true;
  }

The logic of the method is commented in the code - everything is extremely simple here: we calculate the new dimensions of the canvas, change its dimensions and draw a new bitmap on the canvas according to the new dimensions.

In the method that creates the appearance of the info panel, there was an error in calculating the pattern size in bars:

//--- Create strings to describe the pattern, its parameters and search criteria
   string name=::StringFormat("Inside Bar (%lu bars)",int(this.Time()-this.MotherBarTime())/::PeriodSeconds(this.Timeframe())+1);
   string param=this.DirectDescription();

This calculation has the right to exist, but only if adjacent bars of the pattern are not separated by weekends. If there are weekends between the left and right bars of the pattern, they are added to the number of bars, and instead of 2 we will get the value of 4.

Let's correct the calculation:

//--- Create strings to describe the pattern, its parameters and search criteria
   string name=::StringFormat("Inside Bar (%lu bars)",::Bars(this.Symbol(),this.Timeframe(),this.MotherBarTime(),this.Time()));
   string param=this.DirectDescription();

Now we will always get the correct number of bars between the time of two bars of the pattern.

Since the values of the chart width and height are now set in variables in advance, remove receiving the chart properties:

//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart
   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());

and adjust the calculation of the panel coordinates to use preset values from the variables:

//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart
   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());

Remove the code block for drawing dots in the method of drawing a pattern icon on a chart:

//+------------------------------------------------------------------+
//| Draw the pattern icon on the chart                               |
//+------------------------------------------------------------------+
void CPatternInsideBar::Draw(const bool redraw)
  {
//--- If the flag for drawing with dots is set, call the parent class method and leave
   if(this.m_draw_dots)
     {
      CPattern::Draw(redraw);
      return;
     }
//--- If the bitmap object has not yet been created, create it
   if(this.m_bitmap==NULL)
     {
      if(!this.CreateBitmap())
         return;
     }
//--- display
   this.Show(redraw);
  }

Now the method looks simpler:

//+------------------------------------------------------------------+
//| Draw the pattern icon on the chart                               |
//+------------------------------------------------------------------+
bool CPatternInsideBar::Draw(const bool redraw)
  {
//--- If the bitmap object has not yet been created, create it
   if(this.m_bitmap==NULL)
     {
      if(!this.CreateBitmap())
         return false;
     }
//--- display
   this.Show(redraw);
   return true;
  }

All other improvements will be implemented after creating the class of the new "Outside Bar" pattern.


"Outside bar" pattern class

In the \MQL5\Include\DoEasy\Objects\Series\Patterns\ library folder, create a new file PatternOutsideBar.mqh of the CPatternOutsideBar class.

The class should be inherited from the pattern object base class, while its file should be included in the created pattern class file:

//+------------------------------------------------------------------+
//|                                            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    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Pattern.mqh"
//+------------------------------------------------------------------+
//| "Outside bar" pattern class                                      |
//+------------------------------------------------------------------+
class CPatternOutsideBar : public CPattern
  {
  }

Declare the standard methods for pattern object classes:

//+------------------------------------------------------------------+
//| "Outside bar" pattern class                                      |
//+------------------------------------------------------------------+
class CPatternOutsideBar : public CPattern
  {
protected:
//--- Create the (1) image object, the appearance of the (2) info panel and (3) the bitmap object
   virtual bool      CreateBitmap(void);
   virtual void      CreateInfoPanelView(void);
   void              CreateBitmapView(void);
//--- Calculate the bitmap object height
   virtual int       CalculatetBitmapHeight(void);
public:
//--- Return the flag of the pattern supporting the specified property
   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; }
   
//--- Return description of the pattern (1) status and (2) type
   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) Draw and (2) resize the pattern icon on the chart
   virtual bool      Draw(const bool redraw);
   virtual bool      Redraw(const bool redraw);

//--- Constructor
                     CPatternOutsideBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct);
  };

All these methods are the same for all patterns. However, their implementation differs slightly from pattern to pattern. Let's look at each method.

In the class constructor, define the pattern name, number of candles included in the shape and the number of adjacent consecutive patterns equal to the number of pattern candles:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
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);
  }

The method that creates the appearance of the info panel:

//+------------------------------------------------------------------+
//| Create the info panel appearance                                 |
//+------------------------------------------------------------------+
void CPatternOutsideBar::CreateInfoPanelView(void)
  {
//--- If the form object is not created, leave
   if(this.m_form==NULL)
      return;
//--- Change the color tone for bullish and bearish patterns: the bullish ones will have a blue tint, while the bearish ones will have a red tint
   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;
//--- Declare the array for the initial and final colors of the gradient fill
   color clr[2]={};
//--- Depending on the direction of the pattern, change the lightness of the corresponding colors - the initial one is a little darker, the final one is a little lighter
   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;
     }
   
//--- Set the background and form frame colors
   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);
//--- Create strings to describe the pattern, its parameters and search criteria
   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));
//--- Set the coordinates of the panel and calculate its width and height depending on the size of the texts placed on the panel
   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())));
//--- Set the width and height of the panel according to the calculated values
   this.m_form.SetWidth(w);
   this.m_form.SetHeight(h);
//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart
   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);
//--- Fill the background with a gradient color
   this.m_form.Erase(clr,200,true,false);
//--- Draw the panel frame, an icon with (i), draw the header text with the proportions of a candle and separate the header with a horizontal line
   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);
//--- Under the horizontal line, enter the pattern description with its search criteria and the date of the pattern-defining bar
   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);
//--- Update the panel while redrawing the chart
   this.m_form.Update(true);
  }

The logic of the method is commented in the code. Here a panel is drawn with a gradient fill with a red tint for a bearish pattern and a blue tint for a bullish one. At the top, an icon with an (i) sign is drawn and the name of the pattern with its characteristics is written - the ratio of two bars of the pattern. The bottom part describes the direction of the pattern with the values given for the pattern search and the time of the found pattern.

The method that draws the pattern icon on the chart:

//+------------------------------------------------------------------+
//| Draw the pattern icon on the chart                               |
//+------------------------------------------------------------------+
bool CPatternOutsideBar::Draw(const bool redraw)
  {
//--- If the bitmap object has not yet been created, create it
   if(this.m_bitmap==NULL)
     {
      if(!this.CreateBitmap())
         return false;
     }
//--- display
   this.Show(redraw);
   return true;
  }

The method that redraws the pattern icon on the chart with a new size:

//+------------------------------------------------------------------+
//| Redraw the pattern icon on the chart with a new size             |
//+------------------------------------------------------------------+
bool CPatternOutsideBar::Redraw(const bool redraw)
  {
//--- If a drawing object has not yet been created, create and display it in the Draw() method
   if(this.m_bitmap==NULL)
      return CPatternOutsideBar::Draw(redraw);
//--- Calculate the new object dimensions
   int w=this.CalculatetBitmapWidth();
   int h=this.CalculatetBitmapHeight();
//--- If canvas resizing failed, return 'false'
   if(!this.m_bitmap.SetWidth(w) || !this.m_bitmap.SetHeight(h))
      return false;
//--- Draw the new bitmap object appearance with new dimensions
   this.CreateBitmapView();
//--- display and return 'true'
   this.Show(redraw);
   return true;
  }

Simply change the canvas size and re-draw a new pattern bitmap at the new size.

The method that creates the bitmap object:

//+------------------------------------------------------------------+
//| Create the bitmap object                                         |
//+------------------------------------------------------------------+
bool CPatternOutsideBar::CreateBitmap(void)
  {
//--- If the bitmap object has already been created earlier, return 'true'
   if(this.m_bitmap!=NULL)
      return true;
//--- Calculate the object coordinates and dimensions
   datetime time=this.MotherBarTime();
   double   price=(this.BarPriceHigh()+this.BarPriceLow())/2;
   int      w=this.CalculatetBitmapWidth();
   int      h=this.CalculatetBitmapHeight();
   
//--- Create the Bitmap object
   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;
//--- Set the object origin to its center and remove the tooltip
   ::ObjectSetInteger(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_ANCHOR,ANCHOR_CENTER);
   ::ObjectSetString(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_TOOLTIP,"\n");
   
//--- Draw the bitmap object appearance
   this.CreateBitmapView();
   return true;
  }

If the graphical object has already been created previously, then we simply leave the method. Otherwise, we get the price and time, at which the graphical object will be placed, calculate its width and height and create a new object. Then we set the anchor point in the center of the object and draw its appearance on it. The price the central point of the graphical object will stand on is calculated based on the center of the largest candle of the pattern.

The method that creates the appearance of a bitmap object:

//+------------------------------------------------------------------+
//| Create the bitmap object appearance                              |
//+------------------------------------------------------------------+
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);
  }

Here the bitmap is first erased using a completely transparent color. Then the local X coordinate of the start of the painted area is calculated, depending on the chart scale. A filled rectangle is drawn across the entire height of the graphical object with the calculated initial X coordinate, and a rectangular frame is drawn on top, using the same coordinates and with the same size.

The method that calculates the height of a drawing object:

//+------------------------------------------------------------------+
//| Calculate the bitmap object height                               |
//+------------------------------------------------------------------+
int CPatternOutsideBar::CalculatetBitmapHeight(void)
  {
//--- Calculate the chart price range and pattern price range
   double chart_price_range=this.m_chart_price_max-this.m_chart_price_min;
   double patt_price_range=this.BarPriceHigh()-this.BarPriceLow();
//--- Using the calculated price ranges, calculate and return the height of the bitmap object
   return (int)ceil(patt_price_range*this.m_chart_height_px/chart_price_range)+8;
  }

Here we get the price range of the chart - from the maximum price on the chart to the minimum price on the chart, and the price range of the pattern - from the High of the defining candle to the Low of the defining candle. Then we calculate the ratio of one range to another in pixels and return the resulting bitmap object height, adding 8 pixels to it - four at the top and four at the bottom.

The Outside Bar pattern class is ready. Now we need to get rid of a lot of identical methods in the timeseries classes, each of which does the same job for its own pattern. Let's just make one method for each action on the pattern, in which we will indicate the type of the desired pattern.

We will transfer all required parameters of the candlestick relationships included in the pattern to the pattern classes using the MqlParam structure. This will allow us to pass completely different parameters to different classes of different patterns, and not just those strictly defined by formal variables that are the same for all classes.

For now, we will not change the names of these variables, which indicate the various ratios of pattern candles. But if necessary, we will rename them into "faceless" variables, such as "param1", "param2", etc. In class constructors, we will assign the required pattern parameter to each such variable if necessary. For now, we will make do with the already named variables, which are the same for all patterns.

In the bar object class located in \MQL5\Include\DoEasy\Objects\Series\Bar.mqh, replace the AddPattern() method name with AddPatternType():

//--- Return itself
   CBar             *GetObject(void)                                    { return &this;}
//--- Set (1) bar symbol, timeframe and time, (2) bar object parameters
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   void              SetProperties(const MqlRates &rates);
//--- Add the pattern type on bar
   void              AddPatternType(const ENUM_PATTERN_TYPE pattern_type){ this.m_long_prop[BAR_PROP_PATTERNS_TYPE] |=pattern_type;          }

//--- Compare CBar objects by all possible properties (for sorting the lists by a specified bar object property)

Still, this method adds the pattern type to the bar object, and not a pointer to the pattern. Therefore, it is more logical to change the name of the method to a more correct one.

In order to be able to select and receive the required patterns from the lists, in \MQL5\Include\DoEasy\Services\Select.mqh file of the CSelect class, include all pattern files:

//+------------------------------------------------------------------+
//|                                                       Select.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#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"

This will make pattern classes available in timeseries classes for handling patterns.

We will need to compare arrays of MqlParam structures for equality. Let's write functions for comparing structure fields and structure arrays in \MQL5\Include\DoEasy\Services\DELib.mqh:

//+------------------------------------------------------------------+
//| Compare MqlParam structures with each other                      |
//+------------------------------------------------------------------+
bool IsEqualMqlParams(MqlParam &struct1,MqlParam &struct2)
  {
   if(struct1.type!=struct2.type)
      return false;
   switch(struct1.type)
     {
      //--- integer types
      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);
      //--- real types
      case TYPE_FLOAT   :
      case TYPE_DOUBLE  :  return(NormalizeDouble(struct1.double_value-struct2.double_value,DBL_DIG)==0);
      //--- string type
      case TYPE_STRING  :  return(struct1.string_value==struct2.string_value);
      default           :  return false;
     }
  }
//+------------------------------------------------------------------+
//| Compare array of MqlParam structures with each other             |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Pattern management classes are located in the \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh timeseries class file. In the abstract pattern management class, remove the no longer necessary variable and add new ones:

//+------------------------------------------------------------------+
//| Abstract pattern control class                                   |
//+------------------------------------------------------------------+
class CPatternControl : public CBaseObjExt
  {
private:
   ENUM_TIMEFRAMES   m_timeframe;                                                // Pattern timeseries chart period
   string            m_symbol;                                                   // Pattern timeseries symbol
   double            m_point;                                                    // Symbol Point
   bool              m_used;                                                     // Pattern use flag
   bool              m_drawing;                                                  // Flag for drawing the pattern icon on the chart
   bool              m_draw_dots;                                                // Flag for drawing the pattern icon on the chart with dots
//--- Handled pattern
   ENUM_PATTERN_TYPE m_type_pattern;                                             // Pattern type
protected:
//--- Candle proportions
   double            m_ratio_body_to_candle_size;                                // Percentage ratio of the candle body to the full size of the candle
   double            m_ratio_larger_shadow_to_candle_size;                       // Percentage ratio of the size of the larger shadow to the size of the candle
   double            m_ratio_smaller_shadow_to_candle_size;                      // Percentage ratio of the size of the smaller shadow to the size of the candle
   double            m_ratio_candle_sizes;                                       // Percentage of candle sizes
   uint              m_min_body_size;                                            // The minimum size of the candlestick body
   ulong             m_object_id;                                                // Unique object code based on pattern search criteria
//--- List views
   CArrayObj        *m_list_series;                                              // Pointer to the timeseries list
   CArrayObj        *m_list_all_patterns;                                        // Pointer to the list of all patterns
   CPattern          m_pattern_instance;                                         // Pattern object for searching by property
//--- Graph
   ulong             m_symbol_code;                                              // Chart symbol name as a number
   int               m_chart_scale;                                              // Chart scale
   int               m_chart_height_px;                                          // Height of the chart in pixels
   int               m_chart_width_px;                                           // Height of the chart in pixels
   double            m_chart_price_max;                                          // Chart maximum
   double            m_chart_price_min;                                          // Chart minimum

Previously, in the pattern search method, we passed the minimum candle size in the variable to search for a pattern:

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

Now we will transfer this size via the MqlParam variable, so now this variable is removed from all pattern search methods in all pattern management classes:

//--- (1) Search for a pattern, return direction (or -1 if no pattern is found),
//--- (2) create a pattern with a specified direction,
//--- (3) create and return a unique pattern code,
//--- (4) return the list of patterns managed by the object
   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;                                        }

From the public section of the class, remove methods for setting and getting properties of dot pattern drawing:

//--- (1) Set and (2) return the flag for drawing pattern icons as dots
   void              SetDrawingAsDots(const bool flag,const bool redraw);
   bool              IsDrawingAsDots(void)                                 const { return this.m_draw_dots;                            }

Declare the array of pattern parameters, add the methods for handling new variables, from the CreateAndRefreshPatternList() virtual method, remove passing the min_body_size parameter, while in the parametric constructor, add passing the array of pattern properties via the MqlParam structure array. Declare a new method to redraw all existing patterns on the chart:

public:
   MqlParam          PatternParams[];                                            // Array of pattern parameters
//--- Return itself
   CPatternControl  *GetObject(void)                                             { return &this;                                       }
//--- (1) Set and (2) return the pattern usage flag
   void              SetUsed(const bool flag)                                    { this.m_used=flag;                                   }
   bool              IsUsed(void)                                          const { return this.m_used;                                 }
//--- (1) Set and (2) return the pattern drawing flag
   void              SetDrawing(const bool flag)                                 { this.m_drawing=flag;                                }
   bool              IsDrawing(void)                                       const { return this.m_drawing;                              }

//--- Set the necessary percentage ratio of the candle body to the full size of the candle,
//--- size of the (2) upper and (3) lower shadow to the candle size, (4) sizes of candles, (5) minimum size of the candle body
   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;                         }
//--- Return the necessary percentage ratio of the candle body to the full size of the candle,
//--- size of the (2) upper and (3) lower shadow to the candle size, (4) sizes of candles, (5) minimum size of the candle body
   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;                   }
   
//--- Return object ID based on pattern search criteria
   virtual ulong     ObjectID(void)                                        const { return this.m_object_id;                            }

//--- Return pattern (1) type, (2) timeframe, (3) symbol, (4) symbol Point, (5) symbol code
   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;                          }
   
//--- Set the (1) chart scale, (2) height, (3) width in pixels, (4) high, (5) low
   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;                       }
//--- Return the (1) chart scale, (2) height, (3) width in pixels, (4) high, (5) low
   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;                      }

//--- Compare CPatternControl objects by all possible properties
   virtual int       Compare(const CObject *node,const int mode=0) const;

//--- Search for patterns and add found ones to the list of all patterns
   virtual int       CreateAndRefreshPatternList(void);
//--- Display patterns on the chart
   void              DrawPatterns(const bool redraw=false);
//--- Redraw patterns on the chart with a new size
   void              RedrawPatterns(const bool redraw=false);
   
//--- Protected parametric constructor
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[]);
  };

In the parametric constructor of the class, obtain the missing properties of the chart and fill in the pattern parameters from the array passed to the constructor:

//+------------------------------------------------------------------+
//| CPatternControl::Protected parametric constructor                |
//+------------------------------------------------------------------+
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);

//--- fill in the array of parameters with data from the array passed to constructor
   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;
     }
  }

Make the necessary fixes in the method that searches for patterns and adds those found to the list of all patterns:

//+------------------------------------------------------------------+
//| CPatternControl::Search for patterns and add                     |
//| found ones to the list of all patterns                           |
//+------------------------------------------------------------------+
int CPatternControl::CreateAndRefreshPatternList(void)
  {
//--- If not used, leave
   if(!this.m_used)
      return 0;
//--- Reset the timeseries event flag and clear the list of all timeseries pattern events
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- Get the opening date of the last (current) bar
   datetime time_open=0;
   if(!::SeriesInfoInteger(this.Symbol(),this.Timeframe(),SERIES_LASTBAR_DATE,time_open))
      return 0;
      
//--- Get a list of all bars in the timeseries except the current one
   CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,time_open,LESS);
   if(list==NULL || list.Total()==0)
      return 0;
//--- "Mother" bar data structure
   MqlRates pattern_mother_bar_data={};
//--- Sort the resulting list by bar opening time
   list.Sort(SORT_BY_BAR_TIME);
//--- In a loop from the latest bar,
   for(int i=list.Total()-1;i>=0;i--)
     {
      //--- get the next bar object from the list
      CBar *bar=list.At(i);
      if(bar==NULL)
         continue;
      //--- look for a pattern relative to the received bar
      ENUM_PATTERN_DIRECTION direction=this.FindPattern(bar.Time(),pattern_mother_bar_data);
      //--- If there is no pattern, go to the next bar
      if(direction==WRONG_VALUE)
         continue;
         
      //--- Pattern found on the current bar of the loop
      //--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol
      ulong code=this.GetPatternCode(direction,bar.Time());
      //--- Set the pattern code to the sample
      this.m_pattern_instance.SetProperty(PATTERN_PROP_CODE,code);
      //--- Sort the list of all patterns by the unique pattern code
      this.m_list_all_patterns.Sort(SORT_BY_PATTERN_CODE);
      //--- search for a pattern in the list using a unique code
      int index=this.m_list_all_patterns.Search(&this.m_pattern_instance);
      //--- If there is no pattern equal to the sample in the list of all patterns
      if(index==WRONG_VALUE)
        {
         //--- Create the pattern object
         CPattern *pattern=this.CreatePattern(direction,this.m_list_all_patterns.Total(),bar);
         if(pattern==NULL)
            continue;
         //--- Sort the list of all patterns by time and insert the pattern into the list by its time
         this.m_list_all_patterns.Sort(SORT_BY_PATTERN_TIME);
         if(!this.m_list_all_patterns.InsertSort(pattern))
           {
            delete pattern;
            continue;
           }
         //--- Add the pattern type to the list of pattern types of the bar object
         bar.AddPatternType(pattern.TypePattern());
         //--- Add the pointer to the bar the pattern object is found on, together with the mother bar data
         pattern.SetPatternBar(bar);
         pattern.SetMotherBarData(pattern_mother_bar_data);
         
         //--- set the chart data to the pattern object
         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 the drawing flag is set, draw the pattern label on the chart
         if(this.m_drawing)
            pattern.Draw(false);
         //--- Get the time of the penultimate bar in the collection list (timeseries bar with index 1)
         datetime time_prev=time_open-::PeriodSeconds(this.Timeframe());
         //--- If the current bar in the loop is the bar with index 1 in the timeseries, create and send a message
         if(bar.Time()==time_prev)
           {
            // Here is where the message is created and sent
           }
        }
     }
//--- Sort the list of all patterns by time and return the total number of patterns in the list
   this.m_list_all_patterns.Sort(SORT_BY_PATTERN_TIME);
   return m_list_all_patterns.Total();
  }

The method now has no formal parameters. When creating a new pattern, the chart properties are immediately transferred and set into it. At the very end of the pattern search loop block, insert a blank for creating a new pattern event. Further, in the following articles, we will send events about the appearance of a new timeseries pattern.

The method that redraws patterns on a chart with a new size of the bitmap object:

//+-------------------------------------------------------------------+
//|Redraw patterns on a chart with a new size of the bitmap object    |
//+-------------------------------------------------------------------+
void CPatternControl::RedrawPatterns(const bool redraw=false)
  {
//--- Get a list of patterns controlled by the control object
   CArrayObj *list=this.GetListPatterns();
   if(list==NULL || list.Total()==0)
      return;
//--- Sort the obtained list by pattern time
   list.Sort(SORT_BY_PATTERN_TIME);
//--- In a loop from the latest pattern,
   for(int i=list.Total()-1;i>=0;i--)
     {
      //--- get the next pattern object
      CPattern *obj=list.At(i);
      if(obj==NULL)
         continue;
      //--- Redraw the pattern bitmap object on the chart 
      obj.Redraw(false);
     }
//--- At the end of the cycle, redraw the chart if the flag is set
   if(redraw)
      ::ChartRedraw(this.m_chart_id);
  }

We simply loop through the list of timeseries patterns and call the redraw methods for each pattern. While the method is not optimal yet, redrawing is performed across the entire list of patterns. Although, it is possible to arrange redrawing of only the part of the history visible on the chart. I will get to it later.

Now let's refine the pattern management classes. They are located further in the same file. Let's find all occurrences of the string -

const uint min_body_size

in the formal parameters of class methods and delete it. Now the minimum size of the desired pattern candle is passed together with the pattern parameters in the MqlParam structure array.

Simply mark with color all the places where changes were made, so as not to describe here each such code string, which is the same for all classes.

In the Pin Bar pattern management class:

//+------------------------------------------------------------------+
//| Pin Bar pattern control class                                    |
//+------------------------------------------------------------------+
class CPatternControlPinBar : public CPatternControl
  {
protected:
//--- (1) Search for a pattern, return direction (or -1),
//--- (2) create a pattern with a specified direction,
//--- (3) create and return a unique pattern code
//--- (4) return the list of patterns managed by the object
   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
                       {
                        //--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol
                        return(time+PATTERN_TYPE_PIN_BAR+PATTERN_STATUS_PA+direction+this.Timeframe()+this.m_symbol_code);
                       }
   virtual CArrayObj*GetListPatterns(void);
//--- Create object ID based on pattern search criteria
   virtual ulong     CreateObjectID(void);

public:
//--- Parametric constructor
                     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::Search for the pattern                    |
//+------------------------------------------------------------------+
ENUM_PATTERN_DIRECTION CPatternControlPinBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const
  {
//--- Get data for one bar by time
   CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,series_bar_time,EQUAL);
//--- If the list is empty, return -1
   if(list==NULL || list.Total()==0)
      return WRONG_VALUE;
//--- he size of the candle body should be less than or equal to RatioBodyToCandleSizeValue() (default 30%) of the entire candle size,
//--- in this case, the body size should not be less than 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);
//--- If the list is empty - there are no patterns, return -1
   if(list==NULL || list.Total()==0)
      return WRONG_VALUE;
      
//--- Define the bullish pattern

In the Inside Bar pattern management class:

//+------------------------------------------------------------------+
//| Inside Bar pattern control class                                 |
//+------------------------------------------------------------------+
class CPatternControlInsideBar : public CPatternControl
  {
private:
//--- Check and return the presence of a pattern on two adjacent bars
   bool              CheckInsideBar(const CBar *bar1,const CBar *bar0) const
                       {
                        //--- If empty bar objects are passed, return 'false'
                        if(bar0==NULL || bar1==NULL)
                           return false;
                        //--- Return the fact that the bar on the right is completely within the dimensions of the bar on the left
                        return(bar0.High()<bar1.High() && bar0.Low()>bar1.Low());
                       }
   bool              FindMotherBar(CArrayObj *list,MqlRates &rates) const
                       {
                        bool res=false;
                        if(list==NULL)
                           return false;
                        //--- In a loop through the list, starting from the bar to the left of the base one
                        for(int i=list.Total()-2;i>0;i--)
                          {
                           //--- Get the pointers to two consecutive bars 
                           CBar *bar0=list.At(i);
                           CBar *bar1=list.At(i-1);
                           if(bar0==NULL || bar1==NULL)
                              return false;
                           //--- If the obtained bars represent a pattern 
                           if(CheckInsideBar(bar1,bar0))
                             {
                              //--- set mother bar data to the MqlRates variable and set 'res' to 'true'
                              this.SetBarData(bar1,rates);
                              res=true;
                             }
                           //--- If there is no pattern, interrupt the loop
                           else
                              break;
                          }
                        //--- return the result
                        return res;
                       }
protected:
//--- (1) Search for a pattern, return direction (or -1),
//--- (2) create a pattern with a specified direction,
//--- (3) create and return a unique pattern code
//--- (4) return the list of patterns managed by the object
   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
                       {
                        //--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol
                        return(time+PATTERN_TYPE_INSIDE_BAR+PATTERN_STATUS_PA+PATTERN_DIRECTION_BOTH+this.Timeframe()+this.m_symbol_code);
                       }
   virtual CArrayObj*GetListPatterns(void);
//--- Create object ID based on pattern search criteria
   virtual ulong     CreateObjectID(void);

public:
//--- Parametric constructor
                     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::Search for pattern                     |
//+------------------------------------------------------------------+
ENUM_PATTERN_DIRECTION CPatternControlInsideBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const
  {
//--- Get bar data up to and including the specified time

Similar to Pin Bar and Inside Bar management classes, we will implement the class for managing the Outside Bar pattern:

//+------------------------------------------------------------------+
//| Outside Bar pattern management class                             |
//+------------------------------------------------------------------+
class CPatternControlOutsideBar : public CPatternControl
  {
private:
//--- Check and return the flag of compliance with the ratio of the candle body to its size relative to the specified value
   bool              CheckProportions(const CBar *bar) const { return(bar.RatioBodyToCandleSize()>=this.RatioBodyToCandleSizeValue());  }
   
//--- Return the ratio of nearby candles for the specified bar
   double            GetRatioCandles(const CBar *bar)  const
                       {
                        //--- If an empty object is passed, return 0
                        if(bar==NULL)
                           return 0;
                        //--- Get a list of bars with a time less than that passed to the method
                        CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,bar.Time(),LESS);
                        if(list==NULL || list.Total()==0)
                           return 0;
                        //--- Set the flag of sorting by bar time for the list and
                        list.Sort(SORT_BY_BAR_TIME);
                        //--- get the pointer to the last bar in the list (closest to the one passed to the method)
                        CBar *bar1=list.At(list.Total()-1);
                        if(bar1==NULL)
                           return 0;
                        //--- Return the ratio of the sizes of one bar to another
                        return(bar.Size()>0 ? bar1.Size()*100.0/bar.Size() : 0.0);
                       }
//--- Check and return the presence of a pattern on two adjacent bars
   bool              CheckOutsideBar(const CBar *bar1,const CBar *bar0) const
                       {
                        //--- If empty bar objects are passed, or their types in direction are neither bullish nor bearish, or are the same - return '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;
                        //--- Calculate the ratio of the specified candles and, if it is less than the specified one, return 'false'
                        double ratio=(bar0.Size()>0 ? bar1.Size()*100.0/bar0.Size() : 0.0);
                        if(ratio<this.RatioCandleSizeValue())
                           return false;
                        //--- Return the fact that the bar body on the right completely covers the dimensions of the bar body on the left,
                        //--- and the shadows of the bars are either equal, or the shadows of the bar on the right overlap the shadows of the bar on the left
                        return(bar1.High()<=bar0.High() && bar1.Low()>=bar0.Low() && bar1.TopBody()<bar0.TopBody() && bar1.BottomBody()>bar0.BottomBody());
                       }
   
protected:
//--- (1) Search for a pattern, return direction (or -1),
//--- (2) create a pattern with a specified direction,
//--- (3) create and return a unique pattern code
//--- (4) return the list of patterns managed by the object
   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
                       {
                        //--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol
                        return(time+PATTERN_TYPE_OUTSIDE_BAR+PATTERN_STATUS_PA+direction+this.Timeframe()+this.m_symbol_code);
                       }
   virtual CArrayObj*GetListPatterns(void);
//--- Create object ID based on pattern search criteria
   virtual ulong     CreateObjectID(void);

public:
//--- Parametric constructor
                     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;      // Minimum size of pattern candles
                        this.m_ratio_candle_sizes=this.PatternParams[1].double_value;        // Percentage of the size of the absorbing candle to the size of the absorbed one
                        this.m_ratio_body_to_candle_size=this.PatternParams[2].double_value; // Percentage of full size to candle body size
                        this.m_object_id=this.CreateObjectID();
                       }
  };

The necessary conditions for defining the pattern are: the body of the bar on the right must completely cover the dimensions of the body of the bar on the left, and the shadows of the bars are either equal, or the shadows of the bar on the right can cover the shadows of the bar on the left. This search is performed in the CheckOutsideBar() method. In addition, it is necessary to take into account the ratio of the sizes of the candle bodies included in the pattern relative to the full size of the candles. This check is performed by the CheckProportions() method.

In the class constructor, we set the pattern status as "Price Action", the pattern type as "Outside Bar" and set all the pattern candle proportions from the array of structures passed to the constructor.

The method that creates an object ID based on pattern search criteria:

//+------------------------------------------------------------------+
//| Create object ID based on pattern search criteria                |
//+------------------------------------------------------------------+
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);
  }

Two criteria (percentage ratios of candle sizes and the ratio of the candle body to the full candle size) are specified in real numbers (in percent) and cannot exceed 100. Therefore, we convert them to integer values by multiplying by 100, and then create a ulong ID using the UshortToLong() extended standard object library method, filling the specified bits of the long number with ushort values:

//+------------------------------------------------------------------+
//| Pack a 'ushort' number to a passed 'long' number                 |
//+------------------------------------------------------------------+
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));
  }
//+------------------------------------------------------------------+
//| Convert a 'ushort' value to a specified 'long' number byte       |
//+------------------------------------------------------------------+
long CBaseObjExt::UshortToByte(const ushort value,const uchar to_byte) const
  {
   return(long)value<<(16*to_byte);
  }

The method that creates the pattern with the specified direction:

//+------------------------------------------------------------------+
//| Create a pattern with a specified direction                      |
//+------------------------------------------------------------------+
CPattern *CPatternControlOutsideBar::CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar)
  {
//--- If invalid indicator is passed to the bar object, return NULL
   if(bar==NULL)
      return NULL;
//--- Fill the MqlRates structure with bar data
   MqlRates rates={0};
   this.SetBarData(bar,rates);
//--- Create a new Outside Bar pattern
   CPatternOutsideBar *obj=new CPatternOutsideBar(id,this.Symbol(),this.Timeframe(),rates,direction);
   if(obj==NULL)
      return NULL;
//--- set the proportions of the candle the pattern was found on to the properties of the created pattern object
   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));
//--- set the search criteria of the candle the pattern was found on to the properties of the created pattern object
   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());
//--- Set the control object ID to the pattern object
   obj.SetProperty(PATTERN_PROP_CTRL_OBJ_ID,this.ObjectID());
//--- Return the pointer to a created object
   return obj;
  }

The method creates a new object of the "Outside Bar" pattern class, fills in data about its proportions and search criteria, and returns the pointer to the created object.

Pattern search method:

//+------------------------------------------------------------------+
//| CPatternControlOutsideBar::Search for pattern                    |
//+------------------------------------------------------------------+
ENUM_PATTERN_DIRECTION CPatternControlOutsideBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const
  {
//--- Get bar data up to and including the specified time
   CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,series_bar_time,EQUAL_OR_LESS);
//--- If the list is empty, return -1
   if(list==NULL || list.Total()==0)
      return WRONG_VALUE;
//--- Sort the list by bar opening time
   list.Sort(SORT_BY_BAR_TIME);
//--- Get the latest bar from the list (base bar)
   CBar *bar_patt=list.At(list.Total()-1);
   if(bar_patt==NULL)
      return WRONG_VALUE;
//--- In the loop from the next bar (mother),
   for(int i=list.Total()-2;i>=0;i--)
     {
      //--- Get the "mother" bar
      CBar *bar_prev=list.At(i);
      if(bar_prev==NULL)
         return WRONG_VALUE;
      //--- If the proportions of the bars do not match the pattern, return -1
      if(!this.CheckProportions(bar_patt) || !this.CheckProportions(bar_prev))
         return WRONG_VALUE;
      //--- check that the resulting two bars are a pattern. If not, return -1
      if(!this.CheckOutsideBar(bar_prev,bar_patt))
         return WRONG_VALUE;
      //--- Set the "mother" bar data
      this.SetBarData(bar_prev,mother_bar_data);
      //--- If the pattern is found at the previous step, determine and return its direction 
      if(bar_patt.TypeBody()==BAR_BODY_TYPE_BULLISH)
         return PATTERN_DIRECTION_BULLISH;
      if(bar_patt.TypeBody()==BAR_BODY_TYPE_BEARISH)
         return PATTERN_DIRECTION_BEARISH;
     }
//--- No patterns found - return -1
   return WRONG_VALUE;
  }

The method logic is described in the comments. The opening time of the current bar is passed to the method. Get the list of all bars except the current one (we do not look for patterns on the zero bar). In the loop based on the resulting list of bars, check every two nearby bars to see if they meet the pattern criteria. If their ratio is a pattern, determine and return the pattern direction.

The method returning a list of patterns managed by the object:

//+------------------------------------------------------------------+
//| Returns the list of patterns managed by the object               |
//+------------------------------------------------------------------+
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);
  }

From the general list of all patterns we get a list sorted by the chart period, then from the resulting list we get a list sorted by the symbol name. Next, the resulting list is sorted by the Outside Bar pattern type and the resulting list is sorted by the control object ID. As a result, the method returns a list of only those patterns that are managed by this class.

Now we need to completely rework the pattern management class. You can read more about it here.

Instead of long and monotonous methods for handling each of the pattern types, we will create several methods for handling the specified pattern. Then, in the same file, we will remove numerous similar methods from the pattern management class, such as:

... "Return the ... pattern control object" 
     CPatternControl  *GetObjControlPattern ...XXXXX(),

... "Set the flag for using the ... pattern and create a control object if it does not already exist" 
     void SetUsedPattern ... XXXXX(const bool flag),

... "Return the flag of using the ... pattern" 
     bool IsUsedPattern ...XXXXX(void),

... "Set the flag for drawing the ... pattern with dots" 
     void SetDrawingAsDotsPattern ...XXXXX(const bool flag,const bool redraw),

... "Return the flag for drawing the ... pattern with dots" 
     bool IsDrawingAsDotsPattern ...XXXXX(void),

... "Set ... pattern labels on the chart" 
     void DrawPattern ...XXXXX(const bool redraw=false)

In general, there are a lot of such methods - each pattern has its own method. As a result - almost 1300 code strings. Let's remove all the strings of the pattern management class and rewrite the entire class. Now there will be several methods for handling patterns, with the ability to choose which pattern to work with.

The completely rewritten class with all its methods will now look like this:

//+------------------------------------------------------------------+
//| Pattern control class                                            |
//+------------------------------------------------------------------+
class CPatternsControl : public CBaseObjExt
  {
private:
   CArrayObj         m_list_controls;                                   // List of pattern management controllers
   CArrayObj        *m_list_series;                                     // Pointer to the timeseries list
   CArrayObj        *m_list_all_patterns;                               // Pointer to the list of all patterns
//--- Timeseries data
   ENUM_TIMEFRAMES   m_timeframe;                                       // Timeseries timeframe
   string            m_symbol;                                          // Timeseries symbol
   
public:
//--- Return (1) timeframe, (2) timeseries symbol, (3) itself
   ENUM_TIMEFRAMES   Timeframe(void)                                       const { return this.m_timeframe; }
   string            Symbol(void)                                          const { return this.m_symbol;    }
   CPatternsControl *GetObject(void)                                             { return &this;            }
   
protected:
//--- Create an object for managing a specified pattern
   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;
                          }
                       }
//--- Return an object for managing a specified pattern
   CPatternControl  *GetObjControlPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[])
                       {
                        //--- In a loop through the list of control objects,
                        int total=this.m_list_controls.Total();
                        for(int i=0;i<total;i++)
                          {
                           //--- get the next object
                           CPatternControl *obj=this.m_list_controls.At(i);
                           //--- if this is not a pattern control object, go to the next one
                           if(obj==NULL || obj.TypePattern()!=pattern)
                              continue;
                           //--- Check search conditions and return the result
                           if(IsEqualMqlParamArrays(obj.PatternParams,param))
                              return obj;
                          }
                        //--- Not found - return NULL
                        return NULL;
                       }

public:
//--- Search and update all active patterns
   void              RefreshAll(void)
                       {
                        //--- In a loop through the list of pattern control objects,
                        int total=this.m_list_controls.Total();
                        for(int i=0;i<total;i++)
                          {
                           //--- get the next control object
                           CPatternControl *obj=this.m_list_controls.At(i);
                           if(obj==NULL)
                              continue;
                           
                           //--- if this is a tester and the current chart sizes are not set, or are not equal to the current ones - we try to get and set them
                           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);
                                }
                             }
                           
                           //--- search and create a new pattern
                           obj.CreateAndRefreshPatternList();
                          }
                       }

//--- Set chart parameters for all pattern management objects
   void              SetChartPropertiesToPattCtrl(const double price_max,const double price_min,const int scale,const int height_px,const int width_px)
                       {
                        //--- In a loop through the list of pattern control objects,
                        int total=this.m_list_controls.Total();
                        for(int i=0;i<total;i++)
                          {
                           //--- get the next control object
                           CPatternControl *obj=this.m_list_controls.At(i);
                           if(obj==NULL)
                              continue;
                           //--- If the object is received, set the chart parameters
                           obj.SetChartHeightInPixels(height_px);
                           obj.SetChartWidthInPixels(width_px);
                           obj.SetChartScale(scale);
                           obj.SetChartPriceMax(price_max);
                           obj.SetChartPriceMin(price_min);
                          }
                       }
                       
//--- Set the flag for using the specified pattern and create a control object if it does not already exist
   void              SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const bool flag)
                       {
                        CPatternControl *obj=NULL;
                        //--- Get the pointer to the object for managing the specified pattern
                        obj=this.GetObjControlPattern(pattern,param);
                        //--- If the pointer is received (the object exists), set the use flag 
                        if(obj!=NULL)
                           obj.SetUsed(flag);
                        //--- If there is no object and the flag is passed as 'true'
                        else if(flag)
                          {
                           //--- Create a new pattern management object
                           obj=this.CreateObjControlPattern(pattern,param);
                           if(obj==NULL)
                              return;
                           //--- Add pointer to the created object to the list
                           if(!this.m_list_controls.Add(obj))
                             {
                              delete obj;
                              return;
                             }
                           //--- Set the usage flag and pattern parameters to the control object
                           obj.SetUsed(flag);
                           obj.CreateAndRefreshPatternList();
                          }
                       }
//--- Return the flag of using the specified pattern
   bool              IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[])
                       {
                        CPatternControl *obj=this.GetObjControlPattern(pattern,param);
                        return(obj!=NULL ? obj.IsUsed() : false);
                       }

//--- Places marks of the specified pattern on the chart
   void              DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const bool redraw=false)
                       {
                        CPatternControl *obj=GetObjControlPattern(pattern,param);
                        if(obj!=NULL)
                           obj.DrawPatterns(redraw);
                       }
//--- Redraw the bitmap objects of the specified pattern on the chart
   void              RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const bool redraw=false)
                       {
                        CPatternControl *obj=GetObjControlPattern(pattern,param);
                        if(obj!=NULL)
                           obj.RedrawPatterns(redraw);
                       }
//--- Constructor
                     CPatternsControl(const string symbol,const ENUM_TIMEFRAMES timeframe,CArrayObj *list_timeseries,CArrayObj *list_all_patterns);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

The logic of the methods is commented in the code. I would like to make some clarifications concerning the highlighted code block: when working in the visual mode of the tester, OnChartEvent() is almost useless. However, it is there that we track the change in the chart size concerning the CHARTEVENT_CHART_CHANGE event and write the new chart sizes to the pattern management object, from where this data goes to the pattern objects. But it does not work in the tester. Therefore, changes in the size of the chart are monitored in the dedicated tester code block and, when it changes, new data is entered into the pattern control objects.

Further in the code in the same file \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh, we have the CSeriesDE timeseries class. Let's finalize it.

Every time we search for any bar object in the timeseries list, we have a new object with the help of the new operator. Its parameters are set, an object with identical parameters is searched in the list , and then this newly created object is deleted:

//+------------------------------------------------------------------+
//| Return the bar object by time in the timeseries                  |
//+------------------------------------------------------------------+
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);
  }

Thus, in some situations, we get a constant creation-deletion of an object in a loop. This behavior is incorrect. It is better to create a single instance object for searching and use it to set parameters and use as a pattern for searching. This way we will get rid of the constant re-creation of objects.

Declare an instance of the bar object for search:

//+------------------------------------------------------------------+
//| Timeseries class                                                 |
//+------------------------------------------------------------------+
class CSeriesDE : public CBaseObj
  {
private:
   CBar              m_bar_tmp;                                         // Bar object for search
   ENUM_TIMEFRAMES   m_timeframe;                                       // Timeframe
   string            m_symbol;                                          // Symbol

and rewrite the method that returns the bar object by time in the timeseries:

//+------------------------------------------------------------------+
//| Return the bar object by time in the timeseries                  |
//+------------------------------------------------------------------+
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);
  }

Now, instead of creating and deleting a new object, we set the required search parameters in a single instance and search for an identical instance in the list by pattern. The object is created only once in the class constructor and is used continuously without being recreated.

Just like in the pattern management class, we will remove the long lists of methods for handling each pattern and replace them with a few methods that select the desired pattern:

//+------------------------------------------------------------------+
//| Working with patterns                                            |
//+------------------------------------------------------------------+
//--- Set the flag for using the specified pattern and create a control object if it does not already exist
   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);
                       }
//--- Return the flag of using the specified pattern
   bool              IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[])
                       {
                        return(this.m_patterns_control!=NULL ? this.m_patterns_control.IsUsedPattern(pattern,param) : false);
                       }
//--- Places marks of the specified pattern on the chart
   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);
                       }
//--- Redraw the bitmap objects of the specified pattern on the chart
   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);
                       }
//--- Sets chart parameters for pattern management objects
   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);
                       }

Now, to select the desired pattern, it is sufficient to specify its type in the method parameters, rather than using a custom method for each of the patterns.

In the symbol timeseries class in \MetaQuotes\MT5\MQL5\Include\DoEasy\Objects\Series\TimeSeriesDE.mqh, change the methods for handling patterns in the same way.

In the body of the class, at the very end, under the header

//--- Constructors
                     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);
                     
//+------------------------------------------------------------------+
//| Methods for handling patterns                                    |
//+------------------------------------------------------------------+

there is a long list of method declarations for handling patterns. Let's replace all these declarations with declaring several methods with a selection of the desired pattern:

//--- Constructors
                     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);
                     
//+------------------------------------------------------------------+
//| Methods for handling patterns                                    |
//+------------------------------------------------------------------+
//--- Set the flag for using the specified pattern and create a control object if it does not already exist
   void              SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const ENUM_TIMEFRAMES timeframe,const bool flag);
//--- Return the flag of using the specified Harami pattern
   bool              IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const ENUM_TIMEFRAMES timeframe);
//--- Draw marks of the specified pattern on the chart
   void              DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false);
//--- Redraw the bitmap objects of the specified pattern on the chart
   void              RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false);
//--- Set chart parameters for pattern management objects on the specified timeframe
   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);
  };

Outside the class body, at the very end of the file listing, under the same header, the implementations of the declared methods are written. Let's remove this entire long list and replace it with new methods:

//+------------------------------------------------------------------+
//| Handling timeseries patterns                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Set the flag of using the specified pattern                      |
//| and create a control object if it does not exist yet             |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Return the flag of using the specified pattern                   |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Draw marks of the specified pattern on the chart                 |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Redraw the bitmap objects of the specified pattern on the chart  |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Set chart parameters for pattern management objects              |
//| on the specified timeframe                                       |
//+------------------------------------------------------------------+
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);
  }

Each method receives the required timeframe of the chart whose patterns are to be handled, as well as the necessary parameters used to select the pattern control object. Next, a pointer to the required timeseries is obtained, and the result of calling the timeseries class method of the same name is returned.

Let's make some improvements in the timeseries collection class file \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh.

In this class, we will receive the chart sizes and their changes, as well as send them to the pattern management classes. Let's add new variables to store the chart sizes:

//+------------------------------------------------------------------+
//| Symbol timeseries collection                                     |
//+------------------------------------------------------------------+
class CTimeSeriesCollection : public CBaseObjExt
  {
private:
   CListObj                m_list;                    // List of applied symbol timeseries
   CListObj                m_list_all_patterns;       // List of all patterns of all used symbol timeseries
   CChartObjCollection    *m_charts;                  // Pointer to the chart collection
   double                  m_chart_max;               // Chart maximum
   double                  m_chart_min;               // Chart minimum
   int                     m_chart_scale;             // Chart scale
   int                     m_chart_wpx;               // Chart width in pixels
   int                     m_chart_hpx;               // Chart height in pixels
   
//--- Return the timeseries index by symbol name
   int                     IndexTimeSeries(const string symbol);
public:

Under the header  —

//+------------------------------------------------------------------+
//| Handling timeseries patterns                                     |
//+------------------------------------------------------------------+

just like in the previous classes, remove the declared methods and enter new ones indicating the pattern type:

//--- Copy the specified double property of the specified timeseries of the specified symbol to the array
//--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array
   bool                    CopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                const ENUM_BAR_PROP_DOUBLE property,
                                                double &array[],
                                                const double empty=EMPTY_VALUE);
   
//+------------------------------------------------------------------+
//| Handling timeseries patterns                                     |
//+------------------------------------------------------------------+
//--- Set the flag of using the specified pattern
   void                    SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag);
//--- Return the flag of using the specified pattern
   bool                    IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const string symbol,const ENUM_TIMEFRAMES timeframe);
//--- Draw marks of the specified pattern on the chart
   void                    DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false);
//--- Redraw the bitmap objects of the specified pattern on the chart
   void                    RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false);
//--- Set chart parameters for pattern management objects on the specified symbol and timeframe
   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);

At the end of the class body, declare the event handler:

//--- Initialization
   void                    OnInit(CChartObjCollection *charts) { this.m_charts=charts; }

//--- Event handler
   virtual void            OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Constructor
                           CTimeSeriesCollection(void);
  };

In the class constructor, write the chart dimensions into new variables:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
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);
  }

Outside the class body, under the header  —

//+------------------------------------------------------------------+
//| Handling timeseries patterns                                     |
//+------------------------------------------------------------------+

remove all methods for handling each specific pattern. Instead of the removed methods, set new ones - indicating the pattern type:

//+------------------------------------------------------------------+
//| Handling timeseries patterns                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Set the flag of using the specified pattern                      |
//| and create a control object if it does not exist yet             |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Return the flag of using the specified pattern                   |

//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Draw marks of the specified pattern on the chart                 |

//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Redraw the bitmap objects of the specified pattern on the chart  |

//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Set chart parameters for pattern management objects              |
//| on the specified symbol and timeframe                            |
//+------------------------------------------------------------------+
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);
  }

Each method receives the required timeframe and symbol of the chart whose patterns are to be handled, as well as the necessary parameters used to select the pattern control object. Next, a pointer to the required timeseries is obtained, and the result of calling the symbol timeseries class method of the same name is returned.

Let's implement the event handler:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   //--- Get the current chart object
   CChartObj *chart=this.m_charts.GetChart(this.m_charts.GetMainChartID());
   if(chart==NULL)
      return;
   //--- Get the main window object of the current chart
   CChartWnd *wnd=this.m_charts.GetChartWindow(chart.ID(),0);
   if(wnd==NULL)
      return;
   //--- If the chart is changed
   bool res=false;
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- control the change in the chart scale
      int scale=(int)::ChartGetInteger(chart.ID(),CHART_SCALE);
      if(this.m_chart_scale!=scale)
        {
         this.m_chart_scale=scale;
         res=true;
        }
      //--- control the change in the chart width in pixels
      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;
        }
      //--- control the change in the chart height in pixels
      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;
        }
      //--- control the change in the chart maximum
      double chart_max=::ChartGetDouble(chart.ID(),CHART_PRICE_MAX);
      if(this.m_chart_max!=chart_max)
        {
         this.m_chart_max=chart_max;
         res=true;
        }
      //--- control the change in the chart minimum
      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 there is at least one change
      if(res)
        {
         //--- Write new values of the chart properties to the pattern management objects
         this.SetChartPropertiesToPattCtrl(chart.Symbol(),chart.Timeframe(),chart_max,chart_min,scale,chart_hpx,chart_wpx);
         //--- Get a list of patterns on the current chart
         CArrayObj *list=CSelect::ByPatternProperty(this.GetListAllPatterns(),PATTERN_PROP_SYMBOL,chart.Symbol(),EQUAL);
         list=CSelect::ByPatternProperty(list,PATTERN_PROP_PERIOD,chart.Timeframe(),EQUAL);
         //--- If the list of patterns is received
         if(list!=NULL)
           {
            //--- In a loop by the list of patterns,
            int total=list.Total();
            for(int i=0;i<total;i++)
              {
               //--- get the next pattern object
               CPattern *pattern=list.At(i);
               if(pattern==NULL)
                  continue;
               //--- set the new chart data to the pattern object
               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);
               //--- Redraw the pattern bitmap object with new dimensions
               pattern.Redraw(false);
              }
            //--- Update the chart
            ::ChartRedraw(chart.ID());
           }
        }  
     }
  }

The entire logic of the handler is fully described in the comments to the code. This is where, when a change in chart size is detected, the new sizes are passed to the timeseries pattern management classes and the patterns are redrawn according to the new chart size.

Now let's modify the main class of the CEngine library in \MQL5\Include\DoEasy\Engine.mqh.

Here too, in the class body there is a long list of methods for handling patterns. Let's remove only those methods that are responsible for drawing patterns in the form of dots. The remaining methods need to be improved - now it is necessary to send pattern parameters to the methods of handling it using the MqlParam structure array. In all methods that are not used yet, fill only one structure field, in which the minimum size of the pattern candles is sent, for example:

//--- Set the flag for using the Harami pattern and create a control object if it does not already exist
   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);
                          }

The remaining methods for the patterns that are not yet used are identical and there is no need to consider them - you can find them in the files attached to the article.

Let's consider the methods for handling patterns already created in the library.

The method that sets the flag for using the Outside Bar (Engulfing) pattern and creates a control object if it does not exist yet:

//--- Set the flag for using the Pattern Outside and create a control object if it does not already exist
   void                 SeriesSetUsedPatternOutsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                       const bool   flag,                       // Price Action Outside Bar usage flag
                                                       const double ratio_candles=70,           // Percentage ratio of the size of the engulfing color to the size of the engulfed one
                                                       const double ratio_body_to_shadows=80,   // Percentage ratio of shadow sizes to candle body size
                                                       const uint   min_body_size=3)            // Minimum candle body size
                          {
                           MqlParam param[]={};
                           if(::ArrayResize(param,3)==3)
                             {
                              param[0].type=TYPE_UINT;
                              param[0].integer_value=min_body_size;
                              //--- Percentage ratio of the size of the engulfing color to the size of the engulfed one
                              param[1].type=TYPE_DOUBLE;
                              param[1].double_value=ratio_candles;
                              //--- Percentage ratio of shadow sizes to candle body size
                              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);
                          }

The method setting the flag of using the Inside Bar pattern and creating the control object if it does not exist yet:

//--- Set the flag for using the Pattern Inside and create a control object if it does not already exist
   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);
                          }

In this method, only one field of the structure is filled in, since the pattern has no customizable properties, except for the property of the minimum candle size, which is common to all patterns.

The method setting the flag of using the Pin Bar pattern and creating the control object if it does not exist yet:

//--- Set the flag for using the Pin Bar pattern and create a control object if it does not already exist
   void                 SeriesSetUsedPatternPinBar(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                   const bool   flag,                     // Price Action Pin Bar usage flag
                                                   const double ratio_body=30,            // Percentage ratio of the candle body to the full size of the candle
                                                   const double ratio_larger_shadow=60,   // Percentage ratio of the size of the larger shadow to the size of the candle
                                                   const double ratio_smaller_shadow=30,  // Percentage ratio of the size of the smaller shadow to the size of the candle
                                                   const uint   min_body_size=3)          // Minimum candle body size
                          {
                           MqlParam param[]={};
                           if(::ArrayResize(param,4)==4)
                             {
                              param[0].type=TYPE_UINT;
                              param[0].integer_value=min_body_size;
                              //--- Percentage ratio of the candle body to the full size of the candle
                              param[1].type=TYPE_DOUBLE;
                              param[1].double_value=ratio_body;
                              //--- Percentage ratio of the size of the larger shadow to the size of the candle
                              param[2].type=TYPE_DOUBLE;
                              param[2].double_value=ratio_larger_shadow;
                              //--- Percentage ratio of the size of the smaller shadow to the size of the candle
                              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);
                          }

Let's look at methods for displaying patterns on a chart:

  • for already implemented patterns in the library
  • for a pattern that has not yet been created:

//--- Draw Pattern Outside labels on the chart
   void                 SeriesDrawPatternOutsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                    const double ratio_candles=70,           // Percentage ratio of the size of the engulfing color to the size of the engulfed one
                                                    const double ratio_body_to_shadows=80,   // Percentage ratio of shadow sizes to candle body size
                                                    const uint   min_body_size=3,            // Minimum candle body size
                                                    const bool   redraw=false)               // Chart redraw flag
                          {
                           MqlParam param[]={};
                           if(::ArrayResize(param,3)==3)
                             {
                              param[0].type=TYPE_UINT;
                              param[0].integer_value=min_body_size;
                              //--- Percentage ratio of the size of the engulfing color to the size of the engulfed one
                              param[1].type=TYPE_DOUBLE;
                              param[1].double_value=ratio_candles;
                              //--- Percentage ratio of shadow sizes to candle body size
                              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);
                          }
//--- Draw Inside Bar pattern labels on the chart
   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);
                          }
//--- Draw Pin Bar pattern labels on the chart
   void                 SeriesDrawPatternPinBar(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                const double ratio_body=30,               // Percentage ratio of the candle body to the full size of the candle
                                                const double ratio_larger_shadow=60,      // Percentage ratio of the size of the larger shadow to the size of the candle
                                                const double ratio_smaller_shadow=30,     // Percentage ratio of the size of the smaller shadow to the size of the candle
                                                const uint   min_body_size=3,             // Minimum candle body size
                                                const bool   redraw=false)                // Chart redraw flag
                          {
                           MqlParam param[]={};
                           if(::ArrayResize(param,4)==4)
                             {
                              param[0].type=TYPE_UINT;
                              param[0].integer_value=min_body_size;
                              //--- Percentage ratio of the candle body to the full size of the candle
                              param[1].type=TYPE_DOUBLE;
                              param[1].double_value=ratio_body;
                              //--- Percentage ratio of the size of the larger shadow to the size of the candle
                              param[2].type=TYPE_DOUBLE;
                              param[2].double_value=ratio_larger_shadow;
                              //--- Percentage ratio of the size of the smaller shadow to the size of the candle
                              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();
                          }
//--- Draw Rails pattern labels on the chart
   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);
                          }

The remaining methods for other patterns are identical to the method for the not yet implemented Rails pattern, and there is no point in considering them here.

At the very end of the class body, declare the event handler:

public:
//--- Create and return the composite magic number from the specified magic number value, the first and second group IDs and the pending request ID
   uint                 SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0);

//--- Event handler
void                    OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Handling DoEasy library events
void                    OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
//--- Working with events in the tester
void                    EventsHandling(void);

  };

Let's write its implementation outside the class body:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
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);
  }

Here, the event handler of the graphical object collection class is called first followed by the event handler of the timeseries collection.

In the event handler of the CEngine::OnDoEasyEvent() library, namely in the timeseries events handling block, add the block for future handling of new patterns events:

//--- Handling timeseries events
   else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE)
     {
      //--- "New bar" event
      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);
                 }
              }
           }
        }
      //--- "Bars skipped" event
      if(idx==SERIES_EVENTS_MISSING_BARS)
        {
         ::Print(DFUN,TextByLanguage("Пропущены бары на ","Missed bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",(string)lparam);
        }
      //--- "New pattern" event
      if(idx==SERIES_EVENTS_PATTERN)
        {
         // New pattern event is handled here
        }
     }

It is empty here for now, but when we send pattern events in the future, we will handle the appearance of a new pattern here.

This completes the library modification. Now let's check the search for new patterns and handling changes in the horizontal scale of the chart.


Test

To perform the test, let's use the EA from the previous article and save it to the new \MQL5\Experts\TestDoEasy\Part136\ folder as TestDoEasy136.mq5.

Remove the flag for drawing patterns with dots from the settings:

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

Add the flags for using patterns to the settings and parameters for searching for the "Outside Bar" pattern:

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

Since the methods for setting the use of patterns immediately search for and display the found patterns when the flag is set, then for the test it is enough to set the flags in the OnInit() handler to immediately start searching for patterns and displaying them on the chart:

//--- Clear the list of all patterns
   engine.GetListAllPatterns().Clear();
   
//--- Set the flag of using the Pin Bar pattern with the parameters specified in the settings
   engine.SeriesSetUsedPatternPinBar(NULL,PERIOD_CURRENT,(bool)InpSearchPinBar,InpPinBarRatioBody,InpPinBarRatioLarger,InpPinBarRatioSmaller);
   
//--- Set the flag of using the Inside Bar pattern
   engine.SeriesSetUsedPatternInsideBar(NULL,PERIOD_CURRENT,(bool)InpSearchInsideBar);
 
//--- Set the flag of using the Outside Bar pattern
   engine.SeriesSetUsedPatternOutsideBar(NULL,PERIOD_CURRENT,(bool)InpSearchOutsideBar,InpOBRatioCandles,InpOBRatioBodyToCandle);

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

In the OnChartEvent() EA handler, add calling this handler for the main object of the Engine library:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- If working in the tester, exit
   if(MQLInfoInteger(MQL_TESTER))
      return;
//--- Handling mouse events
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Handle pressing the buttons in the panel
      if(StringFind(sparam,"BUTT_")>0)
         PressButtonEvents(sparam);
     }
//--- Handling DoEasy library events
   if(id>CHARTEVENT_CUSTOM-1)
     {
      OnDoEasyEvent(id,lparam,dparam,sparam);
     }
   engine.OnChartEvent(id,lparam,dparam,sparam);
//--- Chart change
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Whenever the chart changes, hide all information panels
      //... ... ...

In the library event handler — OnDoEasyEvent() function

//+------------------------------------------------------------------+
//| Handling DoEasy library events                                   |
//+------------------------------------------------------------------+
void OnDoEasyEvent(const int id,
                   const long &lparam,
                   const double &dparam,
                   const string &sparam)
  {
   int idx=id-CHARTEVENT_CUSTOM;
//--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time

in the block for handling timeseries events, add a message about missed bars, if such an event takes place:

//--- Handling timeseries events
   else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE)
     {
      //--- "New bar" event
      if(idx==SERIES_EVENTS_NEW_BAR)
        {
         Print(TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam));
        }
      //--- "Bars skipped" event
      if(idx==SERIES_EVENTS_MISSING_BARS)
        {
         Print(TextByLanguage("Пропущены бары на ","Missing bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",lparam);
        }
     }
     
//--- Handle chart auto events

At the moment, this does not provide anything technically - it simply displays the number of missed bars in the journal. But it is clear that the library correctly handles bar skips if, for example, the connection was disconnected while the EA was running in the terminal, or the computer was put into sleep mode and then brought out of it. This means that when such an event is received, the required number of bars can be updated to restore the missing time series and pattern data. This will be implemented in the future.

Let's compile the EA and launch it, setting the following values for searching for the Outside Bar pattern:

We set such small values for candle proportions deliberately so that as many patterns as possible are found.
With normal values of candle proportions (50% or more), the patterns are more correct, but quite rare.

After launch, the Outside Bar patterns will be found and displayed:

We see that the patterns are found. When resizing the chart, the sizes of the pattern icons also change.


What's next?

In the next article on price formations, we will continue to create different patterns and send events when they appear.

All created files are attached to the article and can be downloaded for self-study and tests.

Back to contents

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/14710

Attached files |
MQL5.zip (4958.58 KB)
Creating Dynamic MQL5 Graphical Interfaces through Resource-Driven Image Scaling with Bicubic Interpolation on Trading Charts Creating Dynamic MQL5 Graphical Interfaces through Resource-Driven Image Scaling with Bicubic Interpolation on Trading Charts
In this article, we explore dynamic MQL5 graphical interfaces, using bicubic interpolation for high-quality image scaling on trading charts. We detail flexible positioning options, enabling dynamic centering or corner anchoring with custom offsets.
Data Science and ML (Part 37): Using Candlestick patterns and AI to beat the market Data Science and ML (Part 37): Using Candlestick patterns and AI to beat the market
Candlestick patterns help traders understand market psychology and identify trends in financial markets, they enable more informed trading decisions that can lead to better outcomes. In this article, we will explore how to use candlestick patterns with AI models to achieve optimal trading performance.
Automating Trading Strategies in MQL5 (Part 16): Midnight Range Breakout with Break of Structure (BoS) Price Action Automating Trading Strategies in MQL5 (Part 16): Midnight Range Breakout with Break of Structure (BoS) Price Action
In this article, we automate the Midnight Range Breakout with Break of Structure strategy in MQL5, detailing code for breakout detection and trade execution. We define precise risk parameters for entries, stops, and profits. Backtesting and optimization are included for practical trading.
Price Action Analysis Toolkit Development (Part 21): Market Structure Flip Detector Tool Price Action Analysis Toolkit Development (Part 21): Market Structure Flip Detector Tool
The Market Structure Flip Detector Expert Advisor (EA) acts as your vigilant partner, constantly observing shifts in market sentiment. By utilizing Average True Range (ATR)-based thresholds, it effectively detects structure flips and labels each Higher Low and Lower High with clear indicators. Thanks to MQL5’s swift execution and flexible API, this tool offers real-time analysis that adjusts the display for optimal readability and provides a live dashboard to monitor flip counts and timings. Furthermore, customizable sound and push notifications guarantee that you stay informed of critical signals, allowing you to see how straightforward inputs and helper routines can transform price movements into actionable strategies.
OSZAR »