Comparamos a velocidade de indicadores de armazenamento automático em cache

Vladimir Karputov | 24 abril, 2018


Introdução

Vamos supor que nos cansamos do acesso MQL5 clássico a indicadores e queremos comparar a velocidade de acesso em relação com certas alternativas. Por exemplo, queremos compará-lo com o acesso - em estilo MQL4 - a indicadores, sem armazenamento em cache e com armazenamento em cache. As ideias sobre o acesso em estilo MQL4 foram retiradas do artigo LifeHack para traders: preparemos "fast-food" de indicadores e complementadas.


Exploremos a numeração MQL5 de identificadores de indicadores

Existe a suposição de que a numeração dos identificadores do indicador no terminal é de ponta a ponta e começa do zero. Para testar esta hipótese, criaremos um pequeno EA - "iMACD and IndicatorRelease.mq5" - que criará alguns identificadores de indicadores e, em seguida, irá imprimi-los, enquanto, na função OnTick(), acessará estes indicadores:

//+------------------------------------------------------------------+
//|                                   iMACD and IndicatorRelease.mq5 |
//|                              Copyright © 2018, Vladimir Karputov |
//|                                           http://wmua.ru/slesar/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2018, Vladimir Karputov"
#property link      "http://wmua.ru/slesar/"
#property version   "1.003"
//--- input parameter
input int   count=6;   // Count MACD indicators

int    handles_array[]; // array for storing the handles of the iMACD indicators
//+------------------------------------------------------------------+
//| Expert initialization function                                     |
//+------------------------------------------------------------------+
int OnInit()
  {
   int array_resize=ArrayResize(handles_array,count);
   if(array_resize==-1)
     {
      Print("ArrayResize error# ",GetLastError());
      return(INIT_FAILED);
     }
   if(array_resize!=count)
     {
      Print("ArrayResize != \"Count MACD indicators\"");
      return(INIT_FAILED);
     }
   ArrayInitialize(handles_array,0);
   for(int i=0;i<count;i++)
     {
      handles_array[i]=CreateHandleMACD(12+i);
      //--- if the handle is not created 
      if(handles_array[i]==INVALID_HANDLE)
        {
         //--- tell about the failure and output the error code 
         PrintFormat("Failed to create handle of the iMACD indicator for the symbol %s/%s, error code %d",
                     Symbol(),
                     EnumToString(Period()),
                     GetLastError());
         //--- the indicator is stopped early 
         return(INIT_FAILED);
        }
      Print("ChartID: ",ChartID(),": ",Symbol(),",",StringSubstr(EnumToString(Period()),7),
            ", create handle iMACD (",handles_array[i],")");
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   Comment("");
   for(int i=0;i<count;i++)
     {
      Print("ChartID: ",ChartID(),": ",Symbol(),",",StringSubstr(EnumToString(Period()),7),
            ", remove handle iMACD (",handles_array[i],"): ",IndicatorRelease(handles_array[i]));
     }
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   string text="";
   for(int i=0;i<count;i++)
     {
      double macd_main_1=iMACDGet(handles_array[i],MAIN_LINE,1);
      if(i<15)
        {
         text+="\n"+"ChartID: "+IntegerToString(ChartID())+": "+Symbol()+
               ", MACD#"+IntegerToString(i)+" "+DoubleToString(macd_main_1,Digits()+1);
         Comment(text);
        }
      else if(i==15)
        {
         text+="\n"+"only the first 15 indicators are displayed ...";
         Comment(text);
        }
     }
  }
//+------------------------------------------------------------------+
//| Get value of buffers for the iMACD                               |
//|  the buffer numbers are the following:                           |
//|   0 - MAIN_LINE, 1 - SIGNAL_LINE                                 |
//+------------------------------------------------------------------+
double iMACDGet(const int handle_iMACD,const int buffer,const int index)
  {
   double MACD[1];
//--- reset error code 
   ResetLastError();
//--- fill a part of the iMACDBuffer array with values from the indicator buffer that has 0 index 
   if(CopyBuffer(handle_iMACD,buffer,index,1,MACD)<0)
     {
      //--- if the copying fails, tell the error code 
      PrintFormat("Failed to copy data from the iMACD indicator, error code %d",GetLastError());
      //--- quit with zero result - it means that the indicator is considered as not calculated 
      return(0.0);
     }
   return(MACD[0]);
  }
//+------------------------------------------------------------------+
//| Create handle MACD                                               |
//+------------------------------------------------------------------+
int CreateHandleMACD(const int fast_ema_period)
  {
//--- create handle of the indicator iMACD
   return(iMACD(Symbol(),Period(),fast_ema_period,52,9,PRICE_CLOSE));
  }
//+------------------------------------------------------------------+

Experimento 1

Dados de origem: no terminal, são abertos os gráficos AUDJPY M15, USDJPY M15 e EURUSD M15 - neles não há indicadores ou EAs. Parâmetro Count MACD indicators do EA iMACD and IndicatorRelease.mq5 igual a 6.

Imediatamente após reiniciar o terminal, anexamos o EA iMACD and IndicatorRelease.mq5 ao primeiro gráfico AUDJPY, M15 (ChartID 131571247244850509):

2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)
2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (11)
2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (12)
2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (13)
2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (14)
2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (15)

