LifeHack para traders: relatório comparativo de vários testes
Conteúdo
- Introdução
- Ações necessárias
- 1. Parâmetros de entrada. Seleção do EA para o teste
- 2. Mais uma vez sobre o common.ini
- 2.1. common.ini -> original.ini
- 2.2. Busca da seção [Common] usando expressões regulares
- 2.3. criamos quatro arquivos: myconfiguration1.ini, myconfiguration2.ini, myconfiguration3.ini e myconfiguration4.ini
- 2.4. Edição de arquivos ini (copiamos neles uma seção geral [Common] e seções individuais [Tester])
- 3. Análise sintática e edição dos arquivos mq5 do Expert Advisor selecionado
- 3.1 Segredo №3
- 3.2. Incorporamos "#include"
- 3.3. Incorporamos "double OnTester()"
- 3.4. Caso complexo: no código já há DistributionOfProfits.mqh, e/ou OnTester()
- 4. Cópia do Expert Advisor na pasta de terminais satélite
- 5. Execução de terminais satélite
- 6. Relatório comparativo
- Conclusão
Introdução
A execução alternada do teste de EAs em vários símbolos não se trata de um processo muito convincente, uma vez que é necessário, em primeiro lugar, armazenar -em arquivos separados- os resultados dos testes de cada símbolo e, a seguir, compará-los. Eu sugiro mudar essa abordagem e realizar testes simultâneos de EAs em vários símbolos. Neste caso, os resultados do teste podem ser recolhidos em um só lugar e compará-los visualmente.
Algumas soluções já foram parcialmente abordadas nos artigos:
- LifeHack para traders: um back-test bem, e quatro melhor
- LifeHack para traders: otimização "silenciosa" ou traço da distribuição de negociações
- Expressões regulares para traders
O esquema de trabalho é o seguinte:
- Determinamos o Expert Advisor a ser testado (Win API)
- Realizamos a análise sintática do código do Expert Advisor e editamos nele a chamada de biblioteca de relatório gráfico (Win API, MQL5 e expressões regulares)
- Levamos a cabo a análise sintática do common.ini do terminal principal e preparamos os common.ini individuais para cada terminal (Win API, MQL5 e expressões regulares)
- copiamos os common.ini individuais por pastas de terminais (Win API)
- copiamos os common.ini individuais por pastas de terminais (Win API)
- Realizamos a análise sintática dos relatórios dos terminais satélite
- Reduzimos os relatórios de terminais satélites num único relatório
Ações necessárias
Antes de executar o Expert Advisor, é preciso realizar uma "sincronização" dos terminais principal e satélite.
- Tanto no principal quanto nos satélite, deve ser iniciada a mesma conta de negociação.
- Nas configurações de todos os terminais satélite, é necessário habilitar a variante "Permitir dll". Se você executar os terminais com a chave \Portable, vá para o diretório de instalação do terminal (usando Explorer ou outro gerenciador de arquivos), execute o terminal "terminal64.exe" e defina nas configurações "Permitir dll".
- A biblioteca "DistributionOfProfits.mqh" deve estar em todos os diretórios de dados (diretório de dados\MQL5\Include\DistributionOfProfits.mqh) dos terminais satélite.
1. Parâmetros de entrada. Seleção do EA para o teste
Como meu computador tem quatro núcleos, eu só posso executar quatro agentes de teste. Isso quer dizer que eu posso executar ao mesmo tempo (com um pequeno atraso de alguns segundos) apenas quatro terminais, isto é, um para cada agente. É por isso que nos parâmetros de entrada são indicados quatro grupos de configurações:
Parâmetros:
- folder of the MetaTrader#xxx installation — pasta na qual é instalado o terminal
- the tested symbol for the terminal #xxx — símbolo no qual será executado o testador de estratégias
- the tested period for the terminal #xxx — período no qual será executado o testador de estratégias
- correct name of the file of the terminal — nome do arquivo do terminal
- sleeping in milliseconds — pausa entre as execuções dos terminais satélites
- date of beginning testing (only year, month and day) — data de início do teste
- dates of end testing (only year, month and day) — data final do teste
- initial deposit — depósito
- leverage — alavancagem
Antes do início dos algoritmos básicos, é necessário associar as pastas de instalação dos terminais satélites e seus diretórios de dados na pasta AppData. Eis um exemplo de um script simples Check_TerminalPaths.mq5:
//+------------------------------------------------------------------+ //| Check_TerminalPaths.mq5 | //| Copyright 2009, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Print("TERMINAL_PATH = ",TerminalInfoString(TERMINAL_PATH)); Print("TERMINAL_DATA_PATH = ",TerminalInfoString(TERMINAL_DATA_PATH)); Print("TERMINAL_COMMONDATA_PATH = ",TerminalInfoString(TERMINAL_COMMONDATA_PATH)); } //+------------------------------------------------------------------+
Este script exibe três parâmetros:
- TERMINAL_PATH — pasta da qual é iniciado o terminal
- TERMINAL_DATA_PATH — pasta na qual são armazenados os dados do terminal
- TERMINAL_COMMONDATA_PATH — pasta compartilhada para todos os terminais de cliente instalados no computador
Exemplo para três terminais (um deles executado com a chave /Portable):
// O terminal é executado em modo nativo TERMINAL_PATH = C:\Program Files\MetaTrader 5 TERMINAL_DATA_PATH = C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075 TERMINAL_COMMONDATA_PATH = C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common // O terminal é executado em modo nativo TERMINAL_PATH = D:\MetaTrader 5 3 TERMINAL_DATA_PATH = C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\0C46DDCEB43080B0EC647E0C66170465 TERMINAL_COMMONDATA_PATH = C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common // O terminal é executado em modo Portable TERMINAL_PATH = D:\MetaTrader 5 5 TERMINAL_DATA_PATH = D:\MetaTrader 5 5 TERMINAL_COMMONDATA_PATH = C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common
Leia mais sobre a comparação entre as pastas dos terminais e suas pastas em AppData, nestas seções de um dos meus artigos anteriores:
- Vínculo entre a pasta de instalação e o diretório de dados na pasta AppData
- Comparamos a pasta de instalação e a pasta AppData dos Terminais satélite
A escolha do Expert Advisor é realizada usando a caixa de diálogo "Abrir arquivo" (função GetOpenFileNameW):
No arquivo "LifeHack para traders: um back-test bem, e quatro melhor": 4.2., já falamos em detalhe sobre a chamada da caixa de diálogo "Abrir arquivo." Selecionamos o Expert Advisor usando a caixa de diálogo do sistema "Abrir arquivo."
Nesta edição (arquivo GetOpenFileNameW.mqh, versão 1.003) foram feitas alterações na função OpenFileName:
//+------------------------------------------------------------------+ //| Creates an Open dialog box | //+------------------------------------------------------------------+ string OpenFileName(const string filter_description="Editable code", const string filter="\0*.mq5\0", const string title="Select source file") { string path=NULL; if(GetOpenFileName(path,filter_description+filter,TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Experts\\",title)) return(path); else { PrintFormat("Failed with error: %x",kernel32::GetLastError()); return(NULL); } }
Agora definir o filtro de busca de arquivos tornou-se mais conveniente. Além disso, note que o filtro agora procura os arquivos em formato editável *.mq5 (no artigo anterior, era realizada a pesquisa dos arquivos compilados *ex5).
2. Mais uma vez sobre o common.ini
Agora passamos para a descrição da função CopyCommonIni() do arquivo Compare multiple tests.mq5.
A execução de terminais satélites é realizada indicando o próprio arquivo de configuração. Temos quatro terminais satélites, o que significa que os arquivos *.ini também terão quatro, isto é: myconfiguration1.ini, myconfiguration2.ini, myconfiguration3.ini, myconfiguration4.ini. O arquivo myconfigurationX.ini é criado na base do arquivo common.ini do terminal a partir do qual é executado nosso Expert Advisor. Caminho para o arquivo common.ini:
TERMINAL_DATA_PATH\config\common.ini
Algoritmo de trabalho para a criação e edição de arquivos myconfiguration.ini:
- copiamos common.ini na pasta TERMINAL_COMMONDATA_PATH\Files\original.ini (WinAPI CopyFileW)
- no arquivo original.ini, procuramos a seção [Common] (MQL5 + expressões regulares).
No exemplo de meu terminal principal (neste terminal não é realizado a entrada na mql5.communiyty) esta seção tem a seguinte aparência:
[Common] Login=5116256 ProxyEnable=0 ProxyType=0 ProxyAddress= ProxyAuth= CertInstall=0 NewsEnable=0 NewsLanguages=
- criamos quatro arquivos: myconfiguration1.ini, myconfiguration2.ini, myconfiguration3.ini e myconfiguration4.ini (MQL5)
- editamos estes quatro arquivos (copiamos neles a seção compartilhada [Common] e as seções individuais [Tester]) (MQL5)
2.1. common.ini -> original.ini
Este é provavelmente o código mais fácil: obtenho os caminhos variáveis para as pastas "Data Folder" e "Commomm Data Folder", inicializo com o valor "original.ini"
string terminal_data_path=TerminalInfoString(TERMINAL_DATA_PATH); // path to Data Folder string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);// path to Commomm Data Folder string original_ini="original.ini"; string arr_common[]; //--- string full_name_common_ini=terminal_data_path+"\\config\\common.ini"; // full path to the common.ini file string full_name_original_ini=common_data_path+"\\Files\\"+original_ini; // full path to the original.ini file //--- common.ini -> original.ini if(!CopyFileW(full_name_common_ini,full_name_original_ini,false)) { PrintFormat("Failed with error: %x",kernel32::GetLastError()); return(false); }
Usando o Win API da função CopyFileW, copio o arquivo de configuração "common.ini" no arquivo "original.ini".
2.2. Busca da seção [Common] usando expressões regulares
Para localizar e copiar a seção [Common], aplicamos expressões regulares. A tarefa diante de nós não é muito comum, uma vez que o arquivo common.ini consiste em CADEIAS DE CARACTERES muito curtas, no final das cadeias sempre são colocados caracteres de final de cadeia (caracteres invisíveis). É possível abarcar isto de duas maneiras:
Leitura cadeia por cadeia | Leitura e gravação de todo o arquivo numa variável |
---|---|
|
|
Arquivo de teste "test_original.ini":
[Charts] ProfileLast=Default MaxBars=100000 PrintColor=0 SaveDeleted=0 TradeLevels=1 TradeLevelsDrag=0 ObsoleteLasttime=1475473485 [Common] Login=1783501 ProxyEnable=0 ProxyType=0 ProxyAddress= ProxyAuth= CertInstall=0 NewsEnable=0 [Tester] Expert=test Symbol=EURUSD Period=H1 Deposit=10000 Model=4 Optimization=0 FromDate=2016.01.22 ToDate=2016.06.06 Report=TesterReport ReplaceReport=1 UseLocal=1 Port=3000 Visual=0 ShutdownTerminal=0
No arquivo "test_original.ini", é possível treinar-se no uso de expressões regulares usando o script "Receiving lines.mq5". Nas configurações do script é possível selecionar dois modos de trabalho:
- leitura e gravação cadeia em cada cadeia de caracteres
- ou leitura e gravação de todo o arquivo numa única variável.
Alguns exemplos onde são comparados esses dois métodos:
Leitura cadeia por cadeia | Leitura e gravação de todo o arquivo numa variável |
---|---|
Solicitação: "Prox(.*)0" - procuramos a palavra "Prox" Qualquer caractere -exceto a alimentação de linha ou outro separador de cadeia Unicode no qual possa aparecer zero ou mais vezes (algorítimo guloso) "(.*)" - a busca deve finalizar após encontrar o número "0" | |
12: 0: ProxyEnable=0, 13: 0: ProxyType=0, | : 0: ProxyEnable=0ProxyType=0ProxyAddress=ProxyAuth=CertInstall=0NewsEnable=0[Tester]Expert=test Symbol=EURUSD Period=H1 Deposit=10000 Model=4 Optimization=0 FromDate=2016.01.22 ToDate=2016.06.06 Report=TesterReport ReplaceReport=1 UseLocal=1 Port=3000 Visual=0 ShutdownTerminal=0, |
Como você pode ver, foram emitidos dois resultados | Neste caso, na emissão, há apenas um resultado, mas com um monte de excesso (foi executado uma solicitação gulosa) |
Solicitação: "Prox(.*?)0" - procuramos a palavra "Prox" - em seguida, qualquer caractere -exceto a alimentação de linha ou outro separador de cadeia Unicode- no qual possa aparecer zero ou mais vezes (não guloso) "(.*)" - a busca deve finalizar após encontrar o número "0" | |
12: 0: ProxyEnable=0, 13: 0: ProxyType=0, | : 0: ProxyEnable=0, 1: ProxyType=0, 2: ProxyAddress=ProxyAuth=CertInstall=0, |
Aqui, novamente, dois resultados | No entanto, neste caso são obtidos três resultados, além disso, o terceiro não é o que eu queria obter. |
Que método escolher para o isolamento de todo um bloco "[Common]", isto é, a leitura e gravação cadeia por cadeia ou numa variável? Eu escolhi a leitura e gravação e o seguinte algoritmo:
- busca da cadeia de caracteres "[Common]" (MQL5);
- após encontrar, registramos na matriz as cadeias de caracteres localizadas;
- a seguir, continuamos a registrar na matriz da cadeia de caracteres, enquanto a expressão regular não encontrar o caractere "[".
Um exemplo desta abordagem é implementado no script"Receiving lines v.2.mq5":
//+------------------------------------------------------------------+ //| Receiving lines v.2.mq5 | //| Copyright 2016, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.000" #property description "Singling the block \"[Common]\"" #property script_show_inputs #include <RegularExpressions\Regex.mqh> //--- input string file_name="test_original.ini"; // file name input string str_format="(\\[)(.*?)(\\])"; //--- int m_handel; bool m_found_Common=false; // after finding of the word "[Common]" - the flag will be true //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { string arr_text[]; // array for rezult //--- Print("format: ",str_format); m_handel=FileOpen(file_name,FILE_READ|FILE_ANSI|FILE_TXT); if(m_handel==INVALID_HANDLE) { Print("Operation FileOpen failed, error ",GetLastError()); return; } Regex *rgx=new Regex(str_format); while(!FileIsEnding(m_handel)) { string str=FileReadString(m_handel); if(str=="[Common]") { m_found_Common=true; int size=ArraySize(arr_text); ArrayResize(arr_text,size+1,10); arr_text[size]=str; continue; // goto while... } if(m_found_Common) { MatchCollection *matches=rgx.Matches(str); int count=matches.Count(); if(count>0) { if(count>1) { Print("Alarm! matches.Count()==",count); return; } delete matches; break; // goto FileClose... } else { delete matches; // if no match is found } int size=ArraySize(arr_text); ArrayResize(arr_text,size+1,10); arr_text[size]=str; } } FileClose(m_handel); delete rgx; Regex::ClearCache(); //--- testing int size=ArraySize(arr_text); for(int i=0;i<size;i++) { Print(arr_text[i]); } } //+------------------------------------------------------------------+
Resultado de operação do script:
2016.10.05 06:58:09.276 Receiving lines v.2 (EURUSD,M1) format: (\[)(.*?)(\]) 2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) [Common] 2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) Login=1783501 2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) ProxyEnable=0 2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) ProxyType=0 2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) ProxyAddress= 2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) ProxyAuth= 2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) CertInstall=0 2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) NewsEnable=0
Como você pode ver, o script é identificado com precisão a partir do arquivo "test_original.ini", bloco de parâmetros "[Common]". Eu uso praticamente sem alterações o algoritmo a partir do script "Receiving lines v.2.mq5" na função SearchBlock(). A função SearchBlock(), após ter sucesso procurando o bloco de parâmetros "[Common]", registra esse bloco na matriz auxiliar arr_common[].
2.3. criamos quatro arquivos: myconfiguration1.ini, myconfiguration2.ini, myconfiguration3.ini e myconfiguration4.ini
Todos os quatro arquivos são criados por a subsequente chamada desse código (note os sinalizadores usados ao abrir os arquivos):
//+------------------------------------------------------------------+ //| Open new File | //+------------------------------------------------------------------+ bool IniFileOpen(const string name_file,int &handle) { handle=FileOpen(name_file,FILE_WRITE|FILE_ANSI|FILE_TXT|FILE_COMMON); if(handle==INVALID_HANDLE) { Print("Operation FileOpen file ",name_file," failed, error ",GetLastError()); return(false); } //--- return(true); }
2.4. Edição de arquivos ini (copiamos neles uma seção geral [Common] e seções individuais [Tester])
Anteriormente, na matriz auxiliar arr_common[], era registrado o bloco de parâmetros [Common]. Agora essa matriz é registrada em todos os quatro arquivos:
//--- recording block "[Common]" int arr_common_size=ArraySize(arr_common); for(int i=0;i<arr_common_size;i++) { FileWrite(handle1,arr_common[i]); FileWrite(handle2,arr_common[i]); FileWrite(handle3,arr_common[i]); FileWrite(handle4,arr_common[i]); } //--- recording block "[Tester]" string expert_short_name="D0E820_test"; WriteBlockTester(handle1,expert_short_name,ExtTerminal1Symbol,ExtTerminal1Timeframes,ExtDeposit, ExtLeverage,ExtTerminaTick,ExtFromDate,ExtToDate,expert_short_name,3000); WriteBlockTester(handle2,expert_short_name,ExtTerminal2Symbol,ExtTerminal2Timeframes,ExtDeposit, ExtLeverage,ExtTerminaTick,ExtFromDate,ExtToDate,expert_short_name,3001); WriteBlockTester(handle3,expert_short_name,ExtTerminal3Symbol,ExtTerminal3Timeframes,ExtDeposit, ExtLeverage,ExtTerminaTick,ExtFromDate,ExtToDate,expert_short_name,3002); WriteBlockTester(handle4,expert_short_name,ExtTerminal4Symbol,ExtTerminal4Timeframes,ExtDeposit, ExtLeverage,ExtTerminaTick,ExtFromDate,ExtToDate,expert_short_name,3003); //--- close the files FileClose(handle1); FileClose(handle2); FileClose(handle3); FileClose(handle4);
Depois, segue a formação do bloco de parâmetros [Tester]: para cada terminal são preparados parâmetros únicos (símbolo e timeframes), bem como parâmetros gerais (data de início e de término do teste, depósito inicial, alavancagem).
Os arquivos gerados myconfiguration1.ini, myconfiguration2.ini, myconfiguration3.ini e myconfiguration4.ini permanecem na pasta comum de dados (TERMINAL_COMMONDATA_PATH\Files\). Fechamos o manipulador destes arquivos.
3. Análise sintática e edição dos arquivos mq5 do Expert Advisor selecionado
Problemas a serem resolvidos:
- incorporar a chamada include do arquivo de análise do histórico de negociação (para detalhes veja Execução de gráficos de análise a partir do testador de estratégias);
- inclusão no Expert Advisor da função OnTester() com chamada de análise gráfica
Qual é a razão do segredo número três? Como o Segredo №1 e Segredo №2 fora exibidos anteriormente, no artigo LifeHack para traders: um back-test bem, e quatro melhor,
consideramos a seguinte situação: o terminal é executado a partir da linha de comando, e, ao mesmo tempo, é especificado o arquivo de configuração ini. No arquivo ini, especificamos o nome do Expert Advisor que será executado no testador após iniciar o terminal. Neste caso, vamos ter em mente que nós especificamos o nome do Expert Advisor que ainda não foi compilado.
Segredo №3.
Assim, o nome do Expert Advisor DEVE ser escrito sem extensão. Em relação a este artigo, será parecido com isto:
NewsEnable=0 [Tester] Expert=D0E820_test Symbol=GBPAUD
O terminal, após inicializar, primeiro procura o ARQUIVO COMPILADO (em relação ao artigo, o terminal procura o EA Expert=D0E820_test.ex5). E somente se o terminal não localizar um arquivo compilado, ele começará a compilação do Expert Advisor especificado no arquivo in.
Por esta razão, antes de começar a trabalhar na edição do Expert Advisor selecionado, é necessário percorrer as pastas dos terminais satélites e remover as versões compiladas do Expert Advisor selecionado (especificamente no nosso caso, é necessário remover os arquivos D0E820_test.ex5). Vamos remover usando o Win API da função DeleteFileW:
return(INIT_FAILED);
//--- delete all files: expert_short_name+".ex5"
ResetLastError();
string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH); // path to Commomm Data Folder
//---
string edited_expert=common_data_path+"\\Files\\"+expert_short_name+".mq5";
//--- delete expert_short_name+".ex5" files
string compiled_expert=expert_short_name+".ex5";
DeleteFileW(slaveTerminalDataPath1+"\\MQL5\\Experts\\"+compiled_expert);
DeleteFileW(slaveTerminalDataPath2+"\\MQL5\\Experts\\"+compiled_expert);
DeleteFileW(slaveTerminalDataPath3+"\\MQL5\\Experts\\"+compiled_expert);
DeleteFileW(slaveTerminalDataPath4+"\\MQL5\\Experts\\"+compiled_expert);
//--- delete expert_short_name+".set" files
E agora é necessário remover os arquivos *.set, uma vez que se você alterar no EA selecionado alguns parâmetros de entrada, i testador ainda será inicializado com os parâmetros que estavam na execução anterior. Portanto, removemos os arquivos *.set:
string set_files=expert_short_name+".set";
DeleteFileW(slaveTerminalDataPath1+"\\Tester\\"+set_files);
DeleteFileW(slaveTerminalDataPath2+"\\Tester\\"+set_files);
DeleteFileW(slaveTerminalDataPath3+"\\Tester\\"+set_files);
DeleteFileW(slaveTerminalDataPath4+"\\Tester\\"+set_files);
//--- delete expert_short_name+".htm" files (reports)
Além disso, removemos os arquivos de relatórios das pastas dos terminais satélites:
//--- delete expert_short_name+".htm" files (reports)
string file_report=expert_short_name+".htm";
DeleteFileW(slaveTerminalDataPath1+"\\"+file_report);
DeleteFileW(slaveTerminalDataPath2+"\\"+file_report);
DeleteFileW(slaveTerminalDataPath3+"\\"+file_report);
DeleteFileW(slaveTerminalDataPath4+"\\"+file_report);
//--- copying an expert in the TERMINAL_COMMONDATA_PATH\Files folder
if(!CopyFileW(expert_full_name,edited_expert,false))
Por que excluímos os arquivos de relatórios? É necessário para acompanhar o momento em que, em todos os terminais satélites, aparecem os arquivos de relatório, é então quando se torna possível realizar a análise sintática destes arquivos para criação da página de comparação de resultados obtidos de teste em vários símbolos.
E só depois de apagar os arquivos compilados, podemos compilar o arquivo selecionado do Expert Advisor na pasta TERMINAL_COMMONDATA_PATH para prosseguir o trabalho com o arquivo usando recursos MQL5:
//--- copying an expert in the TERMINAL_COMMONDATA_PATH\Files folder
if(!CopyFileW(expert_full_name,edited_expert,false))
{
PrintFormat("Failed CopyFileW expert_full_name with error: %x",kernel32::GetLastError());
return(INIT_FAILED);
}
//--- parsing advisor file
Descrição da função Compare multiple tests.mq5::ParsingEA().
Em geral, é preciso determinar se já existe, no arquivo do EA, uma cadeia de caracteres "#include <DistributionOfProfits.mqh>". Se esta cadeia não estiver presente, será preciso incorporá-la no EA. Neste ponto, é necessário entender que pode haver um grande número de variantes:
variantes | Apropriado/inapropriado |
---|---|
"#include <DistributionOfProfits.mqh>" | apropriado (variante ideal) |
"#include<DistributionOfProfits.mqh>" | apropriado (nesta variante, após a palavra "#include", não deve haver um espaço, mas sim uma tabulação) |
"#include <DistributionOfProfits.mqh>" | apropriado (nesta variante, antes da palavra "#include", deve haver um caractere de tabulação) |
"//#include <DistributionOfProfits.mqh>" | inapropriado (é apenas um comentário) |
também pode ser tal variante, quando, após "#include", não há um espaço, mas sim um caractere de tabulação ou vários espaços. Como resultado, para pesquisa foi criada a expressão regular:
"(\\s+?#include|^#include)(.*?)(<DistributionOfProfits.mqh)"
Eis como se decifra a expressão (\\s+?#include|^#include): (um ou mais espaços, não guloso,em seguida "#include") ou (a cadeia de caracteres começa com a palavra "#include"). A função NumberRegulars() é responsável pela pesquisa. Neste caso, é introduzida a variável "name_Object_CDistributionOfProfits" — nela armazenamos o nome do objeto CDistributionOfProfits. Isso pode ser útil mais tarde, se for preciso realizar pesquisas complexas.
//+------------------------------------------------------------------+ //| Insert #include <DistributionOfProfits.mqh> | //| Insert call graphical analysis of trade | //+------------------------------------------------------------------+ bool ParsingEA() { //--- find #include <DistributionOfProfits.mqh> int number=0; string name_Object_CDistributionOfProfits="ExtDistribution"; // CDistributionOfProfits object name string expressions="(\\s+?#include|^#include)(.*?)(<DistributionOfProfits.mqh)"; if(!NumberRegulars(expert_short_name+".mq5",expressions,number)) return(false); if(number==0) // a regular expression is not found { //--- add #include <DistributionOfProfits.mqh> string array[]; ArrayResize(array,2); array[0]="#include <DistributionOfProfits.mqh>"; array[1]="CDistributionOfProfits "+name_Object_CDistributionOfProfits+";"; if(!InsertLine(expert_short_name+".mq5",0,array)) return(false); Print("Line \"#include\" is insert");
Se a cadeia de caracteres não for encontrada, será preciso incorporá-la no EA (função InsertLine()). O princípio de funcionamento é o seguinte: lemos e gravamos cadeia-por-cadeia o arquivo do EA numa matriz temporária. Quando o número da cadeia de caracteres coincide com o estabelecido ("position"), na matriz é colocado a parte apropriada de código (o código é tomado a partir da matriz "text"). Após ler o arquivo completo, o Expert Advisor é excluído e, em seguida, é criado imediatamente um novo arquivo com o mesmo nome. Nele são registradas informações a partir da matriz temporária:
//+------------------------------------------------------------------+ //| Insert a line in a file | //+------------------------------------------------------------------+ bool InsertLine(const string name_file,const uint position,string &array_text[]) { int handle; int size_arr=ArraySize(array_text); //--- handle=FileOpen(name_file,FILE_READ|FILE_ANSI|FILE_TXT|FILE_COMMON); if(handle==INVALID_HANDLE) { Print("Operation FileOpen file ",name_file," failed, error ",GetLastError()); return(false); } int line=0; string arr_temp[]; ArrayResize(arr_temp,0,1000); while(!FileIsEnding(handle)) { string str_text=FileReadString(handle,-1); if(line==position) { for(int i=0;i<size_arr;i++) { int size=ArraySize(arr_temp); ArrayResize(arr_temp,size+1,1000); arr_temp[size]=array_text[i]; } } int size=ArraySize(arr_temp); ArrayResize(arr_temp,size+1,1000); arr_temp[size]=str_text; line++; } FileClose(handle); FileDelete(name_file,FILE_COMMON); //--- handle=FileOpen(name_file,FILE_WRITE|FILE_ANSI|FILE_TXT|FILE_COMMON); if(handle==INVALID_HANDLE) { Print("Operation FileOpen file ",name_file," failed, error ",GetLastError()); return(false); } int size=ArraySize(arr_temp); for(int i=0;i<size;i++) { FileWrite(handle,arr_temp[i]); } FileClose(handle); //--- return(true); }
3.3. Incorporamos "OnTester()"
Agora, a tarefa se torna muito mais difícil, uma vez que a palavra "OnTester" pode se encontrar no código do programa em muitas formas diferentes. Por exemplo, ela pode estar completamente ausente no código — provavelmente é a variante mais fácil. É possível encontrar a variante clássica:
double OnTester() {
Isto não é muito complexo. Mas os rebeldes entre os desenvolvedores são suficientes, por isso nós podemos enfrentar tal estilo de programação:
double OnTester() {
E, talvez, uma das variantes mais difíceis:
/*
//+-------------------------------+
//| |
//+-------------------------------+
double OnTester()
{
...
}
...
*/
Assim, a fim de determinar se a função OnTetster está declarada no código, aplicamos a expressão regular:
"(\\s+?double|^double)(.+?)(OnTester\\(\\))(.*)" | |
"(\\s+?double" | \\s espaço, \\s+ espaço que é encontrado pelo menos uma vez, \\s+? espaço que é encontrado pelo menos uma vez, operador não guloso, \\s+?double espaço que é encontrado pelo menos uma vez, operador não cobiçoso, e palavra "double". |
"|" | | ou |
"^double)" | a cadeia de caracteres começa com a palavra double |
"(.+?)" | . qualquer caractere, exceto a alimentação de linha ou outro separador de cadeia Unicode, .+ qualquer caractere, exceto a alimentação de linha ou outro separador de cadeia Unicode que é encontrado uma ou mais vezes, .+? qualquer caractere, exceto a alimentação de linha ou outro separador de cadeia Unicode que é encontrado uma ou mais vezes, não guloso |
"(OnTester\\(\\))" | OnTester\\(\\) palavra OnTester() |
"(.*)" | . qualquer caractere, exceto a alimentação de linha ou outro separador de cadeia Unicode, .* qualquer caractere, exceto a alimentação de linha ou outro separador de cadeia Unicode que é encontrado zero ou mais vezes |
No caso mais simples, quando as expressões regulares retornam zero na pesquisa, inserimos a chamada da função OnTester():
//--- expressions="(\\s+?double|^double)(.+?)(OnTester\\(\\))(.*)"; if(!NumberRegulars(expert_short_name+".mq5",expressions,number)) return(false); if(number==0) // a regular expression is not found { //--- add function OnTester if(!InsertLine(expert_short_name+".mq5",2, "double OnTester()"+ " {"+ " double ret=0.0;"+ " ExtDistribution.AnalysisTradingHistory(0);"+ " ExtDistribution.ShowDistributionOfProfits();"+ " return(ret);"+ " }")) return(false); Print("Line \"OnTester\" is insert"); }
No total, se, no código, não havia nem "#include <DistributionOfProfits.mqh>" nem função "OnTester()", o arquivo de origem vai ficar assim (por exemplo, em caso de ter escolhido o arquivo MACD Sample.mq5):
#include <DistributionOfProfits.mqh> CDistributionOfProfits ExtDistribution; double OnTester() { double ret=0.0; ExtDistribution.AnalysisTradingHistory(0); ExtDistribution.ShowDistributionOfProfits(); return(ret); } //+------------------------------------------------------------------+ //| MACD Sample.mq5 | //| Copyright 2009-2016, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2009-2016, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "5.50"
Parece que o código não é muito agradável esteticamente, no entanto, ele leva a cabo sua tarefa. Nos pontos 3.1 e 3.2 foram examinados casos simples (os mais simples), isto é, quando, no código do Expert Advisor, inicialmente não havia nem declaração de biblioteca de análise analítica nem função OnTrade(). Em seguida, vamos considerar os casos mais complicados, isto é, quando inicialmente no código há declaração de biblioteca de análise analítica, e/ou função OnTester().
3.4. Caso complexo: no código já há DistributionOfProfits.mqh, e/ou OnTester()
A pesquisa avançada é feita na função AdvancedSearch():
//+------------------------------------------------------------------+ //| Advanced Search | //| only_ontester=true | //| - search only function OnTester() | //| only_ontester=false | //| - search #include <DistributionOfProfits.mqh> | //| and function OnTester() | //+------------------------------------------------------------------+ bool AdvancedSearch(const string name_file,const string name_object,const bool only_ontester)
Parâmetros:
- name_file — nome do arquivo do Expert Advisor
- name_object — nome do objeto de classe CDistributionOfProfits
- only_ontester — sinalizador de pesquisa, se only_ontester=true, vamos procurar apenas a função OnTester().
No início, todo o arquivo é lido numa matriz temporária
string arr_temp[];
— assim será mais simples trabalhar.
Em seguida, são chamados sucessivamente vários códigos auxiliares:
RemovalMultiLineComments() — neste código são removidos, na matriz,todos os comentários de várias linas.
RemovalComments() — são removidos os comentários de linha única;
DeleteZeroLine() — exclui todas as linhas, na matriz, de comprimento zero. <Microsoft Translator>
Se o parâmetro de entrada only_ontester==false, significa que devemos iniciar a pesquisa de cadeia de caracteres "#include <DistributionOfProfits.mqh> " — a função FindInclude() é responsável por isto:
A função FindInclude() procura as cadeias de entrada "#include <DistributionOfProfits.mqh>" e armazena o número da cadeia de caracteres na variável "function_position" (lembro que no ponto 3.1. Incorporamos "#include", nós, usando expressões regulares, já temos definido que no código é garantida a presença da cadeia de caracteres "#include <DistributionOfProfits.mqh>"). Em seguida, é feita uma tentativa para encontrar a sequência de caracteres "CDistributionOfProfits". Se tal cadeia é encontrada, em seguida, obtém-se a partir dela o nome da variável para a classe "CDistributionOfProfits". Se tal cadeia não é encontrada, então ela deve ser colocada na posição imediatamente após "function_position".
Se o parâmetro de entrada only_ontester==true, significa que devemos executar a pesquisa da função Ontester(). Uma vez localizado, encontramos nele as cadeias de caracteres para acessar à biblioteca de análise gráfica, a função FindFunctionOnTester() responde por isto.
4. Cópia do Expert Advisor na pasta de terminais satélite
Cópia dos EAs é realizada na função OnInit():
//--- parsing advisor file if(!ParsingEA()) return(INIT_FAILED); //--- copying an expert in the terminal folders ResetLastError(); if(!CopyFileW(edited_expert,slaveTerminalDataPath1+"\\MQL5\\Experts\\"+expert_short_name+".mq5",false)) { PrintFormat("Failed CopyFileW #1 with error: %x",kernel32::GetLastError()); return(INIT_FAILED); } if(!CopyFileW(edited_expert,slaveTerminalDataPath2+"\\MQL5\\Experts\\"+expert_short_name+".mq5",false)) { PrintFormat("Failed CopyFileW #2 with error: %x",kernel32::GetLastError()); return(INIT_FAILED); } if(!CopyFileW(edited_expert,slaveTerminalDataPath3+"\\MQL5\\Experts\\"+expert_short_name+".mq5",false)) { PrintFormat("Failed CopyFileW #3 with error: %x",kernel32::GetLastError()); return(INIT_FAILED); } if(!CopyFileW(edited_expert,slaveTerminalDataPath4+"\\MQL5\\Experts\\"+expert_short_name+".mq5",false)) { PrintFormat("Failed CopyFileW #4 with error: %x",kernel32::GetLastError()); return(INIT_FAILED); }
5. Execução de terminais satélite
Antes de iniciar os terminais satélites, por favor, verifique o seguinte: a conta de negociação no terminal principal -no qual é executado nosso Expert Advisor- deve estar em todos os terminais satélites. Além disso, em todos os terminais satélites devem ser autorizados os dll:
Se isso não for feito, o terminal satélite não poderá executar o Expert Advisor (lembre-se, em nosso EA, são usadas de forma muito ativa as chamadas Win API), e no Testador, na guia "Diário" aparecerá aproximadamente tal mensagem de erro:
2016.10.13 11:28:57 Core 1 2016.02.03 00:00:00 DLL loading is not allowed
Saiba mais sobre a função de sistema ShellExecuteW: ShellExecuteW. Entre as execuções de terminais, são feitas pausas, a inicialização direta é gerenciada pela função "LaunchSlaveTerminal".
if(!CopyFileW(edited_expert,slaveTerminalDataPath4+"\\MQL5\\Experts\\"+expert_short_name+".mq5",false)) { PrintFormat("Failed CopyFileW #4 with error: %x",kernel32::GetLastError()); return(INIT_FAILED); } //--- launching Slave Terminals Sleep(ExtSleeping); LaunchSlaveTerminal(ExtInstallationPathTerminal_1,common_data_path+"\\Files\\myconfiguration1.ini"); Sleep(ExtSleeping); LaunchSlaveTerminal(ExtInstallationPathTerminal_2,common_data_path+"\\Files\\myconfiguration2.ini"); Sleep(ExtSleeping); LaunchSlaveTerminal(ExtInstallationPathTerminal_3,common_data_path+"\\Files\\myconfiguration3.ini"); Sleep(ExtSleeping); LaunchSlaveTerminal(ExtInstallationPathTerminal_4,common_data_path+"\\Files\\myconfiguration4.ini"); } //--- return(INIT_SUCCEEDED); }
6. Relatório comparativo
Não foi em vão que colocamos tanto esforço para analisar de modo sintático o Expert Advisor selecionado: no código do EA selecionado, nós introduzimos a chamada de biblioteca de análise gráfica de posições em termos de abertura de posições (esta biblioteca foi descrita no artigo LifeHack para traders: otimização "silenciosa" ou traço da distribuição de negociações"). Graças ao código incorporado, no terminal satélite, cada Expert Advisor -após o teste- cria e abre automaticamente uma página html como a seguir:
Anteriormente, no arquivo de configuração ini, no bloco [Tester], nós registrávamos um parâmetro "Report" desse tipo:
Expert=D0E820_test
Symbol=GBPAUD
Period=PERIOD_H1
Deposit=100000
Leverage=1:100
Model=0
ExecutionMode=0
FromDate=2016.10.03
ToDate=2016.10.15
ForwardMode=0
Report=D0E820_test
ReplaceReport=1
Port=3000
ShutdownTerminal=0
Este é o nome do arquivo (D0E820_test.htm) no qual o terminal vai armazenar o relatório após o teste. Deste relatório (para cada terminal satélite) temos de ter os seguintes dados: nome do símbolo e período nos quais foi testado o EA, indicador a partir do bloco "back-teste" e gráfico de balanço. A partir dos terminais satélites será gerado o seguinte relatório comparativo:
Deixe-me lembrá-lo que os terminais satélites armazenas os relatórios de teste (neste caso, em formato htm) para o diretório raiz de seus diretórios de dados. Isso significa que nosso EA precisa executar terminais satélites, e, nesses diretórios, depois procurar periodicamente os arquivos de relatórios de teste. Uma vez que todos os quatro do relatórios são encontrados, é possível iniciar a formação do relatório comparativo comum.
Para começar, apresentamos o sinalizador "find_report", ele vai permitir que o Expert Advisor inicie a pesquisa de arquivos de relatório:
//---
string arr_path[][2];
bool find_report=false;
//+------------------------------------------------------------------+
//| Enumeration command to start the application |
//+------------------------------------------------------------------+
enum EnSWParam
e adicionamos a função OnTimer():
{
//--- create timer
EventSetTimer(9);
ArrayFree(arr_path);
find_report=false; // true - flag allows the search reports
if(!FindDataFolders(arr_path))
return(INIT_SUCCEEDED);
//| Timer function |
//+------------------------------------------------------------------+
void OnTimer()
{
//---
if(!find_report)
return;
}
Na função OnTimer(), vamos procurar o arquivo "expert_short_name"+".htm". A pesquisa será de nível único, quer dizer, somente na raiz do diretório de dados de cada um dos terminais satélites. esta tarefa será realizada pela função ListingFilesDirectory.mqh::FindFile().
Como a pesquisa é realizada fora da "área restrita", vamos usar Win API função FindFirstFileW. Leia mais sobre FindFirstFileW no artigo anterior:
- FindFirstFileW, FindNextFileW
- Exemplo de uso, FindFirstFileW, FindNextFileW
- Conferimos o conteúdo dos terminas ).
Neste código, nós comparamos o nome obtido do arquivo, e se ele coincidir com o definido, então retornamos true, tendo fechado o manipulador de pesquisa:
//| Find file |
//+------------------------------------------------------------------+
bool FindFile(const string path,const string name)
{
//---
WIN32_FIND_DATA ffd;
long hFirstFind_0;
ArrayInitialize(ffd.cFileName,0);
ArrayInitialize(ffd.cAlternateFileName,0);
//--- stage Search №0.
string filter_0=path+"\\*.*"; // filter_0==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\*.*
hFirstFind_0=FindFirstFileW(filter_0,ffd);
//---
string str_handle="";
if(hFirstFind_0==INVALID_HANDLE)
str_handle="INVALID_HANDLE";
else
str_handle=IntegerToString(hFirstFind_0);
//Print("filter_0: \"",filter_0,"\", handle hFirstFind_0: ",str_handle);
//---
if(hFirstFind_0==INVALID_HANDLE)
{
PrintFormat("Failed FindFirstFile (hFirstFind_0) with error: %x",kernel32::GetLastError());
return(false);
}
//--- list all the files in the directory with some info about them
bool rezult=0;
do
{
string name_0="";
for(int i=0;i<MAX_PATH;i++)
{
name_0+=ShortToString(ffd.cFileName[i]);
}
if(name_0==name)
{
WinAPI_FindClose(hFirstFind_0);
return(true);
}
ArrayInitialize(ffd.cFileName,0);
ArrayInitialize(ffd.cAlternateFileName,0);
ResetLastError();
rezult=WinAPI_FindNextFile(hFirstFind_0,ffd);
}
while(rezult!=0); //if(hFirstFind_1==INVALID_HANDLE), we appear here
if(kernel32::GetLastError()!=ERROR_NO_MORE_FILES)
PrintFormat("Failed FindNextFileW (hFirstFind_0) with error: %x",kernel32::GetLastError());
//else
// Print("filter_0: \"",filter_0,"\", handle hFirstFind_0: ",hFirstFind_0,", NO_MORE_FILES");
WinAPI_FindClose(hFirstFind_0);
//---
return(false);
}
O Expert Advisor verifica a presença de todos os quatro arquivos de relatórios nas pastas dos terminais satélites: isso será um sinal de que os terminais satélites finalizaram o teste.
Agora é preciso processar esta informação. Todos os quatro arquivos de relatórios e seus quatro arquivos gráficos — gráficos de balanço — são copiados na área restrita TERMINAL_COMMONDATA_PATH\Files:
string path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);
if(!CopyFileW(slaveTerminalDataPath1+"\\"+expert_short_name+".htm",path+"\\Files\\"+"report_1"+".htm",false))
{
PrintFormat("Failed with error: %x",kernel32::GetLastError());
return;
}
if(!CopyFileW(slaveTerminalDataPath1+"\\"+expert_short_name+".png",path+"\\Files\\"+"report_1"+".png",false))
{
PrintFormat("Failed with error: %x",kernel32::GetLastError());
return;
}
if(!CopyFileW(slaveTerminalDataPath2+"\\"+expert_short_name+".htm",path+"\\Files\\"+"report_2"+".htm",false))
{
PrintFormat("Failed with error: %x",kernel32::GetLastError());
return;
}
if(!CopyFileW(slaveTerminalDataPath2+"\\"+expert_short_name+".png",path+"\\Files\\"+"report_2"+".png",false))
{
PrintFormat("Failed with error: %x",kernel32::GetLastError());
return;
}
if(!CopyFileW(slaveTerminalDataPath3+"\\"+expert_short_name+".htm",path+"\\Files\\"+"report_3"+".htm",false))
{
PrintFormat("Failed with error: %x",kernel32::GetLastError());
return;
}
if(!CopyFileW(slaveTerminalDataPath3+"\\"+expert_short_name+".png",path+"\\Files\\"+"report_3"+".png",false))
{
PrintFormat("Failed with error: %x",kernel32::GetLastError());
return;
}
if(!CopyFileW(slaveTerminalDataPath4+"\\"+expert_short_name+".htm",path+"\\Files\\"+"report_4"+".htm",false))
{
PrintFormat("Failed with error: %x",kernel32::GetLastError());
return;
}
if(!CopyFileW(slaveTerminalDataPath4+"\\"+expert_short_name+".png",path+"\\Files\\"+"report_4"+".png",false))
{
PrintFormat("Failed with error: %x",kernel32::GetLastError());
return;
}
Porém nos arquivos obtidos de testes há muita informação redundante, e isso complica o uso de expressões regulares. Por isso, na função, Compare multiple tests.mq5::ParsingReportToArray são realizadas algumas manipulações cujo resultado leva os arquivos a ficarem aproximadamente da seguinte maneira:
Este arquivo pode ser facilmente "envenenado" com a expressão regular "(>)(.*?)(<)" — quer dizer, a pesquisa de quaisquer caracteres que se encontrem entre os caracteres ">" e "<", além disso, a quantidade desses caracteres começa do zero.
Os resultados das expressões regulares são colocados em quatro matrizes: arr_report_1, arr_report_2, arr_report_3 e arr_report_4. As informações a partir destas matrizes serão usadas para gerar o código do relatório comparativo final. Após criar o relatório final chamamos WinAPI função ShellExecuteW (mais informações sobre ShellExecuteW, consulte aqui) e executamos o navegador da web:
É aberta a página do navegador da web, onde é possível comparar os resultados dos testes do Expert Advisor imediatamente em quatro símbolos.
Conclusão
No artigo foi descrita uma outra variante de como avaliar os resultados dos testes de EAs em quatro símbolos. O teste nos quatro caracteres selecionados opera paralelamente em quatro terminais, e como resultado temos um quadro-resumo, que contém os resultados de todos estes testes.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2731
- 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