Comparamos a velocidade de indicadores de armazenamento automático em cache
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:
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:
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).
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)
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.
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).
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/4388
- 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