English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Libro de Recetas MQL5: Desarrollar un Asesor Experto Multidivisa con un Número Ilimitado de Parámetros

Libro de Recetas MQL5: Desarrollar un Asesor Experto Multidivisa con un Número Ilimitado de Parámetros

MetaTrader 5Ejemplos | 28 mayo 2014, 11:30
1 692 0
Anatoli Kazharski
Anatoli Kazharski

Introducción

El Asesor Experto multidivisa presentado en el artículo anterior "MQL5 Cookbook: Multi-Currency Expert Advisor - Simple, Neat and Quick Approach" (“Libro de Recetas MQL5: Asesor Experto -Multidivisa - Enfoque Sencillo, Claro y Rápido”) puede ser muy útil si el número de símbolos y parámetros de estrategia de trading que se usan es pequeño. Sin embargo, hay una restricción en el número de parámetros de entrada en un Asesor Experto en MQL5: no deben superar los 1.024.

Y a pesar de que este número a menudo es suficiente, usar una lista de parámetros tan grande puede resultar muy incómodo. Cada vez que se requiere un cambio u optimización de parámetros para un símbolo específico, debe buscar parámetros para ese símbolo específico en la larga lista de parámetros.

Se puede familiarizar con otras restricciones en la sección Inputs (Entradas), en el material de Ayuda del terminal.

En este artículo crearemos un patrón que usa un solo conjunto de parámetros para la optimización del sistema de trading, a la vez que permite un número ilimitado de parámetros. La lista de símbolos se creará en un archivo de texto estándar (*.txt). Los parámetros de entrada para cada símbolo también se almacenarán en archivos.

Debemos mencionar que el Asesor Experto funcionará en un símbolo en el modo de operación normal, pero podrá simularlo en el Probador de Estrategias en una gran variedad de símbolos seleccionados (en cada símbolo por separado).

De hecho, sería conveniente crear una lista de símbolos directamente en la ventana de Market Watch (Observación de Mercado), teniendo en cuenta que incluso permite guardar conjuntos de símbolos ya preparados. También podríamos hacer que el Asesor Experto obtuviera la lista de símbolos en la ventana de Market Watch directamente del Probador de Estrategias. Lamentablemente, en estos momentos no es posible acceder a la ventana de Market Watch desde el Probador de Estrategias, de modo que tenemos que crear antes la lista de símbolos manualmente, o usando un script.


Desarrollo de Asesor Experto

El Asesor Experto multidivisa presentado en el artículo anterior "MQL5 Cookbook: Multi-Currency Expert Advisor - Simple, Neat and Quick Approach" se tomará como plantilla. Decidamos primero cuáles serán los parámetros centrada. Tal y como mencionamos arriba, solo dejaremos un conjunto de parámetros. Abajo tiene la lista de parámetros centrada del Asesor Experto:

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

Solo se escribirán en un archivo los parámetros con el modificador input. Además, debemos expandir los tres nuevos parámetros que hasta ahora no habíamos visto.

  • SymbolNumber - este parámetro indica el número de símbolo del archivo que contiene la lista de símbolos. Si se configura a 0, todos los símbolos de la lista entrarán en la simulación. Si va más allá de la lista, la simulación no se llevará a cabo.
  • RewriteParameters - si este parámetro está configurado como “true”, el archivo con parámetros del símbolo especificado (el número en el parámetro SymbolNumber) se reescribirá usando valores de parámetros de entrada actuales. Alternativamente, si está configurado como false, los parámetros se leerán de un archivo.
  • ParametersReadingMode - este parámetro indica el modo de lectura con respecto a los parámetros de entrada. El tipo del parámetro es la enumeración personalizada ENUM_INPUTS_READING_MODE, que ofrece dos opciones: leer de un archivo y usar parámetros actuales.

El código de la enumeración ENUM_INPUTS_READING_MODE:

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

El número de símbolos se determinó anteriormente usando la constante NUMBER_OF_SYMBOLS. Este valor ahora depende de modos diferentes, de modo que lo cambiaremos a la variable 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;

Al comienzo del código del Asesor Experto declararemos otra constante, TESTED_PARAMETERS_COUNT, que determina los tamaños de array y el número de iteraciones de bucle al iterar en parámetros de entrada:

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

El archivo con la lista de símbolos, así como la carpeta de archivos que contiene los parámetros para cada símbolo, se deben situar en la carpeta común del terminal a la que se puede acceder a nivel del programa, tanto durante la operación normal del Asesor Experto como durante la simulación.

