Como transferir a parte de cálculo de qualquer indicador para o código do EA

9 julho 2018, 14:13
Dmitriy Gizlyk
1
1 527

Sumário

Introdução

Quando um programador cria um EA que recebe sinais de indicadores, ele sempre defronta a questão sobre se usar o indicador ou se transferir o código do indicador para o EA. As razões para isso podem ser diferentes: o desejo de manter em segredo os indicadores e a estratégia usados, a necessidade de distribuir o EA num único arquivo, o desejo de reduzir o número de operações realizadas nos casos em que nem todos os sinais/buffers do indicador são usados, etc. Claro, não sou o primeiro nem o último a fazer essa pergunta, penso eu. Nikolay Kositsin já considerou um tópico similar para o MetaTrader 4. Vamos ver como isso pode ser feito na plataforma MetaTrader 5.

1. Princípios de transferência de código

Antes de começar a trabalhar, analise as diferenças entre o trabalho de indicadores e o trabalho de EAs. Considere o modelo vazio do indicador.

//+------------------------------------------------------------------+
//|                                                        Blanc.mq5 |
//|                                             Copyright 2018, DNG® |
//|                                 http://www.mql5.com/pt/users/dng |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, DNG®"
#property link      "http://www.mql5.com/pt/users/dng"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
//--- plot Buffer
#property indicator_label1  "Buffer"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- indicator buffers
double         BufferBuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,BufferBuffer,INDICATOR_DATA);
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

No início do código do indicador, são declaradas as matrizes-buffer para intercâmbio de dados com outros programas. Estas matrizes são timeseries, e seus elementos estão relacionados às barras de preço. Esta relação é suportada diretamente pelo terminal. O indicador armazena os resultados dos cálculos nestas matrizes, não se preocupando em alterar seu tamanho e em transferir dados quando uma nova vela aparece. Não há matrizes no Expert Advisor, portanto, ao transferir o código do indicador para o Expert Advisor, será preciso criá-las. Além da parte de cálculo, você também precisa organizar a relação entre os elementos da matriz e as barras no gráfico. A desvantagem é que dentro do Expert Advisor existe a possibilidade de não fazer cálculos em todo o histórico (o que acontece no indicador). Basta recalcular com base na profundidade dos dados usados.

Dessa maneira, no Expert Advisor, você precisa criar buffers de indicador. Neste caso, é preciso lembrar que o indicador pode ter não apenas buffers para exibir informações no gráfico, mas também buffers auxiliares para cálculos intermediários. Eles também precisam ser criados. Além disso, os buffers de cor de plotagem podem ser ignorados, quando a alteração da cor nas linhas de indicador não fazem parte da estratégia do EA.

Outra diferença arquitetural entre os indicadores e os EAs é a função de processamento de ticks. Ao contrário do MetaTrader 4, o MetaTrader 5 separa os manipuladores de ticks de entrada para indicadores e EAs. Após um novo tick chegar, a função OnCalculate é chamada no indicador. Nos parâmetros, ela recebe o número total de barras no gráfico, o número de barras na chamada anterior e as timeseries necessárias para calcular o indicador. No Expert Advisor, os novos ticks são processados ​​na função OnTick, que não possui parâmetros. Portanto, você tem que criar acesso independente a timeseries e garantir o rastreamento de alterações no gráfico.

2. Crie a classe de cálculo do indicador

Muitas vezes, nas estratégias dos EAs, é usado um indicador com diferentes parâmetros, é por isso que, na minha opinião, faz sentido aproveitar os recursos da POO e envolver nosso indicador na classe CIndicator.

Em resumo, é isto que você deve fazer para transferir a parte de cálculo do indicador para o EA:

  1. Organizar o trabalho dos buffers de indicador. Para fazer isso, crie uma classe CArrayBuffer e nela - os métodos para armazenar e acessar dados facilmente. Mais tarde, você vai criar uma matriz dessas classes pelo número de buffers no indicador.
  2. Transfira a parte de cálculo do indicador da função OnCalculate para a função Calculate da nossa classe.
  3. O indicador obtém acesso ao timeseries a partir dos parâmetros da função OnCalculate, que não está presente nas funções do EA. Por isso, organize o carregamento dos timeseries necessários na função LoadHistory.
  4. Para unificar o acesso aos dados recalculados do indicador, crie na classe CIndicator a função CopyBuffer com os parâmetros necessários. 

Todo o trabalho adiante pode ser resumido no esquema a seguir:


Além disso, falando do indicador, vou manter em mente a cópia do indicador criado no código do EA.

2.1. Crie um buffer de indicador

Use a classe CArrayDouble, para criar buffers de indicador. Com base nela, crie uma nova classe CArrayBuffer.

class CArrayBuffer   :  public CArrayDouble
  {
public:
                     CArrayBuffer(void);
                    ~CArrayBuffer(void);
//---
   int               CopyBuffer(const int start, const int count, double &double_array[]);
   int               Initilize(void);
   virtual bool      Shift(const int shift);
  };

Crie o método CopyBuffer para que a aquisição de dados do formulário seja similar ao acesso padrão do indicador. Adicione também dois métodos de utilitário: Initilize para limpar os dados do buffer e Shift para deslocamento de dados dentro do buffer quando aparecer uma nova vela. O código das funções pode ser encontrado no anexo.

2.2. Classe pai para indicadores futuros

O próximo passo é criar o "esqueleto" do indicador na classe base CIndicator.

class CIndicator
  {
private:
//---
   datetime             m_last_load;
public:
                        CIndicator(void);
                       ~CIndicator(void);
   virtual bool         Create(const string symbol=NULL, const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const ENUM_APPLIED_PRICE price=PRICE_CLOSE);
//--- Set indicator's main settings
   virtual bool         SetBufferSize(const int bars);
//--- Get indicator's data
   virtual int          CopyBuffer(const uint buffer_num,const uint start, const uint count, double &double_array[]);
   virtual double       GetData(const uint buffer_num,const uint shift);

protected:
   double               m_source_data[];
   CArrayBuffer         ar_IndBuffers[];
   int                  m_buffers;
   int                  m_history_len;
   int                  m_data_len;
//---
   string               m_Symbol;
   ENUM_TIMEFRAMES      m_Timeframe;
   ENUM_APPLIED_PRICE   m_Price;      
//--- Set indicator's main settings
   virtual bool         SetHistoryLen(const int bars=-1);
//---
   virtual bool         LoadHistory(void);
   virtual bool         Calculate()                         {  return true;   }
  };

Existem 6 métodos públicos nesta classe:

  • construtor,
  • destruidor,
  • método de inicialização de classe,
  • método para indicar o tamanho do buffer de indicador,
  • dois métodos para acessar os dados do indicador: um é para o carregamento de dados em lote e o segundo é para o acesso de endereço a um item específico.

Declare a parte principal dos membros da classe na zona protected, isto é:

  • matriz de dados de origem para cálculo (m_source_data);
  • matriz de buffers de indicador (ar_IndBuffers);
  • variáveis ​​para armazenar o número de buffers de indicador (m_buffers), profundidade do histórico necessário dos dados de origem (m_history_len), profundidade do histórico necessário dos valores do indicador (m_data_len);
  • instrumento utilizado (m_Symbol) e timeframe (m_Timeframe);
  • tipo de preço para calcular o indicador (m_Price);
  • métodos para definir: profundidade dos dados de origem (SetHistoryLen); carregamento dos dados históricos de timeseries (LoadHistory); recálculo do indicador (Calculate). 

Todos os métodos são criados como virtuais para que possam ser posteriormente ajustados às necessidades de um indicador específico. No construtor de classe, inicialize as variáveis ​​e libere as matrizes.

CIndicator::CIndicator()   :  m_buffers(0),
                              m_Symbol(_Symbol),
                              m_Timeframe(PERIOD_CURRENT),
                              m_Price(PRICE_CLOSE),
                              m_last_load(0)
  {
   m_data_len=m_history_len  =  Bars(m_Symbol,m_Timeframe)-1;
   ArrayFree(ar_IndBuffers);
   ArrayFree(m_source_data);
  }

Na função de inicialização de classe, primeiro verifique se pode ser usado o símbolo especificado. Para fazer isso, verifique se ele está ativo na observação do mercado, caso contrário, tente escolhê-lo. Se não puder ser usado o símbolo, a função retornará false. Se a verificação for bem-sucedida, salve - nas respectivas variáveis - o símbolo, o timeframe e o preço usado para o cálculo.

bool CIndicator::Create(const string symbol=NULL,const ENUM_TIMEFRAMES timeframe=0,const ENUM_APPLIED_PRICE price=1)
  {
   m_Symbol=(symbol==NULL ? _Symbol : symbol);
   if(!SymbolInfoInteger(m_Symbol,SYMBOL_SELECT))
      if(!SymbolSelect(m_Symbol,true))
         return false;
//---
   m_Timeframe=timeframe;
   m_Price=price;
//---
   return true;
  }

O método que define o tamanho do buffer de indicador possui apenas um parâmetro - o tamanho em si. Neste caso, se você quiser usar todo o histórico disponível, basta passar para a função um número igual ou menor que "0". Na própria função, primeiro salve o valor do parâmetro transferido para a variável correspondente. Em seguida, verifique se há suficientes dados históricos de timeseries para obter o histórico especificado do indicador. Se os valores iniciais forem insuficientes, é aumentado o tamanho dos dados carregados. No final da função, limpe e altere o tamanho de todos os buffers de indicador.

bool CIndicator::SetBufferSize(const int bars)
  {
   if(bars>0)
      m_data_len  =  bars;
   else
      m_data_len  =  Bars(m_Symbol,m_Timeframe);
//---
   if(m_data_len<=0)
     {
      for(int i=0;i<m_buffers;i++)
         ar_IndBuffers[i].Shutdown();
      return false;
     }
//---
   if(m_history_len<m_data_len)
      if(!SetHistoryLen(m_data_len))
         return false;
//---
   for(int i=0;i<m_buffers;i++)
     {
      ar_IndBuffers[i].Shutdown();
      if(!ar_IndBuffers[i].Resize(m_data_len))
         return false;
     }
//---
   return true;
  }

Para obter os dados históricos dos timeseries, use a função LoadHistory. Ela não possui parâmetros e deriva os valores iniciais dos dados armazenados nas funções anteriores.

Muitas vezes, durante a formação de uma vela, também muda o valor atual do indicador, e isso pode levar ao aparecimento de sinais falsos. Por esse motivo, muitas estratégias de indicador usam dados de velas fechadas. Partindo dessa lógica, para as necessidades do EA, bastará carregar uma vez os dados históricos ao se formar uma nova vela. Por essa razão, no início da função, você verifica se existe abertura de uma nova barra. Se a nova barra não abrir e os dados já estiverem carregados, você deve sair da função. Se você quiser carregar os dados, vá para o próximo bloco de funções. Se, para o cálculo do indicador, for suficiente um timeseries, carregue os dados necessários em nossa matriz de dados de origem. Quando o indicador usa um preço mediano, típico ou médio ponderado, primeiro carregue os dados históricos numa matriz de estruturas MqlRates e, no ciclo, organize o cálculo do preço necessário. Os resultados do cálculo são armazenados numa matriz de dados de origem para uso posterior.

