English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Guia prático do MQL5: Desenvolvendo um Consultor Especialista multi-moeda com um número ilimitado de parâmetros

Guia prático do MQL5: Desenvolvendo um Consultor Especialista multi-moeda com um número ilimitado de parâmetros

MetaTrader 5Exemplos | 24 março 2014, 09:17
1 546 0
Anatoli Kazharski
Anatoli Kazharski

Introdução

O Consultor Especialista multi-moeda considerado no arquivo anterior "Guia prático do MQL5: Consultor Especialista multi-moeda - Abordagem simples, organizada e rápida", pode ser muito útil se o número de símbolos e de parâmetros de estratégia de negociação utilizado for pequeno. No entanto, existe uma restrição quanto ao número de parâmetros de entrada de um Consultor Especialista no MQL5: ele não deve ser maior que 1024.

E embora este número seja muitas vezes suficiente, é muito inconveniente usar uma lista de parâmetros tão grande. Toda vez que uma mudança ou otimização dos parâmetros para um dado símbolo for solicitada, você terá que procurar parâmetros para aquele específico símbolo na longa lista de parâmetros.

Você pode ser familiarizar com as futuras restrições na seção de Entradas do terminal do cliente no tópico Ajuda.

Neste artigo, criaremos um padrão que utiliza um único conjunto de parâmetros para otimização do sistema de negociação, enquanto permite um número ilimitado de parâmetros. A lista de símbolos será criada no arquivo de texto padrão (*.txt). Os parâmetros de entrada para cada símbolo também serão armazenados nos arquivos.

É necessário ser mencionado aqui que o Consultor Especialista trabalhará em um símbolo no modo de operação normal, mas você será capaz de testá-lo no Examinador de Estratégia em uma variedade de símbolos selecionados (em cada símbolo separadamente).

De fato, seria ainda mais conveniente criar a lista de símbolos diretamente na janela de Observação do mercado (Market Watch), considerando que ela ainda permite salvar os conjuntos de símbolos já prontos. Poderíamos até fazer o Consultor Especialista obter a lista de símbolos na janela Market Watch diretamente do Examinador de Estratégia. Mas, infelizmente, até o momento não é possível acessar a janela Market Watch a partir do Examinador de Estratégia, então, teremos que criar a lista de símbolos manualmente com antecedência ou usar um script.


O desenvolvimento do Consultor Especialista

O Consultor Especialista multi-moeda destacado no artigo anterior "Guia prático do MQL5: Consultor Especialista multi-moeda - Abordagem simples, organizada e rápida" será considerado como modelo. Vamos primeiro decidir os parâmetros de entrada. Como mencionado acima, deixaremos apenas um conjunto de parâmetros. Abaixo está a lista dos parâmetros de entrada do Consultor Especialista:

//--- Input parameters of the Expert Advisor
sinput long                      MagicNumber          =777;      // Magic number
sinput int                       Deviation            =10;       // Slippage
sinput string                    delimeter_00=""; // --------------------------------
sinput int                       SymbolNumber         =1;        // Number of the tested symbol
sinput bool                      RewriteParameters    =false;    // Rewriting parameters
sinput ENUM_INPUTS_READING_MODE  ParametersReadingMode=FILE;     // Parameter reading mode
sinput string                    delimeter_01=""; // --------------------------------
input  int                       IndicatorPeriod      =5;        // Indicator period
input  double                    TakeProfit           =100;      // Take Profit
input  double                    StopLoss             =50;       // Stop Loss
input  double                    TrailingStop         =10;       // Trailing Stop
input  bool                      Reverse              =true;     // Position reversal
input  double                    Lot                  =0.1;      // Lot
input  double                    VolumeIncrease       =0.1;      // Position volume increase
input  double                    VolumeIncreaseStep   =10;       // Volume increase step

Apenas os parâmetros com o modificador de entrada será escrito em um arquivo. Além disso, devemos expandir os três novos parâmetros que nunca nos deparamos antes.

  • SymbolNumber - este parâmetro indica o número do símbolo a partir do arquivo que contém a lista de símbolos. Se for configurado como 0, todos os símbolos da lista serão testados. Se você for além da lista, o teste não será realizado.
  • RewriteParameters - se o valor deste parâmetro for configurado como verdadeiro, o arquivo com os parâmetros do símbolo especificado (número no parâmetro SymbolNumber) será reescrito utilizando os valores do parâmetro de entrada atual. Alternativamente, se for configurado como falso, os parâmetros serão lidos a partir de um arquivo.
  • ParametersReadingMode - este parâmetro indica o modo de leitura com relação aos parâmetros de entrada. O tipo do parâmetro é a enumeração personalizada ENUM_INPUTS_READING_MODE que oferece duas opções: ler a partir de um arquivo e usar os parâmetros atuais.

O código de enumeração ENUM_INPUTS_READING_MODE:

//+------------------------------------------------------------------+
//| Input parameter reading modes                                    |
//+------------------------------------------------------------------+
enum ENUM_INPUTS_READING_MODE
  {
   FILE             = 0, // File
   INPUT_PARAMETERS = 1  // Input parameters
  };

