Guia prático do MQL5: Analisando propriedades de posição no testador de estratégias do MetaTrader 5
Introdução
Neste artigo, modificaremos o Consultor Especialista criado no artigo anterior "Guia prático do MQL5: Propriedades de posição no painel de informações personalizado" e abordar as seguintes questões:
- Verificando por novos eventos de barra no símbolo atual;
- Obtendo dados de limites;
- Incluindo uma classe de negócios da Biblioteca Padrão para um arquivo;
- Criar uma função para pesquisar por sinais de negociação;
- Criar uma função para executar operações de negócios;
- Determinando eventos de negócios na função OnTrade().
De fato, cada uma das questões acima pode merecer um artigo próprio, mas em minha opinião tal abordagem apenas complicaria o estudo da linguagem.
Usarei exemplos muito simples para mostrar como estes recursos podem ser implementados. Em outras palavras, a implementação de cada tarefa listada acima, literalmente, encaixará em uma função simples e direta. Quando desenvolvermos uma certa ideia em artigos futuros da série, gradualmente faremos estas funções mais complexas, quão necessário for e pela extensão requerida pela tarefa em mãos.
Primeiro, vamos copiar o Consultor Especialista do artigo anterior já que precisaremos de todas suas funções.
Desenvolvendo um Consultor Especialista
Começamos com a inclusão da CTrade class da Biblioteca Padrão para nosso arquivo. Esta classe tem todas as funções necessárias para executar operações de negócios. Para o começo, podemos facilmente utilizá-las, sem mesmo aprofundar em sua visualização, que é o que vamos fazer.
Para incluir a classe, precisamos escrever o seguinte:
//--- Include a class of the Standard Library #include <Trade/Trade.mqh>
Você pode colocar este código no começo do arquivo para ser possível encontrá-lo facilmente depois, por exemplo após a diretiva #define. O comando #include denota que o arquivo Trade.mqh precisa ser obtido do \MQL5\Include\Trade\. A mesma abordagem pode ser usada para incluir qualquer outro arquivo que contenha funções. Isto é especialmente útil quando a quantidade do código do projeto cresce e fica difícil de navegar nele.
Agora, precisamos criar uma instância da classe para obter acesso a todas as suas funções. Isto pode ser feito por escrever o nome da instância após o nome da classe:
//--- Load the class
CTrade trade;
Nesta versão do Consultor Especialista, usaremos apenas uma função de negócios de todas as funções disponíveis na classe CTrade. É a função PositionOpen() que é usada para abrir uma posição. Também pode ser usada para reverter uma posição aberta existente. Como esta função pode ser chamada da classe será mostrado mais tarde neste artigo quando criando uma função responsável pela execução de operações de negócios.
Além disso, adicionamos dois arranjos (arrays) dinâmicos em um escopo global. Estes arranjos tomarão valores da barra.
//--- Price data arrays double close_price[]; // Close (closing prices of the bar) double open_price[]; // Open (opening prices of the bar)
Em seguida, crie uma função CheckNewBar() que o programa usará para verificar novos eventos da barra onde operações de negócios serão executadas apenas em barras completas.
Abaixo está o código da função CheckNewBar() com comentários detalhados:
//+------------------------------------------------------------------+ //| CHECKING FOR THE NEW BAR | //+------------------------------------------------------------------+ bool CheckNewBar() { //--- Variable for storing the opening time of the current bar static datetime new_bar=NULL; //--- Array for getting the opening time of the current bar static datetime time_last_bar[1]={0}; //--- Get the opening time of the current bar // If an error occurred when getting the time, print the relevant message if(CopyTime(_Symbol,Period(),0,1,time_last_bar)==-1) { Print(__FUNCTION__,": Error copying the opening time of the bar: "+IntegerToString(GetLastError())+""); } //--- If this is a first function call if(new_bar==NULL) { // Set the time new_bar=time_last_bar[0]; Print(__FUNCTION__,": Initialization ["+_Symbol+"][TF: "+TimeframeToString(Period())+"][" +TimeToString(time_last_bar[0],TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"]"); return(false); // Return false and exit } //--- If the time is different if(new_bar!=time_last_bar[0]) { new_bar=time_last_bar[0]; // Set the time and exit return(true); // Store the time and return true } //--- If we have reached this line, then the bar is not new, return false return(false); }
Como você pode ver no código acima, a função CheckNewBar() retorna verdadeira se a barra é nova ou falso se ainda não há barra nova. Desta forma, você pode controlar a situação quando negociando/testando, apenas executando operações de negócios em barras completas.
No começo da função, declaramos uma variável estática e um arranjo estático do tipo datetime. Variáveis locais estáticas retém seus valores mesmo depois da função ser encerrada. Em toda chamada de função subsequente, tais variáveis locais conterão os valores que tomaram na chamada prévia da função.
Note adicionalmente a função CopyTime(). Ela nos ajuda a obter o horário da última barra no arranjo time_last_bar. Tenha certeza de verificar a sintaxe da função na Referência MQL5.
Você também pode perceber a função definida pelo usuário TimeframeToString() que nunca foi mencionada nesta série de artigos, anteriormente. Ela converte valores de espaço de tempo para uma cadeia que é clara ao usuário:
string TimeframeToString(ENUM_TIMEFRAMES timeframe) { string str=""; //--- If the passed value is incorrect, take the time frame of the current chart if(timeframe==WRONG_VALUE || timeframe == NULL) timeframe = Period(); switch(timeframe) { case PERIOD_M1 : str="M1"; break; case PERIOD_M2 : str="M2"; break; case PERIOD_M3 : str="M3"; break; case PERIOD_M4 : str="M4"; break; case PERIOD_M5 : str="M5"; break; case PERIOD_M6 : str="M6"; break; case PERIOD_M10 : str="M10"; break; case PERIOD_M12 : str="M12"; break; case PERIOD_M15 : str="M15"; break; case PERIOD_M20 : str="M20"; break; case PERIOD_M30 : str="M30"; break; case PERIOD_H1 : str="H1"; break; case PERIOD_H2 : str="H2"; break; case PERIOD_H3 : str="H3"; break; case PERIOD_H4 : str="H4"; break; case PERIOD_H6 : str="H6"; break; case PERIOD_H8 : str="H8"; break; case PERIOD_H12 : str="H12"; break; case PERIOD_D1 : str="D1"; break; case PERIOD_W1 : str="W1"; break; case PERIOD_MN1 : str="MN1"; break; } //--- return(str); }
Como a função CheckNewBar() é usada será mostrado mais tarde no artigo quando tivermos todas as outras funções necessárias prontas. Vamos agora olhar para a função GetBarsData() que toma valores do número de limites solicitado.
//+------------------------------------------------------------------+ //| GETTING BAR VALUES | //+------------------------------------------------------------------+ void GetBarsData() { //--- Number of bars for getting their data in an array int amount=2; //--- Reverse the time series ... 3 2 1 0 ArraySetAsSeries(close_price,true); ArraySetAsSeries(open_price,true); //--- Get the closing price of the bar // If the number of the obtained values is less than requested, print the relevant message if(CopyClose(_Symbol,Period(),0,amount,close_price)<amount) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the Close price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } //--- Get the opening price of the bar // If the number of the obtained values is less than requested, print the relevant message if(CopyOpen(_Symbol,Period(),0,amount,open_price)<amount) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the Open price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } }
Vamos dar uma olhada mais de perto no código acima. Primeiro, na variável amount, especificamos o número de limites dos quais os dados precisamos obter. Então definimos a ordem de indexação do arranjo para que o valor da última barra (atual) esteja no índice zero do arranjo, usando a função ArraySetAsSeries() . Por exemplo, se você quer usar o valor da última barra em seus cálculos, pode ser escrito como a seguir, se exemplificado pelo preço de abertura: open_price[0]. A anotação para a penúltima barra será de maneira similar: open_price[1].
O mecanismo de obtenção de preços de abertura e fechamento é similar àquele da função CheckNewBar() onde tivemos que obter o horário da última barra. É apenas que neste caso usamos as funções CopyClose() e CopyOpen(). De maneira similar, CopyHigh() e CopyLow() são usadas para obter os preços alto e baixo da barra, respectivamente.
Vamos em frente e considerar um simples exemplo mostrando como determinar sinais para abrir/reverter uma posição. Matrizes de preço armazenam dados por duas barras (barra atual e o completado anteriormente). Usaremos dados da barra completada.
- O sinal de Compra ocorre quando o preço de fechamento está acima do preço de abertura (barra em alta);
- Um sinal de Venda ocorre quando o preço de fechamento está abaixo do preço de abertura (barra em baixa).
O código para implementação desta condições simples é fornecido abaixo:
//+------------------------------------------------------------------+ //| DETERMINING TRADING SIGNALS | //+------------------------------------------------------------------+ int GetTradingSignal() { //--- A Buy signal (0) : if(close_price[1]>open_price[1]) return(0); //--- A Sell signal (1) : if(close_price[1]<open_price[1]) return(1); //--- No signal (3): return(3); }
Como você pode ver, é muito simples. Alguém pode facilmente descobrir como lidar com condições mais complexas de forma similar. A função retorna zero se uma barra completada está para cima ou um se a barra completada está para baixo. Se por qualquer razão não há sinal, a função retornará 3.
Agora precisamos apenas criar uma função TradingBlock() para a implementação de atividades de negociação. Abaixo está o código da função com comentários detalhados:
//+------------------------------------------------------------------+ //| TRADING BLOCK | //+------------------------------------------------------------------+ void TradingBlock() { int signal=-1; // Variable for getting a signal string comment="hello :)"; // Position comment double start_lot=0.1; // Initial volume of a position double lot=0.0; // Volume for position calculation in case of reverse position double ask=0.0; // Ask price double bid=0.0; // Bid price //--- Get a signal signal=GetTradingSignal(); //--- Find out if there is a position pos_open=PositionSelect(_Symbol); //--- If it is a Buy signal if(signal==0) { //--- Get the Ask price ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- If there is no position if(!pos_open) { //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,start_lot,ask,0,0,comment)) { Print("Error opening a BUY position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } //--- If there is a position else { //--- Get the position type pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- If it is a SELL position if(pos_type==POSITION_TYPE_SELL) { //--- Get the position volume pos_volume=PositionGetDouble(POSITION_VOLUME); //--- Adjust the volume lot=NormalizeDouble(pos_volume+start_lot,2); //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,lot,ask,0,0,comment)) { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } //--- return; } //--- If there is a Sell signal if(signal==1) { //-- Get the Bid price bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- If there is no position if(!pos_open) { //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,start_lot,bid,0,0,comment)) { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } //--- If there is a position else { //--- Get the position type pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- If it is a BUY position if(pos_type==POSITION_TYPE_BUY) { //--- Get the position volume pos_volume=PositionGetDouble(POSITION_VOLUME); //--- Adjust the volume lot=NormalizeDouble(pos_volume+start_lot,2); //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,lot,bid,0,0,comment)) { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } //--- return; } }
Acredito que tudo deve estar claro até o ponto onde uma posição é aberta. Como você pode ver no código acima, o apontador (trade) é seguido por um ponto, o qual por sua ver é seguido pelo método PositionOpen(). Assim é como você pode diferenciar um certo método de uma classe. Depois que você colocar um ponto, você verá uma lista contendo todos os métodos de classe. Tudo o que você precisa é selecionar o método necessário da lista.
Fig. 1. Invocando um método de classe.
Há dois blocos principais na função TradingBlock() - comprar e vender. Logo depois de determinar a direção do sinal, obtemos o preço de venda no caso de um sinal de Compra e o preço de compra no caso de um sinal de Venda.
Todos os preços/níveis usados em ordens de negócios devem ser normalizados usando a função NormalizeDouble(), senão uma tentativa de abrir ou modificar uma posição resultará em um erro. O uso desta função é também recomendável quando calculando o lote. Além disso, por favor perceba que os parâmetros Parar Perdas e Obter Lucros tem os valores zerados. Mais informação em definir níveis de negociação serão fornecidos no próximo artigo da série.
Agora que todas as funções definidas pelo usuário estão prontas, podemos organizá-las na ordem correta:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the new bar CheckNewBar(); //--- Get position properties and update the values on the panel GetPositionProperties(); //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Print the deinitialization reason to the journal Print(GetDeinitReasonText(reason)); //--- When deleting from the chart if(reason==REASON_REMOVE) //--- Delete all objects relating to the info panel from the chart DeleteInfoPanel(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- If the bar is not new, exit if(!CheckNewBar()) return; //--- If there is a new bar else { GetBarsData(); // Get bar data TradingBlock(); // Check the conditions and trade } //--- Get the properties and update the values on the panel GetPositionProperties(); }
Há apenas uma última coisa restante a considerar - determinar eventos de negócios usando a função OnTrade(). Aqui, falaremos apenas brevemente sobre ela para passar uma ideia geral. Em nosso caso, precisamos implementar o seguinte cenário: quando abrindo/fechando/modificando uma posição manualmente, os valores na lista de propriedades de posição no painel de informações precisam ser atualizados assim que a operação é completa, ao invés de quando se recebe um novo ponto. Para este propósito, apenas precisamos adicionar o seguinte código:
//+------------------------------------------------------------------+ //| TRADE EVENT | //+------------------------------------------------------------------+ void OnTrade() { //--- Get position properties and update the values on the panel GetPositionProperties(); }
Basicamente, tudo está pronto e podemos proceder para o teste. O Testador de Estratégia permite a você rapidamente executar um teste no modo de visualização e encontrar erros, se houver algum. O uso do Testador de Estratégia pode também ser visto como benéfico devido ao fato que você pode continuar desenvolvendo seu programa mesmo em finais de semana quando os mercados estão fechados.
Configure o Testador de Estratégias, autorize o modo de visualização e clique em Iniciar. O Consultor Especialista começará a negociar no Testador de Estratégia e você verá uma figura similar a mostrada abaixo.
Fig. 2. Modo de visualização no Testador de Estratégia do MetaTrader 5.
Você pode suspender o teste no modo de visualização a qualquer momento e continuar testando passo a passo ao apertar F12. O passo será igual a um bar se você definir o Testador de Estratégia para o modo de apenas preços de Abertura, ou um clique se você selecionar o modo Todo ponto. Você também pode controlar a velocidade dos testes.
Para ter certeza que os valores no painel de informações são atualizados logo após abrir/fechar uma posição manualmente ou adicionar/modificar os níveis de Parar Perdas/Obter Lucros, o Consultor Especialista deverá ser testado em modo de tempo real. Para não esperar demais, apenas execute o Consultor Especialista em um quadro de tempo de 1-minuto para que operações de negociação sejam executadas todo minuto.
Além disso, eu adicionei outro arranjo para nomes de propriedades de posição no painel de informações:
// Array of position property names string pos_prop_texts[INFOPANEL_SIZE]= { "Symbol :", "Magic Number :", "Comment :", "Swap :", "Commission :", "Open Price :", "Current Price :", "Profit :", "Volume :", "Stop Loss :", "Take Profit :", "Time :", "Identifier :", "Type :" };
No artigo anterior, mencionei que precisaríamos desta matriz para reduzir o código da função SetInfoPanel(). Você pode agora ver como isso pode ser feito se você ainda não implementou ou descobriu por conta própria. A nova implementação da lista para criação de objetos relacionados a propriedades de posição é como se segue:
//--- List of the names of position properties and their values for(int i=0; i<INFOPANEL_SIZE; i++) { //--- Property name CreateLabel(0,0,pos_prop_names[i],pos_prop_texts[i],anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[i],2); //--- Property value CreateLabel(0,0,pos_prop_values[i],GetPropertyValue(i),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[i],2); }
No início da função SetInfoPanel(), você pode perceber a seguinte linha:
//--- Testing in the visualization mode if(MQL5InfoInteger(MQL5_VISUAL_MODE)) { y_bg=2; y_property=16; }
Isso transmite para o programa que as coordenadas Y dos objetos no painel de informação precisam ser ajustadas se o programa está atualmente sendo testado no modo de visualização. Isto é devido ao fato de que quando testando no modo de visualização do Testador de Estratégia, o nome do Consultor Especialista não é exibido no canto superior direito do gráfico como em tempo real. Portanto, o entalhe desnecessário pode ser excluído.
Conclusão
Por enquanto é só. No próximo artigo, focaremos na configuração e modificação de níveis de negociação. Abaixo você pode fazer o download do código fonte para o Consultor Especialista, PositionPropertiesTesterEN.mq5.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/642
- 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