Ahora debemos preparar los arrays con los que trabajaremos después. Todos los arrays en el Asesor Experto multidivisa del artículo anterior que ahora vamos a modificar son estáticos, es decir, tienen un tamaño preconfigurado de elementos. Debemos convertirlos en dinámicos, puesto que el tamaño ahora dependerá del número de símbolos usados en el archivo y en el modo de lectura de parámetros de entrada. También vamos añadir arrays nuevos (destacados en amarillo), que se requieren para trabajar con archivos:

//--- 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];

A continuación, debemos determinar los tamaños de array e inicializarlos.

El array de valores de parámetros de entrada creado arriba se inicializará en la función InitializeTestedParametersValues(), que vamos a crear en el archivo InitializeArrays.mqh. Este array se usará para escribir valores de parámetros de entrada al archivo.

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

Tratemos ahora las operaciones de archivo. Primero, crearemos otra biblioteca de funciones, FileFunctions.mqh, en la carpeta UnlimitedParametersEA\Include de su Asesor Experto. Esta biblioteca se usará para crear funciones relativas a la lectura y escritura de datos en un archivo. Inclúyala en el archivo principal del Asesor Experto y otros archivos del proyecto.

//---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"

Empezaremos creando una función para leer la lista de símbolos de un archivo de texto - ReadSymbolsFromFile(). Este archivo (que llamaremos TestedSymbols.txt) se debe colocar en la sub-carpeta \Files del terminal de cliente MetaTrader 5. En mi caso, será C:\ProgramData\MetaQuotes\Terminal\Common, pero usted debe comprobar la ruta en su caso usando la constante estándar TERMINAL_COMMONDATA_PATH:

TerminalInfoString(TERMINAL_COMMONDATA_PATH)
Todos los archivos personalizados se deben colocar en la subcarpeta \Files de la carpeta común del terminal de cliente, o en <Terminal Data Folder>\MQL5\Files\. Las funciones de archivo solo pueden acceder a estas carpetas.

Para comprobar la funcionalidad, añada unos cuantos símbolos al archivo de texto creado, separando cada uno de los símbolos con una barra ("\r\n"):

Fig. 1. Lista de símbolos en el archivo de la carpeta común del terminal.

Fig. 1. Lista de símbolos en el archivo de la carpeta común del terminal.

Echemos también un vistazo al código de la función 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);
  }

Aquí, el archivo de texto o se lee línea por línea. El nombre de cada instrumento financiero que sí ha leído se escribe en el array temporal temporary_symbols[] creado anteriormente (la accesibilidad real del símbolo en el servidor de trading se comprobará más tarde). Además, al final la función devuelve el número de cadenas de caracteres leídas, es decir, el número de símbolos en el que se simulará el Asesor Experto.

Siempre puede acudir al material de referencia MQL5 Para obtener información más detallada sobre funciones e identificadores que todavía no conozca. Los comentarios facilitados en el código simplificarán el proceso de aprendizaje.

Volvamos al archivo InitializeArrays.mqh, donde vamos a crear la función InitializeInputSymbols() para llenar el array InputSymbols[] de nombres de símbolo declarados anteriormente. A los principiantes probablemente les parecerá bastante complejo, por lo que he incluido comentarios detallados en el 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();
     }
//---
  }

Una vez que el tamaño del array de parámetros de entrada se ha determinado por el número de símbolos usados, debe configurar el tamaño de todos los demás arrays de parámetros de entrada. Implementémoslo en una función separada, 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);
  }

Ahora debemos crear la funcionalidad que nos permita leer valores de parámetros de entrada para cada símbolo, así como escribir esos valores de parámetros en un archivo separado (para cada símbolo), dependiendo del modo de entrada seleccionado en la configuración del Asesor Experto. Esto se hará de forma similar a la forma en que leímos la lista de símbolos del archivo. Es una tarea compleja, de modo que la vamos a dividir en varios procedimientos.

En primer lugar, debemos aprender a leer valores de parámetros de entrada de un archivo a un array. El array contendrá valores de tipo doble. Más tarde convertiremos algunos de ellos (flag de período de indicador y de inversión de posición) a los tipos int y bool, respectivamente. El identificador de abrir archivo y el array donde están guardados los valores de los parámetros del archivo se pasan a la función 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);
  }

¿Qué identificadores de archivo y arrays se pasarán a esta función? Dependerá de los símbolos con los que vayamos a trabajar después de leerlos del archivo “TestedSymbols.txt”. Cada símbolo se corresponderá con un determinado archivo de texto que contiene valores de parámetros de entrada. Nos podemos encontrar con dos situaciones:

  1. El archivo existe y podemos leer valores de parámetros de entrada de ese archivo usando la función ReadInputParametersValuesFromFile() descrita arriba.
  2. El archivo no existe o necesitamos reescribir los valores de parámetros de entrada ya existentes.

El formato del archivo de texto (un archivo .ini, aunque puede elegir cualquier otra extensión que considere necesaria) que contiene los valores de parámetros de entrada será sencillo:

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

