Implementação de Indicators como classes por exemplos de Zigzag e ATR
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:- Peça à MetaQuotes para revisar este problema no nível de plataforma
- 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.).
- 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:
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
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)
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 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
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso