English Русский 中文 Español Deutsch 日本語
LifeHack para traders: um back-test bem, e quatro melhor

LifeHack para traders: um back-test bem, e quatro melhor

MetaTrader 5Testador | 23 setembro 2016, 14:17
1 709 0
Vladimir Karputov
Vladimir Karputov

Antes do primeiro teste único, na mente de cada trader surge a mesma pergunta: "Qual dos quatro modos devo usar?" Cada um dos modos oferecidos tem suas vantagens e características, por isso tornamos o trabalho mais fácil, que dizer, executamos todos os modos usando apenas um botão! Este artigo mostra como ver simultaneamente todos os quatro gráficos de teste com ajuda da Win API e um magic pequeno.

Tabela de conteúdos


Introdução

O objetivo principal deste artigo é mostrar como, a partir de um terminal - chamado de Terminal principal -, executar o teste único do EA simultaneamente em quatro terminais que serão chamados de terminais satélite com designações #1, #2, #3 e #4; e lembre-se que não se trata de uma otimização, mas sim de um teste! Enquanto os testadores de estratégias, nos Terminais satélite, serão executados em vários modos de geração de ticks:

  • no terminal #1 — "Cada tick baseado em ticks reais";
  • no terminal #2 —  "Todos os ticks";
  • no terminal #3 — "OHLC em M1";
  • no terminal #4 — "Apenas preços de abertura."

Restrições importantes:

  1. O Terminal principal deve ser executado sem a chave /portable.
  2. É necessário ter como mínimo cinco terminais MetaTrader 5 instalados.
  3. A conta de negociação, no Terminal principal, deve ser executada pelo menos uma vez em cada um dos Terminais satélite. Isto é necessário porque o EA, do presente artigo, não transfere a senha, a partir da conta, através dos arquivos ini nos Terminais satélite. Em vez de uma senha, é transferido o número da conta de negociação, na qual é necessário executar o testador de estratégias. Esse número sempre coincide com o número da conta principal.
    Esse comportamento parece lógico, pois o teste do EA, em diferentes modos de geração de ticks, deve ser realizado na mesma conta de negociação.
  4. Antes de executar o EA, descarregue ao máximo o processador central: desligue jogos on-line, o Media Player e outros programas que usem intensivamente recursos. Caso contrário, um dos núcleos do processador pode ser bloqueado, levando à não-execução do teste nesse núcleo.

1. Princípios gerais

Co zanadto, to nie zdrowo Tudo em excesso faz mal à saúde (Provérbio polaco). 

Eu sempre prefiro usar os recursos padrão do software. No que respeita aos terminais de negociação MetaTrader 5, esta regra soa da seguinte maneira: "Nunca execute o terminal com a chave /portable, Nunca desabilite, no software, o controle de conta de usuário (UAC)". Nesta base, vai trabalhar, na pasta AppData, com os arquivos do Expert Advisor descrito.

Todas as capturas de tela no artigo demonstram o trabalho em um Windows 10 totalmente licenciado. Todo o código descrito neste trabalho será considerado precisamente na implementação para esse sistema.

O EA examinado, juntamente com os recursos MQL5, implementa extensamente dll:


Fig. 1. Dependências 

Em particular, são usadas as chamadas das seguintes funções Windows API:


2. Parâmetros de entrada


Fig. 3. Parâmetros de entrada 

Os caminhos folder of the MetaTrader#X installation são vias para as pastas de instalação de Terminais satélite. Ao definir o caminho no código mq5, é necessário registrar duas barras oblíquas. Também é muito importante colocar dupla barra invertida no final do caminho:

//--- input parameters                                 
input string   ExtInstallationPathTerminal_1="C:\\Program Files\\MetaTrader 5 1\\";    // folder of the MetaTrader#1 installation
input string   ExtInstallationPathTerminal_2="D:\\MetaTrader 5 2\\";                   // folder of the MetaTrader#2 installation
input string   ExtInstallationPathTerminal_3="D:\\MetaTrader 5 3\\";                   // folder of the MetaTrader#3 installation
input string   ExtInstallationPathTerminal_4="D:\\MetaTrader 5 4\\";                   // folder of the MetaTrader#4 installation
input string   ExtTerminalName="terminal64.exe";                                       // correct name of the file of the terminal

O nome do terminal "terminal64.exe" é atribuído para sistemas operacionais de 64 bits.

 

Vínculo entre a pasta de instalação e o diretório de dados na pasta AppData 

Ao executar o terminal da forma habitual ou com a chave /portable, o terminal irá emitir diferentes caminhos para a variável TERMINAL_DATA_PATH. Consideremos esta situação no exemplo do Terminal principal instalado na pasta "C:\Program Files\MetaTrader 5 1".

Quando o terminal principal é iniciado com a chave /portable, a partir deste terminal, a MQL vai emitir os seguintes resultados:

TERMINAL_PATH = C:\Program Files\MetaTrader 5
TERMINAL_DATA_PATH = C:\Program Files\MetaTrader 5
TERMINAL_COMMONDATA_PATH = C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common

Mas a resposta do mesmo terminal sem chave /portable será:

TERMINAL_PATH = C:\Program Files\MetaTrader 5
TERMINAL_DATA_PATH = C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962
TERMINAL_COMMONDATA_PATH = C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common

Isso será útil para nós, somente se recebermos parâmetros do terminal atual. E, então, o que fazer no caso dos terminais satélite, nos quais vamos começar a testar o EA? Como associar os caminhos de instalação dos terminais satélite com os caminhos de seus diretórios de dados?

Aqui é preciso explicar por que é tão importante saber o caminho para o diretório de dados na pasta AppData (referência do guia):

Começando com o MS Windows Vista, para os programas padrão instalados no diretório Program Files, é proibido armazenar dados no diretório de instalação. Todos os dados devem ser armazenados num diretório separado de dados de usuário do Windows.

Em outras palavras, nosso EA pode criar livremente e alterar os arquivos em pastas como esta: C:\Users\nome de usuário\AppData\Roaming\MetaQuotes\Terminal\identificador de terminal\MQL5\Files. Aqui o "identificador de terminal" é o ID do terminal principal.


3. Comparamos a pasta de instalação e a pasta AppData dos Terminais satélite

O EA executa os Terminais satélite indicando o arquivo de configuração. Além disso, para cada terminal é utilizado seu arquivo de configuração. Em cada arquivo de configuração, há uma indicação do fato de que, ao executar o terminal, é necessário iniciar o teste do EA definido. Os comandos respetivos são colocados na seção [Tester] do arquivo de configuração:

...
[Tester]
Expert=test
...

Como você pode ver, o caminho não foi especificado, o que significa que o Expert Advisor pode se encontrar apenas na "área restrita" MQL5. No exemplo do Terminal satélite 1, pode haver dois caminhos:

  1. C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\MQL5\Experts
  2. ou C:\Program Files\MetaTrader 5 1\MQL5\Experts

Eliminamos a opção №2, porque, de acordo com a política de segurança, começando com o Windows Vista, nós somos proibidos de escrever na pasta "Program Files". Fica a opção №1, e isso significa que para todos os terminais satélite, nós devemos fazer a correspondência de pastas de instalação e pastas no AppData. 

3.1. Segredo №1

Em cada diretório de dados há um arquivo "origin.txt". Como exemplo do Terminal satélite 1:


 

Fig. 4. arquivo origin.txt 

e conteúdo do arquivo origin.txt:

C:\Program Files\MetaTrader 5 1

Essa entrada no arquivo indica que na pasta "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962" foi criada pelo terminal, instalado em "C:\Program Files\MetaTrader 5 1".

3.2. FindFirstFileW, FindNextFileW

FindFirstFileW — realiza a busca do diretório para o arquivo ou subdiretório com um nome que corresponde ao nome especificado (ou com parte do nome, se estiverem sendo utilizados símbolos especiais).

HANDLE  FindFirstFileW(
   string           lpFileName,         //
   WIN32_FIND_DATA  &lpFindFileData     //
   ); 

Parâmetros

lpFileName

[in]  Diretório ou caminho e nome do arquivo, que pode incluir carateres curinga, por exemplo, asterisco (*) ou ponto de interrogação (?).

lpFindFileData

[in][out]  Ponteiro para a estrutura WIN32_FIND_DATA, que recebo informação sobre o arquivo ou diretório encontrado. 

valor de retorno

Se a função for realizada com sucesso, o valor de retorno será o manipulador de busca, usado na chamada subseqüente de FindNextFile or FindClose, enquanto o parâmetro lpFindFileData contém informações sobre o primeiro arquivo ou pasta encontrada.

