English Русский 中文 Español Deutsch 日本語
preview
DoEasy. Funções de Serviço (Parte 3): Padrão "Barra Externa"

DoEasy. Funções de Serviço (Parte 3): Padrão "Barra Externa"

MetaTrader 5Exemplos |
122 0
Artyom Trishkin
Artyom Trishkin

Conteúdo


Conceito

Continuamos a seção da biblioteca DoEasy dedicada ao trabalho com padrões de preço.

No artigo anterior, criamos a busca e exibição dos padrões de duas barras Price Action "Barra Interna". Hoje, desenvolveremos a busca do padrão "Barra Externa", que, na essência, é a imagem espelhada da barra interna.

No entanto, há diferenças. Se a barra interna for um padrão bidirecional, onde a entrada pode ocorrer em qualquer lado do padrão, a "Barra Externa" se divide em duas direções — Altista e Baixista:

  • BUOVB (Bullish Outside Vertical Bar) – Barra Externa Vertical Altista. A barra de sinal cobre completamente a barra anterior, e seu preço de fechamento está acima da máxima da barra anterior. A entrada ocorre na quebra da máxima da barra de sinal + filtro (5-10 pontos).
  • BEOVB (Bearish Outside Vertical Bar) – Barra Externa Vertical Baixista. A barra de sinal cobre completamente a barra anterior, e seu preço de fechamento está abaixo da mínima da barra anterior. A entrada ocorre na quebra da mínima da barra de sinal + filtro (5-10 pontos).

Antes de criarmos a classe do padrão, precisamos aprimorar as classes já existentes na biblioteca. Primeiramente, todos os métodos nas classes de acesso aos padrões foram criados de forma não otimizada — era apenas um teste de conceito, onde tudo foi implementado "diretamente" por meio de vários métodos, cada um para um tipo específico de padrão. Agora, criaremos um único método para acessar o trabalho com os padrões indicados. Para cada ação, haverá um método próprio, no qual o padrão requerido será especificado por uma variável. Isso simplificará e reduzirá significativamente o código das classes.

Em segundo lugar, durante o longo período sem desenvolvimento da biblioteca, ocorreram algumas mudanças e adições na linguagem MQL5 (nem todas as mudanças anunciadas foram ainda incorporadas à linguagem), e adicionaremos essas alterações à biblioteca. Além disso, foram realizadas correções em erros encontrados ao longo dos testes da biblioteca — descreveremos todas as melhorias feitas.



Aprimoramento das classes da biblioteca

Na determinação de alguns padrões, é importante a relação de tamanho entre as velas vizinhas que participam da formação do padrão. Para fins de determinação dessas relações, adicionaremos um novo valor às propriedades do padrão. Também adicionaremos às propriedades de cada padrão e classe de gerenciamento de padrões um atributo que indicará o valor usado para buscar a relação entre as velas.

No arquivo da biblioteca \MQL5\Include\DoEasy\Defines.mqh, no enum de propriedades reais do padrão, adicionaremos novos atributos, e o número total de propriedades reais aumentará de 10 para 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

A partir da versão beta 4540 do terminal cliente MetaTrader 5, foi adicionada ao enum ENUM_SYMBOL_SWAP_MODE a opção SYMBOL_SWAP_MODE_CURRENCY_PROFIT.

Se a função SymbolInfoInteger() retornar esse valor, significa que os swaps na conta são creditados em dinheiro na moeda de cálculo do lucro.

Adicionaremos esse valor aos arquivos da biblioteca. No arquivo \MQL5\Include\DoEasy\Data.mqh inseriremos os índices das novas mensagens da biblioteca:

   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

e as mensagens de teste correspondentes aos índices recém-adicionados:

   {"Свопы начисляются в деньгах в маржинальной валюте символа","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"},

No array de mensagens de erro de tempo de execução, adicionaremos a descrição de dois novos erros de tempo de execução com os códigos 4306 e 4307:

//+------------------------------------------------------------------+
//| 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"    
   },                                                                                             
  };

No arquivo \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh, no método que retorna o número de casas decimais dependendo do método de cálculo do swap, inseriremos o tratamento do novo valor da enumeração:

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

No método que retorna a descrição do modelo de cálculo do swap, adicionaremos a string com a descrição do novo modo de cálculo de swaps:

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

No arquivo de definições para a linguagem MQL4 \MQL5\Include\DoEasy\ToMQL4.mqh, na enumeração dos métodos de crédito de swaps ao rolar a posição, adicionaremos uma nova propriedade:

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

No arquivo da classe do objeto base da biblioteca \MQL5\Include\DoEasy\Objects\BaseObj.mqh, corrigiremos as linhas com potenciais vazamentos de memória:

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

Aqui, os métodos retornam o resultado da adição de um objeto à lista (true para adição bem-sucedida e false em caso de erro). Se ocorrer um erro na adição do objeto criado via operador new, esse objeto permanecerá na memória sem que um ponteiro seja salvo na lista, o que levaria a vazamentos de memória. Corrigiremos:

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

Agora, em caso de falha na adição do objeto de evento à lista, o objeto recém-criado será excluído da mesma forma que um já existente na lista, e o método retornará false.