Nós vemos que a numeração dos identificadores não começa com 0, mas sim com 10.

Experimento 2

Dados de origem: ao primeiro gráfico (AUDJPY M15) é anexado o EA iMACD and IndicatorRelease.mq5, parâmetro Count MACD indicatorsigual a 6.

Anexamos o EA iMACD and IndicatorRelease.mq5 ao segundo gráfico USDJPY, M15 (ChartID 131571247244850510):

2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (10)
2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (11)
2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (12)
2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (13)
2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (14)
2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (15)

Nós vemos que a numeração dos identificadores no gráfico (USDJPY M15) também não começa com 0, mas sim com 10.

Conclusão: a numeração - dada ao usuário - dos identificadores de indicadores no terminal NÃO é de ponta a ponta e NÃO começa do zero.

Experimento 3

Dois gráficos idênticos AUDJPY, M15 (ChartID 131571247244850509) e AUDJPY, M15 (ChartID 131571247244850510). Para cada EA iMACD and IndicatorRelease.mq5 em anexo, com parâmetro Count MACD indicators igual a 6.

A numeração de ponta a ponta dos identificadores de indicadores criados confirma que a MQL5 mantém seu registro interno para eles (um contador para cada identificador). Para ter certeza disso, comentamos o incremento de período:

int OnInit()
  {
***
   ArrayInitialize(handles_array,0);
   for(int i=0;i<count;i++)
     {
      handles_array[i]=CreateHandleMACD(12/*+i*/);
      //--- if the handle is not created 

Desse modo, tentamos criar vários identificadores do indicador MACD, com exatamente as mesmas configurações.

Removemos dos gráficos os EAs que ficaram após os experimentos № 1 e 2 e, em seguida, anexamos o EA iMACD and IndicatorRelease.mq5 ao primeiro gráfico AUDJPY, M15 (ChartID 131571247244850509):

2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)

Vemos que em resposta à criação de indicadores totalmente idênticos, foi retornado o mesmo identificador.

Anexamos o EA iMACD and IndicatorRelease.mq5 (também com comentário do incremento de período) ao segundo gráfico AUDJPY,M15 (ChartID 131571247244850510):

2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10)
2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10)

Mais uma vez, vemos que é retornado o mesmo identificador. No entanto, surge a questão: os identificadores "10" no primeiro e segundo gráficos são o mesmo ID ou dois diferentes? Para verificar isso, removemos o EA dos gráficos (lembre que o EA em OnDeinit() percorre a matriz de identificadores e remove cada um usando IndicatorRelease).

2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): true
2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false

2018.02.18 07:53:36.116 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): true
2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false
2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false

O resultado era esperado, se considerarmos a Execução de programas:

O EA é executado em seu próprio "thread", além disso, a quantidade de EAs é igual ao número de "threads" de execução para eles.

Quer dizer, se dois EAs nos mesmos gráficos (símbolo e timeframe idênticos) criarem indicadores com os mesmos parâmetros de entrada, a MQL5 em seu registro interno os identificará como dois identificadores diferentes.

Conclusão geral em relação à criação de indicadores em EAs

A numeração - dada ao usuário - dos identificadores de indicadores no terminal NÃO é de ponta a ponta e NÃO começa do zero, enquanto a MQL5 em seu registro interno de identificadores leva em consideração:

  • a função do indicador técnico (iMA, iAC, iMACD, iIchimoku, etc.);
  • os parâmetros de entrada do indicador;
  • o símbolo em que é criado o indicador;
  • o timeframe em que é criado o indicador;
  • o ChartID do gráfico em que funciona o EA.