Combinemos esta lógica en una sola función, ReadWriteInputParameters(), cuyo código se facilita abajo:

//+------------------------------------------------------------------+
//| 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);
     }
  }

La última función de archivo CreateInputParametersFolder() creará una carpeta con el nombre del Asesor Experto en la carpeta común del terminal de cliente. Esta es la carpeta desde donde se leen y escriben archivos de texto (en nuestro caso, archivos .ini) con valores de parámetros de entrada. Al igual que en la función anterior, comprobaremos si la carpeta existe. Si la carpeta se ha creado recientemente o ya existe, la función devolverá la ruta, o una cadena de caracteres vacía en caso de que se dé un error:

//+----------------------------------------------------------------------------------+
//| 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("");
  }

Ahora pongamos las llamadas de la función de arriba todas juntas en una sola función: InitializeInputParametersArrays(). Esta función cubrirá 4 opciones de inicialización de parámetros de entrada al trabajar con el Asesor Experto:

  1. Modo de operación estándar (u optimización de parámetros para un símbolo seleccionado) usando valores de parámetros de entrada actuales
  2. Reescribir parámetros en archivos durante la simulación o la optimización
  3. Simular un símbolo seleccionado
  4. Simular todos los símbolos en la lista del archivo

Todas las operaciones se explican en los comentarios detallados en el 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;
     }
  }

En los modos #1 y #2 usamos la función InitializeWithCurrentValues(). Inicializará el índice cero (solo) en valores de parámetros de entrada actuales. En otras palabras, esta función se usa cuando solo se requiere un símbolo.

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

Ahora debemos hacer lo más sencillo, que es también lo más importante: implementar llamadas consecutivas de las funciones de arriba desde el punto de entrada, la función 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();
  }

Y así, hemos terminado con el código. Se puede familiarizar con las funciones descritas usando los archivos adjuntos en el artículo, no hay nada complicado en ellos. Ahora continuemos para ver qué hemos obtenido como resultado y cómo se puede usar.


Optimizar Parámetros y Simular el Asesor Experto

Como ya mencionamos antes, debería tener el archivo TestedSymbols.txt con la lista de símbolos en la carpeta común del terminal de cliente. Como ejemplo para la simulación, crearemos una lista de tres símbolos: AUDUSD, EURUSD y NZDUSD. Ahora optimizaremos los parámetros de entrada consecutivamente para cada símbolo por separado. El Probador de Estrategias debe configurarse tal y como se muestra abajo:

Fig. 2. Configuración de Probador de Estrategias.

Fig. 2. Configuración de Probador de Estrategias.

Puede configurar cualquier símbolo (en nuestro caso, EURUSD) en la pestaña "Settings" (“Configuración”), puesto que no afecta al Asesor Experto. A continuación seleccionaremos parámetros de optimización para el Asesor Experto:

Fig. 3. Parámetros de entrada para el Asesor Experto.

Fig. 3. Parámetros de entrada para el Asesor Experto.

La figura de arriba muestra que el parámetro SymbolNumber (número del símbolo simulado) está configurado a 1. Esto significa que, al ejecutar la optimización, el Asesor Experto usará el primer símbolo de la lista del archivo TestedSymbols.txt. En nuestro caso, es AUDUSD.

Nota: a causa de las peculiaridades de este Asesor Experto (la lista de símbolos está configurada con la lectura del archivo de texto), la optimización con agentes remotos no será posible.

Trataremos de evitar esta restricción en uno de los siguientes artículos de esta serie.

Tras completar la optimización podrá llevar a cabo simulaciones, estudiando los resultados de los diferentes pases de optimización. Si desea que el Asesor Experto lea parámetros del archivo, debe seleccionar File (Archivo) en la lista desplegable del parámetro ParametersReadingMode (modo de lectura de parámetros). Para poder usar los parámetros actuales del Asesor Experto (ajustados en la pestaña de configuración), debe seleccionar la opción Input parameters (Parámetros de entrada).

La opción Input parameters es, sin duda, necesaria para ver los resultados de la optimización. Para ejecutar la simulación por primera vez, el Asesor Experto creará una carpeta con el nombre correspondiente en la carpeta común del terminal. La carpeta creada contendrá un archivo con parámetros actuales del símbolo simulado. En nuestro caso, éste será AUDUSD.ini. Puede ver el contenido de este archivo en la figura de abajo:

Fig. 4. Lista de parámetros de entrada en el archivo del símbolo.

Fig. 4. Lista de parámetros de entrada en el archivo del símbolo.