No arquivo \MT5\MQL5\Include\DoEasy\Objects\Orders\Order.mqh, ao calcular a quantidade de pontos, é necessário adicionar arredondamento ao resultado obtido, pois, caso os valores sejam menores que 1, mas próximos de um, ao converter o número real para inteiro, obtemos uma contagem de pontos igual a zero:

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

No arquivo \MQL5\Include\DoEasy\Objects\Events\Event.mqh, foi cometida originalmente um erro ao retornar um valor do tipo long como uma enumeração:

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

Enquanto o valor do ticket da ordem, que é do tipo long, não ultrapassava INT_MAX (o tipo de dados da enumeração é int), os tickets das ordens eram retornados corretamente. Porém, assim que o valor do ticket excedia INT_MAX, ocorria um estouro de valor e um número negativo era retornado. Agora, tudo foi corrigido:

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

Na enumeração que retorna o nome do status do evento do sistema de ordens, o status de modificação foi omitido, e, em alguns casos, o status "Unknown" aparecia na descrição do evento de negociação. Corrigiremos adicionando a linha:

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

Na classe do objeto de negociação, corrigiremos uma falha: a política de preenchimento do volume (da enumeração ENUM_ORDER_TYPE_FILLING) era transmitida incorretamente para o método de abertura de posição.

Faremos as correções necessárias nos métodos de negociação no arquivo MQL5\Include\DoEasy\Objects\Trade\TradeObj.mqh. No método de abertura de posição, no bloco de preenchimento da estrutura da solicitação de negociação, adicionaremos a configuração da política de preenchimento do volume:

//+------------------------------------------------------------------+
//| 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 
  }

Nesta classe, o valor da política de preenchimento é definido na variável m_type_filling inicialmente durante a inicialização da biblioteca com os valores permitidos para ordens (método CEngine::TradingSetCorrectTypeFilling). Se um valor negativo for passado para a política de preenchimento no método de abertura de posição, o valor da variável m_type_filling, definido durante a inicialização da biblioteca, será utilizado. Se for necessário especificar outro tipo de preenchimento de volume, ele deve ser passado como parâmetro do método type_filling, e o valor fornecido será utilizado.

Anteriormente, a linha adicionada não existia, e a política de preenchimento, caso fosse necessário indicar um tipo diferente do padrão, sempre assumia o valor Return (ORDER_FILLING_RETURN), pois o campo type_filling da estrutura MqlTradeRequest não era preenchido e sempre tinha um valor nulo. Agora, isso foi corrigido.

Corrigiremos falhas semelhantes em outros métodos da classe, onde a política de preenchimento de volume também é necessária de alguma forma:

//+------------------------------------------------------------------+
//| 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 
  }

Agora, corrigiremos falhas semelhantes na classe de negociação da biblioteca no arquivo \MQL5\Include\DoEasy\Trading.mqh.

No método de abertura de posição OpenPosition(), a estrutura da solicitação de negociação é preenchida com base nos valores passados para o método:

//--- 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());


E ao chamar o método de abertura de posição do objeto de negociação do símbolo, seus parâmetros não indicam os valores definidos na estrutura da solicitação de negociação, mas aqueles que foram passados para o método de abertura de posição:

//--- 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);
      //... ... ...

Corrigiremos isso:

      //--- 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);

Para monitorar eventos que ocorrem em todos os gráficos abertos, e não apenas naquele ao qual o programa baseado na biblioteca está anexado, um indicador é colocado em cada gráfico aberto (ou já existente). Esse indicador rastreia os eventos do gráfico e os envia para o programa.

Se o terminal cliente foi conectado primeiro a um servidor e gráficos de vários instrumentos foram abertos, e depois o terminal foi conectado a outro servidor onde esses instrumentos não existem, o programa não poderá colocar esses indicadores nos gráficos e o log conterá registros de erros, indicando uma falha na criação do indicador, ao invés da ausência dos símbolos dos gráficos abertos no servidor. Para corrigir esses registros ambíguos no log, é necessário adicionar uma verificação da existência do símbolo no servidor antes de criar o indicador.

No arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, no método que cria o indicador de controle de eventos, adicionaremos a verificação da existência do símbolo no servidor:

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

Agora, se o símbolo não estiver presente no servidor, uma mensagem será registrada no log e o método retornará false.

No arquivo do objeto gráfico base da biblioteca \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh, no método que retorna a descrição do tipo de elemento gráfico, adicionaremos um tipo omitido — o objeto gráfico "Bitmap":

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

Os classes de padrões — sua organização e estrutura — também serão ligeiramente reformuladas. A classe do padrão abstrato base está localizada no arquivo \MQL5\Include\DoEasy\Objects\Series\Patterns\Pattern.mqh, e é nesse mesmo arquivo que os padrões herdados da classe base são escritos.