O número de símbolos foi previamente determinado utilizando a constante NUMBER_OF_SYMBOLS. Esse valor agora depende de modos diferentes, por isso iremos mudá-lo para a variável global SYMBOLS_COUNT:

//--- Number of traded symbols. It is calculated and depends on the testing mode and the number of symbols in the file
int SYMBOLS_COUNT=0;

Ao início do código do Consultor Especialista, declaramos uma outra constante, a TESTED_PARAMETERS_COUNT, que determina os tamanhos dos arranjos e o número de iterações do ciclo ao agir sobre os parâmetros de entrada:

//--- Number of tested/optimized parameters
#define TESTED_PARAMETERS_COUNT 8

O arquivo com a lista de símbolos, assim como a pasta do arquivo contendo os parâmetros para cada símbolo devem ser colocados na pasta comum do terminal para que possa ser acessada a nível de execução do programa tanto durante a operação normal do Consultor Especialista quanto durante seu teste.

Agora precisamos preparar os arranjos com os quais trabalharemos mais tarde. Todos os arranjos no Consultor Especialista multi-moeda do artigo anterior que modificaremos são estáticos, ou seja, com um tamanho predefinido dos elementos. Devemos torná-los todos dinâmicos, já que o tamanho agora dependerá do número de símbolos usados no arquivo e do modo de leitura do parâmetro de entrada. Também adicionaremos novos arranjos (destacados em amarelo) os quais são necessários para trabalhar com arquivos:

//--- Arrays of input parameters
string InputSymbols[];            // Symbol names
//---
int    InputIndicatorPeriod[];    // Indicator periods
double InputTakeProfit[];         // Take Profit values
double InputStopLoss[];           // Stop Loss values
double InputTrailingStop[];       // Trailing Stop values
bool   InputReverse[];            // Values of position reversal flags
double InputLot[];                // Lot values
double InputVolumeIncrease[];     // Position volume increases
double InputVolumeIncreaseStep[]; // Volume increase steps
//--- Array of handles for indicator agents
int spy_indicator_handles[];
//--- Array of signal indicator handles
int signal_indicator_handles[];
//--- Data arrays for checking trading conditions
struct PriceData
  {
   double            value[];
  };
PriceData open[];      // Opening price of the bar
PriceData high[];      // High price of the bar
PriceData low[];       // Low price of the bar
PriceData close[];     // Closing price of the bar
PriceData indicator[]; // Array of indicator values
//--- Arrays for getting the opening time of the current bar
struct Datetime
  {
   datetime          time[];
  };
Datetime lastbar_time[];
//--- Array for checking the new bar for each symbol
datetime new_bar[];
//--- Array of input parameter names for writing to the file
string input_parameters[TESTED_PARAMETERS_COUNT]=
  {
   "IndicatorPeriod",   // Indicator period
   "TakeProfit",        // Take Profit
   "StopLoss",          // Stop Loss
   "TrailingStop",      // Trailing Stop
   "Reverse",           // Position reversal
   "Lot",               // Lot
   "VolumeIncrease",    // Position volume increase
   "VolumeIncreaseStep" // Volume increase step
  };
//--- Array for untested symbols
string temporary_symbols[];
//--- Array for storing input parameters from the file of the symbol selected for testing or trading
double tested_parameters_from_file[];
//--- Array of input parameter values for writing to the file
double tested_parameters_values[TESTED_PARAMETERS_COUNT];

Então precisamos determinar os tamanhos dos arranjos e inicializá-los com os valores.

O arranjo dos valores do parâmetro de entrada criado acima será inicializado na função InitializeTestedParametersValues() que criaremos no arquivo InitializeArrays.mqh. O arranjo será usado na escrita dos valores do parâmetro de entrada no arquivo.

//+------------------------------------------------------------------+
//| Initializing the array of tested input parameters                |
//+------------------------------------------------------------------+
void InitializeTestedParametersValues()
  {
   tested_parameters_values[0]=IndicatorPeriod;
   tested_parameters_values[1]=TakeProfit;
   tested_parameters_values[2]=StopLoss;
   tested_parameters_values[3]=TrailingStop;
   tested_parameters_values[4]=Reverse;
   tested_parameters_values[5]=Lot;
   tested_parameters_values[6]=VolumeIncrease;
   tested_parameters_values[7]=VolumeIncreaseStep;
  }

Agora vamos considerar as operações do arquivo. Primeiro, crie uma outra biblioteca de função, FileFunctions.mqh, na pasta UnlimitedParametersEA\Include de seu Consultor Especialista. Essa biblioteca será usada para criar funções relacionadas com a leitura e escrita de dados em um arquivo. Inclua a mesma no arquivo principal do Consultor Especialista e em outros arquivos do projeto.

//---Include custom libraries
#include "Include\Enums.mqh"
#include "Include\InitializeArrays.mqh"
#include "Include\Errors.mqh"
#include "Include\FileFunctions.mqh"
#include "Include\TradeSignals.mqh"
#include "Include\TradeFunctions.mqh"
#include "Include\ToString.mqh"
#include "Include\Auxiliary.mqh"

