English Русский 中文 Deutsch 日本語 Português
LifeHack para tráders: Informe comparativo de varias simulaciones

LifeHack para tráders: Informe comparativo de varias simulaciones

MetaTrader 5Ejemplos | 26 diciembre 2016, 09:47
1 569 0
Vladimir Karputov
Vladimir Karputov

Contenido

 

Introducción

El inicio por turnos de la simulación en varios símbolos no es un proceso muy visual, puesto que los resultados de las simulaciones de cada símbolo se deben guardar en archivos aparte y solo después de ello pueden compararse. Proponemos modificar este enfoque y realizar la simulación simultánea del asesor directamente en varios en símbolos a la vez. En este caso, podremos reunir los resultados en un solo lugar y compararlos visualmente.

Algunas de las soluciones ya han sido analizadas en los artículos:

El esquema de trabajo será el siguiente:

  1. Decidimos qué asesor vamos a poner a prueba (Win API)
  2. Realizamos el análisis sintáctico del código del asesor y modificamos la llamada de la biblioteca del informe gráfico (Win API, MQL5 y expresiones regulares)
  3. Ejecutamos el análisis sintáctico common.ini del terminal principal y preparamos los common.ini individuales para cada terminal (Win API, MQL5 y expresiones regulares)
  4. copiamos los common.ini individuales en las carpetas de los terminales (Win API)
  5. copiamos los common.ini individuales en las carpetas de los terminales (Win API)
  6. Realizamos el análisis sintáctico de los terminales subordinados
  7. Añadimos los informes de los terminales indiduales a un solo informe

 

Acciones necesarias

Antes de iniciar el asesor, hay que llevar a cabo la "sincronización" entre el terminal principal y los subordinados.

  1. Tanto en el terminal principal como en el subordinado debe haberse iniciado la misma cuenta comercial.
  2. En los ajustes de los terminales subordinados es necesario activar la opción "permitir el uso de dll". Si usted inicia el terminal con la clave \Portable, entre en el catálo de instalación del terminal (con la ayuda del explorador u otro gestor de archivos), inicie el terminal "terminal64.exe" y especifique en los ajustes "Permitir el uso de dll".
  3. La biblioteca "DistributionOfProfits.mqh" deberá estar en todos los catálogos de datos (catálogo de datos\MQL5\Include\DistributionOfProfits.mqh) de los terminales subordinados.

1. Parámetros de entrada. Eligiendo un asesor para la simulación

Puesto que mi computadora tiene cuatro núcleos, puedo iniciar solo cuatro agentes de simulación. Es decir, que solo puedo iniciar simultáneamente (o con un pequeño retraso de varios segundos) cuatro terminales, uno en cada agente. Precisamente por ello, en los parámetros de entrada se muestran cuatro grupos de ajustes:

inputs

Parámetros:

  • folder of the MetaTrader#xxx installation — carpeta en la que está instalado el terminal
  • the tested symbol for the terminal #xxx — símbolo en el que se iniciará el simulador de estretegias
  • the tested period for the terminal #xxx — periodo en el que se iniciará el simulador de estretegias
  • correct name of the file of the terminal — nombre del archivo del terminal
  • sleeping in milliseconds — pausa entre inicios de los terminales subordinados
  • date of beginning testing (only year, month and day) — fecha de comienzo de la simulación
  • dates of end testing (only year, month and day) — fecha de finalización de la simulación
  • initial deposit — depósito
  • leverage — apalancamiento

Antes de que se inicien los algoritmos principales, hay que conectar las carpetas de instalación de los terminales subordinados y sus catálogos de datos en la carpeta AppData. Aquí tenemos un ejemplo del sencillo script Check_TerminalPaths.mq5:

//+------------------------------------------------------------------+
//|                                          Check_TerminalPaths.mq5 |
//|                        Copyright 2009, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Print("TERMINAL_PATH = ",TerminalInfoString(TERMINAL_PATH));
   Print("TERMINAL_DATA_PATH = ",TerminalInfoString(TERMINAL_DATA_PATH));
   Print("TERMINAL_COMMONDATA_PATH = ",TerminalInfoString(TERMINAL_COMMONDATA_PATH));
  }
//+------------------------------------------------------------------+

Este script muestra tres parámetros:

  • TERMINAL_PATH — carpeta desde la que se inicia el terminal
  • TERMINAL_DATA_PATH — carpeta en la que se guardan los datos del terminal
  • TERMINAL_COMMONDATA_PATH — carpeta general de todos los terminales de cliente instalados en la computadora

Ejemplo para los tres terminales (uno de ellos ha sido iniciado con la clave /Portable):

// El terminal se inicia en el modo principal
TERMINAL_PATH 			= C:\Program Files\MetaTrader 5
TERMINAL_DATA_PATH 			= C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075
TERMINAL_COMMONDATA_PATH 			= C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common

// El terminal se inicia en el modo principal
TERMINAL_PATH 			= D:\MetaTrader 5 3
TERMINAL_DATA_PATH 			= C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\0C46DDCEB43080B0EC647E0C66170465
TERMINAL_COMMONDATA_PATH 			= C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common

// El terminal se inicia en el modo Portable
TERMINAL_PATH 			= D:\MetaTrader 5 5
TERMINAL_DATA_PATH 			= D:\MetaTrader 5 5
TERMINAL_COMMONDATA_PATH 			= C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\Common

Podrá leer información más detallada acerca de la comparación de las carpetas de los terminales y sus carpetas en AppData en estos apartados de uno de mis anteriores artículos:

La elección del asesor se realiza con la ayuda de la ventana de diálogo "Abrir archivo" (función GetOpenFileNameW):

open file 

Ya hablamos con más detalle sobre la ventana de diálogo "Abrir archivo" en el artículo "LifeHack para tráders: un back test está bien, pero cuatro están mucho mejor": 4.2. Elegimos el asesor con la ayuda de la ventana de diálogo "Abrir archivo"

En esta edición (archivo GetOpenFileNameW.mqh, versión 1.003) se han introducido cambios en la función OpenFileName:

//+------------------------------------------------------------------+
//| Creates an Open dialog box                                       |
//+------------------------------------------------------------------+
string OpenFileName(const string filter_description="Editable code",
                    const string filter="\0*.mq5\0",
                    const string title="Select source file")
  {
   string path=NULL;
   if(GetOpenFileName(path,filter_description+filter,TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Experts\\",title))
      return(path);
   else
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return(NULL);
     }
  }

Ahora es más cómodo establecer el filtro de búsqueda de archivos. Además, preste atención: ahora el filtro busca archivos en el formato editable *.mq5 (en el artículo anterior se realizaba la búsqueda de los archivos *ex5 compilados). 


2. De nuevo sobre common.ini

Ahora vamos a pasar a la descripción del funcionamiento de la función CopyCommonIni() Compare multiple tests.mq5.

El inicio de los terminales subordinados se realiza indicando el propio archivo de configuración. Los archivos subordinados son cuatro, lo que significa que los archivos *.ini también serán cuatro: myconfiguration1.ini, myconfiguration2.ini, myconfiguration3.ini, myconfiguration4.ini. El archivo myconfigurationX.ini se crea sobre la base del archivo common.ini del terminal desde el que se incia nuestro asesor. Ruta al archivo common.ini:

TERMINAL_DATA_PATH\config\common.ini

El algoritmo de trabajo para crear y editar los archivos myconfiguration.ini tiene el aspecto siguiente:

  • copiamos common.ini en la carpeta TERMINAL_COMMONDATA_PATH\Files\original.ini (WinAPI CopyFileW)
  • en el archivo original.ini buscamos el apartado [Common] (MQL5 + expresiones regulares).

    Usando como ejemplo mi terminal principal (en este terminal no se ha entrado en mql5.community), este apartado tiene el aspecto que sigue:

    [Common]
    Login=5116256
    ProxyEnable=0
    ProxyType=0
    ProxyAddress=
    ProxyAuth=
    CertInstall=0
    NewsEnable=0
    NewsLanguages=
  • creamos los cuatro archivos: myconfiguration1.ini, myconfiguration2.ini, myconfiguration3.ini y myconfiguration4.ini (MQL5)
  • editamos estos cuatro archivos (copiamos en ellos el apartado general [Common] y los apartados individuales [Tester]) (MQL5)

2.1. common.ini -> original.ini

Este es, probablemente, el código más sencillo: recibimos en las variables las rutas hasta las carpetas "Data Folder" y "Commomm Data Folder", inicializamos la variable con el valor "original.ini"

   string terminal_data_path=TerminalInfoString(TERMINAL_DATA_PATH);    // path to Data Folder
   string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);// path to Commomm Data Folder
   string original_ini="original.ini";
   string arr_common[];
//---
   string full_name_common_ini=terminal_data_path+"\\config\\common.ini";     // full path to the common.ini file                                                        
   string full_name_original_ini=common_data_path+"\\Files\\"+original_ini;   // full path to the original.ini file  