Separaremos todas as classes de padrões em seus próprios arquivos. Atualmente, há a possibilidade de exibir os ícones dos padrões como simples pontos no gráfico, desenhados com objetos padrão "Linha de tendência" com ambas as coordenadas de preço e tempo no mesmo candle. Foi decidido abandonar essa funcionalidade para simplificar o código. Todos os ícones serão desenhados em objetos gráficos "Imagem". As imagens dos padrões serão criadas de acordo com o tamanho das barras que compõem o padrão. Para modificar o tamanho dos objetos de imagem ao alterar a escala horizontal do gráfico, é necessário introduzir uma variável que armazenará o valor da escala. As variáveis para armazenar os demais tamanhos do gráfico já estão incluídas no objeto base do padrão. Ao modificar os tamanhos do gráfico, os novos valores serão aplicados a todos os objetos criados dos padrões identificados, e eles serão redesenhados conforme os novos tamanhos.

Na pasta da biblioteca \MT5\MQL5\Include\DoEasy\Objects\Series\Patterns\, criaremos dois novos arquivos PatternPinBar.mqh e PatternInsideBar.mqh — neles, inseriremos via Cut and Paste (recortar e colar) as classes dos padrões "Pin Bar" e "Barra Interna", que anteriormente estavam escritas diretamente no arquivo da classe do padrão abstrato base. Posteriormente, faremos ajustes nesses arquivos, mas por enquanto continuaremos corrigindo a classe do padrão abstrato.

Na seção protegida da classe, removeremos a variável de flag m_draw_dots, que indicava o modo de desenho dos ícones dos padrões com pontos, e declararemos uma variável para armazenar a largura do gráfico em 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:

Os métodos que calculam a largura e a altura do objeto de imagem

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

serão renomeados com nomes mais apropriados e declarados como virtuais:

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

Afinal, Get significa "Obter" e não "Calcular". Os métodos virtuais permitirão que as classes herdadas utilizem seus próprios cálculos para largura e altura da imagem, dependendo do tipo de padrão e da forma como ele é desenhado.

Na seção pública da classe, removeremos o método SetDrawAsDots():

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);

E declararemos o método virtual Redraw():

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);

O método Redraw() redesenhará o objeto de imagem com novos tamanhos. Como cada tipo de padrão pode ter seus próprios tipos de imagens, o método é declarado como virtual e, nesta classe base, simplesmente retorna true. Nas classes herdadas, o método será redefinido para redesenhar a imagem correspondente ao padrão específico.

Ainda na seção pública, escreveremos métodos para definir e retornar a largura do gráfico em 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; }

Ao alterar a largura do gráfico, a classe de gerenciamento de padrões aplicará os novos tamanhos de gráfico a todos os objetos de padrões, para que cada objeto de padrão criado não precise recuperar as propriedades do gráfico repetidamente, mas apenas uma vez no momento da alteração, propagando os valores para todos os padrões criados.

No construtor da classe, no final do código, removeremos a linha que inicializa a variável que não é mais necessária:

//--- 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;
  }

No método que retorna a descrição da propriedade real do padrão, adicionaremos dois blocos de código para exibir as descrições de duas novas propriedades do padrão:

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

No método que exibe o painel de informações no gráfico, agora não há mais necessidade de recuperar as propriedades do gráfico, pois elas já estão armazenadas no objeto do padrão no momento de sua criação ou quando os tamanhos do gráfico são modificados.

Removeremos as linhas que obtêm as propriedades do gráfico do método:

//+------------------------------------------------------------------+
//| 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

Agora, em vez de recuperar as propriedades do gráfico como antes, utilizaremos essas propriedades, que foram predefinidas ao criar o objeto:

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

No método responsável por desenhar, exibir e ocultar os ícones do padrão, removeremos as linhas relacionadas ao desenho dos ícones como pontos:

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

Agora, esses métodos ficaram mais simples:

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

Em seguida, aprimoraremos as classes dos padrões cujos códigos foram transferidos do arquivo do padrão abstrato.

Abriremos o arquivo da classe do padrão "Pin Bar" \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternPinBar.mqh e faremos as modificações necessárias.

Anteriormente, os ícones desse padrão eram desenhados apenas como pontos utilizando objetos gráficos padrão. Agora, precisamos adicionar métodos para desenhar os pontos no objeto gráfico "Bitmap".

Adicionaremos a declaração dos novos métodos no corpo da classe:

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

Os métodos virtuais CalculateBitmapWidth() e CalculateBitmapHeight() sempre retornam dimensões fixas de 20x40 pixels, pois, para esse padrão, que é desenhado apenas em uma barra, não é necessário calcular nem a altura nem a largura da imagem — ele deve sempre ter um tamanho fixo. O ponto de ancoragem da imagem é definido no centro do objeto, e os pontos são sempre desenhados na metade superior ou inferior da imagem, dependendo da direção do padrão. Para um padrão altista, o ponto é desenhado na metade inferior da imagem; para um padrão baixista, na metade superior. Isso permite que os pontos dos padrões sejam exibidos sempre a uma distância fixa da sombra da vela, independentemente da escala vertical e do período do gráfico, o que é bastante conveniente e prático.

Na implementação do método que cria a aparência do painel de informações CreateInfoPanelView(), removeremos as linhas que obtêm as propriedades do gráfico:

//--- 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());

Agora, essas propriedades são predefinidas ao criar o objeto ou atualizadas quando os tamanhos do gráfico mudam. Por isso, agora utilizamos os valores das variáveis onde a largura e a altura do gráfico estão armazenadas:

//--- 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());

Agora, implementaremos os métodos de desenho do ícone do padrão.

