English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Guia prático do MQL5: Analisando propriedades de posição no testador de estratégias do MetaTrader 5

Guia prático do MQL5: Analisando propriedades de posição no testador de estratégias do MetaTrader 5

MetaTrader 5Exemplos | 20 março 2014, 14:06
2 660 0
Anatoli Kazharski
Anatoli Kazharski

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.

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.

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

Arquivos anexados |
Guia prático do MQL5: Como Evitar Erros Quando Configurando/Modificando Níveis de Negociação Guia prático do MQL5: Como Evitar Erros Quando Configurando/Modificando Níveis de Negociação
Em continuação do nosso trabalho no Consultor Especialista do artigo anterior da série chamado "Guia prático: Analisando propriedades de posição no testador de estratégias do MetaTrader 5", o melhoraremos com um punhado de funções úteis, assim como aprimorar e otimizar as já existentes. O Consultor Especialista terá neste momento parâmetros externos que podem ser otimizados no Testador de Estratégias MetaTrader 5 e, em algumas formas, se parecerá com um simples sistema de transações.
Guia prático do MQL5: Propriedades de posição no painel de informações personalizado Guia prático do MQL5: Propriedades de posição no painel de informações personalizado
Agora criaremos um Consultor Especialista simples que obterá propriedades de posição no símbolo atual e as exibirá no painel personalizado de informações durante as negociações manuais. O painel de informações será criado usando objetos gráficos e a informação exibida será atualizada a cada ponto. Isto será muito mais conveniente do que ter que executar manualmente todas as vezes o script descrito no artigo anterior da série, chamado "Guia prático do MQL5: Obter propriedades de posição".
Guia prático do MQL5: O Histórico de transações e a biblioteca de função para obter propriedades de posição Guia prático do MQL5: O Histórico de transações e a biblioteca de função para obter propriedades de posição
É hora de brevemente resumir a informação fornecida nos artigos anteriores sobre as propriedades de posição. Neste artigo, criaremos algumas funções adicionais para obter propriedades que podem apenas serem obtidas após acessar o histórico de transações. Também nos familiarizaremos com estruturas de dados que nos permitirá acessar propriedades de posição e símbolo de forma mais conveniente.
Guia prático do MQL5: obter propriedades de posição Guia prático do MQL5: obter propriedades de posição
Neste artigo, criaremos um script que capta todas as propriedades de posição e as exibe para o usuário em uma caixa de diálogo. Com a execução do script, você será capaz de selecionar entre dois modos disponíveis na lista suspensa nos parâmetros externos: tanto visualizar as propriedades da posição apenas no símbolo atual ou visualizar as propriedades da posição em todos os símbolos.