Começamos com a criação de uma função para ler a lista de símbolos de um arquivo de texto - ReadSymbolsFromFile(). Este arquivo (vamos nomeá-lo TestedSymbols.txt) deve ser colocado na subpasta \Files da pasta comum do terminal do cliente MetaTrader 5. No meu caso será C:\ProgramData\MetaQuotes\Terminal\Commonmas você deve verificar cuidadosamente o caminho utilizando a constante padrão TERMINAL_COMMONDATA_PATH:

TerminalInfoString(TERMINAL_COMMONDATA_PATH)
Todos os arquivos personalizados devem ser colocados na subpasta \Files da pasta comum do terminal do cliente ou na \MQL5\Files\. As funções de arquivo podem apenas acessar essas pastas.

Para verificar a funcionalidade, adicione alguns símbolos ao arquivo de texto criado, separando cada um dos símbolos com uma quebra de linha ("\r\n"):

Fig. 1. Lista de símbolos no arquivo da pasta comum do terminal.

Fig. 1. Lista de símbolos no arquivo da pasta comum do terminal.

Além disso,vamos dar uma olhada no código da função ReadSymbolsFromFile():

//+------------------------------------------------------------------+
//| Returning the number of strings (symbols) in the file and        |
//| filling the temporary array of symbols temporary_symbols[]       |
//+------------------------------------------------------------------+
//--- When preparing the file, symbols in the list should be separated with a line break
int ReadSymbolsFromFile(string file_name)
  {
   int strings_count=0; // String counter
//--- Open the file for reading from the common folder of the terminal
   int file_handle=FileOpen(file_name,FILE_READ|FILE_ANSI|FILE_COMMON);
//--- If the file handle has been obtained
   if(file_handle!=INVALID_HANDLE)
     {
      ulong  offset =0; // Offset for determining the position of the file pointer
      string text ="";  // The read string will be written to this variable
      //--- Read until the current position of the file pointer reaches the end of the file or until the program is deleted
      while(!FileIsEnding(file_handle) || !IsStopped())
        {
         //--- Read until the end of the string or until the program is deleted
         while(!FileIsLineEnding(file_handle) || !IsStopped())
           {
            //--- Read the whole string
            text=FileReadString(file_handle);
            //--- Get the position of the pointer
            offset=FileTell(file_handle);
            //--- Go to the next string if this is not the end of the file
            //    For this purpose, increase the offset of the file pointer
            if(!FileIsEnding(file_handle))
               offset++;
            //--- Move it to the next string
            FileSeek(file_handle,offset,SEEK_SET);
            //--- If the string is not empty
            if(text!="")
              {
               //--- Increase the string counter
               strings_count++;
               //--- Increase the size of the array of strings,
               ArrayResize(temporary_symbols,strings_count);
               //--- Write the read string to the current index
               temporary_symbols[strings_count-1]=text;
              }
            //--- Exit the nested loop
            break;
           }
         //--- If this is the end of the file, terminate the main loop
         if(FileIsEnding(file_handle))
            break;
        }
      //--- Close the file
      FileClose(file_handle);
     }
//--- Return the number of strings in the file
   return(strings_count);
  }

Aqui, o arquivo de texto é lido linha por linha. O nome de cada instrumento financeiro que foi lido é escrito no arranjo temporário temporary_symbols[] criado anteriormente (a acessibilidade real do símbolo no servidor de negociação será verificada posteriormente). Ademais, ao final a função retorna o número das cadeias (strings) lidas, ou seja, o número de símbolos nos quais nosso Consultor Especialista será testado.

Consulte sempre a Referência do MQL5 para informações detalhadas sobre as funções e identificadores os quais você nunca se deparou antes. Os comentários fornecidos no código simplificarão o processo de aprendizagem.

Vamos voltar ao arquivo InitializeArrays.mqh onde criaremos a função InitializeInputSymbols() para preencher o arranjo InputSymbols[] com os nomes dos símbolos definidos anteriormente. Os iniciantes provavelmente considerarão isto muito complexo, por isso, forneci comentários detalhados para o código:

//+------------------------------------------------------------------+
//| Filling the InputSymbol[] array of symbols                       |
//+------------------------------------------------------------------+
void InitializeInputSymbols()
  {
   int    strings_count=0;    // Number of strings in the symbol file
   string checked_symbol="";  // To check the accessibility of the symbol on the trade server
//--- Get the number of symbols from the "TestedSymbols.txt" file
   strings_count=ReadSymbolsFromFile("TestedSymbols.txt");
//--- In optimization mode or in one of the two modes (testing or visualization), provided that the symbol NUMBER IS SPECIFIED
   if(IsOptimization() || ((IsTester() || IsVisualMode()) && SymbolNumber>0))
     {
      //--- Determine the symbol to be involved in parameter optimization
      for(int s=0; s<strings_count; s++)
        {
         //--- If the number specified in the parameters and the current loop index match
         if(s==SymbolNumber-1)
           {
            //--- Check whether the symbol is on the trade server
            if((checked_symbol=GetSymbolByName(temporary_symbols[s]))!="")
              {
               //--- Set the number of symbols
               SYMBOLS_COUNT=1;
               //--- Set the size of the array of symbols
               ArrayResize(InputSymbols,SYMBOLS_COUNT);
               //--- Write the symbol name
               InputSymbols[0]=checked_symbol;
              }
            //--- Exit
            return;
           }
        }
     }
//--- In testing or visualization mode, if you need to test ALL symbols from the list in the file
   if((IsTester() || IsVisualMode()) && SymbolNumber==0)
     {
      //--- Parameter reading mode: from the file
      if(ParametersReadingMode==FILE)
        {
         //--- Iterate over all symbols in the file
         for(int s=0; s<strings_count; s++)
           {
            //--- Check if the symbol is on the trade server
            if((checked_symbol=GetSymbolByName(temporary_symbols[s]))!="")
              {
               //--- Increase the symbol counter
               SYMBOLS_COUNT++;
               //--- Set the size of the array of symbols
               ArrayResize(InputSymbols,SYMBOLS_COUNT);
               //--- Write the symbol name
               InputSymbols[SYMBOLS_COUNT-1]=checked_symbol;
              }
           }
         //--- Exit
         return;
        }
      //--- Parameter reading mode: from input parameters of the Expert Advisor
      if(ParametersReadingMode==INPUT_PARAMETERS)
        {
         //--- Set the number of symbols
         SYMBOLS_COUNT=1;
         //--- Set the size of the array of symbols
         ArrayResize(InputSymbols,SYMBOLS_COUNT);
         //--- Write the current symbol name
         InputSymbols[0]=Symbol();
         //--- Exit
         return;
        }
     }
//--- In normal operation mode of the Expert Advisor, use the current chart symbol
   if(IsRealtime())
     {
      //--- Set the number of symbols
      SYMBOLS_COUNT=1;
      //--- Set the size of the array of symbols
      ArrayResize(InputSymbols,SYMBOLS_COUNT);
      //--- Write the symbol name
      InputSymbols[0]=Symbol();
     }
//---
  }

Uma vez determinado o tamanho do arranjo dos parâmetros de entrada pelo número de símbolos utilizados, você deve definir o tamanho de todos os outros arranjos dos parâmetros de entrada. Vamos implementar isso como uma função distinta - ResizeInputParametersArrays():

//+------------------------------------------------------------------+
//| Setting the new size for arrays of input parameters              |
//+------------------------------------------------------------------+
void ResizeInputParametersArrays()
  {
   ArrayResize(InputIndicatorPeriod,SYMBOLS_COUNT);
   ArrayResize(InputTakeProfit,SYMBOLS_COUNT);
   ArrayResize(InputStopLoss,SYMBOLS_COUNT);
   ArrayResize(InputTrailingStop,SYMBOLS_COUNT);
   ArrayResize(InputReverse,SYMBOLS_COUNT);
   ArrayResize(InputLot,SYMBOLS_COUNT);
   ArrayResize(InputVolumeIncrease,SYMBOLS_COUNT);
   ArrayResize(InputVolumeIncreaseStep,SYMBOLS_COUNT);
  }

Agora precisamos criar a funcionalidade que nos permitirá ler os valores do parâmetro de entrada para cada símbolo, assim como escrever estes valores do parâmetro em um arquivo separado (para cada símbolo) dependendo do modo de entrada selecionado e das configurações do Consultor Especialista. Isso será feito da mesma forma como lemos a lista de símbolos a partir do arquivo. Esta é uma tarefa complexa, então vamos dividí-la em vários procedimentos.

Primeiro, devemos aprender a ler os valores do parâmetro de entrada de um arquivo para um arranjo. O arranjo conterá valores do tipo duplo. Mais tarde converteremos alguns deles (período indicador e sinalização de reversão de posição) para os tipos int e bool, respectivamente. O identificador do arquivo aberto e o arranjo onde os valores do parâmetro do arquivo estão armazenados são passados para a função ReadInputParametersValuesFromFile():

//+----------------------------------------------------------------------+
//| Reading parameters from the file and storing them in the passed array|
//| Text file format: key=value
//+----------------------------------------------------------------------+
bool ReadInputParametersValuesFromFile(int handle,double &array[])
  {
   int    delimiter_position  =0;  // Number of the symbol position "=" in the string
   int    strings_count       =0;  // String counter
   string read_string         =""; // The read string
   ulong  offset              =0;  // Position of the pointer (offset in bytes)
//--- Move the file pointer to the beginning of the file
   FileSeek(handle,0,SEEK_SET);
//--- Read until the current position of the file pointer reaches the end of the file
   while(!FileIsEnding(handle))
     {
      //--- If the user deleted the program
      if(IsStopped())
         return(false);
      //--- Read until the end of the string
      while(!FileIsLineEnding(handle))
        {
         //--- If the user deleted the program
         if(IsStopped())
            return(false);
         //--- Read the string
         read_string=FileReadString(handle);
         //--- Get the index of the separator ("=") in the read string
         delimiter_position=StringFind(read_string,"=",0);
         //--- Get everything that follows the separator ("=") until the end of the string
         read_string=StringSubstr(read_string,delimiter_position+1);
         //--- Place the obtained value converted to the double type in the output array
         array[strings_count]=StringToDouble(read_string);
         //--- Get the current position of the file pointer
         offset=FileTell(handle);
         //--- If it's the end of the string
         if(FileIsLineEnding(handle))
           {
            //--- Go to the next string if it's not the end of the file
            if(!FileIsEnding(handle))
               //--- Increase the offset of the file pointer by 1 to go to the next string
               offset++;
            //--- Move the file pointer relative to the beginning of the file
            FileSeek(handle,offset,SEEK_SET);
            //--- Increase the string counter
            strings_count++;
            //--- Exit the nested loop for reading the string
            break;
           }
        }
      //--- If it's the end of the file, exit the main loop
      if(FileIsEnding(handle))
         break;
     }
//--- Return the fact of successful completion
   return(true);
  }