Faz sentido a cache de identificadores?

Os dados iniciais (timeframe, símbolo, período de teste e tipo de geração de ticks) serão os seguintes:

Cache test Settings

Fig. 1. Configurações

Testes com acesso a indicadores em estilo MQL4 (com cache de identificadores e sem ele) são realizados com a ajuda do EA Cache test.mq5, enquanto os testes com acesso em estilo MQL5 - com a ajuda do EA MQL5 test.mq5:

//+------------------------------------------------------------------+
//|                                                    MQL5 test.mq5 |
//|                              Copyright © 2018, Vladimir Karputov |
//|                                           http://wmua.ru/slesar/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2018, Vladimir Karputov"
#property link      "http://wmua.ru/slesar/"
#property version   "1.000"
//--- input parameters
input bool     UseOneIndicator=false;  // Use indicator: "false" -> 9 indicators, "true" - 1 indicator
//---
int            arr_handle_iMACD[];     // array for storing the handles of the iMACD indicators
//+------------------------------------------------------------------+
//| Expert initialization function                                     |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(UseOneIndicator)
      ArrayResize(arr_handle_iMACD,1);
   else
      ArrayResize(arr_handle_iMACD,9);
   if(!CreateHandle(arr_handle_iMACD))
      return(INIT_FAILED);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int arr_size=ArraySize(arr_handle_iMACD);
   for(int i=0;i<arr_size;i++)
     {
      double macd_main_30=iMACDGet(arr_handle_iMACD[i],MAIN_LINE,0);
     }
  }
//+------------------------------------------------------------------+
//| CreateHandle                                                     |
//+------------------------------------------------------------------+
bool CreateHandle(int &arr_handles[])
  {
   int arr_size=ArraySize(arr_handles);
   for(int i=0;i<arr_size;i++)
     {
      int fast_ema_repiod=30+10*i;
      //--- create handle of the indicator iMACD
      arr_handles[i]=iMACD(NULL,0,fast_ema_repiod,26,9,PRICE_CLOSE);
      //--- if the handle is not created 
      if(arr_handles[i]==INVALID_HANDLE)
        {
         //--- tell about the failure and output the error code 
         PrintFormat("Failed to create handle of the iMACD indicator for the symbol %s/%s, error code %d",
                     Symbol(),
                     EnumToString(Period()),
                     GetLastError());
         //--- the indicator is stopped early 
         return(false);
        }
     }
   return(true);
  }
//+------------------------------------------------------------------+
//| Get value of buffers for the iMACD                               |
//|  the buffer numbers are the following:                           |
//|   0 - MAIN_LINE, 1 - SIGNAL_LINE                                 |
//+------------------------------------------------------------------+
double iMACDGet(const int handle_iMACD,const int buffer,const int index)
  {
   double MACD[1];
//--- reset error code 
   ResetLastError();
//--- fill a part of the iMACDBuffer array with values from the indicator buffer that has 0 index 
   if(CopyBuffer(handle_iMACD,buffer,index,1,MACD)<0)
     {
      //--- if the copying fails, tell the error code 
      PrintFormat("Failed to copy data from the iMACD indicator, error code %d",GetLastError());
      //--- quit with zero result - it means that the indicator is considered as not calculated 
      return(0.0);
     }
   return(MACD[0]);
  }
//+------------------------------------------------------------------+

Parâmetro do EA MQL5 test.mq5:

MQL5 test 1

Fig. 2. "MQL5 test.mq5". Nove indicadores

Parâmetros do EA Cache test.mq5:

  • Use Timer ("0" -> off timer) — utilizar o temporizador (0 indica que não se usa o temporizador).
  • Use indicator ("false" -> 9 indicators, "true" - 1 indicator) — número de indicadores a enviar (1 ou 9).

Cache test 1

Fig. 3. "Cache test.mq5". Sem temporizador, com nove indicadores

Para medição do "estilo MQL4 sem cache de identificadores" são usados os arquivos IndicatorsMQL4.mq conectados com a ajuda de "SimpleCallMQL4.mqh (veja o artigo LifeHack para traders: "amassando" ForEach com os define (#define) ).

#include <SimpleCall\SimpleCallMQL4.mqh> // for tests without caching of the handles
//#include <SimpleCall\SimpleCallMQL4Caching.mqh> // for tests with caching of the handles
//#include <SimpleCall\SimpleCallString.mqh> // for tests with string