Cuando la combinación requerida de parámetros se ha encontrado, deberá configurar true en el parámetro RewriteParameters (Reescritura de parámetros) y ejecutar la simulación de nuevo. El parámetro se actualizará. Después puede configurar false de nuevo y comprobar los resultados de otros pases de optimización. También es conveniente comparar resultados de valores escritos en el archivo con aquellos que están configurados en los parámetros de entrada simplemente cambiando las opciones del parámetro Parameter reading mode.

Después, ejecutaremos la optimización para EURUSD, que es el segundo símbolo en la lista del archivo de la lista de símbolos. Para ello, deberemos configurar el valor del parámetro Number of the tested symbol (Número del símbolo simulado) para que sea igual a 2. Tras la optimización, y después de determinar los parámetros y escribirlos en el archivo, deberemos hacer lo mismo para el tercer símbolo en la lista.

Una vez que los parámetros para todos los símbolos ya se han escrito en el archivo, puede ver los resultados para cada símbolo por separado especificando el número de símbolo, o puede ver el resultado acumulativo de todos los símbolos configurando el número del símbolo simulado a 0. Yo he obtenido el siguiente resultado acumulativo para todos los símbolos:

Fig. 5. El resultado acumulativo del Asesor Experto multidivisa.

Fig. 5. El resultado acumulativo del Asesor Experto multidivisa.


Conclusión

Como resultado, he obtenido un patrón muy conveniente para Asesores Expertos multidivisa. Se puede seguir desarrollando, si así lo desea. Adjunta al artículo puede encontrar la carpeta descargable con los archivos del Asesor Experto para su estudio personal. Tras descomprimirla, coloque la carpeta UnlimitedParametersEA en <MetaTrader 5 terminal folder>\MQL5\Experts. El indicador EventsSpy.mq5 se debe colocar en <MetaTrader 5 terminal folder>\MQL5\Indicators. Además de todo ello, no olvide crear el archivo de texto TestedSymbols.txt en la carpeta común del terminal de cliente.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/650

Archivos adjuntos |
eventsspy.mq5 (7.61 KB)
Libro de Recetas MQL5: Escribir el Historial de Transacciones y Crear Gráficos de Saldo para cada Símbolo en Excel Libro de Recetas MQL5: Escribir el Historial de Transacciones y Crear Gráficos de Saldo para cada Símbolo en Excel
Al explicar mis ideas en varios foros, a menudo utilizo ejemplos de mis resultados de simulación en forma de capturas de pantalla de gráficos de Microsoft Excel. Muchas veces me ha llegado la pregunta de cómo se pueden crear estos gráficos. Ahora por fin tengo algo de tiempo para explicarlo todo en este artículo.
Libro de Recetas MQL5: Asesor Experto Multidivisa: un Enfoque Simple, Claro y Rápido Libro de Recetas MQL5: Asesor Experto Multidivisa: un Enfoque Simple, Claro y Rápido
Este artículo describirá la implementación de un enfoque sencillo y apropiado para un Asesor Experto multidivisa. Esto significa que será posible configurar el Asesor Experto para simular/realizar operaciones de trading bajo condiciones idénticas pero con parámetros diferentes para cada símbolo. Como ejemplo, crearemos un patrón para dos símbolos pero de forma que solo podamos añadir símbolos adicionales, si es necesario, haciendo pequeños cambios en el código.
Fundamentos de programación en MQL5 - Listas Fundamentos de programación en MQL5 - Listas
La nueva versión del lenguaje de programación de estrategias comerciales -MQL [MQL5]- dispone de un conjunto de herramientas más eficaz y potente en comparación con la versión anterior [MQL4]. En primer lugar, esta ventaja se refiere a los medios de programación orientada a objetos. Este artículo se ocupa de la posibilidad del uso del tipo de datos personalizado, correspondiente al tipo complejo, como los nodos y las listas. Se pone el ejemplo del uso de las listas durante la programación de las tareas prácticas en MQL5.
Libro de Recetas MQL5: Desarrollo de un Marco de Trabajo para un Sistema de Trading Basado en la Estrategia de Triple Pantalla Libro de Recetas MQL5: Desarrollo de un Marco de Trabajo para un Sistema de Trading Basado en la Estrategia de Triple Pantalla
En este artículo desarrollaremos un marco de trabajo para un sistema de trading basado en la estrategia de Triple Pantalla en MQL5. El Asesor Experto no se desarrollará de cero. En lugar de ello, simplemente modificaremos el programa del artículo anterior "MQL5 Cookbook: Using Indicators to Set Trading Conditions in Expert Advisors" (“Libro de Recetas MQL5: Usar Indicadores Para Configurar Condiciones de Trading en Asesores Expertos”), que sustancialmente ya vale para nuestros propósitos. El artículo también demostrará cómo se pueden modificar fácilmente los patrones de programas ya hechos.