//--- common.ini -> original.ini
   if(!CopyFileW(full_name_common_ini,full_name_original_ini,false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return(false);
     }

Con la ayuda de la función Win API CopyFileW, copiamos el archivo de configuración "common.ini" en el archivo "original.ini".

2.2. Búsqueda del apartado [Common] con la ayuda de expresiones regulares

Para buscar y copiar el apartado [Common], aplicamos las expresiones regulares. La tarea que encaramos no es muy convencional, porque el archivo common.ini consta de LÍNEAS muy cortas, y al final de las líneas siempre se ponen símbolos de final de línea (símbolos invisibles). Podemos utilizar dos caminos:

Lectura línea por líneaLectura y guardado de todo el archivo en una variable
  • leer línea por línea y buscar una coincidencia con "[Common]" (no es necesario buscar al pie de la letra: se puede establecer la búsqueda de algo como una plantilla "[Com" algunos_caracteres "]")
    • después de encontrarlas, escribimos en la matriz las líneas localizadas
    • buscar la siguiente correspondencia con "["
      • después de encontrarlas, es necesario detener el registro en la matriz, ya que en la matriz en este momento ya estará todo el apartado "[Common]"
  • calcular todas las líneas en una variable de línea
  • buscar la plantilla "[Common]" varios símbolos "]" (aquí tampoco es necesario realizar una búsqueda al pie de la letra: podemos buscar algo semejante a la plantilla "[Com" varios símbolos "]")
  • después de encontrar las coincidencias, se registran en una variable de línea

Archivo de prueba "test_original.ini":

[Charts]
ProfileLast=Default
MaxBars=100000
PrintColor=0
SaveDeleted=0
TradeLevels=1
TradeLevelsDrag=0
ObsoleteLasttime=1475473485
[Common]
Login=1783501
ProxyEnable=0
ProxyType=0
ProxyAddress=
ProxyAuth=
CertInstall=0
NewsEnable=0
[Tester]
Expert=test         
Symbol=EURUSD          
Period=H1             
Deposit=10000     
Model=4              
Optimization=0         
FromDate=2016.01.22    
ToDate=2016.06.06      
Report=TesterReport    
ReplaceReport=1       
UseLocal=1              
Port=3000            
Visual=0              
ShutdownTerminal=0

En el archivo "test_original.ini" es posible entrenarse en la aplicación de expresiones regulares con la ayuda del script "Receiving lines.mq5". En los ajustes se pueden elegir dos modos de trabajo:

  • lectura y grabación línea por línea en cada línea de caracteres 
  • o lectura y grabación de todo el archivo en una única variable.

Algunos ejemplos para comparar el funcionamiento de estos dos métodos:

Lectura línea por líneaLectura y guardado de todo el archivo en una variable
Solicitud: "Prox(.*)0"
- buscamos la palabra "Prox"
- después, cualquier símbolo, excepto el de nueva línea o cualquier separador de línea Unicode, encontrado cero o más veces (avaricioso) "(.*)"
- la búsqueda finalizará una vez que se haya encontrado la cifra "0"
12: 0: ProxyEnable=0,
13: 0: ProxyType=0,
: 0: ProxyEnable=0ProxyType=0ProxyAddress=ProxyAuth=CertInstall=0NewsEnable=0[Tester]Expert=test         Symbol=EURUSD          Period=H1             Deposit=10000     Model=4              Optimization=0         FromDate=2016.01.22    ToDate=2016.06.06      Report=TesterReport    ReplaceReport=1       UseLocal=1              Port=3000            Visual=0              ShutdownTerminal=0, 
Como puede ver, aquí se dan dos resultadosY aquí, en la emisión, solo un resultado, pero en este sobran muchas cosas (se activó una solicitud avariciosa)
  
Solicitud: "Prox(.*?)0"
- buscamos la palabra "Prox"
- después, cualquier símbolo, excepto el de nueva línea o cualquier separador de línea Unicode, encontrado cero o más veces (no avaricioso) "(.*?)"
- la búsqueda finalizará una vez que se haya encontrado la cifra "0"
12: 0: ProxyEnable=0,
13: 0: ProxyType=0,
: 0: ProxyEnable=0, 1: ProxyType=0, 2: ProxyAddress=ProxyAuth=CertInstall=0,
Aquí tenemos de nuevo dos resultadosPero en este caso, se han obtenido tres resultados, además, el tercero no es en absoluto el que yo quería obtener.

¿Qué método usar para el cálculo del bloque completo "[Common]", la lectura línea por línea o la lectura en una variable? Yo he elegido la lectura por líneas y este algoritmo:

  1. búsqueda de la línea "[Common]" (MQL5);
  2. después de encontrarla, se registra la línea encontrada en una matriz;
  3. después, continuamos registrando líneas en la matriz, hasta que la expresión regular no detecte el símbolo "[".

Un ejemplo de este enfoque se ha implementado en el script "Receiving lines v.2.mq5":

//+------------------------------------------------------------------+
//|                                          Receiving lines v.2.mq5 |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.000"
#property description "Singling the block \"[Common]\""
#property script_show_inputs
#include <RegularExpressions\Regex.mqh>
//---
input string   file_name="test_original.ini";         // file name
input string   str_format="(\\[)(.*?)(\\])";
//---
int            m_handel;
bool           m_found_Common=false;                  // after finding of the word "[Common]" - the flag will be true
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   string arr_text[]; // array for rezult
//---
   Print("format: ",str_format);
   m_handel=FileOpen(file_name,FILE_READ|FILE_ANSI|FILE_TXT);
   if(m_handel==INVALID_HANDLE)
     {
      Print("Operation FileOpen failed, error ",GetLastError());
      return;
     }
   Regex *rgx=new Regex(str_format);
   while(!FileIsEnding(m_handel))
     {
      string str=FileReadString(m_handel);
      if(str=="[Common]")
        {
         m_found_Common=true;
         int size=ArraySize(arr_text);
         ArrayResize(arr_text,size+1,10);
         arr_text[size]=str;
         continue;                        // goto while...
        }
      if(m_found_Common)
        {
         MatchCollection *matches=rgx.Matches(str);
         int count=matches.Count();
         if(count>0)
           {
            if(count>1)
              {
               Print("Alarm! matches.Count()==",count);
               return;
              }
            delete matches;
            break;                        // goto FileClose...
           }
         else
           {
            delete matches;               // if no match is found
           }
         int size=ArraySize(arr_text);
         ArrayResize(arr_text,size+1,10);
         arr_text[size]=str;
        }
     }
   FileClose(m_handel);
   delete rgx;
   Regex::ClearCache();

//--- testing
   int size=ArraySize(arr_text);
   for(int i=0;i<size;i++)
     {
      Print(arr_text[i]);
     }
  }
//+------------------------------------------------------------------+

Resultado del funcionamiento del script:

2016.10.05 06:58:09.276 Receiving lines v.2 (EURUSD,M1) format: (\[)(.*?)(\])
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) [Common]
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) Login=1783501
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) ProxyEnable=0
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) ProxyType=0
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) ProxyAddress=
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) ProxyAuth=
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) CertInstall=0
2016.10.05 06:58:09.277 Receiving lines v.2 (EURUSD,M1) NewsEnable=0

Como puede ver, el script ha destacado con precisión en el archivo "test_original.ini" el bloque de parámetros "[Common]". El algoritmo del script "Receiving lines v.2.mq5" lo uso prácticamente sin modificaciones en la función SearchBlock(). La función SearchBlock(), si se halla con éxito el bloque de parámetros "[Common]", escribe este bloque en la matriz auxiliar arr_common[].

2.3. Creando los cuatro archivos: myconfiguration1.ini, myconfiguration2.ini, myconfiguration3.ini y myconfiguration4.ini

Los cuatro archivos se crean con la llamada consecutiva de este código (preste atención a las banderas usadas al abrir los archivos):

//+------------------------------------------------------------------+
//| Open new File                                                    |
//+------------------------------------------------------------------+
bool IniFileOpen(const string name_file,int  &handle)
  {
   handle=FileOpen(name_file,FILE_WRITE|FILE_ANSI|FILE_TXT|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      Print("Operation FileOpen file ",name_file," failed, error ",GetLastError());
      return(false);
     }
//---
   return(true);
  }

2.4. Editando los archivos ini (copiamos en ellos el apartado general [Common] y los apartados individuales [Tester])

Anteriormente, en la matriz auxiliar arr_common[] se ha registrado el bloque de parámetros [Common]. Ahora, esta matriz se registra en cada uno de los cuatro archivos:

//--- recording block "[Common]"
   int arr_common_size=ArraySize(arr_common);
   for(int i=0;i<arr_common_size;i++)
     {
      FileWrite(handle1,arr_common[i]);
      FileWrite(handle2,arr_common[i]);
      FileWrite(handle3,arr_common[i]);
      FileWrite(handle4,arr_common[i]);
     }