Para medição do "estilo MQL4 com cache de identificadores" no arquivo IndicatorsMQL4.mqh, foi adicionado o código de cache de código na publicação #113 (apenas para o MACD, as outras funções são excluídas). O arquivo é salvo com o novo nome IndicatorsMQL4Caching.mqh - ele é conectado usando o arquivo SimpleCallCaching.mqh:

//#include <SimpleCall\SimpleCallMQL4.mqh> // for tests without caching of the handles
#include <SimpleCall\SimpleCallMQL4Caching.mqh> // for tests with caching of the handles
//#include <SimpleCall\SimpleCallString.mqh> // for tests with string

Resultados da comparação dos estilos de acesso a nove indicadores (as configurações são dadas na Fig. 1):


Fig. 4. Tempo gasto no acesso a nove indicadores

Ao comparar os resultados, observe que o EA de teste tornou mais difícil a tarefa:

  • obtemos os dados simultaneamente a partir de NOVE indicadores;
  • acessamos os indicadores EM CADA tick;
  • timeframe M1 - neste caso, foram gerados 26 169 180 ticks e 370 355 barras.

É hora de realizar o teste em que é chamado apenas um indicador (em dois EAs: MQL5 test.mq5 e Cache test.mq5, valor do parâmetro Use indicator...  "true", o valor do parâmetro para o Cache test.mq5 é Use Timer "0")


Fig. 5. Tempo gasto no acesso a um indicador

Conclusão

Em comparação com o estilo MQL4 sem armazenamento em cache, o estilo MQL4 com cache de identificadores dá chance de ganhar - o estilo MQL4 perde para o estilo MQL5 de maneira contundente. 

Falta de controle da validade do identificador

Agora é preciso mencionar o enorme perigo de implementar o armazenamento em cache de identificadores, isto é, em nenhum lugar existe controle sobre a existência do identificador no cache personalizado, quer dizer, a remoção do identificador do indicador não é processada de forma nenhuma. 

Imagine que estamos trabalhando com indicadores em estilo MQL4 e armazenamos em cache os identificadores. Após a primeiro acesso ao EA:

   double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);

o identificador é armazenado no cache do usuário (pode ser uma matriz de estruturas ou uma matriz de cadeias de caracteres). Depois disso, todas as tentativas subsequentes de acesso a partir do EA

   double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);

não poderão passar para o kernel MQL5, em vez disso, serão retornados os valores do indicador, de acordo com o identificador retirado do cache. Agora, em OnTimer(), removemos o identificador - vamos supor que sabemos que é igual a "10". Para o teste, usamos o arquivo Cache test.mq5, ao qual deve ser conectado o arquivo SimpleCallMQL4Caching.mqh:

//#include <SimpleCall\SimpleCallMQL4.mqh> // for tests without caching of the handles
#include <SimpleCall\SimpleCallMQL4Caching.mqh> // for tests with caching of the handles
//#include <SimpleCall\SimpleCallString.mqh> // for tests with string

Temos que colocar o temporizador (neste exemplo, o temporizador é de seis segundos, temos acesso a um indicador)

Cache test 2

Fig. 6 Configuração do teste com remoção do identificador

Após a primeira entrada da OnTimer()

OnTimer, IndicatorRelease(10)=true
iMACD: CopyBuffer error=4807
iMACD: CopyBuffer error=4807
iMACD: CopyBuffer error=4807
iMACD: CopyBuffer error=4807 

obtemos o erro 4807:

 ERR_INDICATOR_WRONG_HANDLE  4807  Identificador de indicador errado

Isso significa que não há controle da validade do identificador do indicador.

Armazenamos em cache os identificadores do indicador. Como funciona

O princípio geral de operação do armazenamento em cache de indicador é o seguinte:

  • cria-se um cache de identificadores personalizado;
  • ao consultar dados a partir do indicador, verificamos se já foi criado esse identificador nas configurações solicitadas (símbolo, timeframe, período de média e assim por diante):
    • se já existir no cache personalizado, retornamos seus dados a partir do indicador;
    • se esse identificador ainda não tiver sido criado, nós o criamos, o colocamos no cache e retornamos seus dados a partir do indicador.

Opção 1: matriz de estruturas

A realização ocorre no arquivo IndicatorsMQL4Caching.mqh (conecta-se ao EA Cache test.mq5 com a ajuda de SimpleCallMQL4Caching.mqh).

No EA Cache test.mq5, conectamos o arquivo SimpleCallMQL4Caching.mqh":