Quais identificadores de arquivo e arranjos passaremos para essa função? Vai depender dos símbolos com os quais trabalharemos após lê-los a partir do arquivo "TestedSymbols.txt". Cada símbolo corresponderá a um certo arquivo de texto contendo os valores do parâmetro de entrada. Existem dois cenários para se considerar:

  1. O arquivo existe e lemos os valores do parâmetro de entrada desse arquivo usando a função ReadInputParametersValuesFromFile() descrita acima.
  2. O arquivo não existe ou precisamos reescrever os valores do parâmetro de entrada existentes.

O formato do arquivo de texto (um arquivo .ini, embora você possa escolher qualquer outra extensão que considerar necessária) contendo os valores do parâmetro de entrada será simples:

input_parameter_name1=value
input_parameter_name2=value
....
input_parameter_nameN=value

Vamos combinar essa lógica em uma única função, aReadWriteInputParameters(), cujo código é fornecido a seguir:

//+------------------------------------------------------------------+
//| Reading/writing input parameters from/to a file for a symbol     |
//+------------------------------------------------------------------+
void ReadWriteInputParameters(int symbol_number,string path)
  {
   string file_name=path+InputSymbols[symbol_number]+".ini"; // File name
//---
   Print("Find the file '"+file_name+"' ...");
//--- Open the file with input parameters of the symbol
   int file_handle_read=FileOpen(file_name,FILE_READ|FILE_ANSI|FILE_COMMON);
   
//--- Scenario #1: the file exists and parameter values do not need to be rewritten
   if(file_handle_read!=INVALID_HANDLE && !RewriteParameters)
     {
      Print("The file '"+InputSymbols[symbol_number]+".ini' exists, reading...");
      //--- Set the array size
      ArrayResize(tested_parameters_from_file,TESTED_PARAMETERS_COUNT);
      //--- Fill the array with file values
      ReadInputParametersValuesFromFile(file_handle_read,tested_parameters_from_file);
      //--- If the array size is correct
      if(ArraySize(tested_parameters_from_file)==TESTED_PARAMETERS_COUNT)
        {
         //--- Write parameter values to arrays
         InputIndicatorPeriod[symbol_number]    =(int)tested_parameters_from_file[0];
         Print("InputIndicatorPeriod[symbol_number] = "+(string)InputIndicatorPeriod[symbol_number]);
         InputTakeProfit[symbol_number]         =tested_parameters_from_file[1];
         InputStopLoss[symbol_number]           =tested_parameters_from_file[2];
         InputTrailingStop[symbol_number]       =tested_parameters_from_file[3];
         InputReverse[symbol_number]            =(bool)tested_parameters_from_file[4];
         InputLot[symbol_number]                =tested_parameters_from_file[5];
         InputVolumeIncrease[symbol_number]     =tested_parameters_from_file[6];
         InputVolumeIncreaseStep[symbol_number] =tested_parameters_from_file[7];
        }
      //--- Close the file and exit
      FileClose(file_handle_read);
      return;
     }
//--- Scenario #2: If the file does not exist or the parameters need to be rewritten
   if(file_handle_read==INVALID_HANDLE || RewriteParameters)
     {
      //--- Close the handle of the file for reading
      FileClose(file_handle_read);
      //--- Get the handle of the file for writing
      int file_handle_write=FileOpen(file_name,FILE_WRITE|FILE_CSV|FILE_ANSI|FILE_COMMON,"");
      //--- If the handle has been obtained
      if(file_handle_write!=INVALID_HANDLE)
        {
         string delimiter="="; // Separator
         //--- Write the parameters
         for(int i=0; i<TESTED_PARAMETERS_COUNT; i++)
           {
            FileWrite(file_handle_write,input_parameters_names[i],delimiter,tested_parameters_values[i]);
            Print(input_parameters_names[i],delimiter,tested_parameters_values[i]);
           }
         //--- Write parameter values to arrays
         InputIndicatorPeriod[symbol_number]    =(int)tested_parameters_values[0];
         InputTakeProfit[symbol_number]         =tested_parameters_values[1];
         InputStopLoss[symbol_number]           =tested_parameters_values[2];
         InputTrailingStop[symbol_number]       =tested_parameters_values[3];
         InputReverse[symbol_number]            =(bool)tested_parameters_values[4];
         InputLot[symbol_number]                =tested_parameters_values[5];
         InputVolumeIncrease[symbol_number]     =tested_parameters_values[6];
         InputVolumeIncreaseStep[symbol_number] =tested_parameters_values[7];
         //--- Depending on the indication, print the relevant message
         if(RewriteParameters)
            Print("The file '"+InputSymbols[symbol_number]+".ini' with parameters of the '"+EXPERT_NAME+".ex5 Expert Advisor has been rewritten'");
         else
            Print("The file '"+InputSymbols[symbol_number]+".ini' with parameters of the '"+EXPERT_NAME+".ex5 Expert Advisor has been created'");
        }
      //--- Close the handle of the file for writing
      FileClose(file_handle_write);
     }
  }