bool CIndicator::LoadHistory(void)
  {
   datetime cur_date=(datetime)SeriesInfoInteger(m_Symbol,m_Timeframe,SERIES_LASTBAR_DATE);
   if(m_last_load>=cur_date && ArraySize(m_source_data)>=m_history_len)
      return true;
//---
   MqlRates rates[];
   int total=0,i;
   switch(m_Price)
     {
      case PRICE_CLOSE:
        total=CopyClose(m_Symbol,m_Timeframe,1,m_history_len,m_source_data);
        break;
      case PRICE_OPEN:
        total=CopyOpen(m_Symbol,m_Timeframe,1,m_history_len,m_source_data);
      case PRICE_HIGH:
        total=CopyHigh(m_Symbol,m_Timeframe,1,m_history_len,m_source_data);
      case PRICE_LOW:
        total=CopyLow(m_Symbol,m_Timeframe,1,m_history_len,m_source_data);
      case PRICE_MEDIAN:
        total=CopyRates(m_Symbol,m_Timeframe,1,m_history_len,rates);
        if(total!=ArraySize(m_source_data))
           total=ArrayResize(m_source_data,total);
        for(i=0;i<total;i++)
           m_source_data[i]=(rates[i].high+rates[i].low)/2;
        break;
      case PRICE_TYPICAL:
        total=CopyRates(m_Symbol,m_Timeframe,1,m_history_len,rates);
        if(total!=ArraySize(m_source_data))
           total=ArrayResize(m_source_data,total);
        for(i=0;i<total;i++)
           m_source_data[i]=(rates[i].high+rates[i].low+rates[i].close)/3;
        break;
      case PRICE_WEIGHTED:
        total=CopyRates(m_Symbol,m_Timeframe,1,m_history_len,rates);
        if(total!=ArraySize(m_source_data))
           total=ArrayResize(m_source_data,total);
        for(i=0;i<total;i++)
           m_source_data[i]=(rates[i].high+rates[i].low+2*rates[i].close)/4;
        break;
     }
//---
   if(total<=0)
      return false;
//---
   m_last_load=cur_date;
   return (total>0);
  }

Se os dados não forem carregados durante a execução da função, a função retornará false.

O método de aquisição de dados do indicador será feito por analogia com o acesso padrão aos buffers de indicador. Para fazer isso, crie a função CopyBuffer para cujos parâmetros você irá transferir o número do buffer, a posição inicial da cópia de dados, o número de elementos necessários e a matriz para aquisição de dados. Após a execução, a função retorna o número de elementos copiados.

int CIndicator::CopyBuffer(const uint buffer_num,const uint start,const uint count,double &double_array[])
  {
   if(!Calculate())
      return -1;
//---
   if((int)buffer_num>=m_buffers)
     {
      ArrayFree(double_array);
      return -1;
     }
//---
   return ar_IndBuffers[buffer_num].CopyBuffer(start,count,double_array);
  }

Para que o usuário obtenha sempre dados relevantes, no início da função, chame a função de recálculo do indicador (nesta classe apenas você tem declarado a função virtual, já o cálculo deve ser feito diretamente na classe final do indicador). Após recalcular os valores do indicador, verifique se ele tem o buffer especificado. Se o número do buffer estiver incorreto, limpe a matriz-coletor e saia da função com o resultado "-1". Se o número do buffer for verificado com sucesso, chame o método CopyBuffer da matriz de buffer correspondente.

A função de acesso de endereço a dados é construída de maneira semelhante.

O código completo da classe e todas as suas funções podem ser encontrados no anexo.

2.3. Classe de indicador da média móvel

Para mostrar o método, escolhi o indicador de média móvel (MA). Minha escolha não é acidental. Este indicador de análise técnica não é usado apenas por traders em sua forma clássica, mas também é amplamente usado para a construção de outros indicadores, entre eles o MACD, o Alligator e muitos outros. Além disso, no original, há um exemplo de MA, do qual podemos receber dados através da função iCustom para comparar a velocidade de acesso ao indicador com a velocidade do cálculo de dados no Expert Advisor.

Vamos calcular o MA na classe CMA. Nossa classe receberá 4 métodos públicos: o construtor, o destruidor, o método de inicialização (Create) e o método para definir a profundidade dos dados históricos do indicador (que iremos reescrever). Os métodos de acesso aos dados do indicador são herdados - do pai - pela nossa classe.

class CMA : public CIndicator
  {
private:
   int               m_Period;
   int               m_Shift;
   ENUM_MA_METHOD    m_Method;
   datetime          m_last_calculate;
   
public:
                     CMA();
                    ~CMA();
   bool              Create(const string symbol, const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE price=PRICE_CLOSE);
   virtual bool      SetBufferSize(const int bars);
   
protected:
   virtual bool      Calculate();
   virtual double    CalculateSMA(const int shift);
   virtual double    CalculateEMA(const int shift);
   virtual double    CalculateLWMA(const int shift);
   virtual double    CalculateSMMA(const int shift);
  };