//--- recording block "[Tester]"
   string expert_short_name="D0E820_test";
   WriteBlockTester(handle1,expert_short_name,ExtTerminal1Symbol,ExtTerminal1Timeframes,ExtDeposit,
                    ExtLeverage,ExtTerminaTick,ExtFromDate,ExtToDate,expert_short_name,3000);
   WriteBlockTester(handle2,expert_short_name,ExtTerminal2Symbol,ExtTerminal2Timeframes,ExtDeposit,
                    ExtLeverage,ExtTerminaTick,ExtFromDate,ExtToDate,expert_short_name,3001);
   WriteBlockTester(handle3,expert_short_name,ExtTerminal3Symbol,ExtTerminal3Timeframes,ExtDeposit,
                    ExtLeverage,ExtTerminaTick,ExtFromDate,ExtToDate,expert_short_name,3002);
   WriteBlockTester(handle4,expert_short_name,ExtTerminal4Symbol,ExtTerminal4Timeframes,ExtDeposit,
                    ExtLeverage,ExtTerminaTick,ExtFromDate,ExtToDate,expert_short_name,3003);
//--- close the files 
   FileClose(handle1);
   FileClose(handle2);
   FileClose(handle3);
   FileClose(handle4);

A continuación, viene la formación del bloque de parámetros [Tester]: para cada terminal se preparan parámetros únicos (símbolo y marco temporal), así como los parámetros generales (fecha de comienzo y finalización de la simulación, depósito inicial, apalancamiento). 

Los archivos creados myconfiguration1.ini, myconfiguration2.ini, myconfiguration3.ini y myconfiguration4.ini permanecen en la carpeta general de datos (TERMINAL_COMMONDATA_PATH\Files\). Cerramos los manejadores de estos archivos.


3. Análisis sintáctico y edición del archivo mq5 del asesor elegido

Tareas a resolver:

3.1. Secreto №3 

¿Cuál es el motivo del secreto número tres? La cuestión es que  el Secreto №1 y  el Secreto №2 se han mostrado con anterioridad en el artículo LifeHack para tráders: un back test está bien, pero cuatro están mucho mejor.

Veamos la situación siguiente: el terminal se inicia desde la línea de comando, y además se indica el archivo de configuración ini. En el archivo ini, indicamos el nombre del asesor que se ejecutará en el simulador al iniciar el terminal. En este caso, tendremos en cuenta que indicamos el nombre de un asesor que aún no se ha compilado.

Secreto №3.

Bien, el nombre del asesor se DEBE escribir sin extensión. Aplicado a aeste artículo, tendrá el aspecto siguiente:

NewsEnable=0
[Tester]
Expert=D0E820_test
Symbol=GBPAUD

El terminal, durante el inicio, en primer lugar buscará EL ARCHIVO COMPILADO (en este artículo, el terminal buscará Expert=D0E820_test.ex5). Y solo en el caso de que el terminal no encuentre el archivo compilado, comenzará la compilación del asesor especificado en el archivo ini.

Partiendo de ello, antes de proceder al trabajo de edición del asesor elegido, es necesario dar un repaso a las carpetas de los terminales subordinados y eliminar las versiones compiladas del asesor elegido (en nuestro caso en concreto, hay que eliminar los archivos D0E820_test.ex5). Los eliminaremos con la ayuda de la función Win API DeleteFileW:

      if(!CopyCommonIni())
         return(INIT_FAILED);
      //--- delete all files: expert_short_name+".ex5"
      ResetLastError();
      string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);   // path to Commomm Data Folder
      //---
      string edited_expert=common_data_path+"\\Files\\"+expert_short_name+".mq5";
      //--- delete expert_short_name+".ex5" files
      string compiled_expert=expert_short_name+".ex5";
      DeleteFileW(slaveTerminalDataPath1+"\\MQL5\\Experts\\"+compiled_expert);
      DeleteFileW(slaveTerminalDataPath2+"\\MQL5\\Experts\\"+compiled_expert);
      DeleteFileW(slaveTerminalDataPath3+"\\MQL5\\Experts\\"+compiled_expert);
      DeleteFileW(slaveTerminalDataPath4+"\\MQL5\\Experts\\"+compiled_expert);

      //--- delete expert_short_name+".set" files

Ahora hay que eliminar necesariamente los archivos *.set. El asunto es que si usted cambia en el asesor elegido solo algunos parámetros de entrada, los simuladores se iniciarán igualmente con los parámetros presentes en el inicio anterior. Por eso, eliminamos los archivos *.set:

      //--- delete expert_short_name+".set" files
      string set_files=expert_short_name+".set";
      DeleteFileW(slaveTerminalDataPath1+"\\Tester\\"+set_files);
      DeleteFileW(slaveTerminalDataPath2+"\\Tester\\"+set_files);
      DeleteFileW(slaveTerminalDataPath3+"\\Tester\\"+set_files);
      DeleteFileW(slaveTerminalDataPath4+"\\Tester\\"+set_files);

      //--- delete expert_short_name+".htm" files (reports)

Asimismo, eliminamos el archivo del informe del simulador de las carpetas de los terminales subordinados:

      DeleteFileW(slaveTerminalDataPath4+"\\MQL5\\Experts\\"+compiled_expert);
      //--- delete expert_short_name+".htm" files (reports)
      string file_report=expert_short_name+".htm";
      DeleteFileW(slaveTerminalDataPath1+"\\"+file_report);
      DeleteFileW(slaveTerminalDataPath2+"\\"+file_report);
      DeleteFileW(slaveTerminalDataPath3+"\\"+file_report);
      DeleteFileW(slaveTerminalDataPath4+"\\"+file_report);

      //--- copying an expert in the TERMINAL_COMMONDATA_PATH\Files folder
      if(!CopyFileW(expert_full_name,edited_expert,false))

¿Por qué eliminamos los archivos de los informes? Esto es necesario para realizar un seguimiento del momento en el que aparezcan en todos los terminales subordinados los archivos de informe: entonces se podrá realizar el análisis sintáctico de estos archivos para crear páginas de comparación de los resultados de simulación obtenidos en varios símbolos.

