English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Implementação de Indicators como classes por exemplos de Zigzag e ATR

Implementação de Indicators como classes por exemplos de Zigzag e ATR

MetaTrader 5Indicadores | 12 fevereiro 2014, 13:37
2 454 0
Aleksandr Chugunov
Aleksandr Chugunov

Para que precisamos disso?

A MetaQuotes Software Corp. revisou o conceito de trabalho com indicadores personalizados na nova, 5ª versão do terminal do cliente MetaTrader. Agora eles são executados muito mais rapidamente; há apenas um exemplo de cada indicador com parâmetros de entrada únicos, portanto, é calculado apenas uma vez, apesar de usar suas cópias em até dez tabelas de um símbolo, etc.

Mas a operação de um algoritmo permanece sem alterações. Na perda de conexão a um servidor ou sincronização de histórico significante, o valor prev_calculaed (ou IndicatorCoundted() para MetaTrader 4) é zerado, o que leva ao completo recálculo do indicador para o histórico inteiro (os desenvolvedores fizeram isso intencionalmente para garantir a precisão dos valores dos indicadores em qualquer circunstância). Há várias coisas que podem afetar a velocidade do cálculo de indicadores:

  • Grande período: rates_total;
  • Cálculos complexos, que consomem recursos;
  • Usar vários símbolos e pontos;
  • Computador pessoal fraco;

Quanto mais itens são aplicáveis à sua situação, mais real para você é o problema do recálculo do indicador para o histórico inteiro. Além disso, a situação torna-se pior com um canal ruim para transmitir as informações.

é claro, você pode limitar a profundidade do cálculo do indicador usando um parâmetro de entrada adicional, mas há uma nuance ao usar os indicadores iCustom. O número máximo de barras que são usadas por qualquer tabela ou qualquer indicador personalizado é definido no escopo global para o terminal inteiro. A memória é alocada para cada buffer de um indicador personalizado, e é limitada somente por TERMINAL_MAXBARS.

Entretanto, há uma adição significativa - se você limitar o número máximo de barras calculadas exatamente no algoritmo do indicador (por exemplo, usando um parâmetro de entrada ou diretamente no código), então a memória será alocada dinamicamente na chegada de cada nova barra (aumenta gradualmente ao limite TERMINAL_MAXBARS especificado (ou um pouco mais - este algoritmo depende totalmente dos desenvolvedores, eles podem mudá-lo em próximas versões)).


Maneiras de evitar o recálculo do indicador pelo histórico inteiro

Por agora, eu vejo as seguintes maneiras para solucionar este problema:
  1. Peça à MetaQuotes para revisar este problema no nível de plataforma
  2. Crie uma classe separada para implementação de um análogo do prev_calculated

Havia uma outra variante como uma suposição que você pode desenvolver exatamente no indicador, um algoritmo de cálculo do prev_calculated, mas parece que o MetaTrader 5, diferentemente do MetaTrader 4, "limpa" todos os buffers indicadores ao zerar o prev_calculated (ex. ele forçadamente executa o zeramento de todo o vetor indicador; você não pode controlá-lo, já que este comportamento é implementado no nível de plataforma).

Vamos analisar cada variante separadamente.

  • A primeira variante depende somente dos desenvolvedores. Talvez eles a considerarão após a publicação do artigo. E, talvez, a implementação de um mecanismo completo afetará fortemente o desempenho do bloco de cálculo dos indicadores personalizados (entretanto, este mecanismo pode ser implementado como opcional) e eles deixarão tudo como está agora.
  • A segunda variante. Criação de uma classe especial que será responsável pela implementação de um análogo do prev_calculated. Nós podemos usá-la tanto em um indicador personalizado (somente para obter os valores de prev_calculated) e em um provedor de dados a serem usados no Expert Advisors (ou scripts) juntos com uma classe desenvolvida separadamente para o cálculo do indicador personalizado necessário.


Vantagens e desvantagens da segunda variante da solução do problema

Vantagens:
  • volume fixo de memória requerida através de alocação simples de memória para um vetor dinâmico com organização de um acesso em anel aos elementos do vetor;
  • sincronia e cálculo do indicador ao usar uma classe separada para este cálculo sob demanda (sem usar semáforos, bandeiras, eventos, etc.);
  • ao usar uma chamada para o cálculo do indicador, o resultado do recálculo é retornado em uma forma estendida (por exemplo: não houveram mudanças, somente o último raio foi alterado, novo raio adicionado, etc.).