Se a função falhar ou não conseguir encontrar um arquivo a partir da cadeia de caracteres de pesquisa, no parâmetro lpFileName, será retornado o valor INVALID_HANDLE_VALUE, e o conteúdo lpFindFileData será indefinido. Para obter informações adicionais sobre o erro, chame a função GetLastError.

Se a função não se executar, porque não podem ser localizados os arquivos respetivos, a função GetLastError retornará ERROR_FILE_NOT_FOUND.


FindNextFileWcontinua a pesquisa do arquivo a partir da chamada anterior da função FindFirstFileFindFirstFileEx, ou FindFirstFileTransacted.

bool  FindNextFileW(
   HANDLE           FindFile,           //
   WIN32_FIND_DATA  &lpFindFileData     //
   );

Parâmetros

FindFile

[in] Manipulador de busca retornado pela chamada anterior da função FindFirstFile ou FindFirstFileEx.

lpFindFileData

[in][out]  Ponteiro para a estrutura WIN32_FIND_DATA, que recebo informação sobre o arquivo ou diretório encontrado. 

valor de retorno

Se a função se concluir com sucesso, o valor de retorno não será igual a zero, enquanto o parâmetro lpFindFileData conterá as informações sobre o seguinte arquivo ou o encontrado no diretório.

Se a função for concluída malsucedida, o valor de retorno será igual a zero, enquanto o conteúdo  lpFindFileData será indefinido. Para obter informações adicionais sobre o erro, chame a função  GetLastError.

Se a função falhar, porque não consegue encontrar os arquivos, a função GetLastError retornará ERROR_NO_MORE_FILES.

Exemplo de declaração de funções FindFirstFileW e FindNextFileWda Win API (o código foi tomado do arquivo ListingFilesDirectory.mqh):

#define MAX_PATH                 0x00000104  //
#define FILE_ATTRIBUTE_DIRECTORY 0x00000010  //
#define ERROR_NO_MORE_FILES      0x00000012  //there are no more files
#define ERROR_FILE_NOT_FOUND     0x00000002  //the system cannot find the file specified
//+------------------------------------------------------------------+
//| FILETIME structure                                               |
//+------------------------------------------------------------------+
struct FILETIME
  {
   uint              dwLowDateTime;
   uint              dwHighDateTime;
  };
//+------------------------------------------------------------------+
//| WIN32_FIND_DATA structure                                        |
//+------------------------------------------------------------------+
struct WIN32_FIND_DATA
  {
   uint              dwFileAttributes;
   FILETIME          ftCreationTime;
   FILETIME          ftLastAccessTime;
   FILETIME          ftLastWriteTime;
   uint              nFileSizeHigh;
   uint              nFileSizeLow;
   uint              dwReserved0;
   uint              dwReserved1;
   ushort            cFileName[MAX_PATH];
   ushort            cAlternateFileName[14];
  };

#import "kernel32.dll"
int      GetLastError();
long     FindFirstFileW(string lpFileName,WIN32_FIND_DATA  &lpFindFileData);
int      FindNextFileW(long FindFile,WIN32_FIND_DATA &lpFindFileData);
int      FindClose(long hFindFile);
int      FindNextFileW(int FindFile,WIN32_FIND_DATA &lpFindFileData);
int      FindClose(int hFindFile);
int      CopyFileW(string lpExistingFileName,string lpNewFileName,bool bFailIfExists);
#import

bool WinAPI_FindClose(long hFindFile)
  {
   bool res;
   if(_IsX64)
      res=FindClose(hFindFile)!=0;      
   else
      res=FindClose((int)hFindFile)!=0;      
//---
   return(res);
  }
  
bool WinAPI_FindNextFile(long hFindFile,WIN32_FIND_DATA &lpFindFileData)
  {
   bool res;
   if(_IsX64)
      res=FindNextFileW(hFindFile,lpFindFileData)!=0;      
   else
      res=FindNextFileW((int)hFindFile,lpFindFileData)!=0;      
//---
   return(res);
  }

3.3. Exemplo de uso FindFirstFileW, FindNextFileW

O script "ListingFilesDirectory.mq5" é tanto um em exemplo quanto quase uma cópia completa do código de trabalho do Expert Advisor. Em outras palavras, esse código é tão próximo da realidade.

Objetivo: obter os nomes de todas as pastas para o caminho TERMINAL_COMMONDATA_PATH — "Common". 

Por exemplo, em meu computador, o caminho TERMINAL_COMMONDATA_PATH retorna o valor "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common". Quer dizer, se cortar a partir de "Common", obteremos o caminho desejado "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\":


Fig. 5. Find First 

Normalmente, para busca de todos os arquivos, é usada a máscara de busca "*.*". Quer dizer que devemos realizar duas operações com as seguintes cadeia de caracteres: cortar a palavra "Common", e depois adicionar a máscara "*.*":

   string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);
   int pos=StringFind(common_data_path,"Common",0);
   if(pos!=-1)
     {
      common_data_path=StringSubstr(common_data_path,0,pos-1);
     }
   else
      return;

   string path_addition="\\*.*";
   string mask_path=common_data_path+path_addition;
   printf("mask_path=%s",mask_path);

Verificamos qual caminho, no final, será obtido. Para isso, definimos o ponto de interrupção e executamos a depuração


 

Fig. 6. Depuração 

Obtemos:

mask_path=C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*

Até agora tudo está correto: está pronta a máscara de busca de TODOS arquivos e pastas no diretório "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\".

Continuamos: inicializamos o manipulador de busca "hFind" (em meu caso, ele é chamado, talvez, por hábito) e chamamos a função FindFirstFileW da Win API:

   printf("mask_path=%s",mask_path);
   hFind=-100;
   hFind=FindFirstFileW(mask_path,ffd);
   if(hFind==INVALID_HANDLE)
     {
      PrintFormat("Failed FindFirstFile (hFind) with error: %x",kernel32::GetLastError());
      return;
     }

// List all the files in the directory with some info about them

Se a chamada do FindFirstFileW falhar, o manipulado de busca "hFind" será igual a "INVALID_HANDLE" e será concluída a execução do script.

Se a chamada da função FindFirstFileW for bem-sucedida, organizamos o ciclo do while, no qual vamos obter o nome do arquivo ou pasta, enquanto no final do ciclo vai ser chamada a a função FindNextFileW da Win API:

// List all the files in the directory with some info about them
   PrintFormat("hFind=%d",hFind);
   bool rezult=0;
   do
     {
      string name="";
      for(int i=0;i<MAX_PATH;i++)
        {
         name+=ShortToString(ffd.cFileName[i]);
        }
      
      Print("\"",name,"\", File Attribute Constants (dec): ",ffd.dwFileAttributes);
      //---
      ArrayInitialize(ffd.cFileName,0);
      ArrayInitialize(ffd.cAlternateFileName,0);
      ffd.dwFileAttributes=-100;
      ResetLastError();
      rezult=WinAPI_FindNextFile(hFind,ffd);
     }
   while(rezult!=0);
   if(kernel32::GetLastError()!=ERROR_NO_MORE_FILES)
      PrintFormat("Failed FindNextFileW (hFind) with error: %x",kernel32::GetLastError());
   WinAPI_FindClose(hFind);

O ciclo do while continuará até que a chamada da função FindNextFileW da Win API retornar um valor diferente de zero. Se a chamada da função FindNextFileW da Win API retornar zero e um erro diferente de "ERROR_NO_MORE_FILES", acontecerá um erro crítico.

No final do trabalho do script, fechamos o manipulador de busca

Resultados do trabalho do script "ListingFilesDirectory.mq5":

mask_path=C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*
hFind=-847293552
".", File Attribute Constants (dec): 16
"..", File Attribute Constants (dec): 16
"038C9E8FAFF9EA373522ECC6D5159962", File Attribute Constants (dec): 16
"0C46DDCEB43080B0EC647E0C66170465", File Attribute Constants (dec): 16
"2A6A33B25AA0984C6AB9D7F28665B88E", File Attribute Constants (dec): 16
"50CA3DFB510CC5A8F28B48D1BF2A5702", File Attribute Constants (dec): 16
"BC11041F9347CD71C5F8926F53AA908A", File Attribute Constants (dec): 16
"Common", File Attribute Constants (dec): 16
"Community", File Attribute Constants (dec): 16
"D0E8209F77C8CF37AD8BF550E51FF075", File Attribute Constants (dec): 16
"D3852169A6E781B7F35488A051432620", File Attribute Constants (dec): 16
"EE57F715BA53F2E183D6731C9376293D", File Attribute Constants (dec): 16
"Help", File Attribute Constants (dec): 16