Y solo después de eliminar los archivos compilados se podrá copiar el archivo elegido del asesor en la carpeta TERMINAL_COMMONDATA_PATH, para trabajar posteriormente con el archivo usando los recursos de MQL5:

      DeleteFileW(slaveTerminalDataPath4+"\\"+file_report);
      //--- copying an expert in the TERMINAL_COMMONDATA_PATH\Files folder
      if(!CopyFileW(expert_full_name,edited_expert,false))
        {
         PrintFormat("Failed CopyFileW expert_full_name with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

      //--- parsing advisor file

3.2. Incorporando "#include"

Descripción de la función Compare multiple tests.mq5::ParsingEA().

En general, hay que determinar si existe ya en el archivo del asesor la línea "#include <DistributionOfProfits.mqh>". Si tal línea no existe, entonces hay que incorporarla al asesor. Además, es necesario comprender que pueden existir muchas variantes:

VariantesApropiada/inapropiada
"#include <DistributionOfProfits.mqh>"apropiada (variante ideal)
"#include <DistributionOfProfits.mqh>"apropiada (en esta variante, la palabra "#include" no va seguida de un espacio, sino del símbolo de tabulación)
" #include <DistributionOfProfits.mqh>"apropiada (en esta variante, antes de la palabra "#include", tenemos el signo de tabulación)
"//#include <DistributionOfProfits.mqh>"inapropiada (es simplemente un comentario)

también podría darse esta variante cuando después de "#include", en lugar de un espacio, tenemos un símbolo de tabulación o varios espacios. Como resultado, para la búsqueda se ha creado esta expresión regular: 

"(\\s+?#include|^#include)(.*?)(<DistributionOfProfits.mqh)"

Aquí tenemos cómo se descifra la expresión (\\s+?#include|^#include): (uno o más espacios, no avaricioso, después "#include") o (la línea comienza con "#include"). La responsable de la búsqueda de la línea es la función NumberRegulars(). Aquí se introduce directamente la variable "name_Object_CDistributionOfProfits", en ella guardamos el nombre del objeto CDistributionOfProfits. Esto podría resultar útil más tarde, si necesitamos realizar un búsqueda compleja.

//+------------------------------------------------------------------+
//| Insert #include <DistributionOfProfits.mqh>                      |
//| Insert call graphical analysis of trade                          |
//+------------------------------------------------------------------+
bool ParsingEA()
  {
//--- find #include <DistributionOfProfits.mqh>
   int number=0;
   string name_Object_CDistributionOfProfits="ExtDistribution";   // CDistributionOfProfits object name
   string expressions="(\\s+?#include|^#include)(.*?)(<DistributionOfProfits.mqh)";
   if(!NumberRegulars(expert_short_name+".mq5",expressions,number))
      return(false);
   if(number==0) // a regular expression is not found
     {
      //--- add #include <DistributionOfProfits.mqh> 
      string array[];
      ArrayResize(array,2);
      array[0]="#include <DistributionOfProfits.mqh>";
      array[1]="CDistributionOfProfits "+name_Object_CDistributionOfProfits+";";
      if(!InsertLine(expert_short_name+".mq5",0,array))
         return(false);
      Print("Line \"#include\" is insert");

Si no se ha localizado la línea, significa que hay que incorporarla al asesor (función InsertLine()). El principio de funcionamiento es el siguiente: leemos y guardamos línea por línea el archivo del asesor en una matriz temporal. Cuando el número de la línea coincide con la ("position") establecida, entonces se pega en la matriz el fragmento necesario de código (el código se toma de la matriz "text"). Una vez leído por completo, el archivo del asesor se elimina, después se crea de inmediato un nuevo archivo con el mismo nombre. En este se registra la información de la matriz temporal:

//+------------------------------------------------------------------+
//| Insert a line in a file                                          |
//+------------------------------------------------------------------+
bool InsertLine(const string name_file,const uint position,string &array_text[])
  {
   int handle;
   int size_arr=ArraySize(array_text);
//---
   handle=FileOpen(name_file,FILE_READ|FILE_ANSI|FILE_TXT|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      Print("Operation FileOpen file ",name_file," failed, error ",GetLastError());
      return(false);
     }
   int line=0;
   string arr_temp[];
   ArrayResize(arr_temp,0,1000);
   while(!FileIsEnding(handle))
     {
      string str_text=FileReadString(handle,-1);
      if(line==position)
        {
         for(int i=0;i<size_arr;i++)
           {
            int size=ArraySize(arr_temp);
            ArrayResize(arr_temp,size+1,1000);
            arr_temp[size]=array_text[i];
           }
        }
      int size=ArraySize(arr_temp);
      ArrayResize(arr_temp,size+1,1000);
      arr_temp[size]=str_text;
      line++;
     }
   FileClose(handle);
   FileDelete(name_file,FILE_COMMON);
//---
   handle=FileOpen(name_file,FILE_WRITE|FILE_ANSI|FILE_TXT|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      Print("Operation FileOpen file ",name_file," failed, error ",GetLastError());
      return(false);
     }
   int size=ArraySize(arr_temp);
   for(int i=0;i<size;i++)
     {
      FileWrite(handle,arr_temp[i]);
     }
   FileClose(handle);
//---
   return(true);
  }

3.3. Incorporando "OnTester()"

Ahora nuestra tarea se hace mucho más complicada, puesto que la palabra "OnTester" puede encontrarse en el código de programa en muchas variantes diferentes. Por ejemplo, puede no existir en absoluto en el código:  probablemente sea la variante más sencilla. Podemos encontrarnos la variante clásica:

double OnTester()
  {

Esto tampoco es muy complicado. Pero hay mucho rebelde entre los desarrolladores, por eso podemos encontrarnos con un estilo de programación semejante:

double OnTester() {

O, quizá, una de las variantes más difíciles:

/*
//+-------------------------------+
//|                               |
//+-------------------------------+
double OnTester()
  {
...
  }
...
*/

Bien, para determinar si la función OnTetster se encuentra en el código, adoptaremos esta expresión regular:

"(\\s+?double|^double)(.+?)(OnTester\\(\\))(.*)"
"(\\s+?double" 
 \\s espacio, \\s+ espacio que se encuentra una vez como mínimo, \\s+? espacio que se encuentra una vez como mínimo, operador no-avaricioso, \\s+?double espacio que se encuentra una vez como mínimo, operador no-avaricioso y la palabra "double".
"|"
 | o
     "^double)" la línea comienza con la palabra double
"(.+?)"
 . cualquier símbolo, excepto el de nueva línea u otro separador de línea Unicode, .+ cualquier símbolo, excepto el de nueva línea u otro separador de línea Unicode que se encuentre una o más veces, .+? cualquier símbolo, excepto el de nueva línea u otro separador de línea Unicode que se encuentre una o más veces, no avaricioso
"(OnTester\\(\\))"
 OnTester\\(\\) palabra OnTester()
"(.*)"
 . cualquier símbolo, excepto el de nueva línea u otro separador de línea Unicode, .* cualquier símbolo, excepto el de nueva línea u otro separador de línea Unicode que se encuentre cero o más veces

En el caso más sencillo, cuando las expresiones regulares retornan cero, incorporamos la llamada OnTester():

//---
   expressions="(\\s+?double|^double)(.+?)(OnTester\\(\\))(.*)";
   if(!NumberRegulars(expert_short_name+".mq5",expressions,number))
      return(false);
   if(number==0) // a regular expression is not found
     {
      //--- add function OnTester  
      if(!InsertLine(expert_short_name+".mq5",2,
         "double OnTester()"+
         "  {"+
         "   double ret=0.0;"+
         "   ExtDistribution.AnalysisTradingHistory(0);"+
         "   ExtDistribution.ShowDistributionOfProfits();"+
         "   return(ret);"+
         "  }"))
         return(false);
      Print("Line \"OnTester\" is insert");
     }

En total, si en el código no se ha encontrado ni "#include <DistributionOfProfits.mqh>" ni la función "OnTester()", el archivo fuente tendrá el aspecto siguiente (por ejemplo, cuando hemos elegido el archivo MACD Sample.mq5):

#include <DistributionOfProfits.mqh>
CDistributionOfProfits ExtDistribution;
double OnTester()
  {
   double ret=0.0;
   ExtDistribution.AnalysisTradingHistory(0);
   ExtDistribution.ShowDistributionOfProfits();
   return(ret);
  }
//+------------------------------------------------------------------+
//|                                                  MACD Sample.mq5 |
//|                   Copyright 2009-2016, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2009-2016, MetaQuotes Software Corp."
#property link        "https://www.mql5.com"
#property version     "5.50"

No tiene un aspecto muy estético, pero por lo menos realiza su tarea. En los puntos 3.1 y 3.2 se han analizado casos sencillos (los más simples): cuando en el código del asesor no ha habido al principio ni declaración de la biblioteca de análisis gráfico, ni la función OnTrade(). Ahora vamos a ver los casos más complicados, cuando en el código están presentes desde el principio la declaración de la biblioteca de análisis gráfico, y/o la función OnTester(). 

3.4. Caso complejo: en el código ya existen DistributionOfProfits.mqh, y/o OnTester()

La búsqueda compleja se ejecuta en la función AdvancedSearch():

//+------------------------------------------------------------------+
//| Advanced Search                                                  |
//|  only_ontester=true                                              |
//|   - search only function OnTester()                              |
//|  only_ontester=false                                             |
//|   - search #include <DistributionOfProfits.mqh>                  |
//|     and function OnTester()                                      |
//+------------------------------------------------------------------+
bool AdvancedSearch(const string name_file,const string name_object,const bool only_ontester)

Parámetros:

  • name_file — nombre del archivo del asesor
  • name_object — nombre del objeto de clase  CDistributionOfProfits
  • only_ontester — bandera de búsqueda, con only_ontester=true buscaremos solo la función OnTester().

Al principio, todo el archivo se lee en una matriz temporal

string arr_temp[];

— así funcionará con mayor comodidad.

Después se llaman de forma consecutiva varios códigos auxiliares:

RemovalMultiLineComments() — en este código se eliminan de la matriz todos los comentarios multilínea;

RemovalComments() — aquí se eliminan los comentarios de una línea;

DeleteZeroLine() — de la matriz se eliminan todas las líineas de longitud cero.

Si el parámetro de entrada only_ontester==false, significa que iniciamos la búsqueda de la línea "#include <DistributionOfProfits.mqh> ", de ello es responsable la función FindInclude():

La función FindInclude() busca la aparición de la línea "#include <DistributionOfProfits.mqh>" y guarda su número en la variable "function_position" (recordemos que lo hace de forma preliminar, en el punto 3.1. incorporando "#include" con la ayuda de expresiones regulares ya hemos definido preliminarmente que en el código existe de forma garantizada la línea "#include <DistributionOfProfits.mqh>"). A continuación, se intenta encontrar la línea "CDistributionOfProfits". Si se localiza tal línea, entonces obtenemos de ella el nombre de la variable para la clase "CDistributionOfProfits". Si, por el contrario, no se ha localizado,  será necesario ponerla en la posición inmediatamente posterior a "function_position".

Si el parámetro de entrada only_ontester==true, iniciamos la búsqueda de la función Ontester(). En cuanto la encontremos, buscamos en ella las líneas de caracteres para entrar en la biblioteca de análisis gráfico, la función  FindFunctionOnTester() es la responsable de ello.


4. Copiando el experto en las carpetas de terminales subordinados

Los expertos se copian en OnInit():

      //--- parsing advisor file
      if(!ParsingEA())
         return(INIT_FAILED);

      //--- copying an expert in the terminal folders
      ResetLastError();
      if(!CopyFileW(edited_expert,slaveTerminalDataPath1+"\\MQL5\\Experts\\"+expert_short_name+".mq5",false))
        {
         PrintFormat("Failed CopyFileW #1 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

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

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

      if(!CopyFileW(edited_expert,slaveTerminalDataPath4+"\\MQL5\\Experts\\"+expert_short_name+".mq5",false))
        {
         PrintFormat("Failed CopyFileW #4 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }

 

5. Iniciando los terminales subordinados

Antes de iniciar los terminales subordinados, por favor, compruebe lo siguiente: la cuenta comercial en el terminal principal, en el que se inicia nuestro asesor, debe estar en todos los terminales subordinados. También es necesario permitir el uso de dll en todos los terminales subordinados:

 

Si no hacemos esto, entonces el terminal subordinado no podrá iniciar el asesor (recordemos que en nuestro asesor se aplican muy activamente las llamadas Win API), y en el Simulador, en la pestaña "Diario", se mostrará aproximadamente este mensaje de error:

2016.10.13 11:28:57     Core 1  2016.02.03 00:00:00   DLL loading is not allowed

Más información sobre la función de sistema ShellExecuteW:  ShellExecuteW. Entre los inicios de los terminales se hacen pausas. El inicio directo corre a cargo de "LaunchSlaveTerminal". 

      if(!CopyFileW(edited_expert,slaveTerminalDataPath4+"\\MQL5\\Experts\\"+expert_short_name+".mq5",false))
        {
         PrintFormat("Failed CopyFileW #4 with error: %x",kernel32::GetLastError());
         return(INIT_FAILED);
        }
      //--- launching Slave Terminals
      Sleep(ExtSleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_1,common_data_path+"\\Files\\myconfiguration1.ini");
      Sleep(ExtSleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_2,common_data_path+"\\Files\\myconfiguration2.ini");
      Sleep(ExtSleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_3,common_data_path+"\\Files\\myconfiguration3.ini");
      Sleep(ExtSleeping);
      LaunchSlaveTerminal(ExtInstallationPathTerminal_4,common_data_path+"\\Files\\myconfiguration4.ini");
     }
//---
   return(INIT_SUCCEEDED);
  }

 

6. Informe comparativo

No en vano hemos invertido tantos esfuerzos en el análisis sintáctico del código del asesor elegido: hemos introducido en el código del asesor elegido la llamada de la biblioteca de análisis gráfico de las posiciones en el segmento temporal de la apertura de posición (esta biblioteca ha sido descrita en el artículo LifeHack para tráders: Optimización "silenciosa" o Trazando la distribución de trades"). Gracias al código introducido, cada asesor en el terminal subordinado, al finalizar la simulación, crea y abre de forma automática una página html así:

scheme

Anteriormente, en el archivo ini de configuración, en el bloque [Tester], hemos registrado este parámetro "Report":

[Tester]
Expert=D0E820_test
Symbol=GBPAUD
Period=PERIOD_H1
Deposit=100000
Leverage=1:100
Model=0
ExecutionMode=0
FromDate=2016.10.03
ToDate=2016.10.15
ForwardMode=0
Report=D0E820_test
ReplaceReport=1
Port=3000
ShutdownTerminal=0

Se trata del nombre del archivo (D0E820_test.htm), en el que terminal guardará el informe después de la simulación. De este informe (para cada terminal subordinado), deberemos tomar los siguientes datos: el nombre del símbolo y el periodo en el que se ha simulado el asesor,  los índices del bloque "Back test" y el gráfico de balance. De todos los terminales subordinados, se formará este informe comparativo:

report

Recordemos que los terminales subordinados guardan los informes de la simulación (en este caso, en formato htm) en la raíz de sus catálogos de datos. Significa que nuestro asesor necesita iniciar los terminales subordinados, y después buscar de forma periódica en sus catálogos los archivos de los informes de simulación. Después de ello, en cuanto se encuentren los cuatro informes, se podrá proceder a formar un archivo comparativo general. 

Para comenzar, introducimos la bandera "find_report", que permitirá al asesor comenzar la búsqueda de los archivos de los informes:

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

y añadimos la función OnTimer():

int OnInit()
  {
//--- create timer
   EventSetTimer(9);

  
   ArrayFree(arr_path);
   find_report=false;                                                      // true - flag allows the search reports
   if(!FindDataFolders(arr_path))
      return(INIT_SUCCEEDED);
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   if(!find_report)
      return;
  }

En OnTimer(), buscaremos el archivo "expert_short_name"+".htm". La búsqueda será a un solo nivel, solo en la raíz del catálogo de datos de cada uno de los terminales subordinados. De esta tarea se encargará la función ListingFilesDirectory.mqh::FindFile().

Como la búsqueda se realiza más allá de los límites del "sandbox", entonces usaremos la función Win API FindFirstFileW. Para más detalles sobre FindFirstFileW, lea en el artículo anterior: 

En este código, comparamos el nombre obtenido del archivo, y si coincide con el establecido, entonces retornamos true, cerrando preliminarmente el manejador de búsqueda: 

//+------------------------------------------------------------------+
//| Find file                                                        |
//+------------------------------------------------------------------+
bool FindFile(const string path,const string name)
  {
//---
   WIN32_FIND_DATA ffd;
   long            hFirstFind_0;

   ArrayInitialize(ffd.cFileName,0);
   ArrayInitialize(ffd.cAlternateFileName,0);
//--- stage Search №0.
   string filter_0=path+"\\*.*"; // filter_0==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\*.*

   hFirstFind_0=FindFirstFileW(filter_0,ffd);
//---
   string str_handle="";
   if(hFirstFind_0==INVALID_HANDLE)
      str_handle="INVALID_HANDLE";
   else
      str_handle=IntegerToString(hFirstFind_0);
//Print("filter_0: \"",filter_0,"\", handle hFirstFind_0: ",str_handle);
//---
   if(hFirstFind_0==INVALID_HANDLE)
     {
      PrintFormat("Failed FindFirstFile (hFirstFind_0) with error: %x",kernel32::GetLastError());
      return(false);
     }

//--- list all the files in the directory with some info about them
   bool rezult=0;
   do
     {
      string name_0="";
      for(int i=0;i<MAX_PATH;i++)
        {
         name_0+=ShortToString(ffd.cFileName[i]);
        }
      if(name_0==name)
        {
         WinAPI_FindClose(hFirstFind_0);
         return(true);
        }

      ArrayInitialize(ffd.cFileName,0);
      ArrayInitialize(ffd.cAlternateFileName,0);
      ResetLastError();
      rezult=WinAPI_FindNextFile(hFirstFind_0,ffd);
     }
   while(rezult!=0); //if(hFirstFind_1==INVALID_HANDLE), we appear here
   if(kernel32::GetLastError()!=ERROR_NO_MORE_FILES)
      PrintFormat("Failed FindNextFileW (hFirstFind_0) with error: %x",kernel32::GetLastError());
//else
//   Print("filter_0: \"",filter_0,"\", handle hFirstFind_0: ",hFirstFind_0,", NO_MORE_FILES");
   WinAPI_FindClose(hFirstFind_0);
//---
   return(false);
  }

El asesor comprueba la presencia de los cuatro archivos de los informes en las carpetas de los terminales subordinados: esto significa que todos los terminales subordinados han finalizado la simulación.

Ahora hay que procesar esta información. Los cuatro archivos de los informes y sus cuatro archivos gráficos - gráficos de balance - se copian en el sandbox TERMINAL_COMMONDATA_PATH\Files:

//--- reports -> TERMINAL_COMMONDATA_PATH\Files\
   string path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);

   if(!CopyFileW(slaveTerminalDataPath1+"\\"+expert_short_name+".htm",path+"\\Files\\"+"report_1"+".htm",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }
   if(!CopyFileW(slaveTerminalDataPath1+"\\"+expert_short_name+".png",path+"\\Files\\"+"report_1"+".png",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }

   if(!CopyFileW(slaveTerminalDataPath2+"\\"+expert_short_name+".htm",path+"\\Files\\"+"report_2"+".htm",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }
   if(!CopyFileW(slaveTerminalDataPath2+"\\"+expert_short_name+".png",path+"\\Files\\"+"report_2"+".png",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }

   if(!CopyFileW(slaveTerminalDataPath3+"\\"+expert_short_name+".htm",path+"\\Files\\"+"report_3"+".htm",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }
   if(!CopyFileW(slaveTerminalDataPath3+"\\"+expert_short_name+".png",path+"\\Files\\"+"report_3"+".png",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }

   if(!CopyFileW(slaveTerminalDataPath4+"\\"+expert_short_name+".htm",path+"\\Files\\"+"report_4"+".htm",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }
   if(!CopyFileW(slaveTerminalDataPath4+"\\"+expert_short_name+".png",path+"\\Files\\"+"report_4"+".png",false))
     {
      PrintFormat("Failed with error: %x",kernel32::GetLastError());
      return;
     }

Pero en los archivos de los informes obtenidos hay mucha información innecesaria, y esto dificulta considerablemente la aplicación de expresiones regulares. Por eso, en la función Compare multiple tests.mq5::ParsingReportToArray se realizan ciertas manipulaciones. Como resultado de las mismas, los archivos adquieren aproximadamente este aspecto:

 

Este archivo ya es más conveniente para usar la expresión regular "(>)(.*?)(<)", es decir, la búsqueda de cualquier símbolo que se encuentre entre los símbolos ">" y "<", además, el número de estos símbolos comienza a partir de cero.

Los resultados del funcionamiento de las expresiones regulares se ubican en cuatro matrices: arr_report_1, arr_report_2, arr_report_3 y arr_report_4. La información de estas matrices se usará para generar el código del informe comparativo final. Después de crear el informe final, llamamos la función WinAPI ShellExecuteW (para más detalles sobre ShellExecuteW, lea aquí ), e iniciamos el navegador:

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

Se abrirá la página del navegador, en la que se podrán comparar los resultados de las simulaciones del asesor en los cuatro símbolos. 

 

Conclusión

En el artículo se ha descrito una variante más entre las formas de valorar los resultados de la simulación del asesor en cuatro símbolos. Además, la simulación en los cuatro símbolos elegidos se da de forma paralela en los cuatro terminales, y como fruto de todo ello, tenemos un recuadro en el que se reúnen los resultados de todas estas pruebas. 


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

Ejemplo de desarrollo de una estrategia con spread para futuros en la bolsa de Moscú Ejemplo de desarrollo de una estrategia con spread para futuros en la bolsa de Moscú
MetaTrader 5 permite desarrollar y simular robots que comercien simultáneamente en varios instrumentos. El simulador de estrategias incorporado en la plataforma descarga de forma automática del servidor comercial del bróker la historia de ticks y tiene en cuenta las especificaciones de los contratos: el desarrollador no tiene que hacer nada con sus propias manos. Esto permite reproducir todas las condiciones del entorno comercial de forma fácil y extraordinariamente fiable. MetaTrader 5 permite desarrollar y poner a prueba robots, incluso simulando intervalos de milisegundos entre la llegada de ticks de diferentes símbolos. En este artículo mostraremos cómo realizar el desarrollo y la simulación de una estretegia de spread con dos futuros de la bolsa de Moscú.
Zigzag universal Zigzag universal
El Zigzag es uno de los indicadores más populares entre los usuario de MetaTrader 5. En este artículo se han analizado las posibilidades de creación de diferentes versiones del Zigzag. Como resultado, obtenemos un indicador universal con amplias posibilidades para la ampliación de la funcionalidad, el cual es muy cómodo utilizar en el desarrollo de los Asesores Expertos y otros indicadores.
Interfaces gráficas X: Control "Hora", control "Lista de las casillas de verificación" y ordenamiento (sort) de la tabla (build 6) Interfaces gráficas X: Control "Hora", control "Lista de las casillas de verificación" y ordenamiento (sort) de la tabla (build 6)
Continuamos con el desarrollo de la librería para la creación de interfaces gráficas. Esta vez mostraremos los controles «Hora» y «Lista de las casillas de verificación». Aparte de eso, a la clase de la tabla tipo CTable se le ha sido añadida la posibilidad de organizar los datos en orden ascendiente y descendiente.
Estrategia de trading '80-20' Estrategia de trading '80-20'
En este artículo se describe la creación de las herramientas (indicador y Asesor Experto) para el análisis de la estrategia comercial '80-20'. Las reglas de esta Estrategia Comercial han sido tomadas del libro titulado «Street Smarts: High Probability Short-Term Trading Strategies» escrito por Linda Raschke y Laurence Connors. Las reglas han sido formalizadas en el lenguaje MQL5, y el indicador y el Asesor Experto diseñados a base de esta estrategia han sido probados en el historial actual del mercado.