Método que desenha o ícone do padrão no gráfico:

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

Se o objeto de imagem ainda não existir fisicamente, ele será criado e, em seguida, exibido no gráfico.

Método que cria o objeto de imagem:

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

A lógica do método está comentada no código. Ao definir as coordenadas do objeto no gráfico, levamos em consideração a direção do padrão. Se for um padrão altista, a coordenada será o preço Low da barra do padrão; se for um padrão baixista, a coordenada será o preço High.

Método que cria a aparência do objeto de imagem:

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

Primeiro, limpamos completamente a tela com uma cor transparente. Depois, determinamos as coordenadas locais do ponto desenhado. Para um padrão altista, a coordenada Y será metade da altura da imagem mais 6 pixels (abaixo do centro da imagem em 6 pixels). Para um padrão baixista, subtraímos 6 pixels da coordenada do centro da imagem. A coordenada X será metade da largura da imagem. O raio do círculo desenhado é definido como 2, o que torna o ponto visível em qualquer timeframe do gráfico.

Agora, faremos as mesmas melhorias no arquivo do padrão "Barra Interna" \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternInsideBar.mqh.

Declararemos o método virtual para redesenhar o ícone do padrão:

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

Fora do corpo da classe, escreveremos a implementação do método que redesenha o ícone do padrão com os novos tamanhos:

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

A lógica do método está comentada no código — é tudo bastante simples: calculamos os novos tamanhos da tela, ajustamos suas dimensões e desenhamos a nova imagem de acordo com os novos tamanhos.

No método que cria a aparência do painel de informações, anteriormente havia um erro no cálculo do tamanho do padrão em barras:

//--- 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();

Esse cálculo faz sentido apenas se as barras adjacentes do padrão não forem separadas por dias sem negociação. Se houver dias sem negociação entre a barra da esquerda e a da direita do padrão, esses dias serão incluídos na contagem de barras, e em vez de 2, o valor retornado será 4.

Corrigiremos esse cálculo:

//--- 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();

Agora, o número correto de barras será sempre retornado entre os tempos das duas barras do padrão.

E, como agora os valores da largura e altura do gráfico já estão armazenados previamente em variáveis, neste mesmo método removeremos a obtenção das propriedades do gráfico:

//--- 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());

Além disso, ajustaremos o cálculo das coordenadas do painel para usar os valores predefinidos das variáveis:

//--- 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());

No método que desenha o ícone do padrão no gráfico, removeremos o bloco de código que desenha os ícones como pontos:

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

Agora, o método ficou mais simples:

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

Todas as demais melhorias serão feitas após a criação da nova classe do padrão "Barra Externa".


Classe do padrão "Barra Externa"

Crie um novo arquivo PatternOutsideBar.mqh da classe CPatternOutsideBar na pasta da biblioteca \MQL5\Include\DoEasy\Objects\Series\Patterns\.

Essa classe deverá ser herdada da classe base do objeto de padrão, e seu arquivo deverá ser incluído no arquivo recém-criado da classe do padrão:

//+------------------------------------------------------------------+
//|                                            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
  {
  }

Declararemos os métodos padrão das classes de objetos de padrões:

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

Todos esses métodos são idênticos para todos os padrões. Apenas sua implementação difere ligeiramente de um padrão para outro. Vamos analisar cada método.

No construtor da classe, definiremos o nome do padrão, o número de velas que compõem a figura, e o número de padrões adjacentes consecutivos, que é igual ao número de velas do padrão:

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

Método que cria a aparência do painel de informações:

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

A lógica do método está comentada no código. Aqui, desenhamos um painel com um preenchimento gradiente — com um tom avermelhado para um padrão baixista e um tom azulado para um padrão altista. Na parte superior, um ícone com o símbolo (i) é desenhado, e o nome do padrão é exibido junto com suas características — a relação entre as duas barras do padrão. Na parte inferior, a direção do padrão é descrita, juntamente com os valores definidos para a busca do padrão e o horário em que o padrão foi identificado.

Método que desenha o ícone do padrão no gráfico:

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

Método que redesenha o ícone do padrão no gráfico com o novo tamanho:

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

Simplesmente ajustamos as dimensões da tela e redesenhamos a nova imagem do padrão no novo tamanho.

Método que cria o objeto de imagem:

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

Se o objeto gráfico já tiver sido criado anteriormente, simplesmente saímos do método. Caso contrário, obtemos o preço e o tempo nos quais o objeto gráfico será posicionado, calculamos sua largura e altura e criamos um novo objeto. Em seguida, definimos o ponto de ancoragem no centro do objeto e desenhamos sua aparência. O preço no qual o ponto central do objeto gráfico será posicionado é calculado com base no centro da maior vela do padrão.

Método que cria a aparência do objeto de imagem:

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

Primeiro, a imagem é apagada usando uma cor totalmente transparente. Em seguida, a coordenada X inicial da área preenchida é calculada com base na escala do gráfico. Um retângulo preenchido com cor é desenhado em toda a altura do objeto gráfico, com a coordenada X inicial calculada. Por fim, um contorno retangular é desenhado sobre a mesma posição e com o mesmo tamanho.

Método que calcula a altura do objeto de imagem:

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