//#include <SimpleCall\SimpleCallMQL4.mqh> // for tests without caching of the handles
#include <SimpleCall\SimpleCallMQL4Caching.mqh> // for tests with caching of the handles
//#include <SimpleCall\SimpleCallString.mqh> // for tests with string

Primeiro, mostrarei um bloco de código grande que é inserido no arquivo e na função iMACD:

...         
//+------------------------------------------------------------------+
//| Struct CHandle                                                   |
//+------------------------------------------------------------------+
template<typename T>
struct SHandle
  {
private:
   int               Handle;
   T                 Inputs;

public:
   //+------------------------------------------------------------------+
   //| A constructor with an initialization list                        |
   //+------------------------------------------------------------------+
                     SHandle() : Handle(INVALID_HANDLE)
     {
     }
   //+------------------------------------------------------------------+
   //| Operation Overloading "=="                                       |
   //+------------------------------------------------------------------+
   bool operator==(const T &Inputs2) const
     {
      return(this.Inputs == Inputs2);
     }
   //+------------------------------------------------------------------+
   //| Operation Overloading "="                                        |
   //+------------------------------------------------------------------+
   void operator=(const T &Inputs2)
     {
      this.Inputs=Inputs2;
     }
   //+------------------------------------------------------------------+
   //| SHandle::GetHandle                                               |
   //+------------------------------------------------------------------+
   int GetHandle()
     {
      return((this.Handle != INVALID_HANDLE) ? this.Handle : (this.Handle = this.Inputs.GetHandle()));
     }
  };
//+------------------------------------------------------------------+
//| Get Handle                                                       |
//+------------------------------------------------------------------+
template<typename T>
int GetHandle(SHandle<T>&Handles[],const T &Inputs)
  {
   const int Size=ArraySize(Handles);

   for(int i=0; i<Size; i++)
      if(Handles[i]==Inputs)
         return(Handles[i].GetHandle());

   ArrayResize(Handles,Size+1);
   Handles[Size]=Inputs;

   return(Handles[Size].GetHandle());
  }
//+------------------------------------------------------------------+
//| Struct Macd                                                      |
//+------------------------------------------------------------------+
struct SMacd
  {
   string            symbol;
   ENUM_TIMEFRAMES   period;
   int               fast_ema_period;
   int               slow_ema_period;
   int               signal_period;
   ENUM_APPLIED_PRICE applied_price;
   //+------------------------------------------------------------------+
   //| An empty default constructor                                     |
   //+------------------------------------------------------------------+
                     SMacd(void)
     {
     }
   //+------------------------------------------------------------------+
   //| A constructor with an initialization list                        |
   //+------------------------------------------------------------------+
                     SMacd(const string             &isymbol,
                                             const ENUM_TIMEFRAMES    &iperiod,
                                             const int                &ifast_ema_period,
                                             const int                &islow_ema_period,
                                             const int                &isignal_period,
                                             const ENUM_APPLIED_PRICE &iapplied_price) :
                                             symbol((isymbol== NULL)||(isymbol == "") ? Symbol() : isymbol),
                                             period(iperiod == PERIOD_CURRENT ? Period() : iperiod),
                                             fast_ema_period(ifast_ema_period),
                                             slow_ema_period(islow_ema_period),
                                             signal_period(isignal_period),
                                             applied_price(iapplied_price)
     {
     }
   //+------------------------------------------------------------------+
   //| SMacd::GetHandle                                                 |
   //+------------------------------------------------------------------+
   int GetHandle(void) const
     {
      return(iMACD(this.symbol, this.period, this.fast_ema_period, this.slow_ema_period, this.signal_period, this.applied_price));
     }
   //+------------------------------------------------------------------+
   //| Operation Overloading "=="                                       |
   //+------------------------------------------------------------------+
   bool operator==(const SMacd &Inputs) const
     {
      return((this.symbol == Inputs.symbol) &&
             (this.period == Inputs.period) &&
             (this.fast_ema_period == Inputs.fast_ema_period) &&
             (this.slow_ema_period == Inputs.slow_ema_period) &&
             (this.signal_period == Inputs.signal_period) &&
             (this.applied_price == Inputs.applied_price));
     }
  };