Desvantagens:
  • necessidade de armazenar a própria cópia do histórico de preço, que é usado para cálculo de um valor indicador;
  • necessidade de sincronização manual do histórico com o histórico do terminal usando operações lógicas de comparação de dados.


Criar a classe CCustPrevCalculated para implementação de um análogo do prev_calculated

A implementação da classe em si não contém nada interessante a ser descrito. O algoritmo considera tanto a expansão do histórico para ambos os lados quanto seu possível "corte" do lado esquerdo. Este algoritmo também pode processar a inserção do histórico dentro dos dados calculados (é real para o MetaTrader 4, no MetaTrader 5 ainda não o encarei). O código-fonte da classe está no arquivo CustPrevCalculated.mqh.

Deixe-me falar sobre os pontos chave.


Criar um anel de acesso para os elementos do vetor

Para criar esta classe, nós vamos utilizar um método não convencional - acesso de anel aos elementos do vetor para a alocação em uma vez da memória para o vetor e para evitar procedimentos excessivos de cópia de vetores. Vamos considerá-lo pelo exemplo de 5 elementos:


Acesso em anel aos elementos do vetor



Inicialmente, nós trabalhamos com o vetor, cujo a numeração começa com 0. Mas o que devemos fazer se precisarmos adicionar o próximo valor, mantendo o tamanho do vetor (adicionar uma nova barra)? Existem duas maneiras:
  • copiar as células de memória 2-5 às células 1-4 respectivamente; assim nós temos a célula de memória vazia 5;
  • alterar a indexação do vetor sem alterar as informações armazenadas nele (endereçamento envolvente).

Para implementar a segunda variante, nós precisamos de uma variável, vamos chamá-la de DataStartInd; ela armazenará a posição do índice zero do vetor. Para conveniência de futuros cálculos, sua numeração corresponderá à indexação usual de um vetor (ex. começará do zero). Na variável BarsLimit nós vamos armazenar o número de elementos do vetor. Assim, o endereço real do elemento do vetor para o índice virtual 'I' será calculado usando a seguinte fórmula simples:

  • (DataStartInd+I) % BarsLimit – para numeração usual
  • (DataStartInd+DataBarsCount-1-I) % BarsLimit – para endereçamento como em timeseries
A variável DataBarsCount armazena o número de células de memória realmente usadas (nós podemos usar somente 3 de 5 células, por exemplo).


Algoritmos de sincronização de histórico

Para mim mesmo, eu selecionei e implementei três modos de trabalho do algoritmo de sincronização de uma cópia do histórico (histórico local) com o histórico no terminal do cliente:
  • CPCHSM_NotSynch – a sincronização do histórico local não é realizada para barras já formadas (por sua conta e risco). Na verdade, este modo pode ser usado livremente por um indicador, onde o desvio significativo dos valores de preço não pode afetar fortemente a precisão dos cálculos (MA, ADX, etc.). Este modo pode ser fatal para ZigZag, por exemplo, onde um excesso de um pico sobre outro é significante.
  • CPCHSM_Normal – o histórico local é sincronizado a cada nova barra pelo algoritmo descrito abaixo.
  • CPCHSM_Paranoid – o histórico local é sincronizado a cada chamada da função de sincronização dos dados descritos abaixo.

O mecanismo de sincronização em si é baseado em um outro parâmetro definido por um programador - HSMinute (armazenado como HistorySynchSecond). Nós supomos que um Dealer Center pode corrigir apenas os últimos minutos HSMinute do histórico. Se nenhuma diferença for encontrada durante a sincronização daquele período, o histórico é considerado como idêntico e a comparação é interrompida. Se uma diferença for encontrada, o histórico inteiro é verificado e corrigido.

Além disso, o algoritmo permite verificar apenas preços/spreads/volumes da estrutura MqlRates especificada na inicialização. Por exemplo, para desenhar o ZigZag, nós precisamos somente preços altos e baixos (High e Low).


Uso prático da classe CCustPrevCalculated

