Guia prático do MQL5: Registrando o histórico de negociações em um arquivo e criando gráficos de saldo para cada símbolo no Excel
Introdução
Ao me comunicar em vários fóruns, utilizei frequentemente exemplos de meus resultados de teste exibidos como capturas de tela de gráficos do Microsoft Excel. Por muitas vezes me foi pedido para explicar como tais gráficos podem ser criados. O Excel oferece características amplas para criação de gráficos e existem vários livros sobre esse assunto. Para encontrar a informação necessária em um livro, talvez seja necessário lê-lo por inteiro. Agora, enfim, eu tenho algum tempo para explicar tudo nesse artigo.
Nos dois artigos anteriores Guia prático do MQL5: Consultor Especialista multi-moeda - Abordagem simples, organizada e rápida e Guia prático do MQL5: Desenvolvendo um Consultor Especialista multi-moeda com número ilimitado de parâmetros lidamos com o desenvolvimento de CEs multi-moeda no MQL5. Sabemos que os resultados de testes no MetaTrader 5 são exibidos como uma curva de balanço/igualdade geral, isto é, se você precisa visualizar os resultados para cada símbolo separadamente, você deve, mais uma vez, ir nos parâmetros externos do Consultor Especialista para desabilitar todos os símbolos exceto daquele cujos resultados são necessários e depois executar o teste novamente. Isso é inconveniente.
Então hoje vou mostrar a você um método simples de como você pode obter gráficos de balanço para todos os símbolos juntamente com o resultado cumulativo de um Consultor Especialista multi-moeda em um único diagrama do Excel com apenas alguns cliques. Para reconstruir o exemplo, tomaremos o Consultor Especialista multi-moeda do artigo anterior. Ele será aperfeiçoado com uma função que escreverá o histórico de negociações e curvas de saldo para todos os símbolos para um arquivo .csv na conclusão do teste. Além disso, adicionaremos outra coluna para o relatório para mostrar as reduções de todos as máximas locais.
Vamos criar um catálogo do Excel montado de forma a ser capaz de conectar-se ao arquivo de dados. O catálogo pode ser aberto o tempo todo, então não será necessário ser fechado antes de executar outro teste. Na conclusão do teste, você só precisará atualizar os dados pressionando uma determinada tecla para ser capaz de ver as alterações no relatório e no gráfico.
Desenvolvimento do Consultor Especialista
Não haverão alterações significativas em nosso CE, apenas adicionaremos algumas funções. Vamos começar adicionando a estrutura e o arranjo para os balanços de símbolo ao arquivo principal.
//--- Arrays for balances struct Balance { double balance[]; }; //--- Array of balances for all symbols Balance symbol_balance[];
Depois, criamos o arquivo incluído Report.mqh em separado para funções que geram relatórios de teste e incluem-no no arquivo principal do Consultor Especialista (veja a linha destacada no código acima):
//--- Include custom libraries #include "Include/Auxiliary.mqh" #include "Include/Enums.mqh" #include "Include/Errors.mqh" #include "Include/FileFunctions.mqh" #include "Include/InitializeArrays.mqh" #include "Include/Report.mqh" #include "Include/ToString.mqh" #include "Include/TradeFunctions.mqh" #include "Include/TradeSignals.mqh"
Vamos primeiro criar uma estrutura de propriedade de negociação, como aquela que já temos no projeto para posição e propriedades do símbolo. Para fazê-lo, adicionamos a enumeração dos identificadores de propriedade ao arquivo Enums.mqh:
//+------------------------------------------------------------------+ //| Enumeration of deal properties | //+------------------------------------------------------------------+ enum ENUM_DEAL_PROPERTIES { D_SYMBOL = 0, // Deal symbol D_COMMENT = 1, // Deal comment D_TYPE = 2, // Deal type D_ENTRY = 3, // Deal entry - entry in, entry out, reverse D_PRICE = 4, // Deal price D_PROFIT = 5, // Deal result (profit/loss) D_VOLUME = 6, // Deal volume D_SWAP = 7, // Cumulative swap on close D_COMMISSION = 8, // Deal commission D_TIME = 9, // Deal time D_ALL = 10 // All of the above mentioned deal properties };
Além disso, no arquivo Report.mqh criamos a estrutura da propriedade da negociação e a função GetHistoryDealProperties() que retorna uma propriedade da negociação. A função aceita dois parâmetros: tíquete de negociação e identificador de propriedade.
Abaixo, você pode ver o código da estrutura e a função GetHistoryDealProperties():
//--- Deal properties in the history struct HistoryDealProperties { string symbol; // Symbol string comment; // Comment ENUM_DEAL_TYPE type; // Deal type ENUM_DEAL_ENTRY entry; // Direction double price; // Price double profit; // Profit/Loss double volume; // Volume double swap; // Swap double commission; // Commission datetime time; // Time }; //--- Variable of deal properties HistoryDealProperties deal; //+------------------------------------------------------------------+ //| Gets deal properties by ticket | //+------------------------------------------------------------------+ void GetHistoryDealProperties(ulong ticket_number,ENUM_DEAL_PROPERTIES history_deal_property) { switch(history_deal_property) { case D_SYMBOL : deal.symbol=HistoryDealGetString(ticket_number,DEAL_SYMBOL); break; case D_COMMENT : deal.comment=HistoryDealGetString(ticket_number,DEAL_COMMENT); break; case D_TYPE : deal.type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket_number,DEAL_TYPE); break; case D_ENTRY : deal.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_number,DEAL_ENTRY); break; case D_PRICE : deal.price=HistoryDealGetDouble(ticket_number,DEAL_PRICE); break; case D_PROFIT : deal.profit=HistoryDealGetDouble(ticket_number,DEAL_PROFIT); break; case D_VOLUME : deal.volume=HistoryDealGetDouble(ticket_number,DEAL_VOLUME); break; case D_SWAP : deal.swap=HistoryDealGetDouble(ticket_number,DEAL_SWAP); break; case D_COMMISSION : deal.commission=HistoryDealGetDouble(ticket_number,DEAL_COMMISSION); break; case D_TIME : deal.time=(datetime)HistoryDealGetInteger(ticket_number,DEAL_TIME); break; case D_ALL : deal.symbol=HistoryDealGetString(ticket_number,DEAL_SYMBOL); deal.comment=HistoryDealGetString(ticket_number,DEAL_COMMENT); deal.type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket_number,DEAL_TYPE); deal.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_number,DEAL_ENTRY); deal.price=HistoryDealGetDouble(ticket_number,DEAL_PRICE); deal.profit=HistoryDealGetDouble(ticket_number,DEAL_PROFIT); deal.volume=HistoryDealGetDouble(ticket_number,DEAL_VOLUME); deal.swap=HistoryDealGetDouble(ticket_number,DEAL_SWAP); deal.commission=HistoryDealGetDouble(ticket_number,DEAL_COMMISSION); deal.time=(datetime)HistoryDealGetInteger(ticket_number,DEAL_TIME); break; //--- default: Print("The passed deal property is not listed in the enumeration!"); return; } }
Também precisaremos de várias funções que converterão algumas propriedades de negociação aos valores da cadeia. Essas simples funções retornam um hífen ("-") se o valor passado for vazio ou zero. Vamos registrá-los no arquivo ToString.mqh:
//+------------------------------------------------------------------+ //| Returns the symbol name, otherwise - dash | //+------------------------------------------------------------------+ string DealSymbolToString(string deal_symbol) { return(deal_symbol=="" ? "-" : deal_symbol); } //+------------------------------------------------------------------+ //| Converts deal type to string | //+------------------------------------------------------------------+ string DealTypeToString(ENUM_DEAL_TYPE deal_type) { string str=""; //--- switch(deal_type) { case DEAL_TYPE_BUY : str="buy"; break; case DEAL_TYPE_SELL : str="sell"; break; case DEAL_TYPE_BALANCE : str="balance"; break; case DEAL_TYPE_CREDIT : str="credit"; break; case DEAL_TYPE_CHARGE : str="charge"; break; case DEAL_TYPE_CORRECTION : str="correction"; break; case DEAL_TYPE_BONUS : str="bonus"; break; case DEAL_TYPE_COMMISSION : str="commission"; break; case DEAL_TYPE_COMMISSION_DAILY : str="commission daily"; break; case DEAL_TYPE_COMMISSION_MONTHLY : str="commission monthly"; break; case DEAL_TYPE_COMMISSION_AGENT_DAILY : str="commission agent daily"; break; case DEAL_TYPE_COMMISSION_AGENT_MONTHLY : str="commission agent monthly"; break; case DEAL_TYPE_INTEREST : str="interest"; break; case DEAL_TYPE_BUY_CANCELED : str="buy canceled"; break; case DEAL_TYPE_SELL_CANCELED : str="sell canceled"; break; //--- Unknown deal type default : str="unknown"; } //--- return(str); } //+------------------------------------------------------------------+ //| Converts direction of deal to string | //+------------------------------------------------------------------+ string DealEntryToString(ENUM_DEAL_ENTRY deal_entry) { string str=""; //--- switch(deal_entry) { case DEAL_ENTRY_IN : str="in"; break; case DEAL_ENTRY_OUT : str="out"; break; case DEAL_ENTRY_INOUT : str="in/out"; break; case DEAL_ENTRY_STATE : str="status record"; break; //--- Unknown direction type default : str="unknown"; } //--- return(str); } //+------------------------------------------------------------------+ //| Converts volume to string | //+------------------------------------------------------------------+ string DealVolumeToString(double deal_volume) { return(deal_volume<=0 ? "-" : DoubleToString(deal_volume,2)); } //+------------------------------------------------------------------+ //| Converts price to string | //+------------------------------------------------------------------+ string DealPriceToString(double deal_price,int digits) { return(deal_price<=0 ? "-" : DoubleToString(deal_price,digits)); } //+------------------------------------------------------------------+ //| Converts deal result to string | //+------------------------------------------------------------------+ string DealProfitToString(string deal_symbol,double deal_profit) { return((deal_profit==0 || deal_symbol=="") ? "-" : DoubleToString(deal_profit,2)); } //+------------------------------------------------------------------+ //| Converts swap to string | //+------------------------------------------------------------------+ string DealSwapToString(double deal_swap) { return(deal_swap<=0 ? "-" : DoubleToString(deal_swap,2)); }
Agora, tudo está pronto para escrever a função CreateSymbolBalanceReport() que prepara os dados para o relatório e os escreve no arquivo LastTest.csv. É muito simples: primeiro escrevemos o cabeçalho (note como a cadeia é ajustada se o teste foi executado para mais de um símbolo), depois as propriedades da negociação necessárias para o relatório são concatenadas consecutivamente dentro da cadeia que é escrita em seguida ao arquivo.
Abaixo está o código da função CreateSymbolBalanceReport():
//+------------------------------------------------------------------+ //| Creates the test report on deals in .csv format | //+------------------------------------------------------------------+ void CreateSymbolBalanceReport() { int file_handle =INVALID_HANDLE; // File handle string path =""; // File path //--- If an error occurred when creating/getting the folder, exit if((path=CreateInputParametersFolder())=="") return; //--- Create file to write data in the common folder of the terminal file_handle=FileOpen(path+"\\LastTest.csv",FILE_CSV|FILE_WRITE|FILE_ANSI|FILE_COMMON); //--- If the handle is valid (file created/opened) if(file_handle>0) { int digits =0; // Number of decimal places in the price int deals_total =0; // Number of deals in the specified history ulong ticket =0; // Deal ticket double drawdown_max =0.0; // Maximum drawdown double balance =0.0; // Balance //--- string delimeter =","; // Delimiter string string_to_write =""; // To generate the string for writing //--- Generate the header string string headers="TIME,SYMBOL,DEAL TYPE,ENTRY TYPE,VOLUME,PRICE,SWAP($),PROFIT($),DRAWDOWN(%),BALANCE"; //--- If more than one symbol is involved, modify the header string if(SYMBOLS_COUNT>1) { for(int s=0; s<SYMBOLS_COUNT; s++) StringAdd(headers,","+InputSymbols[s]); } //--- Write the report headers FileWrite(file_handle,headers); //--- Get the complete history HistorySelect(0,TimeCurrent()); //--- Get the number of deals deals_total=HistoryDealsTotal(); //--- Resize the array of balances according to the number of symbols ArrayResize(symbol_balance,SYMBOLS_COUNT); //--- Resize the array of deals for each symbol for(int s=0; s<SYMBOLS_COUNT; s++) ArrayResize(symbol_balance[s].balance,deals_total); //--- Iterate in a loop and write the data for(int i=0; i<deals_total; i++) { //--- Get the deal ticket ticket=HistoryDealGetTicket(i); //--- Get all the deal properties GetHistoryDealProperties(ticket,D_ALL); //--- Get the number of digits in the price digits=(int)SymbolInfoInteger(deal.symbol,SYMBOL_DIGITS); //--- Calculate the overall balance balance+=deal.profit+deal.swap+deal.commission; //--- Generate a string for writing via concatenation StringConcatenate(string_to_write, deal.time,delimeter, DealSymbolToString(deal.symbol),delimeter, DealTypeToString(deal.type),delimeter, DealEntryToString(deal.entry),delimeter, DealVolumeToString(deal.volume),delimeter, DealPriceToString(deal.price,digits),delimeter, DealSwapToString(deal.swap),delimeter, DealProfitToString(deal.symbol,deal.profit),delimeter, MaxDrawdownToString(i,balance,max_drawdown),delimeter, DoubleToString(balance,2)); //--- If more than one symbol is involved, write their balance values if(SYMBOLS_COUNT>1) { //--- Iterate over all symbols for(int s=0; s<SYMBOLS_COUNT; s++) { //--- If the symbols are equal and the deal result is non-zero if(deal.symbol==InputSymbols[s] && deal.profit!=0) { //--- Display the deal in the balance for the corresponding symbol // Take into consideration swap and commission symbol_balance[s].balance[i]=symbol_balance[s].balance[i-1]+ deal.profit+ deal.swap+ deal.commission; //--- Add to the string StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2)); } //--- Otherwise write the previous value else { //--- If the deal type is "Balance" (the first deal) if(deal.type==DEAL_TYPE_BALANCE) { //--- the balance is the same for all symbols symbol_balance[s].balance[i]=balance; StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2)); } //--- Otherwise write the previous value to the current index else { symbol_balance[s].balance[i]=symbol_balance[s].balance[i-1]; StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2)); } } } } //--- Write the generated string FileWrite(file_handle,string_to_write); //--- Mandatory zeroing out of the variable for the next string string_to_write=""; } //--- Close the file FileClose(file_handle); } //--- If the file could not be created/opened, print the appropriate message else Print("Error creating file: "+IntegerToString(GetLastError())+""); }
A função MaxDrawdownToString() destacada no código acima calcula todas as reduções da máxima local e retorna uma representação de cadeia do tempo do novo máximo local. Em todos os outros casos a função retorna uma cadeia contendo "-" (um hífen).
//+------------------------------------------------------------------+ //| Returns the maximum drawdown from the local maximum | //+------------------------------------------------------------------+ string MaxDrawdownToString(int deal_number,double balance,double &max_drawdown) { //--- The string to be displayed in the report string str=""; //--- To calculate the local maximum and drawdown static double max=0.0; static double min=0.0; //--- If this is the first deal if(deal_number==0) { //--- No drawdown yet max_drawdown=0.0; //--- Set the initial point as the local maximum max=balance; min=balance; } else { //--- If the current balance is greater than in the memory if(balance>max) { //--- calculate the drawdown using the previous values max_drawdown=100-((min/max)*100); //--- update the local maximum max=balance; min=balance; } else { //--- Return zero value of the drawdown max_drawdown=0.0; //--- Update the minimum min=fmin(min,balance); } } //--- Determine the string for the report if(max_drawdown==0) str="-"; else str=DoubleToString(max_drawdown,2); //--- Return result return(str); }
Então todas as funções de geração de relatório estão prontas. Precisamos somente ver como devemos utilizar todo o acima. Isso necessitará da função OnTester() acionada diante da conclusão do teste. Certifique-se de verificar a descrição detalhada dessa função na referência do MQL5.
Simplesmente escreva algumas linhas de código no corpo da função OnTester() para especificar a condição na qual o relatório deve ser gerado. O trecho de código correspondente é fornecido abaixo:
//+------------------------------------------------------------------+ //| Handler of the event of testing completion | //+------------------------------------------------------------------+ double OnTester() { //--- Write the report only after testing if(IsTester() && !IsOptimization() && !IsVisualMode()) //--- Generate the report and write it to the file CreateSymbolBalanceReport(); //--- return(0.0); }
Agora, se você executar o Consultor Especialista no testador de estratégia, no final dos testes você verá uma pasta do Consultor Especialista criada na pasta de terminais comuns C:\ProgramData\MetaQuotes\Terminal\Common\Files. E o arquivo do relatório LastTest.csv será gerado na pasta do Consultor Especialista. Se você abrir o arquivo com o bloco de notas, você verá algo como:
Figura 1. O arquivo do relatório no formato .csv.
Criando gráficos no Excel
Podemos abrir o arquivo criado no Excel e ver que cada tipo de dados está em uma coluna diferente. Dessa forma os dados se apresentam muito mais convenientes para visualização. Nesse ponto, estamos tecnicamente preparados para criar gráficos e gravar o arquivo como catálogos do Excel no formato *.xlsx. No entanto, se depois executarmos o teste e abrirmos o catálogo novamente, ainda veremos os dados antigos.
Se tentarmos atualizar os dados, enquanto e arquivo LastTest.csv ainda estiver sendo usado no Excel, o arquivo não será atualizado, já que o Consultor Especialista não será capaz de abrí-lo para escrita enquanto ele estiver sendo utilizado por outra aplicação.
Figura 2. O arquivo do relatório no formato .csv no Excel 2010.
Existe uma solução que pode ser utilizada em nosso caso. Primeiro criamos um catálogo do Excel no formato *.xlsx em qualquer pasta que desejar. Depois abra-a e vá para a aba Dados.
Figura 3. A aba Dados no Excel 2010.
Na faixa desta aba, selecione a opção Do Texto. O diálogo Importar Arquivo de Texto aparecerá onde você precisa selecionar o arquivo "LastTest.csv". Selecione o arquivo e clique no botão Abrir. O diálogo Assistente de Importação de Texto - Etapa 1 de 3 aparecerá conforme mostrado abaixo:
Figura 4. O diálogo "Assistente de Importação de Texto - Etapa 1 de 3".
Ajuste as configurações conforme mostrado acima e clique em Próximo >. Aqui, (Etapa 2 de 3) você precisa especificar o delimitador utilizado no arquivo de dados. Em nosso arquivo, é a "," (vírgula).
Figura 5. O diálogo "Assistente de Importação de Texto - Etapa 2 de 3".
Clique em Próximo > para avançar para o Assistente de Importação de Texto - Etapa 3 de 3. Aqui, deixe como Geral o formato de dados para todas as colunas. Você pode alterar o formato mais tarde.
Figura 6. O diálogo "Assistente de Importação de Texto - Etapa 3 de 3".
Após clicar no botão Finalizar, a janela Importar Dados aparecerá onde você precisa especificar a planilha e a célula para importar os dados.
Figura 7. Selecionando a célula para a importação de dados no Excel 2010.
Geralmente, selecionamos a célula do topo esquerdo A1. Antes de clicar em OK, clique em no botão Propriedades... para ajustar as propriedades do intervalo de dados externos. Você verá uma caixa de diálogo conforme mostrado abaixo.
Figura 8. Propriedades do intervalo de dados externos ao importar dados de arquivos de texto no Excel 2010.
Ajuste as configurações exatamente conforme mostrado acima e clique em OK na janela atual e na subsequente.
Como resultado, seus dados aparecerão da mesma forma como se você tivesse simplesmente carregado o arquivo .csv. Mas agora você pode executar repetidos testes no MetaTrader 5 sem ter que fechar o catálogo do Excel. Tudo o que você precisa fazer após executar o teste é simplesmente atualizar os dados utilizando o atalho Ctrl+Alt+F5 ou o botão Atualizar Tudo na faixa da aba Dados.
Utilizando as opções da Formatação Condicional na faixa da aba Início, você pode ajustar as propriedades visuais necessárias para a representação dos dados.
Figura 9. Formatação condicional no Excel 2010.
Agora precisamos exibir os dados em gráficos do Excel. Um gráfico mostrará todos os gráficos de saldo e o outro exibirá todas as reduções da máxima local como um histograma.
Primeiro vamos criar um diagrama para os gráficos de saldo. Selecione os cabeçalhos de todos os saldos e o arranjo de dados todo do topo ao final (enquanto mantém pressionada a tecla Shift, pressione a tecla End e depois a tecla Seta para Baixo). Agora na aba Inserir, selecione o tipo de gráfico desejado.
Figura 10. Selecionando um tipo de gráfico no Excel 2010.
Como resultado, o gráfico será criado e poderá ser deslocado para outra planilha para conveniência. Para fazê-lo, simplesmente selecione-o e pressione Ctrl+X (Recortar). Depois vá para a planilha recém-criada, selecione a célula A1 e pressione Ctrl+V (Colar).
O gráfico criado com configurações padrão é mostrado na imagem abaixo:
Figura 11. Aparência do gráfico com as configurações padrão.
Você pode personalizar qualquer elemento do gráfico: alterar seu tamanho, cor, estilo, etc.
Na imagem acima, o eixo horizontal mostra o número de negociações. Vamos modificá-lo de modo a fazê-lo exibir datas ao invés disso. Para essa finalidade, clique direito no gráfico e selecione a opção Selecionar Dados no menu de contexto. O diálogo Selecionar Fonte de Dados aparecerá. Clique no botão Editar, depois selecione o intervalo de dados necessário na coluna TEMPO e clique em OK.
Figura 12. A caixa de diálogo "Selecione a Fonte de Dados".
Tente criar o gráfico de retirada sozinho e o posicionar sob o primeiro gráfico. Agora suas propriedades visuais podem ser personalizadas, se necessário. Pessoalmente, eu geralmente o faço:
Figura 13. Gráficos personalizados no Excel 2010.
Conclusão
Então, temos os gráficos do Excel com resultados de testes que parecem bastantes decentes. Em um de meus futuros artigos, mostrarei a você como criar ainda mais relatórios informativos. Anexo no artigo está o arquivo que pode ser transferido com os arquivos do Consultor Especialista para sua consideração.
Após extrair os arquivos do arquivo, coloque a pasta ReportInExcel dentro do diretório MetaTrader 5\MQL5\Experts . Além disso, o indicador EventsSpy.mq5 deve ser colocado dentro do diretório MetaTrader 5\MQL5\Indicators.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/651
- 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