Aqui, obtemos o intervalo de preços do gráfico — do preço máximo ao preço mínimo — e o intervalo de preços do padrão — do High da vela principal ao Low da vela principal. Depois, calculamos a relação entre esses dois intervalos em pixels e retornamos o valor obtido para a altura do objeto de imagem, adicionando 8 pixels — quatro na parte superior e quatro na inferior.

A classe do padrão "Barra Externa" está pronta. Agora, precisamos eliminar dos objetos de séries temporais a repetição de vários métodos idênticos, cada um realizando a mesma tarefa para um padrão específico. Criaremos um único método para cada ação relacionada ao padrão, onde especificaremos o tipo de padrão necessário.

Todos os parâmetros de relação entre as velas que compõem o padrão serão passados para as classes de padrões por meio da estrutura MqlParam. Isso permitirá que diferentes padrões recebam parâmetros distintos, ao invés de dependerem apenas de variáveis formais fixas para todos os padrões.

Por enquanto, não alteraremos os nomes dessas variáveis, que indicam as diferentes relações entre as velas dos padrões. Se necessário, mais adiante poderemos renomeá-las para variáveis genéricas, como "param1", "param2", etc. Nos construtores das classes, cada variável receberá o parâmetro específico do padrão correspondente. Mas isso será feito apenas se necessário. Por enquanto, manteremos as variáveis existentes, que são as mesmas para todos os padrões.

Na classe do objeto-barra, localizada no arquivo \MQL5\Include\DoEasy\Objects\Series\Bar.mqh, renomearemos o método AddPattern() para 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)

Afinal, esse método adiciona um tipo de padrão ao objeto-barra, e não um ponteiro para o padrão. Portanto, faz mais sentido alterar o nome do método para algo mais preciso.

Para permitir a seleção e recuperação de padrões específicos das listas de padrões, no arquivo \MQL5\Include\DoEasy\Services\Select.mqh, da classe CSelect, incluiremos todos os arquivos de padrões:

//+------------------------------------------------------------------+
//|                                                       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"

Isso tornará as classes de padrões acessíveis nas classes de séries temporais, onde o gerenciamento dos padrões é implementado.

Precisaremos comparar arrays de estruturas MqlParam para verificar igualdade. Escreveremos funções para comparar os campos das estruturas e arrays de estruturas no arquivo \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;
  }
//+------------------------------------------------------------------+

As classes de gerenciamento de padrões estão no arquivo da classe de séries temporais \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh. Na classe de gerenciamento de padrões abstratos, removeremos agora as variáveis desnecessárias e adicionaremos novas variáveis.

//+------------------------------------------------------------------+
//| 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

Anteriormente, no método de busca de padrões, passávamos o tamanho mínimo da vela como uma variável para a busca do padrão:

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

Agora, esse tamanho será passado através da variável MqlParam. Por isso, essa variável foi removida de todos os métodos de busca de padrões em todas as classes de gerenciamento de padrões.

//--- (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;                                        }

Na seção pública da classe, removeremos os métodos para definir e obter as propriedades de desenho dos padrões como pontos:

//--- (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;                            }

Declararemos um array de parâmetros do padrão, adicionaremos métodos para trabalhar com as novas variáveis, removeremos a passagem do parâmetro min_body_size do método virtual CreateAndRefreshPatternList(), e no construtor paramétrico, adicionaremos a passagem do array de propriedades do padrão por meio da estrutura MqlParam. Declararemos um novo método para redesenhar todos os padrões existentes no gráfico:

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[]);
  };

No construtor paramétrico da classe, obteremos as propriedades do gráfico que estavam faltando e preencheremos os parâmetros do padrão a partir do array passado no construtor:

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

No método que busca os padrões e adiciona os encontrados à lista geral de padrões, faremos algumas correções:

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

O método agora não possui parâmetros formais. Ao criar um novo padrão, as propriedades do gráfico são passadas e definidas diretamente nele. No final do bloco de loop de busca do padrão, prepararemos a estrutura para a criação do evento de um novo padrão — em artigos futuros, implementaremos o envio de eventos sobre a aparição de um novo padrão na série temporal.

Método que redesenha os padrões no gráfico com o novo tamanho do objeto de imagem:

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

Aqui, simplesmente percorremos a lista de padrões da série temporal e chamamos os métodos de redesenho de cada padrão. Atualmente, o método não está otimizado — a atualização ocorre para toda a lista de padrões. Entretanto, podemos implementar a atualização apenas para a parte visível do histórico no gráfico. Isso será feito mais adiante.

Agora, aprimoraremos as classes de gerenciamento de padrões. Elas estão localizadas mais adiante neste mesmo arquivo. Procuraremos todas as ocorrências da string

const uint min_body_size

nos parâmetros formais dos métodos das classes e removeremos essas linhas. Agora, o tamanho mínimo da vela do padrão será passado junto com os demais parâmetros dos padrões no array da estrutura MqlParam.

Apenas marcaremos com cor todos os locais onde as mudanças foram feitas, em vez de listar cada linha repetitiva para todas as classes.

Na classe de gerenciamento do padrão "Pin Bar":

//+------------------------------------------------------------------+
//| 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

Na classe de gerenciamento do padrão "Barra Interna":

//+------------------------------------------------------------------+
//| 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

Seguindo a mesma lógica das classes de gerenciamento dos padrões "Pin Bar" e "Barra Interna", agora escreveremos a classe de gerenciamento do padrão "Barra Externa":

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

As condições necessárias para identificar o padrão: O corpo da vela à direita deve cobrir completamente o tamanho do corpo da vela à esquerda, e as sombras das velas devem ser iguais ou a sombra da vela à direita pode cobrir a sombra da vela à esquerda. Essa verificação é realizada no método CheckOutsideBar(). Além disso, é necessário considerar a proporção entre os corpos das velas que compõem o padrão em relação ao tamanho total das velas. Essa verificação é feita pelo método CheckProportions().

No construtor da classe, definimos o status do padrão como "Price Action", o tipo do padrão como "Outside Bar" e configuramos todas as proporções das velas do padrão a partir do array de estruturas passado no construtor.

Método que cria um identificador de objeto com base nos critérios de busca do padrão:

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

Dois critérios (as relações percentuais entre os tamanhos das velas e a relação entre o corpo da vela e o tamanho total da vela) são definidos como números reais (em porcentagem) e não podem ser superiores a 100. Por isso, os convertamos em valores inteiros multiplicando por 100 e, em seguida, criamos um identificador do tipo ulong utilizando o método da biblioteca UshortToLong(), que preenche os bits especificados de um número do tipo long com valores do tipo ushort:

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

Método que cria o padrão com a direção especificada:

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

Este método cria um novo objeto da classe do padrão "Barra Externa", preenche os dados sobre suas proporções e critérios de busca e retorna um ponteiro para o objeto criado.

Método de busca do padrão:

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

A lógica do método está comentada no código. O método recebe o horário de abertura da barra atual. Obtemos a lista de todas as barras, exceto a atual (os padrões não são buscados na barra zero). No loop da lista de barras obtida, verificamos cada par de barras adjacentes para determinar se atendem aos critérios do padrão. Se sua relação corresponder a um padrão válido, determinamos e retornamos a direção do padrão.

Método que retorna a lista de padrões gerenciados por este objeto:

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

A partir da lista geral de todos os padrões, filtramos por período do gráfico. Depois, filtramos novamente pelo nome do símbolo. Em seguida, filtramos pelo tipo de padrão "Barra Externa" e, por fim, pelo identificador do objeto de gerenciamento. O método retorna apenas a lista dos padrões gerenciados por esta classe.

Agora, precisamos reformular completamente a classe de gerenciamento de padrões. Mais detalhes sobre ela podem ser encontrados neste artigo.

Em vez de métodos longos e repetitivos para cada tipo de padrão, criaremos alguns métodos que permitam trabalhar com qualquer padrão especificado. Em seguida, no mesmo arquivo, removeremos da classe de gerenciamento de padrões os numerosos métodos redundantes, como:

... "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)

No geral, há muitos desses métodos — um para cada padrão. Isso resultava em quase 1300 linhas de código. Removeremos todas essas linhas da classe de gerenciamento de padrões e reescreveremos a classe do zero. Agora, para manipular os padrões, haverá apenas alguns métodos que permitem escolher com qual padrão trabalhar.

O novo código da classe completa, incluindo todos os métodos, agora será assim:

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

A lógica dos métodos está comentada no código. Gostaria de esclarecer um ponto sobre o bloco de código destacado: no modo visual do testador, o manipulador OnChartEvent() é praticamente inútil. É nele que rastreamos as mudanças de tamanho do gráfico pelo evento CHARTEVENT_CHART_CHANGE e gravamos os novos tamanhos do gráfico no objeto de gerenciamento de padrões, de onde esses dados são repassados aos objetos dos padrões. No entanto, no testador, isso não funciona. Portanto, no bloco de código destacado, no testador, rastreamos manualmente a alteração dos tamanhos do gráfico e, ao detectar uma mudança, os novos dados são armazenados nos objetos de gerenciamento de padrões.

Mais adiante no código, neste mesmo arquivo \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh, temos a classe de séries temporais CSeriesDE. Faremos melhorias nesta classe.

Cada vez que buscamos um objeto-barra na lista da série temporal, um novo objeto é criado usando o operador new, são definidos seus parâmetros, o objeto com os mesmos parâmetros é buscado na lista, e em seguida, esse novo objeto criado é excluído:

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

Isso resulta em uma criação e exclusão contínuas de objetos dentro do loop em algumas situações. Isso não é correto. É melhor criar uma única instância do objeto para busca e usá-lo para definir os parâmetros e como referência na pesquisa. Dessa forma, eliminamos a recriação constante de objetos.

Declararemos uma instância do objeto-barra para busca:

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

E reescreveremos o método que retorna um objeto-barra pelo horário na série temporal:

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

Agora, em vez de criar e excluir um novo objeto, definimos os parâmetros necessários na única instância disponível para a busca e procuramos um objeto idêntico na lista com base nessa referência. O objeto é criado apenas uma vez no construtor da classe e reutilizado continuamente sem recriação.