A última função do arquivo, a CreateInputParametersFolder(), criará uma pasta com o nome do Consultor Especialista na pasta comum do terminal do cliente. Esta é a pasta na qual os arquivos de texto (no nosso caso, arquivos .ini) com os valores do parâmetro de entrada serão lidos/escritos. Assim como na função anterior, verificaremos se a pasta existe. Se a pasta foi criada com sucesso ou já existe, a função retornará o caminho ou uma cadeia vazia no caso de um erro:

//+----------------------------------------------------------------------------------+
//| Creating a folder for files of input parameters in case the folder does not exist|
//| and returns the path in case of success                                          |
//+----------------------------------------------------------------------------------+
string CreateInputParametersFolder()
  {
   long   search_handle       =INVALID_HANDLE;   // Folder/file search handle
   string EA_root_folder      =EXPERT_NAME+"\\"; // Root folder of the Expert Advisor
   string returned_filename   ="";               // Name of the found object (file/folder)
   string search_path         ="";               // Search path
   string folder_filter       ="*";              // Search filter (* - check all files/folders)
   bool   is_root_folder      =false;            // Flag of existence/absence of the root folder of the Expert Advisor

//--- Find the root folder of the Expert Advisor
   search_path=folder_filter;
//--- Set the search handle in the common folder of the terminal
   search_handle=FileFindFirst(search_path,returned_filename,FILE_COMMON);
//--- If the first folder is the root folder, flag it
   if(returned_filename==EA_root_folder)
      is_root_folder=true;
//--- If the search handle has been obtained
   if(search_handle!=INVALID_HANDLE)
     {
      //--- If the first folder is not the root folder
      if(!is_root_folder)
        {
         //--- Iterate over all files to find the root folder
         while(FileFindNext(search_handle,returned_filename))
           {
            //--- Process terminated by the user
            if(IsStopped())
               return("");
            //--- If it is found, flag it
            if(returned_filename==EA_root_folder)
              {
               is_root_folder=true;
               break;
              }
           }
        }
      //--- Close the root folder search handle
      FileFindClose(search_handle);
      //search_handle=INVALID_HANDLE;
     }
//--- Otherwise print an error message
   else
      Print("Error when getting the search handle or "
            "the folder '"+TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"' is empty: ",ErrorDescription(GetLastError()));

//--- Based on the check results, create the necessary folder
   search_path=EXPERT_NAME+"\\";
//--- If the root folder of the Expert Advisor does not exist
   if(!is_root_folder)
     {
      //--- Create it. 
      if(FolderCreate(EXPERT_NAME,FILE_COMMON))
        {
         //--- If the folder has been created, flag it
         is_root_folder=true;
         Print("The root folder of the '..\\"+EXPERT_NAME+"\\ Expert Advisor has been created'");
        }
      else
        {
         Print("Error when creating "
               "the root folder of the Expert Advisor: ",ErrorDescription(GetLastError()));
         return("");
        }
     }
//--- If the required folder exists
   if(is_root_folder)
      //--- Return the path to create a file for writing parameters of the Expert Advisor
      return(search_path+"\\");
//--- In case of errors, return an empty string
   return("");
  }

Vamos agora colocar as ativações de função juntas em uma única função - InitializeInputParametersArrays(). Essa função abrange 4 opções de inicialização do parâmetro de entrada ao trabalhar com o Consultor Especialista:

  1. O modo de operação padrão (ou parâmetro de otimização para um símbolo selecionado) utilizando os valores do parâmetro de entrada atuais
  2. Reescrevendo os parâmetros nos arquivos ao testar ou otimizar
  3. Testando um símbolo selecionado
  4. Testando todos os símbolos na lista do arquivo

Todas as operações estão explicadas em comentários detalhados para o código:

//+-------------------------------------------------------------------+
//| Initializing arrays of input parameters depending on the mode     |
//+-------------------------------------------------------------------+
void InitializeInputParametersArrays()
  {
   string path=""; // To determine the folder that contains files with input parameters
//--- Mode #1 :
//    - standard operation mode of the Expert Advisor OR
//    - optimization mode OR
//    - reading from input parameters of the Expert Advisor without rewriting the file
   if(IsRealtime() || IsOptimization() || (ParametersReadingMode==INPUT_PARAMETERS && !RewriteParameters))
     {
      //--- Initialize parameter arrays to current values
      InitializeWithCurrentValues();
      return;
     }
//--- Mode #2 :
//    - rewriting parameters in the file for the specified symbol
   if(RewriteParameters)
     {
      //--- Initialize parameter arrays to current values
      InitializeWithCurrentValues();
      //--- If the folder of the Expert Advisor exists or in case no errors occurred when it was being created
      if((path=CreateInputParametersFolder())!="")
         //--- Write/read the file of symbol parameters
         ReadWriteInputParameters(0,path);
      //---
      return;
     }
//--- Mode #3 :
//    - testing (it may be in visualization mode, without optimization) the Expert Advisor on a SELECTED symbol
   if((IsTester() || IsVisualMode()) && !IsOptimization() && SymbolNumber>0)
     {
      //--- If the folder of the Expert Advisor exists or in case no errors occurred when it was being created
      if((path=CreateInputParametersFolder())!="")
        {
         //--- Iterate over all symbols (in this case, the number of symbols = 1)
         for(int s=0; s<SYMBOLS_COUNT; s++)
            //--- Write or read the file of symbol parameters
            ReadWriteInputParameters(s,path);
        }
      return;
     }
//--- Mode #4 :
//    - testing (it may be in visualization mode, without optimization) the Expert Advisor on ALL symbols
   if((IsTester() || IsVisualMode()) && !IsOptimization() && SymbolNumber==0)
     {
      //--- If the folder of the Expert Advisor exists and
      //    no errors occurred when it was being created
      if((path=CreateInputParametersFolder())!="")
        {
         //--- Iterate over all symbols
         for(int s=0; s<SYMBOLS_COUNT; s++)
            //--- Write or read the file of symbol parameters
            ReadWriteInputParameters(s,path);
        }
      return;
     }
  }

Nos modos n.º1 e n.2 utilizamos a função InitializeWithCurrentValues(). Ela inicializa o índice zero (único) aos valores do parâmetro de entrada atuais. Em outras palavras, essa função é usada quando apenas um símbolo é solicitado:

//+------------------------------------------------------------------+
//| Initializing arrays of input parameters to current values        |
//+------------------------------------------------------------------+
void InitializeWithCurrentValues()
  {
   InputIndicatorPeriod[0]=IndicatorPeriod;
   InputTakeProfit[0]=TakeProfit;
   InputStopLoss[0]=StopLoss;
   InputTrailingStop[0]=TrailingStop;
   InputReverse[0]=Reverse;
   InputLot[0]=Lot;
   InputVolumeIncrease[0]=VolumeIncrease;
   InputVolumeIncreaseStep[0]=VolumeIncreaseStep;
  }

Agora precisamos executar a mais fácil, porém, a etapa mais importante: implementar as chamadas consecutivas das funções acima a partir do ponto de entrada, a função OnInit():

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
void OnInit()
  {
//--- Initialize the array of tested input parameters for writing to the file
   InitializeTestedParametersValues();
//--- Fill the array of symbol names
   InitializeInputSymbols();
//--- Set the size of arrays of input parameters
   ResizeInputParametersArrays();
//--- Initialize arrays of indicator handles
   InitializeIndicatorHandlesArrays();
//--- Initialize arrays of input parameters depending on the operation mode of the Expert Advisor
   InitializeInputParametersArrays();
//--- Get agent handles from the "EventsSpy.ex5" indicator
   GetSpyHandles();
//--- Get indicator handles
   GetIndicatorHandles();
//--- Initialize the new bar
   InitializeNewBarArray();
//--- Initialize price arrays and indicator buffers
   ResizeDataArrays();
  }

Assim, terminamos o código. Você pode se familiarizar com as funções descritas utilizando os arquivos anexados ao artigo, não há nada de complicado neles. Agora vamos mais adiante para ver o que temos como resultado e como ele pode ser usado.


Otimizando os parâmetros e testando o Consultor Especialista

Como já mencionado, você deve ter o arquivo TestedSymbols.txt com a lista dos símbolos na pasta comum do terminal do cliente. Como um exemplo/para fins de teste, criaremos uma lista de três símbolos: AUDUSD, EURUSD e NZDUSD. Agora, consecutivamente, otimizaremos os parâmetros de entrada para cada símbolo separadamente. O Examinador de Estratégia precisa ser configurado como mostrado a seguir:

Fig. 2. Configurações do Testador de Estratégia.

Fig. 2. Configurações do Testador de Estratégia.

Você pode configurar qualquer símbolo (no nosso caso, o EURUSD) na guia "Configurações" desde que isso não afete o Consultor Especialista. Então selecionamos os parâmetros para a otimização do Consultor Especialista:

Fig. 3. Parâmetros de entrada do Consultor Especialista

Fig. 3. Parâmetros de entrada do Consultor Especialista

A figura acima mostra que o parâmetro SymbolNumber (número do símbolo testado) está definido como 1. Isso significa que ao executar a otimização o Consultor Especialista utilizará o primeiro símbolo da lista do arquivo TestedSymbols.txt. Em nosso caso, o AUDUSD.

Nota: devido às peculiaridades deste Consultor Especialista (a lista de símbolos é configurada através da leitura do arquivo de texto), a otimização com os agentes remotos não será possível.

Tentaremos contornar essa restrição em um de nossos seguintes artigos desta série.

Após completar a otimização, você pode executar testes, estudando os resultados dos diferentes passos da otimização. Se você desejar que o Consultor Especialista leia os parâmetros do arquivo, você deve selecionar File na lista suspensa do parâmetro ParametersReadingMode (modo de leitura do parâmetro). Para ser capaz de usar os parâmetros atuais do Consultor Especialista (configurar na guia "Configurações"), você deve selecionar a opção Input parameters.