3.4. Conferimos o conteúdo das pastas dos terminais

O exemplo acima mostra que o trabalho na camada superior está na pasta "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\". as estamos atentos da seção 3.1. Segredo №1, de acordo com o qual, temos de olhar para todas as subpastas.

Para fazer isso, vamos organizar uma pesquisa de duas camadas; para busca nas subpastas, é preciso utilizar a masxa da pesquisa primária para a função FindFirstFileW da Win API:

"C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\" + nome da pasta encontrada da camada superior + "origin.txt".

Assim, a pesquisa primária FindFirstFileW na subpasta irá pesquisar apenas um arquivo, isto é, "origin.txt".

Apresentamos uma lista completa da função FindDataPath():

//+------------------------------------------------------------------+
//| Find and read the origin.txt                                     |
//+------------------------------------------------------------------+
void FindDataPath(string &array[][2])
  {
//---
   WIN32_FIND_DATA ffd;
   long            hFirstFind_0,hFirstFind_1;

   ArrayInitialize(ffd.cFileName,0);
   ArrayInitialize(ffd.cAlternateFileName,0);
//+------------------------------------------------------------------+
//| Get common path for all of the terminals installed on a computer.|
//| The common path on my computer:                                  |
//| C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common          |
//+------------------------------------------------------------------+
   string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);
   int pos=StringFind(common_data_path,"Common",0);
   if(pos!=-1)
     {
      //+------------------------------------------------------------------+
      //| Cuts "Common" ... and we get:                                    |
      //| C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal                 |
      //+------------------------------------------------------------------+
      common_data_path=StringSubstr(common_data_path,0,pos-1);
     }
   else
      return;

//--- stage Search №0. 
   string filter_0=common_data_path+"\\*.*"; // filter_0==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*

   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;
     }

//--- list all the files in the directory with some info about them
   bool rezult=0;
   do
     {
      if((ffd.dwFileAttributes  &FILE_ATTRIBUTE_DIRECTORY)==FILE_ATTRIBUTE_DIRECTORY)
        {
         string name_0="";
         for(int i=0;i<MAX_PATH;i++)
           {
            name_0+=ShortToString(ffd.cFileName[i]);
           }
         if(name_0!="." && name_0!="..")
           {
            ArrayInitialize(ffd.cFileName,0);
            ArrayInitialize(ffd.cAlternateFileName,0);
            //--- stage Search №1. search origin.txt file in the folder
            string filter_1=common_data_path+"\\"+name_0+"\\origin.txt";
            ResetLastError();
            hFirstFind_1=FindFirstFileW(filter_1,ffd);
            //---
            if(hFirstFind_1==INVALID_HANDLE)
               str_handle="INVALID_HANDLE";
            else
               str_handle=IntegerToString(hFirstFind_1);
            Print("   filter_1: \"",filter_1,"\", handle hFirstFind_1: ",str_handle);
            //---
            if(hFirstFind_1==INVALID_HANDLE)
              {
               if(kernel32::GetLastError()!=ERROR_FILE_NOT_FOUND)
                 {
                  PrintFormat("Failed FindFirstFile (hFirstFind_1) with error: %x",kernel32::GetLastError());
                  break;
                 }
               WinAPI_FindClose(hFirstFind_1);
               ArrayInitialize(ffd.cFileName,0);
               ArrayInitialize(ffd.cAlternateFileName,0);
               ResetLastError();
               rezult=WinAPI_FindNextFile(hFirstFind_0,ffd);
               continue;
              }
            //--- origin.txt file in this folder is found
            bool rezultTwo=0;
            string name_1="";
            for(int i=0;i<MAX_PATH;i++)
              {
               name_1+=ShortToString(ffd.cFileName[i]);
              }
            string origin=CopiedAndReadFile(filter_1); //--- receiving a string of the file found origin.txt
            if(origin!=NULL)
              {
               //--- write a string into an array
               int size=ArrayRange(array,0);
               ArrayResize(array,size+1,0);
               array[size][0]=common_data_path+"\\"+name_0;
               //value array[][0]==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962
               array[size][1]=origin;
               //value array[][1]==C:\Program Files\MetaTrader 5 1\
              }
            WinAPI_FindClose(hFirstFind_1);
           }
        }
      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);
  }

A função FindDataPath() imprime mais o menos as seguintes informações:

filter_0: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*", handle hFirstFind_0: 1901014212592
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\origin.txt", handle hFirstFind_1: 1901014213744
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\0C46DDCEB43080B0EC647E0C66170465\origin.txt", handle hFirstFind_1: 1901014213840
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\2A6A33B25AA0984C6AB9D7F28665B88E\origin.txt", handle hFirstFind_1: INVALID_HANDLE
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\50CA3DFB510CC5A8F28B48D1BF2A5702\origin.txt", handle hFirstFind_1: 1901014218448
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\BC11041F9347CD71C5F8926F53AA908A\origin.txt", handle hFirstFind_1: 1901014213936
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common\origin.txt", handle hFirstFind_1: INVALID_HANDLE
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Community\origin.txt", handle hFirstFind_1: INVALID_HANDLE
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\origin.txt", handle hFirstFind_1: 1901014216720
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D3852169A6E781B7F35488A051432620\origin.txt", handle hFirstFind_1: 1901014217104
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\EE57F715BA53F2E183D6731C9376293D\origin.txt", handle hFirstFind_1: 1901014218640
   filter_1: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Help\origin.txt", handle hFirstFind_1: INVALID_HANDLE
filter_0: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*", handle hFirstFind_0: 1901014212592, NO_MORE_FILES 

Explicação das primeiras linhas de impressão: primeiro é criado o filtro "filter_0" da pesquisa primária (filtro igual "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\*.*") e obtemos o manipulador da pesquisa primária "hFirstFind_0", igual 1901014212592. Como o valor "hFirstFind_0" não é igual a "INVALID_HANDLE", então, o filtro "filter_0" de busca primária, transferida para a função FindFirstFileW(filter_0,ffd) da Win API, é correto. Após uma chamada bem-sucedida do FindFirstFileW(filter_0,ffd) obtemos o nome da primeira pasta localizada: a pasta "038C9E8FAFF9EA373522ECC6D5159962". 

Em seguida, é necessário realizar a pesquisa do arquivo origin.txt" dentro da pasta 038C9E8FAFF9EA373522ECC6D5159962. Para fazer isso, formamos a máscara de filtro. Por exemplo, para a pasta 038C9E8FAFF9EA373522ECC6D5159962, esta máscara será: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\origin.txt". Se o manipulador for "hFirstFind_1" e não for igual a "INVALID_HANDLE", na pasta definina (038C9E8FAFF9EA373522ECC6D5159962) estará o arquivo desejado (origin.txt). 

Na impressão, pode ser visto claramente que a pesquisa primária em subpastas, por vezes, retorna "INVALID_HANDLE". Isto significa que o papkkah não tem o arquivo "origin.txt". 

Agora reparemos no que faremos quando o arquivo "origin.txt" for encontrado na subpasta.

3.5. CopyFileW

CopyFileW — copia o arquivo existente para um novo arquivo.

bool  CopyFileW(
   string lpExistingFileName,     //
   string lpNewFileName,          //
   bool bFailIfExists             //
   );

Parâmetros

lpExistingFileName

[in] Nome do arquivo existente.

Aqui é forçosamente aceite a limitação no comprimento do nome — MAX_PATH de carateres é suficiente para nosso exemplo.

Se o arquivo chamado lpExistingFileName não existir, a função falhará e GetLastError retornará ERROR_FILE_NOT_FOUND.

lpNewFileName

[in]  Nome do arquivo novo. 

Aqui é forçosamente aceite a limitação no comprimento do nome — MAX_PATH de carateres é suficiente para nosso exemplo.

bFailIfExists
[in] 
Se esse parâmetro for TRUE e um novo arquivo, indicado em lpNewFileName, existir, a função falhará. Se esse parâmetro for FALSE e o novo arquivo existir, a função substitui o arquivo existente e conclui o trabalho com sucesso.

valor de retorno

Se a função se concluir com sucesso, o valor de retorno não será igual a zero.

Se a função falhar, o valor de retorno será igual a zero. Para obter informações adicionais sobre o erro, chame a função  GetLastError.

Exemplo de declaração da função CopyFileW da Win API (o código foi tomado do arquivo ListingFilesDirectory.mqh):

#import "kernel32.dll"
int      GetLastError();
bool     CopyFileW(string lpExistingFileName,string lpNewFileName,bool bFailIfExists);
#import

3.6. Trabalhamos com o arquivo "origin.txt"

Descrição do trabalho da função ListingFilesDirectory.mqh::CopiedAndReadFile(string full_file_name).

No parâmetro de entrada, a função recebe o nome completo do arquivo "origin.txt", que foi encontrado em uma das subpastas. Este pode ser mais o menos o seguinte caminho: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\origin.txt". Vamos abrir o arquivo "origin.txt" e ler seu conteúdo usando a MQL5, o que significa que o arquivo deve estar na "área restrita". Então, precisamos copiar o arquivo "origin.txt" da subpasta para a área restrita (neste caso, a "área restrita" nos arquivos gerais de todos os terminais). Fazemos essa cópia usando a chamada da função CopyFileW da Win API.

Registramos a variável "new_path" caminho para o arquivo "origin.txt" na área restrita:

//+------------------------------------------------------------------+
//| Copying to the Common Data Folder                                |
//| for all client terminals ***\Terminal\Common\Files               |
//+------------------------------------------------------------------+
string CopiedAndReadFile(string full_file_name)
  {
   string new_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"\\Files\\origin.txt";
// => new_path==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common\Files\origin.txt
//--- Win API

e chamamos a função CopyFileW da Win API com um terceiro parâmetro igual a false — habilitamos a substituição do arquivo "origin.txt" na área restrita:

//--- Win API
   if(!CopyFileW(full_file_name,new_path,false))
     {
      Print("Error CopyFile ",full_file_name," to ",new_path);
      return(NULL);
     }
//--- open the file using MQL5

Usando os recursos MQL5 abrimos o arquivo "origin.txt" para leitura, além disso, devemos não esquecer indicar o sinalizador FILE_COMMON, pois o arquivo se encontra na pasta de arquivos comuns:

//--- open the file using MQL5
   string str;
   ResetLastError();
   int file_handle=FileOpen("origin.txt",FILE_READ|FILE_TXT|FILE_COMMON);
   if(file_handle!=INVALID_HANDLE)
     {
      //--- read a string using the MQL5 
      str=FileReadString(file_handle,-1)+"\\";
      //--- close the file using the MQL5
      FileClose(file_handle);
     }
   else
     {
      PrintFormat("File %s open failed , MQL5 error=%d","origin.txt",GetLastError());
      return(NULL);
     }
   return(str);
  }

Lemos apenas uma vez, uma cadeia de caracteres, escrevemos nela no final "\\" e retornamos o resultado obtido.

3.7. Último traço

Nos parâmetros de entrada do Expert Advisor, são especificados os caminhos para as pastas de instalação para quatro terminais:

//--- input parameters                                 
input string   ExtInstallationPathTerminal_1="C:\\Program Files\\MetaTrader 5 1\\";    // folder of the MetaTrader#1 installation
input string   ExtInstallationPathTerminal_2="D:\\MetaTrader 5 2\\";                   // folder of the MetaTrader#2 installation
input string   ExtInstallationPathTerminal_3="D:\\MetaTrader 5 3\\";                   // folder of the MetaTrader#3 installation
input string   ExtInstallationPathTerminal_4="D:\\MetaTrader 5 4\\";                   // folder of the MetaTrader#4 installation

Esses caminhos são registrados rigorosamente e devem indicar corretamente a pasta de instalação do terminal.

Também abaixo, na camada comum, quatro variáveis de cadeia e uma matriz são declaradas:

string         slaveTerminalDataPath1=NULL;                                // the path to the Data Folder of the terminal #1
string         slaveTerminalDataPath2=NULL;                                // the path to the Data Folder of the terminal #2
string         slaveTerminalDataPath3=NULL;                                // the path to the Data Folder of the terminal #3
string         slaveTerminalDataPath4=NULL;                                // the path to the Data Folder of the terminal #4
//---
string         arr_path[][2];

Vai ser necessário registrar, nestas variáveis, os caminhos para as pastas dos terminais no AppData, com ajuda do matriz bidimensional. Agora é possível fazer o esboço geral de como fazer corresponder as pastas de instalação dos Terminais satélite com suas pasatas no AppData:

GetStatsFromAccounts_EA.mq5::OnInit() >de chagadas> GetStatsFromAccounts_EA.mq5::FindDataFolders(arr_path) 
>de chamadas> ListingFilesDirectory.mqh::FindDataPath(string &array[][2]) >de chamadas> CopiedAndReadFile(string full_file_name) 

            string origin=CopiedAndReadFile(filter_1); //--- receiving a string of the file found origin.txt
            if(origin!=NULL)
              {
               //--- write a string into an array
               int size=ArrayRange(array,0);
               ArrayResize(array,size+1,0);
               array[size][0]=common_data_path+"\\"+name_0;
               //value array[][0]==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962
               array[size][1]=origin;
               //value array[][1]==C:\Program Files\MetaTrader 5 1\
              }
            FindClose(hFirstFind_1);

Na função ListingFilesDirectory.mqh::FindDataPath(string &array[][2]), quando, nas subpastas dos terminais, são localizados arquivos "origin.txt", é chamada a função CopiedAndReadFile(string full_file_name), e após sua chamada, é realizado o registro na matriz bidimensional. Na dimensão da matriz "0", escreve-se o caminho para a pasta do terminal no AppData, enquanto na dimensão da matriz "1", é escrito o caminho para a pasta de instalação (este caminho, deixe-me lembrá-lo, é obtido por nós a partir do arquivo encontrado "origin.txt").

>retornamos o controle no> GetStatsFromAccounts_EA.mq5::FindDataFolders(arr_path): 

Aqui, através de um simples rastreamento na matriz bidimensional, são preenchidas as variáveis slaveTerminalDataPath1, slaveTerminalDataPath2, slaveTerminalDataPath3 e slaveTerminalDataPath4:

   FindDataPath(array);
   for(int i=0;i<ArrayRange(array,0);i++)
     {
      //Print("array[",i,"][0]: ",array[i][0]);
      //Print("array[",i,"][1]: ",array[i][1]);
      if(StringCompare(ExtInstallationPathTerminal_1,array[i][1],true)==0)
         slaveTerminalDataPath1=array[i][0];
      if(StringCompare(ExtInstallationPathTerminal_2,array[i][1],true)==0)
         slaveTerminalDataPath2=array[i][0];
      if(StringCompare(ExtInstallationPathTerminal_3,array[i][1],true)==0)
         slaveTerminalDataPath3=array[i][0];
      if(StringCompare(ExtInstallationPathTerminal_4,array[i][1],true)==0)
         slaveTerminalDataPath4=array[i][0];
     }
   if(slaveTerminalDataPath1==NULL || slaveTerminalDataPath2==NULL ||
      slaveTerminalDataPath3==NULL || slaveTerminalDataPath4==NULL)
     {
      Print("slaveTerminalDataPath1 ",slaveTerminalDataPath1,", slaveTerminalDataPath2 ",slaveTerminalDataPath2);
      Print("slaveTerminalDataPath3 ",slaveTerminalDataPath3,", slaveTerminalDataPath4 ",slaveTerminalDataPath4);
      return(false);
     }

Se chegámos a esta fase, significa que o Expert Advisor fez a correspondência do caminho de instalação do terminal e o das pastas no AppData. Se, pelo menos, um caminho para a pasta do terminal no AppData não for encontrado (isto é, se for igual a NULL), todos os caminhos serão impressos nas últimas cadeias de caracteres e o Expert Advisor falhará.


4. Seleção do EA para o teste

Antes de executar os quatro Terminais satélite, é necessário, primeiro, selecionar o arquivo do EA de teste. Este EA dever pré-compilado e colocado no diretório de dados do Terminal principal.

4.1. GetOpenFileName

GetOpenFileName — cria a caixa de diálogo "Abrir", ela permite indicar a unidade, pasta e nome de arquivo ou conjunto de arquivo que serão abertos. A declaração e implementação da caixa de diálogo "Abrir" estão plenamente apresentados na arquivo anexo GetOpenFileNameW.mqh.

4.2. Selecionamos o Expert Advisor usando a caixa de diálogo de sistema "Abrir arquivo"

Caixa de diálogo de sistema "Abrir" é chamada a partir do OnInit() do Expert Advisor:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArrayFree(arr_path);
   if(!FindDataFolders(arr_path))
      return(INIT_SUCCEEDED);