Para inicializar a classe CCustPrevCalculated nós precisamos chamar a função InitData(), a qual retorna 'true' (verdadeiro) em caso de sucesso:
CCustPrevCalculated CustPrevCalculated;
CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15);
Para sincronizar o histórico, nós precisamos chamar a função PrepareData():
CPCPrepareDataResultCode resData;
    resData = CustPrevCalculated.PrepareData();

Variantes dos valores que podem ser retornados pela função PrepareData():

enum CPCPrepareDataResultCode
  {
   CPCPDRC_NoData,                     // Returned when there is no data for calculation (not prepared by the server)
   CPCPDRC_FullInitialization,         // Full initialization of the array has been performed
   CPCPDRC_Synch,                      // Synchronization with adding new bars has been performed
   CPCPDRC_SynchOnlyLastBar,           // Synchronization of only the last bar has been performed (possible cutting of the history)
   CPCPDRC_NoRecountNotRequired        // Recalculation has not been performed, since the data was not changed
  };


Funções da classe CCustPrevCalculated para Data Access

Nota: para acelerar os cálculos, as verificações para excesso do vetor são excluídas. Para ser mais preciso, valores errados serão retornados se o índice for incorreto.

Nome
Propósito
uint GetDataBarsCount()
Retorna o número de barras disponíveis
uint GetDataBarsCalculated()
Retorna o número de barras não alteradas
uint GetDataStartInd()
Retorna o índice de acesso recorrente (para indicadores personalizados)
bool GetDataBarsCuttingLeft()
Retorna o resultado do corte de barras a partir da esquerda
double GetDataOpen(int shift, bool AsSeries)
Retorna 'Open' para a barra de movimento
double GetDataHigh(int shift, bool AsSeries)
Retorna 'High' para a barra de movimento
double GetDataLow(int shift, bool AsSeries)
Retorna 'Low' para a barra de movimento
double GetDataClose(int shift, bool AsSeries)
Retorna 'Close' para a barra de movimento
datetime GetDataTime(int shift, bool AsSeries)
Retorna 'Time' para a barra de movimento
long GetDataTick_volume(int shift, bool AsSeries)
Retorna 'Tick_volume' para a barra de movimento
long GetDataReal_volume(int shift, bool AsSeries)
Retorna 'Real_volume' para a barra de movimento
int GetDataSpread(int shift, bool AsSeries)
Retorna 'Spread' para a barra de movimento


Exemplos de otimização adicional da classe CCustPrevCalculated

  • Recusa de MqlRates com troca para vários vetores (determinados por um certo propósito) (diminui os requerimentos de memória, mas aumenta a carga no número de chamadas de cópia de vetores).
  • Dividir cada função de acesso em duas independentes para uso definido com certo tipo de indexação de vetor (recusa do parâmetro «bool AsSeries»). A vantagem é somente na condição lógica «if (AsSeries)».


Criar o CCustZigZagPPC para cálculo do indicador personalizado ZigZag na base de dados da classe CCustPrevCalculated

O algoritmo é baseado no indicador personalizado Professional ZigZag. O código-fonte desta classe está no arquivo ZigZags.mqh; além disso, a biblioteca OutsideBar.mqh é usada para trabalhar com barras externas.

Vamos criar uma estrutura separada para a descrição de uma barra de nosso indicador:

struct ZZBar
  {
   double UP, DN;                      // Buffers of the ZigZag indicator
   OrderFormationBarHighLow OB;       // Buffer for caching of an external bar
  };

Vamos também determinar o resultado do retorno dos cálculos da classe:

enum CPCZZResultCode
  {
   CPCZZRC_NotInitialized,             // Class is no initialized
   CPCZZRC_NoData,                     // Faield to receive data (including the external bar)
   CPCZZRC_NotChanged,                 // No changes of ZZ rays
   CPCZZRC_Changed                     // ZZ rays changed
  };

Para inicializar a classe CCustZigZagPPC nós precisamos chamar a função Init() por uma vez; ela retorna 'true' em caso de sucesso:

CCustZigZagPPC ZZ1;
ZZ1.Init(CustPrevCalculated, _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10);

Para os cálculos do indicador, nós precisamos começar a atualização dos dados baseada nos dados previamente calculados da classe CCustPrevCalculated:

CPCPrepareDataResultCode resZZ1;
    resZZ1 = ZZ1.PrepareData(resData);

E então chamar o procedimento Calculate():

if ( (resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired) )
   ZZ1.Calculate();
    