Como você pode ver no cabeçalho da classe acima, nesta etapa os elementos aparecem para o cálculo direto do indicador. Trata-se de variáveis privadas para armazenar o período, o deslocamento e método de cálculo do indicador. No bloco protected, reescreva a função virtual de cálculo do indicador Calculate. Dependendo do método definido para o cálculo do indicador, ele chamará as subfunções CalculateSMA, CalculateEMA, CalculateLWMA ou CalculateSMMA.

No construtor da classe, inicialize as variáveis, especifique o número de buffers do indicador e crie um buffer de indicador.

CMA::CMA()  :  m_Period(25),
               m_Shift(0),
               m_Method(MODE_SMA)
  {
   m_buffers=1;
   ArrayResize(ar_IndBuffers,1);
  }

Nos parâmetros da função de inicialização de classe, indique o símbolo, timeframe e parâmetros necessários para o cálculo do indicador. Na própria função, primeiro chame a função de inicialização da classe pai. Em seguida, verifique a validade do período médio definido (deve ser positivo). Depois disso, salve os parâmetros do indicador nas variáveis ​​de classe apropriadas e defina a profundidade do histórico para o buffer de indicador e para os dados carregados dos timeseries. Se ocorrer um erro, a função retornará false. Após uma inicialização bem-sucedida, será retornado true.

bool CMA::Create(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int ma_shift,const ENUM_MA_METHOD ma_method,const ENUM_APPLIED_PRICE price=1)
  {
   if(!CIndicator::Create(symbol,timeframe,price))
      return false;
//---
   if(ma_period<=0)
      return false;
//---
   m_Period=ma_period;
   m_Shift=ma_shift;
   m_Method=ma_method;
//---
   if(!SetBufferSize(ma_period))
      return false;
   if(!SetHistoryLen(2*ma_period+(m_Shift>0 ? m_Shift : 0)))
      return false;
//---
   return true;
  }

A função Calculate calculará diretamente o indicador. Anteriormente, ao criar a classe pai, decidimos que carregaríamos os dados históricos dos timeseries ao abrir uma nova vela. Portanto, recalcularemos os dados do indicador com a mesma frequência. Para fazer isso, no início da função, verifique a abertura de uma nova vela. Realizado o cálculo na barra atual, saia da função com o resultado true.

Além disso, se uma nova barra for aberta, chamaremos a função de carregar os dados das timeseries. Se os dados históricos forem carregados com sucesso, verifique o número de velas geradas após o último recálculo do indicador. Se o número de novas velas for maior do que o tamanho do nosso buffer de indicador, inicialize-o novamente. Se houver menos velas novas, desloque os dados dentro do nosso buffer de acordo com o número de barras que apareceram. A seguir, recalcule apenas novos elementos.

Agora organize um ciclo para o recálculo de novos elementos do buffer de indicador. Atenção: se o elemento recalculado exceder o tamanho do buffer de indicador atual (isso é possível quando o cálculo é iniciado pela primeira vez ou o cálculo após a conexão ser interrompida, quando o número de velas novas excede o tamanho do buffer), os dados são adicionados ao buffer pelo método Add. Se o elemento recalculado coincidir com o tamanho do buffer existente, o valor do elemento será atualizado pelo método Update. O cálculo direto dos valores dos indicadores é realizado em subfunções correspondentes ao método de cálculo da média. A lógica de cálculo é tomada do indicador Custom Moving Average.mq5 da entrega standard do MetaTrader 5.

Após a conversão bem-sucedida do buffer de indicador, salve o tempo do último recálculo e saia da função com o resultado true.

