English Русский 中文 Español Deutsch 日本語
LifeHack para traders: relatório comparativo de vários testes

LifeHack para traders: relatório comparativo de vários testes

MetaTrader 5Exemplos | 7 dezembro 2016, 12:50
1 484 0
Vladimir Karputov
Vladimir Karputov

Conteúdo

 

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:

O esquema de trabalho é o seguinte:

  1. Determinamos o Expert Advisor a ser testado (Win API)
  2. 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)
  3. 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)
  4. copiamos os common.ini individuais por pastas de terminais (Win API)
  5. copiamos os common.ini individuais por pastas de terminais (Win API)
  6. Realizamos a análise sintática dos relatórios dos terminais satélite
  7. 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.

  1. Tanto no principal quanto nos satélite, deve ser iniciada a mesma conta de negociação.
  2. 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".
  3. 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:

inputs

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:

A escolha do Expert Advisor é realizada usando a caixa de diálogo "Abrir arquivo" (função GetOpenFileNameW):

open file 

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 cadeiaLeitura e gravação de todo o arquivo numa variável
  • ler cadeia por cadeia e procura uma correspondência com "[Common]" (não necessariamente à procura de algo simples de mais: é possível definir na busca algo do tipo "[Com" alguns_caracteres "]")
    • após encontrar, registramos na matriz as cadeias de caracteres localizadas
    • pesquisar a seguinte correspondência com "["
      • após encontrar, é preciso parar o registro na matriz, porque na matriz neste ponto já estará a toda seção "[Common]"
  • calcular todas as cadeias de caracteres numa variável de cadeia
  • pesquisar o padrão "[Common]" vários caracteres "]" (aqui também não necessariamente à procura de algo simples de mais:  é possível definir na busca algo do tipo "[Com" vários caracteres "]")
  • após encontrar, registrar as coincidências numa variável de cadeia

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 cadeiaLeitura 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 resultadosNeste 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 resultadosNo 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:

  1. busca da cadeia de caracteres "[Common]" (MQL5);
  2. após encontrar, registramos na matriz as cadeias de caracteres localizadas;
  3. 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:

3.1. Segredo №3 

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:

      if(!CopyCommonIni())
         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:

      //--- delete expert_short_name+".set" files
      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:

      DeleteFileW(slaveTerminalDataPath4+"\\MQL5\\Experts\\"+compiled_expert);
      //--- 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:

      DeleteFileW(slaveTerminalDataPath4+"\\"+file_report);
      //--- 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

3.2. Incorporamos "#include"

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:

variantesApropriado/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:

scheme

Anteriormente, no arquivo de configuração ini, no bloco [Tester], nós registrávamos um parâmetro "Report" desse tipo:

[Tester]
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:

report

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         slaveTerminalDataPath4=NULL;                                // the path to the Data Folder of the terminal #4
//---
string         arr_path[][2];
bool           find_report=false;
//+------------------------------------------------------------------+
//| Enumeration command to start the application                     |
//+------------------------------------------------------------------+
enum EnSWParam

e adicionamos a função OnTimer():

int OnInit()
  {
//--- 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: 

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:

//--- reports -> 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:

ShellExecuteW(hwnd,"open",path,NULL,NULL,SW_SHOWNORMAL);

É 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

Oscilador universal com interface gráfica do usuário Oscilador universal com interface gráfica do usuário
No artigo, descreve-se a criação de um indicador universal baseado em todos os osciladores do terminal com uma interface gráfica do usuário própria. Isto permite rápida e facilmente alterar os parâmetros de cada oscilador individual diretamente a partir da janela do gráfico (em vez de abrir a janela de opções), comparar seu desempenho e selecionar a melhor opção para uma tarefa específica.
Interfaces Gráficas X: O Controle Gráfico Padrão (build 4) Interfaces Gráficas X: O Controle Gráfico Padrão (build 4)
Desta vez, nós vamos discutir o controle gráfico padrão. Ele permitirá criar arrays de objetos gráficos com a possibilidade de sincronizar o deslocamento horizontal. Além disso, nós continuaremos a otimizar o código da biblioteca para reduzir o consumo de recursos do CPU.
Fundamentos Básicos da Programação: Variáveis Globais do Terminal  MetaTrader 5 Fundamentos Básicos da Programação: Variáveis Globais do Terminal MetaTrader 5
As variáveis globais do terminal possibilitam uma ferramenta essencial para o desenvolvimento de Expert Advisors sofisticados e confiáveis. Se você dominar as variáveis globais, você nunca mais vai querer desenvolver EAs no MQL5 sem elas.
MQL5 Programações Básicas: Arquivos MQL5 Programações Básicas: Arquivos
Este artigo de orientação prática se concentra em trabalhar com arquivos no MQL5. Ele oferece uma série de tarefas simples, o qual nos permite compreender os conceitos básicos e aprimorar suas habilidades.