O exemplo completo de uso de uma classe CCustPrevCalculated junto com várias classes CCustZigZagPPC é dado no arquivo ScriptSample_CustZigZagPPC.mq5.


Função de acesso de dados da classe CCustZigZagPPC

Nome
Propósito
uint GetBarsCount()
Retorna o número de barras disponíveis
uint GetBarsCalculated() Retorna o número de barras calculadas
double GetUP(uint shift, bool AsSeries)
Retorna o valor de pico ZigZag para uma barra
double GetDN(uint shift, bool AsSeries)
Retorna o valor de ZigZag mínimo para uma barra
OrderFormationBarHighLow GetOB(uint shift, bool AsSeries) Retorna o valor 'Outside' para uma barra


Verificação visual e do programa

Para a verificação visual, vamos anexar o indicador original a uma tabela, e sobre ela, anexar o indicador de teste especialmente escrito Indicator_CustZigZag.mq5 com parâmetros de entrada idênticos (mas você deve selecionar outras cores, para ver ambos os indicadores); aqui está o resultado de seu trabalho:

Vermelho - original, azul - o nosso, calculado nas últimas 100 barras.

Do mesmo modo, nós podemos compará-los em um Expert Advisor, haverá uma diferença? Os resultados obtidos de iCustom("AlexSTAL_ZigZagProf") e a classe CCustZigZagPPC são comparados em cada tick no teste Expert_CustZigZagPPC_test.mq5. As informações sobre o cálculo são exibidas no diário (podem não haver cálculos nas primeiras barras, por causa da falta de histórico para o algoritmo):

(EURUSD,M1)                1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116; 1.35971; // it is normal
(EURUSD,M1) Tick processed: 1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116; 
(EURUSD,M1) Divergence on the bar: 7 

Vamos considerar este Expert Advisor com mais detalhes. Determine as variáveis globais para trabalhar:

#include <ZigZags.mqh>

CCustPrevCalculated CustPrevCalculated;
CCustZigZagPPC ZZ1;
int HandleZZ;

Inicialize as variáveis:

int OnInit()
  {
   // Creating new class and initializing it
   CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15);
   
   // Initializing the class ZZ
   ZZ1.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10);
   
   // Receiving handle for the custom indicator
   HandleZZ = iCustom(_Symbol, _Period, "AlexSTAL_ZigZagProf", 12, 10, 0 , true);
   Print("ZZ_handle = ", HandleZZ, "  error = ", GetLastError());

   return(0);
  }
Processar ticks no Expert Advisor:
void OnTick()
  {
   // Calculation of data
   CPCPrepareDataResultCode resData, resZZ1;
   resData = CustPrevCalculated.PrepareData();
   
   // Start recalculation for each indicator! PrepareData obligatory!
   resZZ1 = ZZ1.PrepareData(resData);
   
   // Расчет данных ZZ1
   if ( !((resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired)) )
      return;

   // Получим результаты расчета
   ZZ1.Calculate();

Agora nós temos barras ZZ1.GetBarsCalculated() calculadas pelo CCustZigZagPPC. Vamos adicionar o código de comparação de dados do iCustom("AlexSTAL_ZigZagProf") e a classe CCustZigZagPPC:

   int tmpBars = (int)ZZ1.GetBarsCalculated();
   double zzUP[], zzDN[];
   CopyBuffer(HandleZZ, 0, 0, tmpBars, zzUP);
   CopyBuffer(HandleZZ, 1, 0, tmpBars, zzDN);
   
   // Perform comparison
   string tmpSt1 = "", tmpSt2 = "";
   for (int i = (tmpBars-1); i >= 0; i--)
     {
      double tmpUP = ZZ1.GetUP(i, false);
      double tmpDN = ZZ1.GetDN(i, false);
      if (tmpUP != zzUP[i])
         Print("Divergence on the bar: ", i);
      if (tmpDN != zzDN[i])
         Print("Divergence on the bar: ", i);
      if (tmpUP != EMPTY_VALUE)
         tmpSt1 = tmpSt1 + DoubleToString(tmpUP, _Digits) + "; ";
      if (tmpDN != EMPTY_VALUE)
         tmpSt1 = tmpSt1 + DoubleToString(tmpDN, _Digits) + "; ";

      if (zzUP[i] != EMPTY_VALUE)
         tmpSt2 = tmpSt2 + DoubleToString(zzUP[i], _Digits) + "; ";
      if (zzDN[i] != EMPTY_VALUE)
         tmpSt2 = tmpSt2 + DoubleToString(zzDN[i], _Digits) + "; ";
     }
  Print("Tick processed: ", tmpSt1);
  Print("                              ", tmpSt2);
  }