bool CMA::Calculate(void)
  {
   datetime cur_date=(datetime)SeriesInfoInteger(m_Symbol,m_Timeframe,SERIES_LASTBAR_DATE);
   if(m_last_calculate==cur_date && ArraySize(m_source_data)==m_history_len)
      return true;
//---
   if(!LoadHistory())
      return false;
//---
   int shift=Bars(m_Symbol,m_Timeframe,m_last_calculate,cur_date)-1;
   if(shift>m_data_len)
     {
      ar_IndBuffers[0].Initilize();
      shift=m_data_len;
     }
   else
      ar_IndBuffers[0].Shift(shift);
//---
   for(int i=(m_data_len-shift);i<m_data_len;i++)
     {
      int data_total=ar_IndBuffers[0].Total();
      switch(m_Method)
        {
         case MODE_SMA:
           if(i>=data_total)
              ar_IndBuffers[0].Add(CalculateSMA(i+m_Shift));
           else
              ar_IndBuffers[0].Update(i,CalculateSMA(i+m_Shift));
           break;
         case MODE_EMA:
           if(i>=data_total)
              ar_IndBuffers[0].Add(CalculateEMA(i+m_Shift));
           else
              ar_IndBuffers[0].Update(i,CalculateEMA(i+m_Shift));
           break;
         case MODE_SMMA:
           if(i>=data_total)
              ar_IndBuffers[0].Add(CalculateSMMA(i+m_Shift));
           else
              ar_IndBuffers[0].Update(i,CalculateSMMA(i+m_Shift));
           break;
         case MODE_LWMA:
           if(i>=data_total)
              ar_IndBuffers[0].Add(CalculateLWMA(i+m_Shift));
           else
              ar_IndBuffers[0].Update(i,CalculateLWMA(i+m_Shift));
           break;
        }
     }
//---
   m_last_calculate=cur_date;
   m_data_len=ar_IndBuffers[0].Total();
//---
   return true;
  }

Nesta classe, também reescreva a função virtual da classe pai que define o tamanho desejado do buffer de indicador. Isso é necessário devido à verificação da correspondência entre a profundidade do buffer de indicador e a profundidade dos dados históricos dos timeseries. Na classe pai, especifique que o número de elementos nos timeseries não deve ser menor do que os elementos no buffer de indicador. No que diz respeito ao cálculo do MA, o número de elementos nos timeseries deve ser maior do que o tamanho do buffer de indicador, pelo menos para o período de média.

3. Exemplo de adição de uma classe de indicador ao EA

Quando planejei escrever este artigo, um dos meus objetivos era comparar a velocidade do processamento de dados dentro do EA com a velocidade da aquisição de dados do indicador. É por isso que, para demonstrar o trabalho da classe, decidi não criar um robô de negociação completo. Vamos agora para a preparação de um EA que você poderá completar usando sua lógica de processamento dos sinais do indicador.

Crie o novo arquivo do Expert Advisor Test_Class.mq5. Seus parâmetros de entrada serão semelhantes aos parâmetros do indicador usado.

input int                  MA_Period   =  25;
input int                  MA_Shift    =  0;
input ENUM_MA_METHOD       MA_Method   =  MODE_SMA;
input ENUM_APPLIED_PRICE   MA_Price    =  PRICE_CLOSE;

Globalmente, declare a instância da nossa classe de indicador e a matriz para obter os dados do indicador.

CMA   *MA;
double c_data[];

Na função OnInit, você deve inicializar a instância da nossa classe de indicador e passar os dados de origem para ela.

int OnInit()
  {
//---
   MA=new CMA;
   if(CheckPointer(MA)==POINTER_INVALID)
      return INIT_FAILED;
//---
   if(!MA.Create(_Symbol,PERIOD_CURRENT,MA_Period,MA_Shift,MA_Method,MA_Price))
      return INIT_FAILED;
   MA.SetBufferSize(3);
//---
   return(INIT_SUCCEEDED);
  }

Após o EA concluir seu trabalho, é necessário limpar a memória e excluir a instância da nossa classe, o que faremos na função OnDeinit.

void OnDeinit(const int reason)
  {
//---
   if(CheckPointer(MA)!=POINTER_INVALID)
      delete MA;
  }

Agora a classe está pronta para trabalhar. Resta apenas adicionar a aquisição de dados do indicador na função OnTick. No início da função, verifique a abertura de uma nova barra, em seguida, chame o método CopyBuffer da nossa classe. Depois, seguirão seu próprio código de processamento de sinal e código de operações de negociação.

void OnTick()
  {
//---
   static datetime last_bar=0;
   datetime cur_date=(datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE);
   if(last_bar==cur_date)
      return;
   last_bar=cur_date;
//---
   if(!MA.CopyBuffer(MAIN_LINE,0,3,c_data))
      return;

//---
//     Aqui é adicionado seu código de processamento de sinal e de operações de negociação
//---
   return;
  }

O código completo de todos os programas e de todas as classes está disponível no anexo.

4. O "custo" de usar o indicador transferido

Outra pergunta muito importante é: como a transferência do código do indicador afeta o funcionamento do EA? Para respondê-la, vamos realizar vários experimentos.

4.1. Experimento 1

Eu já disse que escolhi o indicador MA não por acaso. Agora podemos verificar a velocidade de aquisição dos mesmos dados de três maneiras:

  • através da função do indicador (iMA) embutido no terminal;
  • através da chamada de um indicador personalizado similar (iCustom);
  • através do cálculo diretamente dentro do EA.

A primeira coisa que vem à mente é usar a função de criação de perfil do MetaEditor. Para fazer isso, criamos um EA não comercial que receba simultaneamente dados de todas as três fontes. Acho que não há necessidade de dar aqui uma descrição completa do funcionamento deste EA, uma vez que você pode encontrar seu código no anexo. Digamos que, para manter a integridade do experimento, as três fontes de dados são acessadas somente na abertura de uma nova vela.

A criação de perfil é realizada no testador de estratégia para 15 meses no timeframe M15. Como resultado do experimento, são obtidos os seguintes dados.

Função Tempo médio de execução, µs Fração do tempo total
OnTick
99.14%
Verificação de abertura de nova barra 0.528 67,23%
Cálculo interno
21.524 2.36%
      incluindo CopyClose 1.729  0.19%
 iMA  2.231  0.24%
 iCustom  0.748  0.08%
 OnInit  241439  0.86%
 Adquisição do identificador de iCustom  235676  0.84%

A primeira coisa que chama a atenção é a grande quantidade de tempo que leva obter o identificador do indicador através da função iCustom. São dezenas de vezes o tempo para inicializar a classe de indicador e obter o identificador do indicador através da função iMA. Ao mesmo tempo, a aquisição de dados do indicador, inicializado pela função iCustom, ocorre três vezes mais rápido do que a aquisição de dados do indicador iMA e 30 vezes mais rápido do que calcular o valor do indicador na classe.


Vamos considerar mais detalhadamente o tempo de execução das diferentes funções da nossa classe de indicador. Observe que o tempo de aquisição de dados históricos pela função CopyClose é comparável ao tempo de aquisição de dados do indicador. Será que o indicador quase não perde tempo calculando? Na realidade, as coisas funcionam de uma maneira um pouco diferente. A arquitetura do MetaTrader 5 dispõe de um acesso assíncrono aos valores dos indicadores. Em outras palavras, ao obter o identificador do indicador, ele é anexado ao gráfico. Em seguida, esse indicador faz seus cálculos fora do fluxo do EA. Eles interagem apenas na etapa de transferência de dados, semelhante à aquisição de dados de timeseries. Portanto, o tempo para realizar essas operações é comparável.

Resumindo este experimento, provamos o erro ao usar a função de criação de perfil do MetaEditor para estimar o tempo gasto no cálculo dos indicadores usados ​​nos EAs.

4.2. Experimento 2

Crie 4 EAs separados:

  1. Um EA vazio de referência que não executa nenhuma função. Servirá para estimar o tempo gasto pelo próprio terminal para revisar o histórico de cotações.
  2. Um EA que recebe dados calculando valores na classe de indicador.
  3. Um EA que recebe dados do indicador iMA.
  4. Um EA que recebe dados do indicador personalizado.

Depois disso, inicializamos sua otimização em 11 passagens no testador de estratégia e compararemos o tempo médio de uma passagem.

Experimento 2Experimento 2

Os resultados do teste mostram uma economia de tempo ao usar cálculos no EA. A tarefa mais demorada é obter os dados do indicador personalizado.


Repare que, durante o experimento, o MA é calculado de acordo com o preço de fechamento. A parte de cálculo deste indicador é bastante simples. Surge a pergunta: como mudará a situação se os cálculos se tornarem mais complicados? Vamos descobrir isso, realizando mais um experimento.