A opção Input parameters é certamente solicitada na visualização dos resultados da otimização. Ao executar o teste pela primeira vez, o Consultor Especialista criará uma pasta com o nome correspondente na pasta comum do terminal. A pasta criada conterá um arquivo com os parâmetros atuais do símbolo testado. Em nosso caso, ele é o AUDUSD.ini. Você pode ver o conteúdo desse arquivo na figura abaixo:

Fig. 4. Lista dos parâmetros de entrada no arquivo do símbolo.

Fig. 4. Lista dos parâmetros de entrada no arquivo do símbolo.

Quando a combinação dos parâmetros necessária for encontrada, você deverá configurar como true no parâmetro RewriteParameters (parâmetro de reescrita) e executar o teste novamente. O arquivo do parâmetro será atualizado. Você pode mais tarde configurar como false novamente e verificar outros resultados dos passos da otimização. Também é conveniente comparar os resultados através dos valores escritos no arquivo com aqueles que estão configurados nos parâmetros de entrada através da simples mudança entre as opções do parâmetro Modo de leitura do parâmetro.

Depois executamos a otimização para o EURUSD, que é o segundo símbolo na lista do arquivo da lista dos símbolos. Para fazer isso, precisamos configurar o valor do parâmetro Número do símbolo testado igual a 2. Seguinte a otimização e após determinar os parâmetros e escrevê-los no arquivo, o mesmo precisará ser feito para o terceiro símbolo na lista.

Uma vez que os parâmetros para todos os símbolos estejam escritos no arquivo, você pode tanto visualizar os resultados para cada símbolo separadamente, pela especificação do número do símbolo, ou visualizar o resultado cumulativo para todos os símbolos, ao ajustar o Número do símbolo testado para 0. Eu obtive o seguinte resultado cumulativo para todos os símbolos:

Fig. 5. O resultado cumulativo do Consultor Especialista multi-moeda.

Fig. 5. O resultado cumulativo do Consultor Especialista multi-moeda.


Conclusão

Como resultado, obtemos um padrão bastante conveniente para os Consultores Especialistas multi-moeda. Isso pode ser desenvolvido posteriormente, se desejar. Anexo no artigo está o arquivo que pode ser transferido com os arquivos do Consultor Especialista para sua consideração. Após descompactar, coloque a pasta UnlimitedParametersEA sob a \MQL5\Experts. O indicador EventsSpy.mq5 deve ser colocado na \MQL5\Indicators. Além disso, não esqueça de criar o arquivo de texto TestedSymbols.txt na pasta comum da plataforma do cliente.

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

Arquivos anexados |
eventsspy.mq5 (7.61 KB)
Guia prático do MQL5: Registrando o histórico de negociações em um arquivo e criando gráficos de saldo para cada símbolo no Excel Guia prático do MQL5: Registrando o histórico de negociações em um arquivo e criando gráficos de saldo para cada símbolo no Excel
Ao me comunicar em vários fóruns, utilizei frequentemente exemplos de meus resultados de teste exibidos como capturas de tela de gráficos do Microsoft Excel. Por muitas vezes me foi pedido para explicar como tais gráficos podem ser criados. Agora, enfim, eu tenho algum tempo para explicar tudo nesse artigo.
Guia prático do MQL5: Consultor Especialista multi-moeda - Abordagem simples, organizada e rápida" Guia prático do MQL5: Consultor Especialista multi-moeda - Abordagem simples, organizada e rápida"
Este artigo descreverá uma implementação de uma abordagem simples, adequada para um Consultor Especialista multi-moeda. Isso significa que você será capaz de montar o Consultor Especialista para testes/negócios sob condições idênticas, mas com parâmetros diferentes para cada símbolo. Como um exemplo, criaremos um padrão para dois símbolos, mas de forma a ser capaz de somar símbolos adicionais, se necessário, fazendo pequenas alterações ao código.
Guia prático do MQL5: Reduzindo o efeito de sobreajuste e lidando com a falta de cotações Guia prático do MQL5: Reduzindo o efeito de sobreajuste e lidando com a falta de cotações
Seja qual for a estratégia de negociação que você usa, sempre haverá uma questão de quais parâmetros escolher para garantir lucros futuros. Este artigo fornece um exemplo de um Expert Advisor com a possibilidade de otimizar vários parâmetros símbolos ao mesmo tempo. Esse método destina-se a reduzir o efeito dos parâmetros de sobreajuste e a lidar com situações em que os dados a partir de um único símbolo não são suficientes para o estudo.
Guia prático do MQL5: Desenvolvendo uma estrutura para um sistema de negócios baseado na estratégia de tela tripla Guia prático do MQL5: Desenvolvendo uma estrutura para um sistema de negócios baseado na estratégia de tela tripla
Nesse artigo, desenvolveremos uma estrutura para um sistema de negócios baseado na estratégia de tela tripla no MQL5. O Consultor Especialista não será desenvolvido do zero. Ao invés disso, simplesmente modificaremos o programa do artigo anterior "Guia prático do MQL5: Usando indicadores para definir condições de negócios em Consultores Especialistas" que já servem substancialmente ao nosso propósito. Então o artigo também demonstrará como você pode modificar facilmente padrões de programas já prontos.