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
- 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