
LifeHack para traders: preparemos "fast-food" de indicadores
Se é proibido, mas você está realmente com vontade, então pode fazê-lo.
Provérbio russo
Simplicidade vs Confiabilidade
Em 2005, foi realizado o lançamento oficial do MetaTrader 4, nele a simples linguagem de script MQL-II foi substituída pela MQL4. É engraçado se lembrar, mas, no início, os traders receberam a nova linguagem com duas pedras na mão, nos fóruns, eles debatiam e acusavam o desenvolvedor MetaQuotes Software Corp. de ter criado uma linguagem muito complexa e impossível de aprender.
Agora, depois de 12 anos, os traders da atualidade acham incompreensível toda queixa sobre a complexidade da MQL4, mas a história se repete. Pois, alguns deles dizem que, ao contrário da MQL4, a MQL5 é difícil de estudar e complexa no desenvolvimento de estratégias. Ao longo dos anos, o nível geral da programação de robôs aumentou significativamente, graças à coragem de desenvolvedores que seguiram em frente e criaram ferramentas ainda mais poderosas para a linguagem C ++, a fim de realizar soluções para o trading algorítmico. A nova MQL5 permite os programadores verificarem cuidadosamente os resultados de todas as operações, conforme a necessidade, usando a RAM, o que é feito especialmente para o processamento de transações. Na antiga MQL4, antes de ser levada até o nível da MQL5, não existiam tantas funcionalidades desse tipo. A própria sintaxe era menos rigorosa, inclusive.
Acredito que o debate sobre a dificuldade do aprendizado da linguagem MQL5 terminará em breve e será parte da história; porém, como muitos traders têm saudades da MQL4, tentaremos mostrá-lhes que aparência podem ter as conhecidas funções MQL4 se forem realizadas em MQL5.
Se você estiver mudando para a MQL5 agora, este artigo é para você, porque, por um lado, o acesso aos dados dos indicadores e às séries é realizado na nossa conhecida linguagem MQL4, por outro lado, toda a realização é escrita em MQL5. Todas as funções são o mais claras quanto possível e são perfeitamente adequadas para uma depuração passo a passo.
1. Será que, em MQL5, se pode trabalhar com os indicadores em estilo MQL4?
A principal diferença no trabalho com indicadores é que, na MQL4, a linha de consulta de dados de indicador é, em essência, o comando para criar um indicador ( iMACD(NULL,0,12,26,9,PRICE_CLOSE ) e, em seguida, o pedido de dados do buffer de indicador ( MODE_MAIN ) e índice ( 1 ) necessários.
//+------------------------------------------------------------------+ //| iMACd.mq4 | //| Copyright © 2018, Vladimir Karputov | //| http://wmua.ru/slesar/ | //+------------------------------------------------------------------+ #property copyright "Copyright © 2018, Vladimir Karputov" #property link "http://wmua.ru/slesar/" #property version "1.00" #property strict //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- double macd_main_1=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1); } //+------------------------------------------------------------------+
No total, é apenas uma linha e apenas um passo.
Em MQL5, o análogo deste código contém várias etapas:
- declaração da variável em que será armazenado o identificador do indicador;
- criação e verificação do identificador do indicador;
- função separada que dá o valor do indicador.
//+------------------------------------------------------------------+ //| iMACD.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" int handle_iMACD; // variable for storing the handle of the iMACD indicator //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create handle of the indicator iMACD handle_iMACD=iMACD(Symbol(),Period(),12,26,9,PRICE_CLOSE); //--- if the handle is not created if(handle_iMACD==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); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- double macd_main_1=iMACDGet(MAIN_LINE,1); } //+------------------------------------------------------------------+ //| Get value of buffers for the iMACD | //| the buffer numbers are the following: | //| 0 - MAIN_LINE, 1 - SIGNAL_LINE | //+------------------------------------------------------------------+ double iMACDGet(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]); } //+------------------------------------------------------------------+
Reescrevemos o mesmo código em MQL4.
Numa função, realizamos a criação do ID do indicador e a aquisição de dados a partir do indicador:
//+------------------------------------------------------------------+ //| iMACD function in MQL4 notation | //+------------------------------------------------------------------+ double iMACD( string symbol, // symbol name ENUM_TIMEFRAMES period, // period 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=NULL; //--- int handle=iMACD(symbol,period,fast_ema_period,slow_ema_period,signal_period, applied_price); double val[1]; int copied=CopyBuffer(handle,buffer,shift,1,val); if(copied>0) result=val[0]; return(result); }
E agora ATENÇÃO! Após escrever esta função, criaremos o ID do indicador EM CADA tick. A documentação não recomenda abusar de tal "criatividade". Eis aqui o que diz o guia Funções para trabalhar com indicadores técnicos:
Não se deve acessar os dados imediatamente após serem criados, uma vez que o cálculo dos valores do indicador precisa de algum tempo. Portanto, o melhor é criar o ID do indicador na OnInit().
Por que esse código funciona e não consome memória? A resposta é encontrada na mesma seção, abaixo:
Comentário. No que diz respeito a um só programa MQL5, ao usar repetidamente a função do indicador com os mesmos parâmetros, o contador aumenta em 1, uma vez, isto é, não faz com que o contador de referências aumente muitas vezes. No entanto, recomenda-se obter o ID do indicador na função Oninit() ou no construtor da classe, usando os ID obtidos, noutras funções. O contador de referências diminui ao deinicializar o programa MQL5.
Em outras palavras, a MQL5 é concebida de forma ideal: ela controla a criação de identificadores e não permite criar repetidamente o mesmo indicador com os mesmos parâmetros. Quando se tenta realizar reiteradamente uma cópia do identificador, simplesmente é retornado o ID do indicador criado anteriormente, com as configurações correspondentes. No entanto, recomenda-se obter os identificadores de vez na OnInit(). Veremos o porquê um pouco para a frente.
Atenção, não se pode verificar se a criação do ID é correta, uma vez que não existe tal verificação.
Agora, o código que obtém os valores do indicador iMACD será parecido com isto:
//+------------------------------------------------------------------+ //| MACD MQL4 style EA.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" #define MODE_MAIN 0 #define MODE_SIGNAL 1 //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- double macd_main_1=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1); } //+------------------------------------------------------------------+ //| iMACD function in MQL4 notation | //+------------------------------------------------------------------+ double iMACD( string symbol, // symbol name ENUM_TIMEFRAMES period, // period 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=NULL; //--- int handle=iMACD(symbol,period,fast_ema_period,slow_ema_period,signal_period, applied_price); double val[1]; int copied=CopyBuffer(handle,buffer,shift,1,val); if(copied>0) result=val[0]; return(result); } //+------------------------------------------------------------------+
ATENÇÃO: o desejo de usar os indicadores em estilo MQL4 nos priva de opções para verificar o valor de retorno, porque todas as funções em estilo MQL4 retornam APENAS valores double. Apresentaremos uma possível solução na seção 1.1.
Até agora tudo parece bastante complicado, por isso levamos o bloco define e a função double iMACD() a um arquivo "IndicatorsMQL5.mqh" separado que colocamos numa pasta separada "[data folder]\MQL5\Include\SimpleCall". Dessa maneira, o código se torna bastante curto. Atenção, nós incluímos o arquivo "IndicatorsMQL5.mqh". Isso significa que, ao usar o MACD, o nome das linhas de indicador deve ser enviado na forma de MQL5 MAIN_LINE, ao invés de MQL4 MODE_MAIN:
//+------------------------------------------------------------------+ //| MACD MQL4 style EA short.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" #include <SimpleCall\IndicatorsMQL5.mqh> //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- double macd_main_1=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MAIN_LINE,1); Comment("MACD, main buffer, index 1: ",DoubleToString(macd_main_1,Digits()+1)); } //+------------------------------------------------------------------+
Eu inseri "Comment" exclusivamente para verificação. No testador, pode-se confrontar o trabalho iniciando "MACD MQL4 style EA short.mq5", em modo visual, e colocando o cursor na barra com o índice #1:
Fig. 1. "MACD MQL4 style EA short.mh5" in tester
1.1. Sutilezas ao trabalhar com "IndicatorsXXXX.mqh"
Processamento de erros no valor de retorno
Todos os indicadores enviam seus dados como double. Este é o problema de enviar uma mensagem ao usuário quando não é possível obter os dados do indicador. Esta situação pode se apresentar quando recusada a criação do indicador (por exemplo, ao definir um símbolo inexistente) ou surgido um erro de cópia ao chamar CopyBuffer.
Em caso de erro, simplesmente enviar "0.0" não é uma boa opção, porque para muitos indicadores "0.0" é um valor bastante comum (por exemplo, no caso do MACD). Retornar a constante EMPTY_VALUE (que, aliás, tem o valor DBL_MAX) também não é uma opção, porque o indicador Fractals preenche os índices do buffer com os valores EMPTY_VALUE, o que significa que, para ele, isso não é um erro.
Resta apenas a hipótese de enviar "Not a Number", isto é, NaN. Para fazer isso, a nível global, é declarada a variável "NaN" que, justamente, é inicializada por um "Not a Number":
double NaN=double("nan"); //+------------------------------------------------------------------+ //| iAC function in MQL4 notation | //+------------------------------------------------------------------+ double iAC( string symbol, // symbol name ENUM_TIMEFRAMES timeframe, // timeframe int shift // shift ) { double result=NaN; //--- int handle=iAC(symbol,timeframe); if(handle==INVALID_HANDLE) { Print(__FUNCTION__,": INVALID_HANDLE error=",GetLastError()); return(result); } double val[1]; int copied=CopyBuffer(handle,0,shift,1,val); if(copied>0) result=val[0]; else Print(__FUNCTION__,": CopyBuffer error=",GetLastError()); return(result); }
A vantagem desta abordagem é que, em caso de erro, é retornada NaN, enquanto false é o resultado da comparação desta variável com qualquer número.
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- exemplo comparativo de NaN double NaN=double("nan"); double a=10.3; double b=-5; double otherNaN=double("nan"); Print("NaN>10.3=",NaN>a); Print("NaN<-5=",NaN<b); Print("(NaN==0)=",NaN==0); Print("(NaN==NaN)=",NaN==otherNaN); //--- resultado NaN>10.3=false NaN<-5=false (NaN==0)=false (NaN==NaN)=false //--- }
Portanto, se quisermos usar essas funções em estilo MQL4, é necessário realizar operações de negociação, somente se o resultado da comparação é true. No entanto, neste caso, eu, pessoalmente, insisto em verificar o valor de retorno usando a função MathIsValidNumber.
Identificadores de linhas do indicadores em MQL4 e MQL5
Há um problema de compatibilidade em termos de constantes que descrevem a linha do indicador. Por exemplo, peguemos o iAlligator:
- MQL4: 1 - MODE_GATORJAW, 2 - MODE_GATORTEETH, 3 - MODE_GATORLIPS
- MQL5: 0 - GATORJAW_LINE, 1 - GATORTEETH_LINE, 2 - GATORLIPS_LINE
No final das contas, o problema é que, na função "IndicatorsXXXX.mqh", a linha do indicador vem como um número. E, por exemplo, quando este número é igual a 1, não se pode saber se o usuário esteve trabalhando em estilo MQL4 (e se referia a 1 - MODE_GATORJAW) ou se esteve trabalhando em estilo MQL5 (e se referia a uma linha completamente diferente do indicador 1 - GATORTEETH_LINE).
Devido a isso, foi tomada a decisão de criar dois arquivos anexados quase gêmeos: "IndicatorsMQL4.mqh" e "IndicatorsMQL5.mqh". A diferença entre eles é que o arquivo "IndicatorsMQL4.mqh" compreende as linas dos indicadores APENAS em estilo MQL4, enquanto o arquivo "IndicatorsMQL5.mqh" - APENAS em estilo MQL5. Sendo que, no indicador "IndicatorsMQL4.mqh", a conversão da linha do indicador, no parâmetro de entrada, é realizada diretamente nas funções iADX, iAlligator, quer dizer, é impossível realizar estas conversões em #define.
Passo a explicar o porquê de tal proibição pegando como exemplos o iBands e iEnvelopes:
//+------------------------------------------------------------------+ //| iBands function in MQL4 notation | //| The buffer numbers are the following: | //| MQL4 0 - MODE_MAIN, 1 - MODE_UPPER, 2 - MODE_LOWER | //| MQL5 0 - BASE_LINE, 1 - UPPER_BAND, 2 - LOWER_BAND | //+------------------------------------------------------------------+ double iBands( ... //+------------------------------------------------------------------+ //| iEnvelopes function in MQL4 notation | //| The buffer numbers are the following: | //| MQL4 0 - MODE_MAIN, 1 - MODE_UPPER, 2 - MODE_LOWER | //| MQL5 0 - UPPER_LINE, 1 - LOWER_LINE, -/- | //+------------------------------------------------------------------+ double iEnvelopes(
Em MQL4, MODE_UPPER para o indicador Bands é convertido em 1, enquanto para o indicador Envelopes - em 0.
2. O que acontece com o consumo de memória quando, em cada tick, trabalhamos com indicadores em estilo MQL4?
Comparemos o consumo de memória de dois experts: "iMACD.mq5" é um EA com acesso a indicadores, enquanto o "MACD MQL4 style EA short.mq5" é um EA com acesso a indicadores em estilo MQL4. Nas configurações do terminal, o número máximo de barras, na janela, está definido como "100 000". Criamos dois perfis de 14 gráficos:
- perfil do "iMACd" - o EA "iMACd.mq5" é anexado a 13 gráficos, cada um deles tem o timeframe М30;
- perfil do "MACD MQL4 style EA short" - o EA "MACD MQL4 style EA short.mq5" é anexado a 13 gráficos.
No gráfico catorze, estará o indicador "Terminal memory used.mq5". A cada 10 segundos, ele imprime o indicador TERMINAL_MEMORY_USED.
Vamos comparar dois valores, isto é, a quantidade de memória (RAM) usada pelo terminal (dados do gerenciador de tarefas) e o identificador impresso TERMINAL_MEMORY_USED. O controle durará 10 minutos, se houver consumo excessivo de memória, veremos isso. A principal condição é que, após executar o terminal, não se deve mexer em nada: nem abrir novas abas, nem ler o bate-papo.
Perfil | Gerenciador de Tarefas | TERMINAL_MEMORY_USED | Gerenciador de Tarefas (após 10 minutos) | TERMINAL_MEMORY_USED (após 10 minutos) |
---|---|---|---|---|
iMACd | 279.7 MB | 745 MB | 279.7 MB | 745 MB |
MACD MQL4 style EA short | 279.9 MB | 745 MB | 280.0 MB | 745 MB |
Agora, modificamos o teste: após 10 minutos, mudamos o timeframe dos 13 gráficos para o timeframe H1.
Perfil | Gerenciador de Tarefas | TERMINAL_MEMORY_USED | Gerenciador de Tarefas (após 10 minutos) | TERMINAL_MEMORY_USED (após 10 minutos) |
---|---|---|---|---|
iMACd | 398.0 MB | 869 MB | 398.3 MB | 869 MB |
MACD MQL4 style EA short | 319.2 MB | 874 MB | 330.5 MB | 874 MB |
Tabela de resultados para maior clareza sobre o consumo de memória:
Perfil | Gerenciador de tarefas (M30), Mb |
TERMINAL_MEMORY_USED (M30), Mb |
Gerenciador de tarefas (H1), Mb |
TERMINAL_MEMORY_USED (H1), Mb |
||||
---|---|---|---|---|---|---|---|---|
início | após 10 min. | início | após 10 min. | início | após 10 min. | início | após 10 min. | |
iMACd | 279.7 | 279.7 | 745 | 745 | 398.0 | 869 | 398.3 | 869 |
MACD MQL4 style EA short | 279.9 | 280.0 | 745 | 745 | 319.2 | 874 | 330.5 | 874 |
3. Nova vida do EA MACD Sample.mq4
Verificamos a velocidade de execução, o consumo de memória e os correspondentes trades do EA [data folder]\MQL4\Experts\MACD Sample.mq4 (que escrevemos em MQL5, mas em estilo MQL4, isto é, como "MACD MQL4 style EA short.mq5") e o EA [data folder]\MQL5\Experts\Examples\MACD\MACD Sample.mq5.
3.1. Alteramos o EA "MACD Sample.mq5", quer dizer, só vamos receber um valor de cada vez
"MACD Sample.mq5" da distribuição padrão recebe imediatamente dois valores do indicador:
//+------------------------------------------------------------------+ //| main function returns true if any position processed | //+------------------------------------------------------------------+ bool CSampleExpert::Processing(void) { //--- refresh rates if(!m_symbol.RefreshRates()) return(false); //--- refresh indicators if(BarsCalculated(m_handle_macd)<2 || BarsCalculated(m_handle_ema)<2) return(false); if(CopyBuffer(m_handle_macd,0,0,2,m_buff_MACD_main) !=2 || CopyBuffer(m_handle_macd,1,0,2,m_buff_MACD_signal)!=2 || CopyBuffer(m_handle_ema,0,0,2,m_buff_EMA) !=2) return(false); // m_indicators.Refresh(); //--- to simplify the coding and speed up access //--- data are put into internal variables m_macd_current =m_buff_MACD_main[0]; m_macd_previous =m_buff_MACD_main[1]; m_signal_current =m_buff_MACD_signal[0]; m_signal_previous=m_buff_MACD_signal[1]; m_ema_current =m_buff_EMA[0]; m_ema_previous =m_buff_EMA[1];
Após isto, à variável são atribuídos os dados de matrizes de dimensão "2". Por que é feito precisamente dessa maneria? É óbvio que, ao copiar pelo menos um ou dois valores de cada vez, nós ainda vamos usar CopyBuffer. Mas, ao copiar dois valores de vez, é poupada uma operação de gravação na matriz.
No entanto, o EA "MACD Sample.mq4" recebe, de cada vez, um valor do indicador:
//--- to simplify the coding and speed up access data are put into internal variables MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0); MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1); SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0); SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,1); MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0); MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);
Dois vezes é consultada a linha principal do MACD, dois vezes - a linha de sinal do MACD e dois vezes - Moving Average. Por isso, o EA "MACD Sample.mq5" deve ter a mesma aparência. Chamamos esta versão do expert de "MACD Sample One value at a time.mq5". Veja as alterações em que obtemos um valor de cada vez:
//--- refresh indicators if(BarsCalculated(m_handle_macd)<2 || BarsCalculated(m_handle_ema)<2) return(false); // if(CopyBuffer(m_handle_macd,0,0,2,m_buff_MACD_main) !=2 || // CopyBuffer(m_handle_macd,1,0,2,m_buff_MACD_signal)!=2 || // CopyBuffer(m_handle_ema,0,0,2,m_buff_EMA) !=2) // return(false); // m_indicators.Refresh(); //--- to simplify the coding and speed up access //--- data are put into internal variables CopyBuffer(m_handle_macd,0,0,1,m_buff_MACD_main); m_macd_current=m_buff_MACD_main[0]; CopyBuffer(m_handle_macd,0,1,1,m_buff_MACD_main); m_macd_previous=m_buff_MACD_main[0]; CopyBuffer(m_handle_macd,1,0,1,m_buff_MACD_signal); m_signal_current=m_buff_MACD_signal[0]; CopyBuffer(m_handle_macd,1,1,1,m_buff_MACD_signal); m_signal_previous=m_buff_MACD_signal[0]; CopyBuffer(m_handle_ema,0,0,1,m_buff_EMA); m_ema_current=m_buff_EMA[0]; CopyBuffer(m_handle_ema,0,1,1,m_buff_EMA); m_ema_previous=m_buff_EMA[0];
Este código é armazenado no EA "MACD Sample One value at a time.mq5", anexado ao artigo.
3.2. Convertemos o expert "MACD Sample.mq4" em código MQL5
Para que, no expert advisor, seja possível usar os indicadores em estilo MQL4, bem como trabalhar com posições e operar, anexamos o arquivo "IndicatorsMQL4.mqh" (lembre que este arquivo, ao trabalhar com indicadores, compreende apenas os nomes das linhas de indicador MQL4) e as classes de negociação CPositionInfo, CTrade, CSymbolInfo e CAccountInfo. Além disso, para acessar corretamente o "IndicatorsMQL4.mqh", ao EA é necessário adicionar o bloco dos defin, isto é, os nomes das linas de indicador:
#property description " and the indicators are accessed in the style of MQL4" #define MODE_MAIN 0 #define MODE_SIGNAL 1 #include <SimpleCall\IndicatorsMQL4.mqh> //--- #include <Trade\PositionInfo.mqh> #include <Trade\Trade.mqh> #include <Trade\SymbolInfo.mqh> #include <Trade\AccountInfo.mqh> CPositionInfo m_position; // trade position object CTrade m_trade; // trading object CSymbolInfo m_symbol; // symbol info object CAccountInfo m_account; // account info wrapper //--- input double TakeProfit =50;
Adicionalmente, para construir cotações de três ou cinco dígitos, é necessário um multiplicador especial:
input double MACDCloseLevel=2; input int MATrendPeriod =26; //--- double m_adjusted_point; // point value adjusted for 3 or 5 points //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+
Para obter os preços atuais, eu uso o objeto m_symbol da classe de negociação CSymbolInfo:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- if(!m_symbol.Name(Symbol())) // sets symbol name return(INIT_FAILED); RefreshRates();
O método RefreshRates() atualiza os preços e garante que não haja preços com valores "0.0":
//+------------------------------------------------------------------+ //| Refreshes the symbol quotes data | //+------------------------------------------------------------------+ bool RefreshRates(void) { //--- refresh rates if(!m_symbol.RefreshRates()) { Print("RefreshRates error"); return(false); } //--- protection against the return value of "zero" if(m_symbol.Ask()==0 || m_symbol.Bid()==0) return(false); //--- return(true); }
A inicialização do multiplicador m_adjusted_point é realizada na OnInit(), após a inicialização do objeto m_symbol:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- if(!m_symbol.Name(Symbol())) // sets symbol name return(INIT_FAILED); RefreshRates(); //--- tuning for 3 or 5 digits int digits_adjust=1; if(m_symbol.Digits()==3 || m_symbol.Digits()==5) digits_adjust=10; m_adjusted_point=m_symbol.Point()*digits_adjust; //--- return(INIT_SUCCEEDED); }
Em OnTick(), graças ao arquivo anexado "IndicatorsMQL4Style.mqh", acessamos o arquivo em estilo MQL4:
if(!RefreshRates()) return; //--- to simplify the coding and speed up access data are put into internal variables MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MAIN_LINE,0); MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MAIN_LINE,1); SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,SIGNAL_LINE,0); SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,SIGNAL_LINE,1); MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0); MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);
3.2.1. Trabalhando com posições
Para obter um resultado adequado, definimos a ausência de posições como
total=PositionsTotal(); if(total<1) {
Porém, esta abordagem não é inteiramente correta, porque não leva em conta a presença de posições em outros símbolos e/ou com outros identificadores (mágicas).
3.2.2. As posições Buy são abertas com ajuda do método Buy da classe de negociação CTrade, e com o método ResultDeal desta classe verificamos que seja corretamente executada. ResultDeal retorna o bilhete da transação, se ela for feita.
//--- check for long position (BUY) possibility if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDOpenLevel*m_adjusted_point) && MaCurrent>MaPrevious) { m_trade.Buy(Lots,m_symbol.Name(),m_symbol.Ask(), 0.0, m_symbol.NormalizePrice(m_symbol.Ask()+TakeProfit*m_adjusted_point), "macd sample"); if(m_trade.ResultDeal()!=0) Print("BUY position opened : ",m_trade.ResultPrice()); else Print("Error opening BUY position : ",m_trade.ResultRetcodeDescription()); return; }
Repare que o preço, no pedido de negociação, é normalizado pelo método NormalizePrice da classe de negociação CSymbolInfo. Este método permite levar em conta a quantização, isto é, a alteração mínima no preço e o número de dígitos depois do ponto decimal.
Para abertura da posição Sell, utilizamos métodos semelhantes.
3.2.3. Bloco de rastreamento de posições: fechamento ou modificação.
O ciclo em si ocorre entre o número total de posições menos um e zero. Para poder trabalhar com as posições, é preciso, primeiro, selecionar uma de acordo com o índice na lista geral:
for(int i=PositionsTotal()-1;i>=0;i--) if(m_position.SelectByIndex(i)) // selects the position by index for further access to its properties
Fechamento de posição ocorre por meio do método PositionClose, enquanto a modificação através do PositionModify. Repare que, a cada modificação, novamente é utilizado o método de normalização de preços NormalizePrice da classe de negociação CSymbolInfo.
Bloco inteiro de rastreamento de posições:
//--- it is important to enter the market correctly, but it is more important to exit it correctly... for(int i=PositionsTotal()-1;i>=0;i--) if(m_position.SelectByIndex(i)) // selects the position by index for further access to its properties if(m_position.Symbol()==m_symbol.Name()) { //--- long position is opened if(m_position.PositionType()==POSITION_TYPE_BUY) { //--- should it be closed? if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && MacdCurrent>(MACDCloseLevel*m_adjusted_point)) { //--- close position and exit if(!m_trade.PositionClose(m_position.Ticket())) Print("PositionClose error ",m_trade.ResultRetcodeDescription()); return; } //--- check for trailing stop if(TrailingStop>0) { if(m_position.PriceCurrent()-m_position.PriceOpen()>m_adjusted_point*TrailingStop) { if(m_position.StopLoss()<m_symbol.Bid()-m_adjusted_point*TrailingStop) { //--- modify position and exit if(!m_trade.PositionModify(m_position.Ticket(), m_symbol.NormalizePrice(m_position.PriceCurrent()-m_adjusted_point*TrailingStop), m_position.TakeProfit())) Print("PositionModify error ",m_trade.ResultRetcodeDescription()); return; } } } } if(m_position.PositionType()==POSITION_TYPE_SELL) { //--- should it be closed? if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*m_adjusted_point)) { //--- close position and exit if(!m_trade.PositionClose(m_position.Ticket())) Print("PositionClose error ",m_trade.ResultRetcodeDescription()); return; } //--- check for trailing stop if(TrailingStop>0) { if((m_position.PriceOpen()-m_position.PriceCurrent())>(m_adjusted_point*TrailingStop)) { if((m_position.StopLoss()>(m_symbol.Ask()+m_adjusted_point*TrailingStop)) || (m_position.StopLoss()==0.0)) { //--- modify position and exit if(!m_trade.PositionModify(m_position.Ticket(), m_symbol.NormalizePrice(m_symbol.Ask()+m_adjusted_point*TrailingStop), m_position.TakeProfit())) Print("PositionModify error ",m_trade.ResultRetcodeDescription()); return; } } } } }
Isso tudo são alterações, o arquivo final "MACD Sample 4 to 5 MQL4 style.mq5" se encontra anexado no final deste artigo.
3.3. Comparamos a velocidade de execução de EAs com base no MACD
Na comparação, participarão:
- "MACD Sample.mq5" - EA da distribuição padrão com acesso correto aos indicadores
- "MACD Sample One value at a time.mq5" - análogo do "MACD Sample.mq5", em que, de cada vez, obtemos um valor dos indicadores
- "MACD Sample 4 to 5 MQL4 style.mq5" - EA MQL4, reescrito em MQL5 com alterações mínimas e com acesso aos indicadores em estilo MQL4
O teste foi realizado em USDJPY, M30, de 2017.02.01 a 2018.01.16, no servidor MetaQuotes-Demo. Após cada teste (alteração quer do EA quer do modo de geração de ticks), o terminal era reinicializado. Configuração do computador:
Windows 10 (build 16299) x64, IE 11, UAC, Intel Core i3-3120M @ 2.50GHz, Memory: 4217 / 8077 Mb, Disk: 335 / 464 Gb, GMT+2
№ p/p | Expert Advisor | Cada tick baseado em ticks reais | Todos os ticks | OHLC | ||||||
---|---|---|---|---|---|---|---|---|---|---|
Hora do teste | Trades | Transações | Hora do teste | Trades | Transações | Hora do teste | Trades | Transações | ||
1 | MACD Sample.mq5 | 0:01:19.485 | 122 | 244 | 0:00:53.750 | 122 | 244 | 0:00:03.735 | 119 | 238 |
2 | MACD Sample One value at a time.mq5 | 0:01:20.344 | 122 | 244 | 0:00:56.297 | 122 | 244 | 0:00:03.687 | 119 | 238 |
3 | MACD Sample 4 to 5 MQL4 style.mq5 | 0:02:37.422 | 122 | 244 | 0:01:52.171 | 122 | 244 | 0:00:06.312 | 119 | 238 |
Em modo "Todos os ticks", os três experts têm demonstrado os mesmos gráficos:
Fig. 2. MACD Sample XXXX no testador de estratégias
CONCLUSÃO: o EA "MACD Sample 4 to 5 MQL4 style.mq5", com acesso aos indicadores em estilo MQL4 é duas vezes mais lento do que EAs semelhantes com acesso correto a indicadores.
3.4. Comparemos o consumo de memória de EAs com base no MACD
Para isso, são usados os mesmos 14 gráficos, como no ponto 2. O que acontecerá com o consumo de memória quando, em cada tick, trabalhemos com indicadores em estilo MQL4? No primeiro gráfico, mantem-se o indicador "Terminal memory used.mq5". Ele, a cada 10 segundos, imprime o identificador TERMINAL_MEMORY_USED, enquanto EAs são fixados aos 13 restantes, por turno. Antes de cada medição, o indicador era reinicializado.
№ p/p | Expert Advisor | Gerenciador de tarefas, MB | TERMINAL_MEMORY_USED, MB |
---|---|---|---|
1 | MACD Sample.mq5 | 334.6 | 813 |
2 | MACD Sample One value at a time.mq5 | 335.8 | 813 |
3 | MACD Sample 4 to 5 MQL4 style.mq5 | 342.2 | 818 |
CONCLUSÃO: Quanto ao consumo de memória, o EA baseado no MACD com acesso correto aos indicadores é comparável ao EA baseado no MACD com acesso ao indicador em estilo MQL4. Ou seja, eles consomem quase a mesma quantidade de memória.
4. Nova vida do EA [data folder]\MQL4\Experts\Moving Average.mq4
Como, no capítulo 3, nós convertemos MQL4 em MQL5, eu proponho, no caso com Movinge Average.mq4, simplesmente alterar o EA Moving Average.mq5 mediante conexão do arquivo "IndicatorsMQL5.mqh"
#property version "1.00" #include <SimpleCall\IndicatorsMQL5.mqh> #include <Trade\Trade.mqh>
e substituição de CopyBuffer
//--- get current Moving Average double ma[1]; if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) { Print("CopyBuffer from iMA failed, no data"); return; }
pelo estilo MQL4 de acesso aos indicadores:
//--- get Moving Average ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
O uso dos indicadores em estilo MQL4 nos deixa com apenas uma chance para testar o resultado da operação, isto é, comparar os dados com zero. Com isto em mente, a entrada final nos blocos "CheckForOpen" e "CheckForClose" era como segue:
//--- get current Moving Average double ma[1]; if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) { Print("CopyBuffer from iMA failed, no data"); return; }
e será a seguinte:
//--- get current Moving Average double ma[1]; ma[0]=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0); //if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) if(ma[0]==0.0) { //Print("CopyBuffer from iMA failed, no data"); Print("Get iMA in MQL4 style failed, no data"); return; }
Estas são todas as mudanças que conservamos no EA "Moving Average MQL4 style.mq5". Advisor é anexado no final do artigo. Medimos da produtividade e consumo de memória entre o "Moving Average.mq5" convencional e o "Moving Average MQL4 style.mq5".
Lembre que os testes foram realizados em equipamentos
Windows 10 (build 16299) x64, IE 11, UAC, Intel Core i3-3120M @ 2.50GHz, Memory: 4217 / 8077 Mb, Disk: 335 / 464 Gb, GMT+2
e, após cada teste, o terminal foi reinicializado. Testamos em EURUSD, M15 de 2017.02.01 a 2018.01.16, no servidor MetaQuotes-Demo.
№ p/p | Expert Advisor | Cada tick baseado em ticks reais | Todos os ticks | OHLC | ||||||
---|---|---|---|---|---|---|---|---|---|---|
Hora do teste | Trades | Transações | Hora do teste | Trades | Transações | Hora do teste | Trades | Transações | ||
1 | Moving Average.mq5 | 0:00:33.359 | 1135 | 2270 | 0:00:22.562 | 1114 | 2228 | 0:00:02.531 | 1114 | 2228 |
2 | Moving Average MQL4 style.mq5 | 0:00:34.984 | 1135 | 2270 | 0:00:23.750 | 1114 | 2228 | 0:00:02.578 | 1114 | 2228 |
CONCLUSÃO: é provável que, no MACD Sample, ao acessar os indicadores em estilo MQL4, o kernel da MQL5 deva ter procurado entre dois identificadores, perdendo tempo.
No caso do EA Moving Average, ao usar o indicador em estilo MQL4, o kernel da MQL5 não perde tempo procurando o ID necessário, porque ele é o único.
Comparamos o consumo de memória de EAs com base no Moving Average
Para isso, são usados os mesmos 14 gráficos, como no ponto 2. No primeiro gráfico, mantem-se o indicador "Terminal memory used.mq5". Ele, a cada 10 segundos, imprime o identificador TERMINAL_MEMORY_USED, enquanto EAs são fixados aos 13 restantes, por turno. Antes de cada medição, o indicador era reinicializado.
№ p/p | Expert Advisor | Gerenciador de tarefas, MB | TERMINAL_MEMORY_USED, MB |
---|---|---|---|
1 | Moving Average.mq5 | 295.6 | 771 |
2 | Moving Average MQL4 style.mq5 | 283.6 | 760 |
CONCLUSÃO: o consumo de memória é quase idêntico. Pequenas diferenças podem ser atribuídas à "vida interior" do terminal: atualizações de notícias, etc.
5. Análogos de séries iXXXX
Como fizemos a recepção dos valores dos indicadores em estilo MQL4, neste mesmo momento, escrevemos a função de seção Acesso a TimeSeries e indicadores. A realização será em [data folder]\MQL5\Include\SimpleCall\Series.mqh.
Lista de funções em "Series.mqh" que fornecem acesso aos valores do TimeSeries como em MQL4:
Para as funções iHighest e iLowest, estão disponíveis os identificadores predefinidos de séries MODE_OPEN, MODE_LOW, MODE_HIGH, MODE_CLOSE, MODE_VOLUME, MODE_TIME.
Exemplo de realização da função iClose:
//+------------------------------------------------------------------+ //| iClose function in MQL4 notation | //+------------------------------------------------------------------+ double iClose( string symbol, // symbol ENUM_TIMEFRAMES timeframe, // timeframe int shift // shift ) { double result=0.0; //--- double val[1]; ResetLastError(); int copied=CopyClose(symbol,timeframe,shift,1,val); if(copied>0) result=val[0]; else Print(__FUNCTION__,": CopyClose error=",GetLastError()); //--- return(result); }
Obtemos o valor do preço de fechamento da barra shift com ajuda de CopyClose, isto é, a primeira forma da chamada (tratamento segundo a primeira posição e número de elemento requeridos):
int CopyClose( string symbol_name, // nome do símbolo ENUM_TIMEFRAMES timeframe, // período int start_pos, // por onde começamos int count, // quanto copiamos double close_array[] // matriz para copiar preços de fechamento );
Fim do artigo
Como podemos ver, MQL5 permite os adeptos da MQL4 receberem valores dos indicadores e timeseries em seu estilo favorito. Eles dizem que um código desse tipo é mais curto e mais fácil de ler. Os desenvolvedores da plataforma, por sua vez, exigem um trabalho mais cuidadoso com o código e o máximo de verificações ao chamar as funções (e eu concordo plenamente com eles). Enumeremos brevemente os prós e contras das funções discutidas neste artigo.
Contras:
- restrição no processamento do erro de retorno ao acessar o indicador;
- queda da velocidade de teste ao acessar simultaneamente mais de um indicador;
- necessidade de especificar corretamente a linha de indicadores dependendo da conexão "IndicatorsMQL5.mqh" ou "IndicatorsMQL4.mqh".
- simplicidade na escrita do código, isto é, uma linha em vez de várias;
- clareza e concisão, ou seja, quanto menos código, é mais fácil de entendê-lo.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/4318
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.





- 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
Se eu executar um iVolume simples em um loop, sobre todos os instrumentos disponíveis, em MQL5, a plataforma simplesmente congela, as chamadas massivas de copybuffer agem como um ataque de ddos no sistema operacional, ele simplesmente trava, enquanto que se eu fizer esse mesmo processo em MQL4, ele funciona sem problemas como um pacote de manteiga derretida quente.
Na mesma máquina, e isso me diz mais do que eu preciso saber.
Combine isso com todos os sinais e ruídos provenientes da comunidade, o que me diz que minhas conclusões estavam certas o tempo todo.
O problema é: por que tenho que copiar o buffer toda vez que preciso ler o valor do indicador? O buffer está lá, já foi calculado, por que não posso acessar seu valor diretamente?
Isso não faz sentido!
Eu só deveria ter acesso direto ao buffer do indicador, copiá-lo repetidamente só prejudicará o desempenho.
Eu realmente não entendo por que a Metaquotes seguiu essa forma de trabalho.
CopyBuffer(...) vs buffer[i] ?
O problema é: por que tenho que copiar o buffer toda vez que preciso ler o valor do indicador? O buffer está lá, já foi calculado, por que não posso acessar seu valor diretamente?
Isso não faz sentido!
Eu só deveria ter acesso direto ao buffer do indicador, copiá-lo repetidamente só prejudicará o desempenho.
Eu realmente não entendo por que a Metaquotes seguiu essa forma de trabalho.
CopyBuffer(...) vs buffer[i] ?
Porque isso não é tão simples. O indicador é executado em um thread e um EA em outro thread (e você pode ter vários indicadores/vários EAs). Se você está reclamando de um procedimento tão simples como handle/CopyBuffer, não vai querer continuar com um aplicativo multithread, acredite.
O mql5 fornece soluções genéricas, capazes de gerenciar a maioria das situações "normais". Se você tiver um problema específico em seu projeto, sempre haverá uma solução.