4.3. Experimento 3

Este experimento repete o anterior, mas para aumentar a carga na parte de cálculo, os indicadores são calculados para a média linear ponderada do preço médio ponderado.

Experimento 3Experimento 3

Vemos que o gasto de tempo aumenta em todos os métodos de aquisição de dados. Ao acontecer isto, observa-se um aumento proporcional no tempo de uma passagem, o que, de modo geral, confirma os resultados do experimento anterior.


Fim do artigo

O artigo mostrou uma maneira de transferir a parte de cálculo do indicador para um EA. O uso de POO permite que o acesso aos dados finais do indicador se torne o mais próximo possível da aquisição de dados padrão dos buffers de indicador. Isso requer intervenção mínima no código fonte do EA ao ser repensado.

Com base nos resultados dos experimentos realizados, essa abordagem também pode economizar tempo durante os testes e durante a otimização dos EAs. Mas, quando o EA trabalha em tempo real, essa vantagem pode ser atenuada pela arquitetura multi-threaded do MetaTrader 5.

Programas utilizados no artigo:

#
 Nome
Tipo 
Descrição 
1 Indicarot.mqh  Biblioteca de classes  Classe base para transferência de indicadores
2 MA.mqh  Biblioteca de classes  Classe para calcular o indicador MA dentro do EA
3 Test.mq5  Expert Advisor  EA para o experimento 1
4 Test_Class.mq5  Expert Advisor  EA com cálculo do indicador dentro do EA (experimentos 2 e 3)
5 Test_iMA.mq5  Expert Advisor  EA com aquisição de dados do indicador via iMA
6 Test_iCustom.mq5  Expert Advisor  EA com aquisição de dados do indicador via iCustom


Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/4602

Arquivos anexados |
MQL5.zip (164.68 KB)
Últimos Comentários | Ir para discussão (1)
Joao Luiz Sa Marchioro
Joao Luiz Sa Marchioro | 11 jul 2018 em 00:07
Estava procurando por algo assim. Muito obrigado. Artigo excelente.
Melhoramos o trabalho com Painéis, adicionando transparência, alterando a cor do plano de fundo e herdando da CAppDialog/CWndClient Melhoramos o trabalho com Painéis, adicionando transparência, alterando a cor do plano de fundo e herdando da CAppDialog/CWndClient

Continuamos a estudar o trabalho com a CAppDialog. Agora, aprenderemos a como definir a cor de fundo, de borda e de barra de título para o painel gráfico. Consideraremos passo a passo como adicionar transparência à janela do aplicativo ao movê-la no gráfico. Em seguida, analisaremos a criação de descendentes da CAppDialog ou da CWndClient e veremos novas sutilezas ao trabalhar com controles. Finalmente, olharemos de uma nova perspectiva os novos Projetos.

Visualização dos resultados de otimização pelo critério selecionado Visualização dos resultados de otimização pelo critério selecionado

No artigo, continuamos a desenvolver o aplicativo MQL para trabalhar com resultados de otimização que foi iniciado em artigos anteriores. Desta vez, veremos um exemplo em que podemos gerar uma tabela de melhores resultados após a otimização de parâmetros, especificando através da interface gráfica outro critério.

Como analisar os trades do Sinal selecionado no gráfico Como analisar os trades do Sinal selecionado no gráfico

O serviço Sinais de negociação se desenvolve rapidamente. E como você está confiando seu dinheiro a um provedor do sinais, seria bom minimizar o risco de perder o depósito. Como lidar com essa selva de sinais de negociação? Como encontrar esse sinal que trará o lucro para você? O artigo propõe a criação de uma ferramenta para analisar visualmente o histórico de trades de sinais de negociação no gráfico do instrumento.

EA com interface gráfica: Criação do painel (Parte I) EA com interface gráfica: Criação do painel (Parte I)

Apesar de muitos traders ainda preferirem negociar manualmente, há poucas hipóteses de fazer o trabalho sem automatizar as operações de rotina. O artigo mostra um exemplo em que é criado um EA multissímbolo de sinal para negociação manual.