Assim como fizemos na classe de gerenciamento de padrões, removeremos longas listas de métodos específicos para cada padrão e os substituiremos por alguns métodos genéricos, onde será possível selecionar o padrão desejado:

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

Agora, basta indicar o tipo do padrão nos parâmetros do método, em vez de ter um método separado para cada padrão.

Na classe da série temporal do símbolo, localizada no arquivo D:\MetaQuotes\MT5\MQL5\Include\DoEasy\Objects\Series\TimeSeriesDE.mqh, faremos as mesmas mudanças nos métodos de gerenciamento de padrões.

No corpo da classe, no final do código, sobre o cabeçalho,

//--- 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                                    |
//+------------------------------------------------------------------+

há uma longa lista de declarações de métodos para gerenciamento de padrões. Removeremos todas essas declarações e as substituiremos por algumas declarações de métodos que permitem selecionar o padrão desejado:

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

Fora do corpo da classe, no final do arquivo, sob o mesmo cabeçalho, estão as implementações dos métodos declarados. Removeremos essa longa lista e a substituiremos pelos novos métodos:

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

Cada método recebe o timeframe do gráfico onde os padrões devem ser gerenciados e os parâmetros necessários para selecionar o objeto de gerenciamento do padrão. Em seguida, um ponteiro para a série temporal necessária é obtido e o resultado da chamada do método correspondente da classe da série temporal é retornado.

Faremos ajustes também no arquivo da classe da coleção de séries temporais \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh.

Nesta classe, obteremos os tamanhos do gráfico, suas alterações e os enviaremos para as classes de gerenciamento de padrões. Adicionaremos novas variáveis para armazenar os tamanhos do gráfico:

//+------------------------------------------------------------------+
//| 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:

Sob o cabeçalho

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

assim como nas classes anteriores, removeremos os métodos declarados e escreveremos novos métodos com a especificação do tipo de padrão:

//--- 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);

No final do corpo da classe, declararemos o manipulador de eventos:

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

No construtor da classe, gravaremos os tamanhos do gráfico nas novas variáveis:

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

Fora do corpo da classe, sob o cabeçalho

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

removeremos todos os métodos para o gerenciamento de padrões individuais. Em vez dos métodos removidos, escreveremos novos métodos que permitem especificar o tipo de padrão:

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

Cada método recebe o timeframe necessário e o símbolo do gráfico onde os padrões devem ser gerenciados, além dos parâmetros que determinam o objeto de gerenciamento do padrão. Em seguida, obtemos um ponteiro para a série temporal necessária e retornamos o resultado da chamada ao método correspondente da classe de séries temporais do símbolo.

Abaixo, escreveremos o manipulador de eventos:

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

Toda a lógica do manipulador está detalhada nos comentários do código. É aqui que, ao detectar uma mudança nos tamanhos do gráfico, os novos tamanhos são enviados para as classes de gerenciamento de padrões da série temporal, e os padrões são redesenhados de acordo com os novos tamanhos do gráfico.

Agora, melhoraremos a classe principal da biblioteca, CEngine, no arquivo \MQL5\Include\DoEasy\Engine.mqh.

Nesta classe, também há uma longa lista de métodos para o gerenciamento de padrões. Removeremos apenas os métodos responsáveis por desenhar padrões na forma de pontos. Os demais métodos precisarão ser ajustados — agora será necessário passar os parâmetros do padrão para os métodos por meio do array de estruturas MqlParam. Em todos os métodos que ainda não são utilizados, preencheremos apenas um campo da estrutura, que transmite o tamanho mínimo das velas do padrão, por exemplo:

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

Os demais métodos para padrões ainda não utilizados são idênticos e não precisam ser analisados agora — eles estão disponíveis nos arquivos anexos ao artigo.

Vamos examinar os métodos para trabalhar com os padrões já implementados na biblioteca.

Método que define a flag de uso do padrão Barra Externa (Engulfing) e cria o objeto de gerenciamento se ele ainda não existir:

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

Método que define a flag de uso do padrão Barra Interna e cria o objeto de gerenciamento se ele ainda não existir:

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

Neste método, apenas um campo da estrutura é preenchido, pois o padrão não possui propriedades configuráveis além da propriedade geral de tamanho mínimo das velas, comum a todos os padrões.

Método que define a flag de uso do padrão Pin Bar e cria o objeto de gerenciamento se ele ainda não existir:

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

Agora, vejamos os métodos para exibição dos padrões no gráfico:

  • Para os padrões já implementados na biblioteca.
  • Para um padrão ainda não criado:

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

Os demais métodos para outros padrões são idênticos ao método do padrão "Trilhos" ainda não implementado, e não há necessidade de analisá-los aqui.

No final do corpo da classe, declararemos o manipulador de eventos:

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);

  };

Fora do corpo da classe, escreveremos sua implementação:

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

Aqui, primeiro, o manipulador de eventos da classe de coleção de objetos gráficos é chamado, seguido pelo manipulador de eventos da coleção de séries temporais.

No manipulador de eventos da biblioteca CEngine::OnDoEasyEvent(), no bloco de processamento de eventos da série temporal, adicionaremos um bloco para o futuro tratamento de eventos de aparecimento de novos padrões:

//--- 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
        }
     }