Aqui está o uso prático simples da classe CCustZigZagPPC em um Expert Advisor ou script. As funções de acesso direto GetUP(), GetDN(), GetOB() ao invés de CopyBuffer().


Movendo nosso indicador para uma classe separada (pelo exemplo de iATR)

Na base do ZigZags.mqh eu criei o modelo MyIndicator.mqh para desenvolvimento rápido dos indicadores personalizados de acordo com os princípios descritos acima.

Plano geral:

1. Estágio preparatório.

  • Copie MyIndicator.mqh como um arquivo com um outro nome (é ATRsample.mqh em meu exemplo) e abra o segundo no MetaEditor 5.
  • Substitua o texto "MyInd" pelo nome do seu indicador (é "ATR" em meu exemplo).

2. Escolha os parâmetros externos que serão tomados do indicador inicial (original) para a classe, declare e inicialize.

Em meu exemplo, o indicador ATR possui um parâmetro externo:
input int InpAtrPeriod=14;  // ATR period
  • adicione este parâmetro para nossa classe e para a função de inicialização da classe:
class CCustATR
  {
protected:
   ...
   uchar iAtrPeriod;
   ...
public:
   ...
   bool Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod);
  • altere o cabeçalho do corpo da função Init e inicialize o parâmetro variável com o valor de entrada:
bool CCustATR::Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod)
{
      ...
      BarsLimit = Limit;
      iAtrPeriod = AtrPeriod;
      ...

3. Determine o número requerido de buffers no indicador inicial, declare-os em nossa classe. Declare também as funções de retorno dos buffers INDICATOR_DATA.

  • Altere a estrutura
struct ATRBar
  {
   double Val;                          // Indicator buffers
  };

para nossa própria estrutura:

struct ATRBar
  {
   double ATR;
   double TR;
  };
  • Determine os valores zero:
CPCPrepareDataResultCode CCustATR::PrepareData(CPCPrepareDataResultCode resData)
{
   ...
   for (uint i = (DataBarsCalculated == 0)?0:(DataBarsCalculated+1); i < DataBarsCount; i++)
     {
      Buf[PInd(i, false)].ATR = EMPTY_VALUE;
      Buf[PInd(i, false)].TR = EMPTY_VALUE;
     }
   ...
  • Altere e adicione a função de valores de retorno dos buffers INDICATOR_DATA:

altere (se houver apenas um buffer, você pode ignorara a alteração)

class CCustATR
  {
   ...
   double GetVal(uint shift, bool AsSeries);                      // returns the Val value of the buffer for a bar
   ...

para

class CCustATR
  {
   ...
   double GetATR(uint shift, bool AsSeries);                      // Возвращает значение буфера ATR для бара
   ...

e altere o código da função correspondente:

double CCustATR::GetATR(uint shift, bool AsSeries)
{
   if ( shift > (DataBarsCount-1) )
      return(EMPTY_VALUE);
   return(Buf[PInd(shift, AsSeries)].ATR);
}
    
Nota: ao invés de várias funções de valores de buffer de retorno, você pode utilizar apenas um, que possui um parâmetro adicional - número ou nome do buffer.


4. Copie a lógica da função OnCalculate() do indicador inicial à função correspondente da classe

  • Verificações primárias
CPCATRResultCode CCustATR::Calculate()
{
   ...
   // Check if there are enough bars for the calculation
   if (DataBarsCount <= iAtrPeriod)
      return(CPCATRRC_NoData);
   ...
  • Cálculos: no primeiro tick e o número de barras para cálculos nos próximos ticks:
   if ( DataBarsCalculated != 0 )
      BarsForRecalculation = DataBarsCount - ATRDataBarsCalculated - 1;
   else
     {
      Buf[PInd(0, false)].TR = 0.0;
      Buf[PInd(0, false)].ATR = 0.0;
      //--- filling out the array of True Range values for each period
      for (uint i = 1; i < DataBarsCount; i++)
         Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - 
                                  MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false));
      //--- first AtrPeriod values of the indicator are not calculated
      double firstValue = 0.0;
      for (uint i = 1; i <= iAtrPeriod; i++)
        {
         Buf[PInd(i, false)].ATR = 0;
         firstValue += Buf[PInd(i, false)].TR;
        }
      //--- calculating the first value of the indicator
      firstValue /= iAtrPeriod;
      Buf[PInd(iAtrPeriod, false)].ATR = firstValue;
      
      BarsForRecalculation = DataBarsCount - iAtrPeriod - 2;
     }
  • O cálculo em cada tick em si:
   for (uint i = (DataBarsCount - BarsForRecalculation - 1); i < DataBarsCount; i++)
     {
      Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - 
                               MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false));
      Buf[PInd(i, false)].ATR = Buf[PInd(i-1, false)].ATR + (Buf[PInd(i, false)].TR-Buf[PInd(i-iAtrPeriod, false)].TR) / iAtrPeriod;
      ...

Isso é tudo. Nossa classe foi criada. Para a verificação visual, você pode criar um indicador de teste (em meu exemplo, é Indicator_ATRsample.mq5):



Eu tive uma ideia enquanto corrigia o artigo, de que se você usar a classe CCustPrevCalculated junto com apenas um indicador personalizado, você pode integrar a criação, inicialização e sincronização desta classe no indicador personalizado (em meus exemplos são CCustZigZagPPC e CCustATR). Ao chamar a função de inicialização dos indicadores personalizados para este propósito, você precisa usar o apontador zero para o objeto:

   ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);

Assim a estrutura geral

#include <CustPrevCalculated.mqh>
#include <ATRsample.mqh>
CCustPrevCalculated CustPrevCalculated;
CCustATR ATR;

int OnInit()
  {
   CustPrevCalculated.InitData(_Symbol, _Period, iBars, CPCHSM_Normal, 0, 30);
   ATR.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);
  }

int OnCalculate(...)
  {
   CPCPrepareDataResultCode resData = CustPrevCalculated.PrepareData();
   CPCPrepareDataResultCode resATR = ATR.PrepareData(resData);
   if ( (resATR != CPCPDRC_NoData) && (resATR != CPCPDRC_NoRecountNotRequired) )
      ATR.Calculate();
  }

será simplificada para:

#include <ATRsample.mqh>
CCustATR ATR;

int OnInit()
  {
   ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);
  }

int OnCalculate(...)
  {
   ATR.Calculate();
  }
Um exemplo prático é dado no arquivo Indicator)ATRsample2.mq5.

Influência da tecnologia descrita sobre o desempenho no Strategy Tester

Para verificação, eu criei um Expert Advisor de teste (TestSpeed_IndPrevCalculated.mq5) que recebe o valor do indicador da barra zero em cada tick, de acordo com uma de três variantes:

enum eTestVariant
  {
   BuiltIn,    // Built-in indicator iATR
   Custom,     // Custom indicator iCustom("ATR")
   IndClass    // Calculation in the class
  };

Este Expert Advisor foi executado 10 vezes sobre 1 agente com os seguintes parâmetros de otimização:

  • Símbolo: EURUSD
  • Período: histórico inteiro [1993..2001]
  • Modo de trade: a cada tick
  • Parâmetro externo: FalseParameter [0..9]

Eu medi o tempo de otimização ao usar cada uma das três variantes do indicador. O resultado da verificação é mostrado como um histograma linear.