//---
   if(MessageBox("Ready?",NULL,MB_YESNO)==IDYES)
     {
      expert_name=OpenFileName();
      if(expert_name==NULL)
         return(INIT_FAILED);
      //--- editing and copying of the ini-file in the folder of the terminals

onde acontece a chamada GetOpenFileNameW.mqh::OpenFileName(void)

//+------------------------------------------------------------------+
//| Creates an Open dialog box                                       |
//+------------------------------------------------------------------+
string OpenFileName(void)
  {
   string path=NULL;
   string filter=NULL;
   if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
      filter="Código compilado";
   else
      filter="Compiled code";
   if(GetOpenFileName(path,filter+"\0*.ex5\0",TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Experts\\","Select source file"))
      return(path);
   else
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return(NULL);
     }
  }

Se a chamada da função GetOpenFileName da Win API for bem-sucedida, a variável "path" irá conter o nome completo do arquivo selecionado, algo assim: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Experts\Examples\MACD\MACD Sample.ex5".

A variável "filter" responde pelo texto ① Fig. 2. A cadeia de caracteres "\0*.ex5\0" responde pelo filtro nos tipos de arquivos (② Fig. 2). A cadeia de caracteres "TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Experts\\"" define o caminho para a pasta que será aberta na caixa de diálogo de sistema "Abrir".

4.3. Arquivo de configuração ini

Para executar o terminal para teste do Expert Advisor a partir da linha de comando (ou usando a Win API), é necessário ter o arquivo de configuração ini, no qual devem estar presentes a seção [Tester] e as indicações necessárias:

[Tester]
Expert=test             //nome do arquivo do EA, que deve ser executado para teste
Symbol=EURUSD           //nome do instrumento que será usado como símbolo básico de teste.
Period=H1               //período do gráfico de teste
Deposit=10000           //montante do depósito inicial para teste
Model=4                 //modo de geração de ticks
Optimization=0          //habilitar/desabilitar a otimização e especificação de seu tipo
FromDate=2016.01.22     //data inicial do teste
ToDate=2016.06.06       //data de término do teste
Report=TesterReport     //nome do arquivo no qual será armazenado o relatório de resultados de teste
ReplaceReport=1         //habilitar/desabilitar re-escrever no arquivo de relatório 
UseLocal=1              //ativar/desativar a possibilidade de usar agentes locais para teste
Port=3000               //porta do agente de teste
Visual=0                //ativar ou desativar o teste no modo visual
ShutdownTerminal=0      //habilitar/desabilitar a plataforma de negociação ao finalizar o teste 

Olhando para o futuro, eu diria que vamos adicionar, ao arquivo, esta seção [Tester].

Foi decidido tomar como base o arquivo ini do terminal principal. Este arquivo (common.ini) está localizado no diretório de dados do terminal, na pasta "config ". Para o meu terminal, o caminho para ele tem a seguinte aparência: "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\config\common.ini".

Regime de trabalho com o arquivo ini:

  1. Obter o caminho completo para o "common.ini" do Terminal principal. O caminho completo é uma cadeia de caracteres de tipo 
    "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\config\common.ini". (MQL5)
  2. Obter novo caminho para o arquivo ini na área restrita "\Files". O caminho completo é uma cadeia de caracteres de tipo:
    "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Files\myconfiguration.ini" do Terminal principal. (MQL5)
  3. Cópia do arquivo "common.ini" no "myconfiguration.ini". (função CopyFileW da WIn API).
  4. Redação do arquivo "myconfiguration.ini". (MQL5).
  5. Obter novo caminho para o arquivo ini da área restrita do Terminal satélite. Isto é uma cadeia de caracteres de tipo (como exemplo, meu terminal №1)
    "C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\MQL5\Files\myconfiguration.ini". (MQL5)
  6. Copiar o arquivo ini modificado "myconfiguration.ini" a partir da área restrita do Terminal principal para a área restrita do Terminal satélite. (função CopyFileW da WIn API).
  7. Exclusão do arquivo "myconfiguration.ini" a partir da área restrita do Terminal principal. (MQL5)

Para cada Terminal satélite, é necessário repetir este esquema. Embora haja um lugar para a otimização, neste artigo, não focamos nesse processo. 

Editaremos os arquivos ini de configuração após ter selecionado o Expert Advisor para teste, GetStatsFromAccounts_EA.mq5::OnInit():

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArrayFree(arr_path);
   if(!FindDataFolders(arr_path))
      return(INIT_SUCCEEDED);
//---
   if(MessageBox("Ready?",NULL,MB_YESNO)==IDYES)
     {
      expert_name=OpenFileName();
      if(expert_name==NULL)
         return(INIT_FAILED);
      //--- editing and copying of the ini-file in the folder of the terminals
      if(!CopyCommonIni())
         return(INIT_FAILED);
      if(!CopyTerminalIni())
         return(INIT_FAILED);
      //--- copying an expert in the terminal folders

Regime de trabalho com o arquivo ini, no exemplo do Terminal satélite №1, GetStatsFromAccounts_EA.mq5::CopyCommonIni():

//+------------------------------------------------------------------+
//| Copying common.ini - file in a shared folder of client           |
//| terminals. Edit the ini-file and copy obtained                   |
//| ini-files into folders                                           |
//| ...\AppData\Roaming\MetaQuotes\Terminal\"id terminal"\MQL5\Files |
//+------------------------------------------------------------------+
bool CopyCommonIni()
  {
//0 — "Evey tick", "1 — 1 minute OHLC", 2 — "Open price only"
//3 — "Math calculations", 4 — "Every tick based on real ticks" 
//--- path to Data Folder
   string terminal_data_path=TerminalInfoString(TERMINAL_DATA_PATH);
//--- path to Commomm Data Folder
   string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);
//---
   string existing_file_name=terminal_data_path+"\\config\\common.ini"; // full path to the ini-file                                                        
   string temp_name_ini=terminal_data_path+"\\MQL5\\Files\\"+common_file_name;
   string test=NULL;
//--- terminal #1
   if(!CopyFileW(existing_file_name,temp_name_ini,false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return(false);
     }
   EditCommonIniFile(common_file_name,3000,4);
   test=slaveTerminalDataPath1+"\\MQL5\\Files\\"+common_file_name;
   if(!CopyFileW(temp_name_ini,test,false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return(false);
     }
   ResetLastError();
   if(!FileDelete(common_file_name,0))
      Print("#1 file ",common_file_name," not deleted, an error ",GetLastError());
//--- terminal #2

Para a chamada da função EditCommonIniFile(common_file_name,3000,4) é enviado:

common_file_name — nome do arquivo ini que você deseja editar.

3000 — nome da porta para o agente de teste. Cada terminal deve se executar no seu agente de teste. A numeração dos agentes é feita começando com o número 3000. Os números de porta dos agentes de teste podem ser vistos assim: no terminal MetaTrader 5, vá para o testador de estratégias e, depois, clique com botão direito do mouse na guia "histórico". Além disso, no menu drop-down você verá a numeração de portas dos agentes de teste:


 

Fig. 7. Agentes de teste 

4 - tipo de teste: 

  • 0 — "Todos os ticks",
  • 1 — "OHLC em M1",
  • 2 — "Apenas preços de abertura",
  • 3 — "Cálculos matemáticos",
  • 4 — "Cada tick baseado em ticks reais"

A edição do arquivo commom.ini é executado na função GetStatsFromAccounts_EA.mq5::EditCommonIniFile(string name,const int port,const int model) — as operações de abertura de arquivo, leitura a partir de arquivo e registro no arquivo são realizadas através dos recursos da MQL5:

//+------------------------------------------------------------------+
//| Editing common.ini file                                          |
//+------------------------------------------------------------------+
bool EditCommonIniFile(string name,const int port,const int model)
  {
   bool tester=false;      // if false - means the section [Tester] not found
   int  count_tester=0;    // counter discoveries section [Tester]
//--- abrimos o arquivo 
   ResetLastError();
   int file_handle=FileOpen(name,FILE_READ|FILE_WRITE|FILE_TXT);
   if(file_handle!=INVALID_HANDLE)
     {
      //--- auxiliary variable
      string str;
      //--- read data
      while(!FileIsEnding(file_handle))
        {
         //--- read line 
         str=FileReadString(file_handle,-1);
         //--- find [Tester]
         if(StringFind(str,"[Tester]",0)!=-1)
           {
            tester=true;
            count_tester++;
           }
        }
      if(!tester)
        {
         FileWriteString(file_handle,"[Tester]\n",-1);
         FileWriteString(file_handle,"Expert=test\n",-1);
         FileWriteString(file_handle,"Symbol=EURUSD\n",-1);
         FileWriteString(file_handle,"Period=H1\n",-1);
         FileWriteString(file_handle,"Deposit=10000\n",-1);
         //0 — "Evey tick", "1 — 1 minute OHLC", 2 — "Open price only"
         //3 — "Math calculations", 4 — "Every tick based on real ticks" 
         FileWriteString(file_handle,"Model="+IntegerToString(model)+"\n",-1);
         FileWriteString(file_handle,"Optimization=0\n",-1);
         FileWriteString(file_handle,"FromDate=2016.01.22\n",-1);
         FileWriteString(file_handle,"ToDate=2016.06.06\n",-1);
         FileWriteString(file_handle,"Report=TesterReport\n",-1);
         FileWriteString(file_handle,"ReplaceReport=1\n",-1);
         FileWriteString(file_handle,"UseLocal=1\n",-1);
         FileWriteString(file_handle,"Port="+IntegerToString(port)+"\n",-1);
         FileWriteString(file_handle,"Visual=0\n",-1);
         FileWriteString(file_handle,"ShutdownTerminal=0\n",-1);
        }
      //--- close file
      FileClose(file_handle);
     }
   else
     {
      PrintFormat("Unable to open file %s, error = %d",name,GetLastError());
      return(false);
     }
   return(true);
  }

4.4. Segredo №2

Antes de concluir o trabalho do terminal, a MetaTrader 5 armazena a localização de janelas e painéis, bem como suas dimensões no arquivo "terminal.ini". O próprio arquivo está localizado no diretório de dados do terminal, na subpasta "config". Por exemplo, para meu Terminal satélite №1, o caminho completo para "terminal.ini" será o seguinte:

"C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\config\terminal.ini".

No mesmo arquivo "terminal.ini", apenas o bloco "[Window]" será de nosso interesse. Minimizamos na janela do terminal MetaTrader 5. O terminal terá um tamanho de aproximadamente:


Fig. 8. Terminal minimizado na janela

Se este terminal for aberto, no arquivo terminal.ini, o bloco [Window] ficará assim:

Arrange=1
[Window]
Fullscreen=0
Type=1
Left=412
Top=65
Right=1212
Bottom=665
LSave=412

Ou seja, o bloco [Window] armazena as coordenadas do terminal e seu estado. 


4.5. Definimos o tamanho do terminal (largura, altura). Inserimos cadeias de caracteres no meio do arquivo 

É necessário alterar as coordenadas nos arquivos terminal.ini dos Terminais satélite, porque os quatro terminais satélite, durante sua execução, são alinhados assim:

 

Fig. 9. Localização do terminal

Como mencionado acima, é preciso editar o arquivo "terminal.imi" para cada Terminal satélite. Aqui é necessário chamar a atenção para o fato de que as cadeias de caracteres deverão ser inseridas no meio do arquivo "terminal.ini". Abaixo são mostradas as características deste procedimento.

Vou explicar usando este exemplo: temos o arquivo "test.txt" na "área restrita" do terminal. Conteúdo do arquivo "test.txt":

s=0
df=12
asf=3
g=3
n=0
param_f=123

É preciso alterar a informação na segunda e terceira cadeias de caracteres , para obter o seguinte:

s=0
df=1256
asf=5
g=3
n=0
param_f=123

No início, vale a pena fazer assim:

  • abrir o arquivo para leitura e registro, ler a primeira cadeia (esta operação move o ponteiro de arquivo para o início da segunda cadeia);
  • registrar na segunda cadeia o novo valor "df=1256";
  • registrar na terceira cadeia o novo valor "asf=5";
  • fechar o arquivo.
Olhemos no exemplo do código do script "InsertRowsMistakenly.mq5":
//+------------------------------------------------------------------+
//|                                         InsertRowsMistakenly.mq5 |
//|                              Copyright © 2016, Vladimir Karputov |
//|                                           http://wmua.ru/slesar/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2016, Vladimir Karputov"
#property link      "http://wmua.ru/slesar/"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- open file
   ResetLastError();
   string name="test.txt";
   int file_handle=FileOpen(name,FILE_READ|FILE_WRITE|FILE_TXT);
   if(file_handle!=INVALID_HANDLE)
     {
      FileReadString(file_handle,-1);
      FileWriteString(file_handle,"df=1256"+"\r\n",-1);
      FileWriteString(file_handle,"asf=5"+"\r\n",-1);
      //--- close file
      FileClose(file_handle);
     }
   else
     {
      PrintFormat("Unable to open file %s, error = %d",name,GetLastError());
      return;
     }
  }
//+------------------------------------------------------------------+

Obtemos um resultado inesperado, quer dizer, na quarta cadeia de caracteres, faltam os carateres "g=":

Foi Tornou-se 
s=0
df=12
asf=3
g=3
n=0
param_f=123
s=0
df=1256
asf=5
3
n=0
param_f=123

Por que aconteceu isso? Imagine que um arquivo é composto de muitas células que vêm uma após a outra. EM cada célula é colocado um caractere. Por isso, quando nós escrevemos no arquivo, começando a partir do meio, de fato, nós só estamos reescrevendo a célula. Se você adicionar mais caracteres do que havia originalmente neste lugar (como no exemplo acima: havia "df=12", e nós gravamos mais dois carateres, nomeadamente, "df = 1256), os caracteres extras simplesmente danificarão ainda mais o código. Assim ficaria:

write string

Fig. 10. Informações danificadas.

Para evitar danificar informações, ao inserir cadeia de caracteres no meio de um arquivo, procedemos do seguinte modo.

  • Copiamos a partir do Terminal satélite "terminal.ini" para a área restrita do Terminal principal com nome "terminal_ext.ini" (Win API CopyFileW).
  • Criamos, na área restrita do Terminal principal, o arquivo "terminal.ini", abrimo-lo no registro (MQL5).
  • Abrimos, na área restrita do Terminal principal, o arquivo "terminal_ext.ini" para leitura (MQL5).
  • Na área restrita do terminal: contamos as cadeias de caracteres a partir do "terminal_ext.ini" e registramo-las no arquivo "terminal.ini" (MQL5).
  • Uma vez que a cadeia contada é igual a "[Window]", registramos, no arquivo "terminal.ini", as novas coordenadas (seis cadeia de caracteres), enquanto no arquivo "terminal_ext.ini" deslocamos o ponteiro de arquivo seis cadeias de caracteres (MQL5).
  • Na área restrita do Terminal principal: contamos as cadeias de caracteres a partir do "terminal_ext.ini" e registramo-las no arquivo "terminal.ini", até encontrar o final do arquivo "terminal_ext.ini" (MQL5).
  • Na área restrita do Terminal principal: fechamos os arquivos "terminal.ini" e "terminal_ext.ini" (MQL5).
  • Copiamos a partir do Terminal principal "terminal.ini" para o terminal satélite, para o arquivo "terminal.ini" (Win API CopyFileW).
  • Na área restrita do terminal principal: excluímos todos os arquivo "terminal.ini" e "terminal_ext.ini" (MQL5).

Ordem de chamadas de funções:

GetStatsFromAccounts_EA.mq5::OnInit() >de chamadas> GetStatsFromAccounts_EA.mq5::CopyTerminalIni()

//+------------------------------------------------------------------+
//| Editing Files "terminal.ini"                                     |
//+------------------------------------------------------------------+
bool CopyTerminalIni()
  {
//--- path to the terminal data folder 
   string terminal_data_path=TerminalInfoString(TERMINAL_DATA_PATH);
//---
   string existing_file_name=NULL;
   string ext_ini=terminal_data_path+"\\MQL5\\Files\\terminal_ext.ini";
   string ini=terminal_data_path+"\\MQL5\\Files\\terminal.ini";
   int left=0;
   int top=0;
   int right=0;
   int bottom=0;
//---
   for(int i=1;i<5;i++)
     {
      switch(i)
        {
         case 1:
            existing_file_name=slaveTerminalDataPath1+"\\config\\terminal.ini";
            left=0; top=0; right=682; bottom=420;
            break;
         case 2:
            existing_file_name=slaveTerminalDataPath2+"\\config\\terminal.ini";
            left=682; top=0; right=1366; bottom=420;
            break;
         case 3:
            existing_file_name=slaveTerminalDataPath3+"\\config\\terminal.ini";
            left=0; top=738-413; right=682; bottom=738;
            break;
         case 4:
            existing_file_name=slaveTerminalDataPath4+"\\config\\terminal.ini";
            left=682; top=738-413; right=1366; bottom=738;
            break;
        }
      //---
      if(!CopyFileW(existing_file_name,ext_ini,false))
        {
         PrintFormat("Failed with error: %x",kernel32::GetLastError());
         return(false);
        }
      if(!EditTerminalIniFile("terminal_ext.ini",left,top,right,bottom))
         return(false);
      if(!CopyFileW(ini,existing_file_name,false))
        {
         PrintFormat("Failed with error: %x",kernel32::GetLastError());
         return(false);
        }
      ResetLastError();
      if(!FileDelete("terminal.ini",0))
         Print("#",i," file terminal.ini not deleted, an error ",GetLastError());
      ResetLastError();
      if(!FileDelete("terminal_ext.ini",0))
         Print("#",i," file terminal_ext.ini not deleted, an error ",GetLastError());
     }
//---
   return(true);
  }

 >de chamadas> GetStatsFromAccounts_EA.mq5::EditTerminalIniFile

//+------------------------------------------------------------------+
//| Editing terminal.ini file                                        |
//+------------------------------------------------------------------+
bool EditTerminalIniFile(string ext_name,const int Left=0,const int Top=0,const int Right=1366,const int Bottom=738)
  {
//--- creates and opens files
   string name="terminal.ini";
   ResetLastError();
   int terminal_ini_handle=FileOpen(name,FILE_WRITE|FILE_TXT);
   int terminal_ext_ini__handle=FileOpen(ext_name,FILE_READ|FILE_TXT);
   if(terminal_ini_handle==INVALID_HANDLE)
     {
      PrintFormat("Unable to open file %s, error = %d",name,GetLastError());
     }
   if(terminal_ext_ini__handle==INVALID_HANDLE)
     {
      PrintFormat("Unable to open file %s, error = %d",ext_name,GetLastError());
     }
   if(terminal_ini_handle==INVALID_HANDLE && terminal_ext_ini__handle==INVALID_HANDLE)
     {
      FileClose(terminal_ext_ini__handle);
      FileClose(terminal_ini_handle);
      return(false);
     }

//--- auxiliary variable
   string str=NULL;
//--- read data
   while(!FileIsEnding(terminal_ext_ini__handle))
     {
      //--- read line
      str=FileReadString(terminal_ext_ini__handle,-1);
      FileWriteString(terminal_ini_handle,str+"\r\n",-1);
      //--- find [Window]
      if(StringFind(str,"[Window]",0)!=-1)
        {
         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Fullscreen=0\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Type=1\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Left="+IntegerToString(Left)+"\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Top="+IntegerToString(Top)+"\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Right="+IntegerToString(Right)+"\r\n",-1);

         FileReadString(terminal_ext_ini__handle,-1);
         FileWriteString(terminal_ini_handle,"Bottom="+IntegerToString(Bottom)+"\r\n",-1);
        }
     }
//--- close files
   FileClose(terminal_ext_ini__handle);
   FileClose(terminal_ini_handle);
   return(true);
  }

Assim, são editados os arquivos "terminal.ini" nos Terminais satélite, permitindo executá-los como na Fig. 9. Além disso, é possível monitorar os gráficos de teste e comparar a precisão do teste em diferentes modos. 


5. Execução de Terminais satélite para teste

Até agora, temos tudo pronto para executar os Terminais satélite no modo de teste de Expert Advisor:

  • nós preparamos os arquivos de configuração "myconfiguration.ini" para todos os Terminais satélite;
  • nós editamos/modificamos os arquivos "terminal.ini" de todos os Terminais satélite;
  • nós sabemos o nome do Expert Advisor que realizará o teste.
Restam duas tarefas: copiar o Expert Advisor selecionado para a área restrita dos Terminais satélite e executar estes terminais.

5.1. Cópia do Expert Advisor na pasta de Terminais satélite

A cópia do Expert Advisor selecionado anteriormente (seu nome é armazenado na variável "expert_name") é realizada na OnInit():

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArrayFree(arr_path);
   if(!FindDataFolders(arr_path))
      return(INIT_SUCCEEDED);
//---
   if(MessageBox("Ready?",NULL,MB_YESNO)==IDYES)
     {
      expert_name=OpenFileName();
      if(expert_name==NULL)
         return(INIT_FAILED);
      //--- editing and copying of the ini-file in the folder of the terminals
      if(!CopyCommonIni())
         return(INIT_FAILED);
      if(!CopyTerminalIni())
         return(INIT_FAILED);
      //--- copying an expert in the terminal folders
      ResetLastError();
      if(!CopyFileW(expert_name,slaveTerminalDataPath1+"\\MQL5\\Experts\\test.ex5",false))
        {
         PrintFormat("Failed CopyFileW #1 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

      if(!CopyFileW(expert_name,slaveTerminalDataPath2+"\\MQL5\\Experts\\test.ex5",false))
        {
         PrintFormat("Failed CopyFileW #2 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

      if(!CopyFileW(expert_name,slaveTerminalDataPath3+"\\MQL5\\Experts\\test.ex5",false))
        {
         PrintFormat("Failed CopyFileW #3 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

      if(!CopyFileW(expert_name,slaveTerminalDataPath4+"\\MQL5\\Experts\\test.ex5",false))
        {
         PrintFormat("Failed CopyFileW #4 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }
      //---
      Sleep(sleeping);

5.2. ShellExecuteW

ShellExecuteW — executa a operação para o arquivo indicado.

//--- x64
long ShellExecuteW(
   long hwnd,               //
   string lpOperation,      //
   string lpFile,           //
   string lpParameters,     //
   string lpDirectory,      //
   int nShowCmd             //
   );
//--- x32
int ShellExecuteW(
   int hwnd,                //
   string lpOperation,      //
   string lpFile,           //
   string lpParameters,     //
   string lpDirectory,      //
   int nShowCmd             //
   );

Parâmetros

hwnd

[in] Manipulador de janela pai, usada para exibição da interface de usuário e mensagens de erro. Este valor deverá ser NULL, se a operação não estiver associada às janelas.

lpOperation

[in] Cadeias de caracteres com nomes de comandos, que definem a ação que será executada. O conjunto de comandos disponíveis depende de um arquivo ou pasta determinado. Como regra, estas ações estão disponíveis no menu de contexto de objeto. Geralmente, são usados os seguintes comandos:

"edit"

Executa o editor e abre o documento para edição. Se o lpFile não for arquivo do documento, a função não será executada.

"explore"

Abre a pasta especificada no lpFile.

"find"

Inicia uma pesquisa começando no diretório especificado em lpDirectory.

"open"

Abre um elemento especificado pelo parâmetro lpFile. Este elemento pode ser um arquivo ou uma pasta.

"print"

Imprime o arquivo especificado pelo lpFile. Se o lpFile não for arquivo do documento, a função falhará.

"NULL"

Se existir o nome do comando, ele será usado por padrão. Se não existir esse comando, será usado o comando "open". Se não for usado comando nenhum, o sistema usa o primeiro comando que está listado no registro.

lpFile 

[in] Cadeia de caracteres, que define o arquivo ou objeto, no qual é possível executar o comando. É enviado o nome completo (incluindo não nó o nome do arquivo mas também o caminho para ele). Observe que o objeto pode não suportar todos os comandos. Por exemplo, não todos os documentos suportam o comando "print". Se um caminho relativo é usado para o parâmetro lpDirectory, então não use o caminho relativo para o lpFile.

lpParameters

[in] Se o lpFile indicar o arquivo executável, esse parâmetro será a cadeia de caracteres que definirá os parâmetros que serão enviados para o aplicativo. O formato dessa cadeia de caracteres é determinado pelo nome do comando que deve ser executado. Se o lpFile indicar o nome do documento, lpParameters deverá ser NULL.

lpDirectory

[in] Cadeia de caracteres que define o diretório de trabalho. Se este valor for NULL, será usado o diretório de trabalho atual. Se um caminho relativo é definido no lpFile, então não use o caminho relativo para o lpDirectory.

nShowCmd

[in] Sinalizadores que determinam como o aplicativo deve ser exibido ao ser aberto. Se o lpFile definir o arquivo do documento, o sinalizador simplesmente será enviado para o aplicativo respetivo. Sinalizadores usados:

//+------------------------------------------------------------------+
//| Enumeration command to start the application                     |
//+------------------------------------------------------------------+
enum EnSWParam
  {
   //+------------------------------------------------------------------+
   //| Displays the window as a minimized window. This value is similar |
   //| to SW_SHOWMINIMIZED, except the window is not activated.         |
   //+------------------------------------------------------------------+
   SW_SHOWMINNOACTIVE=7,
   //+------------------------------------------------------------------+
   //| Activates and displays a window. If the window is minimized or   |
   //| maximized, the system restores it to its original size and       |
   //| position. An application should specify this flag when           |
   //| displaying the window for the first time.                        |
   //+------------------------------------------------------------------+
   SW_SHOWNORMAL=1,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it as a minimized window.      |
   //+------------------------------------------------------------------+
   SW_SHOWMINIMIZED=2,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it as a maximized window.      |
   //+------------------------------------------------------------------+
   SW_SHOWMAXIMIZED=3,
   //+------------------------------------------------------------------+
   //| Hides the window and activates another window.                   |
   //+------------------------------------------------------------------+
   SW_HIDE=0,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it in its current size         |
   //| and position.                                                    |
   //+------------------------------------------------------------------+
   SW_SHOW=5,
  };

valor de retorno

Se a função se concluir com sucesso, o valor de retorno será superior a 32.

Exemplo de declaração da função ShellExecuteW da Win API:

#import  "shell32.dll"
int  GetLastError();
//+------------------------------------------------------------------+
//| ShellExecute function                                            |
//| https://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx
//| Performs an operation on a specified file                        |
//+------------------------------------------------------------------+
//--- x64
long ShellExecuteW(long hwnd,string lpOperation,string lpFile,string lpParameters,string lpDirectory,int nShowCmd);
//--- x32
int ShellExecuteW(int hwnd,string lpOperation,string lpFile,string lpParameters,string lpDirectory,int nShowCmd);
#import
#import "kernel32.dll"

//+------------------------------------------------------------------+
//| Enumeration command to start the application                     |
//+------------------------------------------------------------------+
enum EnSWParam
  {
   //+------------------------------------------------------------------+
   //| Displays the window as a minimized window. This value is similar |
   //| to SW_SHOWMINIMIZED, except the window is not activated.         |
   //+------------------------------------------------------------------+
   SW_SHOWMINNOACTIVE=7,
   //+------------------------------------------------------------------+
   //| Activates and displays a window. If the window is minimized or   |
   //| maximized, the system restores it to its original size and       |
   //| position. An application should specify this flag when           |
   //| displaying the window for the first time.                        |
   //+------------------------------------------------------------------+
   SW_SHOWNORMAL=1,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it as a minimized window.      |
   //+------------------------------------------------------------------+
   SW_SHOWMINIMIZED=2,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it as a maximized window.      |
   //+------------------------------------------------------------------+
   SW_SHOWMAXIMIZED=3,
   //+------------------------------------------------------------------+
   //| Hides the window and activates another window.                   |
   //+------------------------------------------------------------------+
   SW_HIDE=0,
   //+------------------------------------------------------------------+
   //| Activates the window and displays it in its current size         |
   //| and position.                                                    |
   //+------------------------------------------------------------------+
   SW_SHOW=5,
  };

5.3. Execução de terminais

Os Terminais satélite são executados a partir da OnInit():

      //---
      Sleep(sleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_1,slaveTerminalDataPath1+"\\MQL5\\Files\\"+common_file_name);
      Sleep(sleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_2,slaveTerminalDataPath2+"\\MQL5\\Files\\"+common_file_name);
      Sleep(sleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_3,slaveTerminalDataPath3+"\\MQL5\\Files\\"+common_file_name);
      Sleep(sleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_4,slaveTerminalDataPath4+"\\MQL5\\Files\\"+common_file_name);
     }
//---
   return(INIT_SUCCEEDED);
  }

Além disso, entre execuções, o Expert Advisor espera "sleeping" milissegundos. Por padrão, o parâmetro "sleeping" é igual a 9000 (isto é, 9 segundos). Se ocorrer um erro de autorização de agentes nos Terminais satélite, aumente esse parâmetro. 

Os parâmetros enviados para a função da Win API (como exemplo, meu terminal №1) têm a seguinte aparência:

LaunchSlaveTerminal("C:\Program Files\MetaTrader 5 1\",
"C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\MQL5\Files\myconfiguration.ini");


6. Possíveis erros

Pode acontecer que quando um dos Terminais satélite é executado, o testador não pode se conectar ao agente de teste.

No testador, na guia "Diário" aparecerá mais o menos a seguinte entrada:

2016.07.15 15:10:48.327 Tester  EURUSD: history data begins from 2014.01.14 00:00
2016.07.15 15:10:49.212 Core 1  agent process started
2016.07.15 15:10:49.717 Core 1  connecting to 127.0.0.1:3002
2016.07.15 15:11:00.771 Core 1  tester agent authorization error
2016.07.15 15:11:01.417 Core 1  connection closed

Nos logs de agente, as entradas serão as seguintes:

2016.07.15 16:08:45.416 Startup MetaTester 5 x64 build 1368 (13 Jul 2016)
2016.07.15 16:08:45.612 Server  MetaTester 5 started on 127.0.0.1:3000
2016.07.15 16:08:45.612 Startup initialization finished
2016.07.15 16:09:36.811 Server  MetaTester 5 stopped
2016.07.15 16:09:38.422 Tester  shutdown tester machine

Em tais casos, é recomendável aumentar a pausa entre as execuções de terminais (variável "sleeping"), bem como descarregar todas as aplicações - que usem intensivamente recursos - que podem capturar, no seu uso, núcleos de processador.


Conclusão

A tarefa para executar o teste do Expert Advisor selecionado, em quatro modos de teste, tem sido realizada. Após executar o Expert Advisor, é possível observar simultaneamente como acontece o teste imediatamente em quatro terminais.

Também, no artigo, foi mostrado como chamar essas Win API de função:

  • CopyFileW — copia arquivos para a "área restrita" e a partir da "área restrita" MQL5.
  • FindClose — fecha os manipuladores de busca.
  • FindFirstFileW — procura o diretório o subdiretório do arquivo, cujo nome corresponde ao nome do arquivo indicado.
  • FindNextFileW — continua a procura do arquivo a partir da chamada anterior da função FindFirstFile.
  • GetOpenFileNameW — chama a caixa de diálogo de sistema para abertura do arquivo
  • ShellExecuteW — execução do aplicativo

 

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2552

Arquivos anexados |
2552.zip (14.18 KB)
Usando arquivos de texto para armazenar parâmetros de entrada dos Expert Advisors, indicadores e scripts Usando arquivos de texto para armazenar parâmetros de entrada dos Expert Advisors, indicadores e scripts
O artigo descreve a aplicação de arquivos de texto para armazenar objetos dinâmicos, arrays e outras variáveis, as quais são ​​utilizadas como propriedades dos Expert Advisors, indicadores e scripts. Os arquivos servem como um complemento útil para a funcionalidade das ferramentas padrão oferecidas pelas linguagens MQL.
Dicas para o trader: indicadores de saldo, drawdown, carregamento e ticks durante o teste Dicas para o trader: indicadores de saldo, drawdown, carregamento e ticks durante o teste
Como tornar o teste mais claro? A resposta é simples: no testador, você precisa usar um ou mais indicadores, a saber: os indicadores de ticks, saldo e eqüidade, drawdown e carga de depósito. Isso permitirá monitorar visualmente quer a natureza dos ticks, quer as alterações de saldo e eqüidade, quer o drawdown e a carga de depósito.
Aprimorando o Testador de Estratégia para Otimizar Indicadores Exclusivamente nos Exemplos dos Mercados Lateral e de Tendência Aprimorando o Testador de Estratégia para Otimizar Indicadores Exclusivamente nos Exemplos dos Mercados Lateral e de Tendência
É essencial detectar se um mercado é lateral ou se o mesmo não está para muitas estratégias. Usando o conhecido ADX, demonstraremos como podemos usar o Testador de Estratégia, tanto para otimizar esse indicador quanto ao nosso objetivo específico, como também podemos decidir se este indicador irá satisfazer as nossas necessidades quanto a variação média dos mercados lateral e de tendência, que são muito importantes para determinar os stops e os alvos dos mercados.
Como copiar sinais pelas suas regras usando um EA ? Como copiar sinais pelas suas regras usando um EA ?
Ao assinar um sinal, a seguinte situação pode ocorrer: sua conta de negociação tem uma alavancagem de 1:100, o provedor tem uma alavancagem de 1:500 e as negociações usam um lote mínimo, seus saldos são praticamente iguais - porém o coeficiente de cópia irá abranger somente de 10% a 15%. Este artigo descreve como aumentar a taxa de cópia em tais casos.