Atualmente, este bloco está vazio, mas, quando implementarmos o envio de eventos de padrões, escreveremos o processamento do aparecimento de um novo padrão aqui.

Com isso, a reformulação da biblioteca está concluída. Agora, testaremos a busca por novos padrões e o processamento da mudança na escala horizontal do gráfico.


Testes

Para testar, utilizaremos o EA da última parte do artigo e o salvaremos em uma nova pasta \MQL5\Experts\TestDoEasy\Part136\ com o novo nome TestDoEasy136.mq5.

Removeremos a flag de desenho de padrões na forma de pontos das configurações:

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

Adicionaremos flags de uso dos padrões e os parâmetros para a busca do padrão "Barra Externa".

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

Como os métodos de ativação dos padrões, quando a flag está ativada, imediatamente buscam e exibem os padrões encontrados, para o teste basta definir as flags no manipulador OnInit() para iniciar imediatamente a busca dos padrões e sua exibição no gráfico:

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

No manipulador OnChartEvent() do EA, adicionaremos a chamada desse manipulador para o objeto principal da biblioteca Engine:

//+------------------------------------------------------------------+
//| 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
      //... ... ...

No manipulador de eventos da biblioteca — função OnDoEasyEvent(),

//+------------------------------------------------------------------+
//| 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

no bloco de processamento de eventos da série temporal, adicionaremos uma mensagem sobre os candles ausentes, caso esse evento ocorra:

//--- 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

No momento, isso não tem impacto técnico — apenas exibe no log a quantidade de candles ausentes. Porém, permite visualizar que a biblioteca processa corretamente as falhas de candles, caso o EA esteja em execução no terminal e, por exemplo, ocorra uma perda de conexão ou o computador entre no modo de suspensão e seja posteriormente retomado. Isso significa que, ao detectar tal evento, poderemos atualizar a quantidade necessária de candles para restaurar os dados ausentes das séries temporais e padrões. Essa funcionalidade será implementada futuramente.

Agora, compilaremos o EA e o executaremos, configurando os seguintes valores para a busca do padrão "Barra Externa":

Esses valores pequenos para as proporções das velas foram propositalmente definidos para encontrar o maior número possível de padrões.
Com proporções normais das velas (50% ou mais), os padrões são mais precisos, mas ocorrem com menor frequência.

Após a execução, os padrões "Barra Externa" serão encontrados e exibidos:

Podemos ver que os padrões estão sendo identificados corretamente e, ao alterar os tamanhos do gráfico, os ícones dos padrões também ajustam seus tamanhos.


O que vem a seguir

No próximo artigo sobre formações de preço, continuaremos criando diversos padrões e implementando o envio de eventos sobre sua aparição.

Todos os arquivos criados estão anexados ao artigo e podem ser baixados para estudo e testes individuais.

Voltar ao conteúdo

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/14710

Arquivos anexados |
MQL5.zip (4958.58 KB)
Negociação de Notícias Facilitada (Parte 3): Realizando Negócios Negociação de Notícias Facilitada (Parte 3): Realizando Negócios
Neste artigo, nosso especialista em negociação de notícias começará a abrir negociações com base no calendário econômico armazenado em nosso banco de dados. Além disso, melhoraremos os gráficos do especialista para exibir informações mais relevantes sobre os próximos eventos do calendário econômico.
Redes neurais em trading: Abordagem sem máscara para previsão do movimento de preços Redes neurais em trading: Abordagem sem máscara para previsão do movimento de preços
Neste artigo, apresentamos o método Mask-Attention-Free Transformer (MAFT) e sua aplicação na área de trading. Ao contrário dos Transformers tradicionais, que exigem mascaramento de dados ao processar sequências, o MAFT otimiza o processo de atenção, eliminando a necessidade de mascaramento, o que melhora significativamente a eficiência computacional.
MQL5 Trading Toolkit (Parte 2): Expansão e Aplicação da Biblioteca EX5 para Gerenciamento de Posições MQL5 Trading Toolkit (Parte 2): Expansão e Aplicação da Biblioteca EX5 para Gerenciamento de Posições
Aqui, você aprenderá a importar e utilizar bibliotecas EX5 em seu código ou projetos MQL5. Neste artigo, expandiremos a biblioteca EX5 criada anteriormente, adicionando mais funções de gerenciamento de posições e criando dois Expert Advisors (EA). No primeiro exemplo, usaremos o indicador técnico Variable Index Dynamic Average para desenvolver um EA baseado em uma estratégia de trailing stop. No segundo, implementaremos um painel de negociação para monitorar, abrir, fechar e modificar posições. Esses dois exemplos demonstrarão como utilizar a biblioteca EX5 aprimorada para o gerenciamento de posições.
Simulação de mercado (Parte 10): Sockets (IV) Simulação de mercado (Parte 10): Sockets (IV)
Aqui neste artigo mostrei o que você precisa fazer para começar a usar o Excel para controlar o MetaTrader 5. Mas faremos isto de uma forma bastante interessante. Para fazer isto iremos usar um Add-in no Excel. Isto para não precisar de fato fazer uso do VBA presente no Excel. Se você não sabe de que Add-in estou falando. Veja este artigo e aprenda como fazer para programar em Python diretamente dentro do Excel.