O tempo de otimização para três tipos de implementação do indicador ATR

    O código-fonte do Expert Advisor usado para medir o tempo de otimização:

    //+------------------------------------------------------------------+
    //|                                  TestSpeed_IndPrevCalculated.mq5 |
    //|                                         Copyright 2011, AlexSTAL |
    //|                                           http://www.alexstal.ru |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2011, AlexSTAL"
    #property link      "http://www.alexstal.ru"
    #property version   "1.00"
    //--- connect the include file with the CustATR class
    #include <ATRsample.mqh>
    //--- set the selection of the parameter as an enumeration
    enum eTestVariant
      {
       BuiltIn,    // Built-in indicator iATR
       Custom,     // Custom indicator iCustom("ATR")
       IndClass    // Calculation withing the class
      };
    //--- input variables
    input eTestVariant TestVariant;
    input int          FalseParameter = 0;
    //--- period of the ATR indicator
    const uchar        InpAtrPeriod = 14;
    //--- handle of the built-in or custom indicator
    int                Handle;
    //--- indicator based on the class 
    CCustATR           *ATR;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       //---
       switch(TestVariant)
         {
          case BuiltIn:
             Handle = iATR(_Symbol, _Period, InpAtrPeriod);
             break;
          case Custom:
             Handle = iCustom(_Symbol, _Period, "Examples\ATR", InpAtrPeriod);
             break;
          case IndClass:
             ATR = new CCustATR;
             ATR.Init(NULL, _Symbol, _Period, 100, CPCHSM_Normal, 0, 30, InpAtrPeriod);
             break;
         };
       //---
       return(0);
      }
    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
       switch(TestVariant)
         {
          case IndClass:
             delete ATR;
             break;
         };
      }
    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick()
      {
       double tmpValue[1];
       switch(TestVariant)
         {
          case BuiltIn:
             CopyBuffer(Handle, 0, 0, 1, tmpValue);
             break;
          case Custom:
             CopyBuffer(Handle, 0, 0, 1, tmpValue);
             break;
          case IndClass:
             ATR.Calculate();
             tmpValue[0] = ATR.GetATR(0, true);
             break;
         };
      }
    //+------------------------------------------------------------------+

    Como vemos, esta tecnologia não diminui o desempenho no strategy tester significativamente, em comparação com o uso de um indicador personalizado comum.


    Notas para o uso prático desta tecnologia

    • ao testar um Expert Advisor no strategy tester, o valor prev_calculated não pode ser zerado em um indicador personalizado, é por isso que a sincronização de histórico está desativada neste modo;
    • o cálculo do indicador é realizado somente nas últimas barras 'n' que são estritamente definidas na primeira inicialização das classes:
    • o cálculo implica ligação estrita a um certo símbolo e período da classe inicializada. Para realizar cálculos em outros símbolos ou períodos, você precisa criar novas instâncias das classes.


    Conclusão

    Em cada situação, um programador deve considerar todos os prós e contras das diferentes variantes de implementação da tarefa. A implementação sugerida no artigo é apenas um modo com suas próprias vantagens e desvantagens.

    P.S. Quem não erra, não faz nada! Se você encontrar erros, por favor me informe.

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

    Caminhada aleatória e indicador de tendência Caminhada aleatória e indicador de tendência
    A caminhada aleatória parece muito similar com os dados de mercado reais, mas possui alguns recursos significativos. Neste artigo, considerarei as propriedades da Caminhada Aleatória, simulada usando o jogo de cara e coroa. Para estudar as propriedades dos dados, foi desenvolvido o indicador de modismo.
    Gráficos e diagramas em HTML Gráficos e diagramas em HTML
    Hoje é difícil encontrar um computador que não tenha um WEB-browser instalado. Por um longo tempo os browsers têm evoluído e melhorado. Este artigo discute o modo simples e seguro de criar gráficos e diagramas, com base nas informações obtidas a partir do terminal de cliente MetaTrader 5 para exibí-los no navegador.
    Expondo código C# para MQL5 usando exportações não gerenciadas Expondo código C# para MQL5 usando exportações não gerenciadas
    Neste artigo eu apresentei diferentes métodos de interação entre o código MQL5 e o código C# gerenciado. Eu também ofereci vários exemplos de como organizar estruturas MQL5 contra C# e como invocar funções de DLL exporta em scripts MQL5. Eu acredito que forneci exemplos que podem servir como uma base para pesquisas futuras sobre escrever DLLs em código gerenciado. Este artigo também abre portas para o MetaTrader usar muitas bibliotecas que já estão implementadas em C#.
    O jogador de negociação baseado no histórico de acordo O jogador de negociação baseado no histórico de acordo
    O reprodutor de negócio. Apenas quatro palavras, não há necessidade de explicação. Pensamentos sobre uma pequena caixa com botões vêm à mente. Pressione um botão - ele reproduz, move a alavanca - a velocidade da reprodução muda. Na realidade, é bastante similar. Neste artigo, quero mostrar meu desenvolvimento que reproduz o histórico de negócio quase como em tempo real. O artigo cobre algumas nuances de OOP, trabalhando com indicadores e gráficos de gerenciamento.