//+------------------------------------------------------------------+
//| iMACD2 function in MQL4 notation                                 |
//|   The buffer numbers are the following:                          |
//|      MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL                         |
//|      MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE                         |
//+------------------------------------------------------------------+
int iMACD2(const string             symbol,
           const ENUM_TIMEFRAMES    period,
           const int                fast_ema_period,
           const int                slow_ema_period,
           const int                signal_period,
           const ENUM_APPLIED_PRICE applied_price)
  {
   static SHandle<SMacd>Handles[];
   const SMacd Inputs(symbol,period,fast_ema_period,slow_ema_period,signal_period,applied_price);

   return(GetHandle(Handles, Inputs));
  }
//+------------------------------------------------------------------+
//| iAC function in MQL4 notation                                    |
...
//+------------------------------------------------------------------+
//| iMACD function in MQL4 notation                                  |
//|   The buffer numbers are the following:                          |
//|      MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL                         |
//|      MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE                         |
//+------------------------------------------------------------------+
double   iMACD(
               string                     symbol,              // symbol name 
               ENUM_TIMEFRAMES            timeframe,           // timeframe 
               int                        fast_ema_period,     // period for Fast average calculation 
               int                        slow_ema_period,     // period for Slow average calculation 
               int                        signal_period,       // period for their difference averaging 
               ENUM_APPLIED_PRICE         applied_price,       // type of price or handle 
               int                        buffer,              // buffer 
               int                        shift                // shift
               )
  {
   double result=NaN;
//---
   int handle=iMACD2(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price);
   if(handle==INVALID_HANDLE)
...

Descrevamos seu funcionamento. Primeiro, no EA ocorre uma solicitação de dados a partir do indicador MACD:

   double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);

Em seguida, entramos na função do iMACD e vamos para o iMACD2:

//+------------------------------------------------------------------+
//| iMACD2 function in MQL4 notation                                 |
//|   The buffer numbers are the following:                          |
//|      MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL                         |
//|      MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE                         |
//+------------------------------------------------------------------+
int iMACD2(const string             symbol,
           const ENUM_TIMEFRAMES    period,
           const int                fast_ema_period,
           const int                slow_ema_period,
           const int                signal_period,
           const ENUM_APPLIED_PRICE applied_price)
  {
   static SHandle<SMacd>Handles[];
   const SMacd Inputs(symbol,period,fast_ema_period,slow_ema_period,signal_period,applied_price);

   return(GetHandle(Handles, Inputs));
  }

Aqui é declarada a matriz estática Handles[] com o tipo SMacd (ela é criada na primeira entrada e não é criada novamente em entradas ulteriores) e é criado o objeto Inputs com o tipo SMacd, que é inicializado imediatamente pelos parâmetros.

Depois disso, enviamos pelas referências a matriz Handles[] e o objeto Inputs para a função GetHandle (atenção, não é para SHandle::GetHandle e também não para SMacd::GetHandle):

//+------------------------------------------------------------------+
//| Get Handle                                                       |
//+------------------------------------------------------------------+
template<typename T>
int GetHandle(SHandle<T>&Handles[],const T &Inputs)
  {
   const int Size=ArraySize(Handles);

   for(int i=0; i<Size; i++)
      if(Handles[i]==Inputs)
         return(Handles[i].GetHandle());

   ArrayResize(Handles,Size+1);
   Handles[Size]=Inputs;
   return(Handles[Size].GetHandle());
  }

Neste função, quer retornamos o identificador encontrado do indicador para a matriz quer, se o identificador não for encontrado, nós o obtemos em SHandle::GetHandle.

Mas como este é o primeiro acesso e não existe esse identificador

   //+------------------------------------------------------------------+
   //| SHandle::GetHandle                                               |
   //+------------------------------------------------------------------+
   int GetHandle()
     {
      return((this.Handle != INVALID_HANDLE) ? this.Handle : (this.Handle = this.Inputs.GetHandle()));
     }

nós o criamos em SMacd::GetHandle:

   //+------------------------------------------------------------------+
   //| SMacd::GetHandle                                                 |
   //+------------------------------------------------------------------+
   int GetHandle(void) const
     {
      return(iMACD(this.symbol, this.period, this.fast_ema_period, this.slow_ema_period, this.signal_period, this.applied_price));
     }

Opção 2: matriz de cadeias de caracteres

A realização ocorre no arquivo IndicatorsMQL4String.mqh (conecta-se ao EA Cache test.mq5com a ajuda de SimpleCallString.mqh).

No EA Cache test.mq5, conectamos o arquivo SimpleCallString.mqh:

//#include <SimpleCall\SimpleCallMQL4.mqh> // for tests without caching of the handles
//#include <SimpleCall\SimpleCallMQL4Caching.mqh> // for tests with caching of the handles
#include <SimpleCall\SimpleCallString.mqh> // for tests with string

Sabia que trabalhar com strings é terrivelmente caro em termos de velocidade. Um pouco mais tarde, vamos ter certeza disso. Bem, a ideia de armazenar parâmetros na forma de uma string se parece com isto:

   string Hashes[];
   static int Handles[];
   string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+
               (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+
               (string)(fast_ema_period)+
               (string)(slow_ema_period)+
               (string)(signal_period)+
               (string)(applied_price);

Vamos acessar o iMACD a partir do EA com os parâmetros mostrados acima, na Fig. 1.

 NN  Código Hora
  1
//--- NN2
//static string Hashes[];
//static int Handles[];
//string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+
//            (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+
//            (string)(fast_ema_period)+
//            (string)(slow_ema_period)+
//            (string)(signal_period)+
//            (string)(applied_price);
//--- NN3
//static string Hashes[];
//static int Handles[];
//string hash="";
//StringConcatenate(hash,
//                  ((symbol==NULL) || (symbol=="") ? Symbol() : symbol),
//                  (timeframe==PERIOD_CURRENT ? Period() : timeframe),
//                  fast_ema_period,
//                  slow_ema_period,
//                  signal_period,
//                  applied_price);
 0:01:40.953
  2
//--- NN2
   static string Hashes[];
   static int Handles[];
   string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+
               (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+
               (string)(fast_ema_period)+
               (string)(slow_ema_period)+
               (string)(signal_period)+
               (string)(applied_price);
//--- NN3
//static string Hashes[];
//static int Handles[];
//string hash="";
//StringConcatenate(hash,
//                  ((symbol==NULL) || (symbol=="") ? Symbol() : symbol),
//                  (timeframe==PERIOD_CURRENT ? Period() : timeframe),
//                  fast_ema_period,
//                  slow_ema_period,
//                  signal_period,
//                  applied_price);
 0:05:20.953
  3
//--- NN2
//static string Hashes[];
//static int Handles[];
//string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+
//            (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+
//            (string)(fast_ema_period)+
//            (string)(slow_ema_period)+
//            (string)(signal_period)+
//            (string)(applied_price);
//--- NN3
   static string Hashes[];
   static int Handles[];
   string hash="";
   StringConcatenate(hash,
                     ((symbol==NULL) || (symbol=="") ? Symbol() : symbol),
                     (timeframe==PERIOD_CURRENT ? Period() : timeframe),
                     fast_ema_period,
                     slow_ema_period,
                     signal_period,
                     applied_price);
 0:04:12.672

O teste 1 é um teste de referência com acesso a indicadores em estilo MQL4, sem trabalhar com strings. No teste 2, já se trabalha com strings, formadas através do "+". No teste 3, a cadeia de caracteres é formada usando StringConcatenate.

De acordo com as medições do tempo, pode-se observar que, em comparação com o teste 2, StringConcatenate dá um ganho de tempo de 21%, no entanto o desempenho geral ainda é 2,5 vezes menor do que no teste 1.

Daí que descartamos a idéia de salvar os identificadores na forma de cadeias de caracteres.

A opção 3 é uma classe que armazena em cache identificadores  (classe iIndicators.mqh é conectada ao EA Cache test.mq5 com a ajuda de SimpleCallMQL4CachingCiIndicators.mqh).

No EA Cache test.mq5 conectamos o arquivo SimpleCallMQL4CachingCiIndicators.mqh:

//#include <SimpleCall\SimpleCallMQL4.mqh> // for tests without caching of the handles
//#include <SimpleCall\SimpleCallMQL4Caching.mqh> // for tests with caching of the handles
//#include <SimpleCall\SimpleCallString.mqh> // for tests with string
#include <SimpleCall\SimpleCallMQL4CachingCiIndicators.mqh>

Para cada indicador (dentro da função correspondente em estilo MQL4), cria-se um objeto estático de classe CHandle. Ele serve como um repositório dos objetos da classe CiIndicators, isto é, da classe que contém os parâmetros e configurações do indicador.

Scheme

Fig. 6 O esquema

A classe CiIndicators em si é construída sobre cinco variáveis private:

//+------------------------------------------------------------------+
//| Class iIndicators                                                |
//+------------------------------------------------------------------+
class CiIndicators
  {
private:
   string            m_symbol;                        // symbol name 
   ENUM_TIMEFRAMES   m_period;                        // timeframe 
   ENUM_INDICATOR    m_indicator_type;                // indicator type from the enumeration ENUM_INDICATOR 
   int               m_parameters_cnt;                // number of parameters 
   MqlParam          m_parameters_array[];            // array of parameters 

public:

Eles correspondem com exatidão às variáveis da função  IndicatorCreate. Isso não é feito em vão, pois nós obtemos o identificador do indicador, precisamente, através de IndicatorCreate.

A classe CHandle é construída sobre dois matrizes:

//+------------------------------------------------------------------+
//| Class CHandle                                                    |
//+------------------------------------------------------------------+
class CHandle
  {
private:
   int               m_handle[];
   CiIndicators      m_indicators[];

public:

A matriz m_handle contém a os identificadores de indicadores criados, enquanto a matriz m_indicators é um array de classe CiIndicators.

O código de trabalho com as classes CiIndicators e CHandle, usando o exemplo do MACD, se assemelha a isso:

//+------------------------------------------------------------------+
//| iMACD function in MQL4 notation                                  |
//|   The buffer numbers are the following:                          |
//|      MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL                         |
//|      MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE                         |
//+------------------------------------------------------------------+
double   iMACD(
               string                     symbol,              // symbol name 
               ENUM_TIMEFRAMES            timeframe,           // timeframe 
               int                        fast_ema_period,     // period for Fast average calculation 
               int                        slow_ema_period,     // period for Slow average calculation 
               int                        signal_period,       // period for their difference averaging 
               ENUM_APPLIED_PRICE         applied_price,       // type of price or handle 
               int                        buffer,              // buffer 
               int                        shift                // shift
               )
  {
//---
   static CHandle Handles_MACD;
//--- fill the structure with parameters of the indicator      
   MqlParam pars[4];
//--- period of fast ma 
   pars[0].type=TYPE_INT;
   pars[0].integer_value=fast_ema_period;
//--- period of slow ma 
   pars[1].type=TYPE_INT;
   pars[1].integer_value=slow_ema_period;
//--- period of averaging of difference between the fast and the slow moving average 
   pars[2].type=TYPE_INT;
   pars[2].integer_value=signal_period;
//--- type of price 
   pars[3].type=TYPE_INT;
   pars[3].integer_value=applied_price;

   CiIndicators MACD_Indicator;
   MACD_Indicator.Init(Symbol(),Period(),IND_MACD,4);
   int handle=Handles_MACD.GetHandle(MACD_Indicator,Symbol(),Period(),IND_MACD,4,pars);
//---
   double result=NaN;
//---
   if(handle==INVALID_HANDLE)
     {
      Print(__FUNCTION__,": INVALID_HANDLE error=",GetLastError());
      return(result);
     }
   double val[1];
   int copied=CopyBuffer(handle,buffer,shift,1,val);
   if(copied>0)
      result=val[0];
   else
      Print(__FUNCTION__,": CopyBuffer error=",GetLastError());
   return(result);
  }
  • É declarada a matriz Handles_MACD de classe CHandle - ela armazenará os identificadores criados e os parâmetros dos indicadores MACD.
  • Cria-se e inicializa-se o objeto MACD_Indicator de classe CiIndicators.
  • O próprio indicador é criado (ou dado, ser já tiver sido criado para esses parâmetros) na função  Handles_MACD::GetHandle.

O trabalho da classe CiIndicators.mqh com acesso em estilo MQL4 e armazenamento em cache de identificadores demorou 2 minutos e 30 segundos.


Gráfico final da velocidade de acesso a nove indicadores

Verificamos o estilo MQL4 sem e com armazenamento em cache, com a ajuda do Expert Advisor "Cache test.mq5", enquanto verificamos os testes do estilo MQL5 padrão, usando o Expert Advisor "MQL5 test.mq5".


Fim do artigo

Realizamos alguns experimentos interessantes que entram em conflito com o paradigma do correto acesso MQL5 a indicadores. Finalmente, aprendemos mais sobre o mecanismo interno de trabalho com os identificadores dentro do kernel MQL5:

  • sobre o contador de identificadores;
  • sobre o armazenamento em cache e controle das identificadores.

No que diz respeito às formas de acesso a indicadores, os resultados do teste mostraram que a velocidade do estilo MQL5 ultrapassou os estilos MQL4 (com e sem armazenamento em cache de identificadores).