Fundamentos de la Simulación en MetaTrader 5

MetaQuotes | 20 diciembre, 2013

Por qué necesitamos un Probador de Estrategias

La idea del trading automatizado es atractiva por el hecho de que el robot de trading puede trabajar sin parar 24 horas al día, siete días a la semana. El robot no se cansa, no duda ni tiene miedo, está totalmente exento de cualquier problema psicológico. Es lo suficientemente independiente para formalizar claramente las reglas de trading e implementarlas en los algoritmos, y el robot está listo para trabajar incansablemente. Pero antes, debe asegurarse de que las siguientes dos condiciones se cumplen:

Para obtener respuestas a estas preguntas, acudimos al Probador de Estrategias, incluido en el terminal de cliente del MetaTrader 5.


Modos de Generación de Ticks

Un Asesor Experto es un programa escrito en MQL5 que funciona como respuesta a un evento externo. El EA tiene una función correspondiente (controlador de eventos) para cada evento predefinido.

El evento NewTick (cambio de precio) es el evento principal para el EA y, por tanto, debemos generar una secuencia de ticks para la simulación del EA. Hay 3 modos de generación de ticks implementados en el Probador de Estrategias del terminal de cliente de MetaTrader 5:

El modo básico y más detallado es el modo "Todos los ticks". Los otros dos modos son la simplificación del básico, y se describirán en comparación con éste. Considere los tres modos en orden para entender las diferencias entre ellos.


"Todos los Ticks"

Los datos de cotizaciones del historial para instrumentos financieros se transfiere del servidor de trading al terminal de cliente MetaTrader 5 en forma de paquetes de barras de minuto. Puede obtener información detallada sobre la llegada de solicitudes y la construcción de los intervalos requeridos en el capítulo Organizar Acceso a Datos de la documentación de referencia de MQL5.

El elemento mínimo del historial de precio es la barra de minuto, de la cual puede obtener información en los cuatro valores del precio:

La nueva barra de minuto no se abre en el momento en el que comienza el nuevo minuto (el número de segundos pasa a 0), sino cuando ocurre un tick - un cambio de precio de al menos un punto. La figura muestra la primera barra de minuto de la nueva semana de trading, que tiene el momento de apertura en 2011.01.10 00:00. La diferencia de precio entre viernes y lunes, que podemos observar en el gráfico, es normal, puesto que los porcentajes de divisas fluctúan incluso durante fines de semana en respuesta a las últimas noticias.

Fig. 1. La diferencia de precio entre viernes y lunes

Para esta barra, solo sabemos que la barra de minuto se abrió el 10 de enero de 2011 a las 00 horas 00 minutos, pero no sabemos nada sobre los segundos. Se podría haber abierto a las 00:00:12 o a las 00:00:36 (12 o 36 después del comienzo de un nuevo día), o en cualquier otro momento dentro de ese minuto. Pero sabemos que el precio de Apertura de EURUSD estaba a 1.28940 en el momento de apertura de la nueva barra de minuto.

Tampoco sabemos en qué segundo se recibió el tick correspondiente con el precio de cierre de la barra de minuto considerada. Solo sabemos una cosa: el último precio de Cierre de la barra de minuto. Para este minuto, el precio era de 1.28958. Igualmente se desconoce el momento de aparición de los precios Alto y Bajo, pero sabemos que los precios máximo y mínimo estaban en niveles de 1.28958 y 1.28940, respectivamente.

Para simular la estrategia de trading, necesitamos una secuencia de ticks en la que se simule el trabajo del Asesor Experto. Por tanto, por cada barra de minuto, conocemos los 4 puntos de control donde ha estado el precio definitivamente. Si una barra solo tiene 4 ticks, es suficiente información para realizar una simulación, pero generalmente el volumen de ticks es mayor a 4.

Por tanto, se deben generar puntos de control adicionales que sucedieron entre los precios de Apertura, Alto, Bajo y de Cierre. Los principios del modo de generación de ticks "Todos los ticks" se describe en El Algoritmo de la Generación de Ticks en el Probador de Estrategias del Terminal de MetaTrader 5, una figura que puede ver abajo.

Fig. 2. Algoritmo de generación de ticks

Al hacer la simulación en el modo "Todos los Ticks", la función OnTick() del EA se llamará en cada punto de control. Cada punto de control es un tick de una secuencia generada. El EA recibirá el tiempo y precio del tick simulado, igual que lo haría al trabajar online.

Importante: el modo de simulación "Todos los ticks" es el más preciso, pero al mismo tiempo, el que más tiempo tarda. Para una simulación inicial de la mayoría de las estrategias de trading, generalmente es suficiente usar uno de los otros dos modos de simulación.


"1 Minuto OHLC"

El modo "Todos los ticks" es el más preciso de los tres, pero al mismo tiempo es el más lento. El funcionamiento del controlador OnTick() se da con cada tick, y el volumen de ticks puede ser muy grande. Para una estrategia en la que la secuencia de ticks de movimiento de precio a través de la barra no es importante, hay un modo de simulación más rápido y genérico: "1 minuto OHLC".

En el modo "1 minuto OHLC", la secuencia de ticks se basa solo en los precios OHLC de las barras de minuto, y así el número de los puntos de control generados se reduce significativamente. Por consiguiente, se reduce también el tiempo de la simulación. La activación de la función OnTick () se realiza en todos los puntos de control, que se basan en los precios de las barras de minuto OHLC.

El rechazo a generar ticks intermedios adicionales entre los precios de Apertura, Alto, Bajo y de Cierre lleva a la aparición de un determinismo rígido en el desarrollo de los precios desde el momento en el que se determina el precio de Apertura. Esto hace posible crear un "Grial de Simulación", que muestra un gráfico hacia arriba del saldo de simulación.

Un ejemplo de tal Grial se presenta en el Código Base - Grr-al.

Fig. 3. El grial del Asesor Experto usa las cualidades especiales de los precios OHLC

La figura muestra un gráfico muy atractivo de esta simulación del EA. ¿Cómo se obtuvo? Sabemos 4 precios por barra de minuto, y sabemos también que el primero es el precio de Apertura, y que el último es el precio de Cierre. Tenemos los precios Alto y Bajo entre ellos, y la secuencia del momento en que ocurrieron nos es desconocida, pero sabemos que el precio Alto es mayor o igual al precio de Apertura (y el Bajo es menor o igual al precio de Apertura).

Esto es suficiente para determinar el momento en el que se recibió el precio de Apertura, y después analizar el siguiente tick para determinar si el precio Alto o Bajo vino antes que él. Si el precio está por debajo del precio de Apertura, entonces tenemos un precio Bajo y compra en este tick. El siguiente tick se corresponderá con el precio Alto, en el cual cerraremos la compra y abriremos para la venta. El siguiente tick es el último, el precio de Cierre, y con él cerramos la venta. 

Si después del precio recibimos un tick con un precio mayor al precio de Apertura, entonces la secuencia de transacciones se invierte. Procese una barra de minuto en este modo "trampa", y espere al siguiente.

Al simular tal EA en el historial, todo funciona correctamente, pero una vez que lo lanzamos online, la verdad se revela: el saldo de la línea se mantiene estable, pero va hacia abajo. Para revelar este truco, simplemente debemos abrir el EA en el modo "Todos los ticks".

Nota: Si los resultados de la simulación del EA en los modos de simulación genéricos ("1 minuto OHLC" y "Solo Precios de Apertura") parecen demasiado buenos, asegúrese de hacer la simulación en el modo "Todos los ticks".


"Solo Precios de Apertura"

En este modo, los ticks se generan basándose en los precios OHLC del intervalo seleccionado para la simulación. La función OnTick() del Asesor Experto solo se activa una vez al principio de la barra en el precio de Apertura. A causa de esta cualidad, los niveles de detención y operaciones pendientes pueden activarse a un precio que difiere del especificado (especialmente cuando se hacen simulaciones en intervalos más altos). En lugar de ello, nos da la oportunidad de hacer una simulación de evaluación rápida del Asesor Experto.

Una excepción en la generación de ticks en el modo "Solo Precios de Apertura" son los períodos W1 y MN1: para estos intervalos, se generan ticks para los precios OHLC de cada día, no precios OHLC de la semana o mes.

Supongamos que hacemos una simulación de un Asesor Experto en EURUSD H1 en el modo "Solo precios de Apertura". En este caso, el número total de ticks (puntos de control) no sobrepasará la cuadruplicación de las barras de una hora dentro del intervalo de la simulación. Pero el controlador OnTick() se llama solo una vez en la apertura de la barra de una hora. Las revisiones requeridas para una simulación correcta se dan en el resto de los ticks (que están "ocultos" para el EA).

Si no hay posiciones abiertas u órdenes abiertas, no tendremos que realizar estas revisiones en ticks ocultos, y el aumento de velocidad será notable. Este modo de "Solo precios de Apertura" es recomendable para estrategias de simulación que procesan transacciones solo durante la apertura de la barra y no usan órdenes pendientes, así como órdenes de StopLoss y TakeProfit. Para la clase de estas estrategias se preserva la precisión necesaria de la simulación.

Utilicemos el Asesor Experto de Medias Móviles del paquete estándar como ejemplo de un EA que se puede simular en cualquier modo. La lógica de este EA está hecha de tal modo que todas las decisiones se hacen al abrir una barra, y las transacciones se llevan a cabo inmediatamente, sin el uso de órdenes pendientes.

Lleve a cabo una simulación del EA en EURUSD H1 en un intervalo desde 2010.01.09 a 2010.31.12, y compare los gráficos. La figura muestra el gráfico de saldo del informe de la simulación para los tres modos.


Fig. 4. El gráfico de la simulación del EA de Medias Móviles (Moving Average.mq5) del paquete estándar no depende del modo de simulación (haga click en la imagen para hacerla más grande).

Como puede ver, los gráficos en diferentes modos de simulación son exactamente iguales para el EA de Medias Móviles del paquete estándar.

Hay algunas limitaciones en el modo "Solo Precios de Apertura":

Nota: El modo "Solo Precios de Apertura" es el más rápido, pero no es recomendable para todas las estrategias de trading. Seleccione el modo de simulación deseado basándose en las características del sistema de trading. 

Para concluir con la sección de los modos de generación de ticks, tomemos una comparación visual de los diferentes modos de generación de ticks para EURUSD, para dos barras M15 en un intervalo de 2011.01.11 21:00:00 - 2011.01.11 21:30:00.

Los ticks se guardaron en diferentes archivos usando el EA WriteTicksFromTester.mq5 y el final de esos nombres de archivos se especifica en los parámetros de entrada filenamEveryTick, filenameOHLC y filenameOpenPrice.

Fig. 5. Podemos especificar las fechas inicial y de finalización de los ticks (las variables "start" y "end") para el WriteTicksFromTester Asesor Experto

Para obtener tres archivos con tres secuencias de ticks (para cada uno de los siguientes modos: "Todos los ticks", "1 Minuto OHLC" y "Solo precios de Apertura"), el EA se ejecutó tres veces en los modos correspondientes, en procesos independientes. A continuación, los datos de estos tres archivos se mostraron en el gráfico usando el indicador TicksFromTester.mq5. El código del indicador está adjunto con este artículo.


Fig. 6. La secuencia de ticks en el Probador de Estrategias del terminal de MetaTrader 5 en tres modos de simulación diferentes

Por defecto, todas las operaciones de archivo en el lenguaje MQL5 se hacen dentro de ciertos directorios del programa, o "sandbox de archivos", y durante la simulación, el EA tiene acceso solo a su propia "sandbox de archivos". Para que el indicador y el EA trabajen con archivos de una carpeta concreta durante la simulación, usamos la señal FILE_COMMON. Un ejemplo de código del EA:

//--- open the file
   file=FileOpen(filename,FILE_WRITE|FILE_CSV|FILE_COMMON,";");
//--- check file handle
   if(file==INVALID_HANDLE)
     {
      PrintFormat("Error in opening of file %s for writing. Error code=%d",filename,GetLastError());
      return;
     }
   else
     {
      PrintFormat("The file will be created in %s folder",TerminalInfoString(TERMINAL_COMMONDATA_PATH));
     }

Para leer los datos en el indicador, usamos también la señal FILE_COMMON. Esto nos permitirá evitar la transferencia manual de los archivos necesarios de una carpeta a otra.

//--- open the file
   int file=FileOpen(fname,FILE_READ|FILE_CSV|FILE_COMMON,";");
//--- check file handle
   if(file==INVALID_HANDLE)
     {
      PrintFormat("Error in open of file %s for reading. Error code=%d",fname,GetLastError());
      return;
     }
   else
     {
      PrintFormat("File will be opened from %s",TerminalInfoString(TERMINAL_COMMONDATA_PATH)); 
     }


Simulación de diferencial

La diferencia de precio entre el precio de Compra y el precio de Venta se llama "diferencial". Durante una simulación, el diferencial no se modela, sino que se toma de datos del historial. Si el diferencial es menor o igual a cero en los datos del historial, entonces el agente de simulación usará el diferencial para el tiempo de los datos del historial solicitados.

En el Probador de Estrategias, el diferencial siempre se considera flotante. Esto significa que el SymbolInfoInteger(símbolo, SYMBOL_SPREAD_FLOAT) siempre devuelve un valor "true".

Además, los datos del historial contienen valores de tick y volúmenes de trading. Para el almacenamiento y recuperación de datos usamos una estructura MqlRates especial:

struct MqlRates
  {
   datetime time;         // opening bar time
   double   open;         // opening price Open
   double   high;         // the highest price High
   double   low;          // the lowest price Low
   double   close;        // the closing price Close
   long     tick_volume;  // the tick volume
   int      spread;       // spread
   long     real_volume;  // market volume  
  };


Las Variables Globales del Terminal de Cliente

Durante la simulación, las variables globales del terminal de cliente también se emulan, pero no están relacionadas con las variables globales del terminal actuales, que se pueden ver en el terminal pulsado la tecla F3. Esto significa que todas las operaciones con las variables globales del terminal, durante la simulación, tienen lugar fuera del terminal de cliente (en el agente de simulación).


El Cálculo de Indicadores Durante una Simulación

En el modo de tiempo real, los valores del indicador se calculan en cada tick. El Probador de Estrategias adoptó un modelo económico y eficaz para calcular indicadores: los indicadores se recalculan solo inmediatamente antes de ejecutar el EA. Esto significa que el recálculo de los indicadores se hace antes de la llamada a las funciones OnTick(), OnTrade() y OnTimer().

No importa si hay una llamada al indicador o no en un controlador de eventos específico: todos los indicadores cuyos identificadores hayan sido creados por las funciones iCustom() o IndicatorCreate() se recalcularán antes de llamar al controlador de eventos.

Consecuentemente, al hacer la simulación en el modo "Todos los ticks", el cálculo de los indicadores tiene lugar antes de la llamada a la función OnTick().

Si el temporizador está en el EA usando la función EventSetTimer(), entonces el indicador se recalculará antes de cada llamada al controlador OnTimer(). Por tanto, el tiempo de simulación puede aumentar considerablemente con el uso de un indicador no escrito de forma óptima.


Cargar el Historial durante la Simulación

El historial de un símbolo simulado se sincroniza y carga por el terminal desde el servidor de trading antes de empezar el proceso de simulación. Durante la primera vez, el terminal carga todo el historial disponible de un símbolo para no solicitarlo más tarde. Después de eso, solo se cargan los datos nuevos.

Un agente de simulación recibe el historial de un símbolo simulado del terminal de cliente justo después del comienzo de la simulación. Si se usan datos de otros instrumentos en el proceso de simulación (por ejemplo, si es un Asesor Experto multidivisa), el agente de simulación solicitará el historial requerido del terminal de cliente durante la primera llamada a tales datos. Si los datos del historial no están disponibles en el terminal, se pasan inmediatamente al agente de simulación. Si los datos no están disponibles, el terminal los solicitará y descargará del servidor, y después los pasará al agente de simulación.

También se requiere datos de instrumentos adicionales para calcular porcentajes cruzados para operaciones de trading. Por ejemplo, al simular una estrategia en EURCHF con el depósito actualmente en USD, antes de procesar la primera operación de trading, el agente de simulación solicitará el historial de datos de EURUSD y USDCHF del terminal de cliente, aunque la estrategia no usará una llamada directa a todos estos símbolos.

Antes de simular una estrategia multidivisa, se recomienda descargar todos los datos del historial necesarios al terminal de cliente. Esto le ayudará a evitar retrasos en la simulación/optimización asociados con la descarga de los datos necesarios. Puede descargar el historial, por ejemplo, abriendo los gráficos correspondientes y recorriéndolos hasta el comienzo del historial. Un ejemplo de carga forzada del historial al terminal está disponible en la sección Organizar Acceso a Datos de la documentación de referencia de MQL5.

El terminal carga el historial de un servidor de trading solo una vez, la primera vez que el agente solicita el historial de un símbolo simulado desde el terminal. El historial se carga en un paquete para reducir el tráfico.
Los agentes de simulación, a su vez, reciben el historial del terminal en forma de paquete. Durante la siguiente simulación, el Probador no carga el historial del terminal porque los datos requeridos ya están disponibles desde la simulación anterior.


Simulación de Múltiples Divisas

El Probador de Estrategias nos permite ejecutar una simulación de estrategias, realizando trading en múltiples símbolos. Tales EAs reciben normalmente el nombre de Asesores Expertos multidivisa, puesto que, originariamente, en las plataformas anteriores, la simulación se hacía solo para un símbolo individual. En el Probador de Estrategias del terminal de MetaTrader 5 podemos modelar el trading para todos los símbolos disponibles.

El Probador carga el historial en los símbolos usados del terminal de cliente (¡no del servidor de trading!) automáticamente durante la primera llamada a los datos del símbolo.

El agente de simulación descarga solo el historial que falta, con un pequeño margen para facilitar los datos necesarios en el historial para el cálculo de los indicadores en el momento del comienzo de la simulación. Para los intervalos D1 y menores, el volumen mínimo del historial descargado es de un año.

Por tanto, si hacemos una simulación con un intervalo 2010.11.01-2010.12.01 (simulación para un intervalo de un mes) con un período de M15 (cada barra equivale a 15 minutos), entonces el terminal solicitará el historial para el instrumento del año 2010 entero. Para el intervalo semanal, se solicitará un historial de 100 barras, lo que equivale a dos años (un año tiene 52 semanas). Para la simulación de un intervalo mensual, el agente solicitará el historial de 8 años (12 meses x 8 años = 96 meses).

Si no hay barras necesarias, la fecha inicial de la simulación cambiará automáticamente del pasado al presente para facilitar la reserva necesaria de barras antes de la simulación.

Durante la simulación se emula también la "Observación del Mercado", de la cual se puede obtener información sobre símbolos.

Por defecto, al principio de la simulación solo hay un símbolo en la "Observación del Mercado" del Probador de Estrategias: el símbolo sobre el que se ejecuta la simulación. Todos los símbolos necesarios se conectan a la "Observación de Mercado" del Probador de Estrategias (¡no del terminal!) automáticamente cuando se hace referencia a ellos.  

Antes de comenzar con la simulación de un Asesor Experto multidivisa, es necesario seleccionar los símbolos requeridos para la simulación en la "Observación del Mercado" en el terminal y cargar los datos requeridos. Durante la primera llamada a un símbolo "desconocido", su historial se sincronizará automáticamente entre el agente de simulación y el terminal de cliente. Un símbolo "desconocido" es un símbolo que no es aquel en el que se ejecuta la simulación.

Se dan referencias a los datos en "otro" símbolo en los siguientes casos:

En el momento de la primera llamada a "otro" símbolo, el proceso de simulación se detiene y se descarga el historial para el símbolo/intervalo desde el terminal al agente de simulación. Al mismo tiempo, se crea la secuencia de generación de ticks para este símbolo.

Se genera una secuencia de ticks individual para cada símbolo, de acuerdo con el modo de generación de ticks seleccionado. También puede solicitar el historial explícitamente para los símbolos deseados llamando a la función SymbolSelect() en el controlador OnInit() - la descarga del historial se llevará a cabo inmediatamente antes de la simulación en el Asesor Experto.

Por tanto, realizar simulaciones multidivisa en el terminal de cliente MetaTrader 5 no requiere ningún esfuerzo adicional. Simplemente, abra los gráficos de los símbolos correspondientes en el terminal de cliente. El historial se cargará automáticamente del servidor de trading para todos los símbolos requeridos, suponiendo que contenga estos datos.


La Hora en el Simulador del Probador de Estrategias

Durante la simulación, la hora local TimeLocal() siempre es igual a la hora del servidor TimeTradeServer(). Por otra parte, la hora del servidor siempre es igual a la hora correspondiente a la hora GMT - TimeGMT(). De este modo, todas las funciones muestran la misma hora durante la simulación.

La falta de diferencia entre la hora GMT, la hora local y la hora del servidor en el Probador de Estrategias se hace de forma deliberada para el caso de que no haya una conexión al servidor. Los resultados de la simulación siempre deberían ser los mismos, independientemente de si hay o no una conexión. La información sobre la hora del servidor no se guarda localmente, y se toma del servidor.

 

La Función OnTimer() en el Probador de Estrategias

MQL5 facilita la oportunidad de gestionar eventos del temporizador. La llamada del controlador OnTimer() se hace independientemente del modo de simulación.

Esto significa que si una simulación se efectúa en el modo "Solo precios de Apertura" para el período H4, y el EA tiene un temporizador configurado para hacer una llamada por segundo, entonces, en la apertura de cada barra H4, el controlador OnTick() se llamará una vez, y el controlador OnTimer() se llamará 14.400 veces (3600 segundos * 4 horas). La cantidad en la que aumentará el tiempo de simulación del EA depende de la lógica del EA.

Para comprobar la dependencia del tiempo de simulación de la frecuencia dada en el temporizador, escribimos un EA más sencillo, sin ninguna operación de trading.

//--- input parameters
input int      timer=1;              // timer value, sec
input bool     timer_switch_on=true; // timer on
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- run the timer if  timer_switch_on==true
   if(timer_switch_on)
     {
      EventSetTimer(timer);
     }
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- stop the timer
   EventKillTimer();
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
// take no actions, the body of the handler is empty
  }
//+------------------------------------------------------------------+

Se tomaron medidas de tiempo de simulación con diferentes valores del parámetro del temporizado (periodicidad del evento del temporizador). En los datos obtenidos, representamos un tiempo de simulación como función del período del temporizador.

Fig. 7. Tiempo de simulación como función de Período del temporizador

Se puede ver claramente que cuanto más pequeño es el parámetro del temporizador durante la inicialización de la función EventSetTimer(Temporizador), más pequeño es el período entre llamadas del controlador OnTimer(), y mayor es el tiempo de simulación bajo las mismas condiciones.


La Función Sleep() en el Probador de Estrategias

La función Sleep() permite al EA o al script suspender la ejecución del programa mql5 durante un rato al trabajar en el gráfico. Esto puede ser útil al solicitar datos que no están listos en el momento de la solicitud y debe esperar hasta que estén disponibles. Puede encontrar un ejemplo detallado del uso de la función Sleep() en la sección Distribución de acceso a datos.

El proceso de simulación no se ralentiza con las llamadas a Sleep().Cuando se llama a la función Sleep(), los ticks generados se "reproducen" con un retraso especificado, que podría resultar en la activación de órdenes pendientes, detenciones, etc. Tras una llamada a Sleep(), el tiempo simulado en el Probador de Estrategias aumenta por un intervalo especificado en el parámetro de la función Sleep.

Si, como resultado de la función Sleep(), el tiempo actual en el Probador de Estrategias sobrepasara el período de simulación, entonces recibiría un error: "Ciclo infinito de Sleep detectado durante la simulación". Si recibe este error, los resultados de la simulación no serán rechazados; todas las computaciones se realizarán en su volumen completo (el número de transacciones, disminuciones, etc), y los resultados de esta simulación se pasan al terminal.

La función Sleep() no funciona en OnDeinit(), puesto que tras llamarla, el tiempo de simulación sobrepasará seguro el alcance del intervalo de la simulación.

Fig. 7. El esquema del uso de la función Sleep() en el Probador de Estrategias del terminal de MetaTrader 5.

Fig. 8. El esquema del uso de la función Sleep() en el Probador de Estrategias del terminal de MetaTrader 5.


Usar el Probador de Estrategias para Problemas de Optimización en Cálculos Matemáticos

El Probador del terminal de MetaTrader 5 se puede usar no solo para simular estrategias de trading, sino también para cálculos matemáticos. Para usarlo, debe seleccionar el modo "Cálculos Matemáticos":

En este caso, solo se llamará a tres funciones: OnInit(), OnTester(), OnDeinit(). En el modo "Cálculos Matemáticos", el Probador de Estrategias no genera tick alguno ni descarga el historial.

El Probador de Estrategias trabaja también en el modo "Cálculos Matemáticos" si especifica una fecha inicial superio a la fecha de finalización.

Al usar el Probador para resolver problemas matemáticos, no se realiza la carga del historial ni la generación de ticks.

Un problema matemático típico para el Probador de Estrategias del MetaTrader 5: buscar el extremo de la función con varias variables.

Para resolverlo, debemos hacer lo siguiente:

Compile el EA, y abra la ventana "Probador de Estrategias". En la pestaña de "parámetros de entrada", seleccione las variables de entrada, y defina el conjunto de valores de parámetro especificando los valores de inicio, detención y pasos para cada una de las variables de la función.

Seleccione el tipo de optimización: "Lenta - Algoritmo completo" (búsqueda completa de espacio de parámetros) o "Rápida - Algoritmo genérico". Para una búsqueda simple del extremo de la función, es mejor elegir una optimización rápida, pero si prefiere calcular los valores de todos los conjuntos de variables, entonces es mejor usar la optimización lenta.

Seleccione el modo "Cálculos Matemáticos" y, pulsando el botón "Inicio", ejecute el proceso de optimización. Note que cuando se hace una optimización, el Probador de Estrategias buscará los valores máximos de las funciones. Para encontrar un mínimo local, devuelva el inverso del valor de la función computada de la función OnTester():

return(1/function_value);

Es necesario comprobar que function_value no es igual a cero, puesto que de lo contrario podemos obtener el error crítico de dividir entre cero.

Hay otra forma más conveniente que no distorsiona los resultados de la optimización, sugerida por uno de los lectores de este artículo:

return(-function_value);

Esta opción no requiere la comprobación de si el function_value_ for es igual a cero, y la superficie de los resultados de la optimización en una representación en 3D tiene la misma forma, pero es un reflejo del original.

Como ejemplo, facilitamos aquí la función sink():

El código del EA para encontrar el extremo de esta función se coloca en el OnTester():

//+------------------------------------------------------------------+
//|                                                         Sink.mq5 |
//|                        Copyright 2011, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- input parameters
input double   x=-3.0; // start=-3, step=0.05, stop=3
input double   y=-3.0; // start=-3, step=0.05, stop=3
//+------------------------------------------------------------------+
//| Tester function                                                  |
//+------------------------------------------------------------------+
double OnTester()
  {
//---
   double sink=MathSin(x*x+y*y);
//---
   return(sink);
  } 
//+------------------------------------------------------------------+
Lleve a cabo una optimización y vea los resultados de la optimización en forma de gráfico en 2D.

Fig. 9. Los resultados de una optimización completa de la función sink (x*x+y*y) como un gráfico en 2D

Cuanto mejor sea el valor de un par de parámetros dados (x, y), más saturado será el color. Como se esperaba de la forma de la fórmula de la función sink(), sus valores forman círculos concéntricos con un centro (0,0). Se puede ver en el gráfico en 3D que la función sink() no tiene un extremo global individual:

Gráfico en 3D de la función Sink


La Sincronización de Barras en el modo "Solo precios de Apertura"

El Probador en el terminal de cliente de MetaTrader 5 nos permite comprobar los llamados EAs "multidivisa". Un EA multidivisa es un EA que hace trading en dos o más símbolos.

La simulación de estrategias que hacen trading en múltiples símbolos supone unos requisitos técnicos adicionales en el Probador:

El Probador de Estrategias genera y reproduce una secuencia de ticks para cada instrumento de acuerdo con el modo de trading seleccionado. Al mismo tiempo, se abre una barra nueva para cada símbolo, independientemente de cómo se abrió la barra en otro símbolo. Esto significa que al simular un EA multidivisa, se puede dar una situación (y a menudo ocurre) en la que ya se ha abierto una barra para un instrumento, pero para el otro no. Por tanto, en la simulación, todo ocurre igual que lo haría en la realidad.

Esta simulación auténtica del historial en el Probador no causa ningún problema mientras que se usen los modos de simulación "Todos los ticks" y "1 minute OHLC". Para estos modos, se generan suficientes ticks para una vela para poder esperar hasta que se complete la sincronización de barras de diferentes símbolos. ¿Pero cómo simulamos estrategias multidivisa en el modo "Solo precios de Apertura", si la sincronización de barras en instrumentos de trading es obligatoria? En este modo, el EA se llama solo en un tick, que se corresponde con el momento de la apertura de las barras.

Lo mostraremos con un ejemplo: si simulamos un EA en el EURUSD, y una vela de una hora se ha abierto en EURUSD, entonces podremos reconocer fácilmente este hecho: en la simulación en el modo "Solo precios de Apertura", el evento NewTick se corresponde con el momento de apertura de la barra en el período de simulación. Pero no hay garantía alguna de que la nueva vela se haya abierto en el símbolo USDJPY, que se usó en el EA.

Bajo circunstancias normales, es suficiente completar el trabajo de la función OnTick() para comprobar la emergencia de una nueva barra en USCJPY en el siguiente tick. Pero al hacer la simulación en el modo "Solo precios de Apertura", no habrá otro tick, y así puede parecer que este modo no está hecho para simular EAs multidivisa. Pero no es el caso: no olvide que el Probador en el MetaTrader 5 se comporta igual que lo haría en la vida real. ¡Puede esperar a que se abra una barra en otro símbolo usando la función Sleep()!

El código del EA Synchronize_Bars_Use_Sleep.mq5 muestra un ejemplo de sincronización de barras en el modo "Solo precios de Apertura":

//+------------------------------------------------------------------+
//|                                   Synchronize_Bars_Use_Sleep.mq5 |
//|                        Copyright 2011, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- input parameters
input string   other_symbol="USDJPY";
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- check symbol
   if(_Symbol==other_symbol)
     {
      PrintFormat("You have to specify the other symbol in input parameters or select other symbol in Strategy Tester!");
      //--- forced stop testing
      return(-1);
     }
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- static variable, used for storage of last bar time
   static datetime last_bar_time=0;
//--- sync flag
   static bool synchonized=false;

//--- if static variable isn't initialized
   if(last_bar_time==0)
     {
      //--- it's first call, save bar time and exit
      last_bar_time=(datetime)SeriesInfoInteger(_Symbol,Period(),SERIES_LASTBAR_DATE);
      PrintFormat("The last_bar_time variable is initialized with value %s",TimeToString(last_bar_time));
     }

//--- get open time of the last bar of chart symbol
   datetime curr_time=(datetime)SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);
//--- if times aren't equal
   if(curr_time!=last_bar_time)
     {
      //--- save open bar time to the static variable
      last_bar_time=curr_time;
      //--- not synchronized
      synchonized=false;
      //--- print message
      PrintFormat("A new bar has appeared on symbol %s at %s",_Symbol,TimeToString(TimeCurrent()));
     }
//--- open time of the other symbol's bar
   datetime other_time;

//--- loop until the open time of other symbol become equal to curr_time
   while(!(curr_time==(other_time=(datetime)SeriesInfoInteger(other_symbol,Period(),SERIES_LASTBAR_DATE)) && !synchonized))
     {
      PrintFormat("Waiting 5 seconds..");
      //--- wait 5 seconds and call SeriesInfoInteger(other_symbol,Period(),SERIES_LASTBAR_DATE)
      Sleep(5000);
     }
//--- bars are synchronized
   synchonized=true;
   PrintFormat("Open bar time of the chart symbol %s: is %s",_Symbol,TimeToString(last_bar_time));
   PrintFormat("Open bar time of the symbol %s: is %s",other_symbol,TimeToString(other_time));
//--- TimeCurrent() is not useful, use TimeTradeServer()
   Print("The bars are synchronized at ",TimeToString(TimeTradeServer(),TIME_SECONDS));
  } 
//+------------------------------------------------------------------+

Note la última línea en el EA, que muestra la fecha actual cuando se estableció el hecho de la sincronización:

   Print("The bars synchronized at ",TimeToString(TimeTradeServer(),TIME_SECONDS));

Para mostrar la fecha actual, usamos la función TimeTradeServer (), en lugar de la función TimeCurrent (). La función TimeCurrent() devuelve la fecha del último tick, que no cambia tras usar Sleep(). Pruebe el EA en el modo "Solo precios de Apertura", y verá un mensaje sobre la sincronización de las barras.


Use la función TimeTradeServer() en lugar de TimeCurrent() si necesita obtener la fecha actual del servidor, y no la fecha de llegada del último tick.

Hay otra forma de sincronizar barras: usando un temporizador. Un ejemplo de tal EA es Synchronize_Bars_Use_OnTimer.mq5, que está adjunto a este artículo.


La función IndicatorRelease() en el Probador

Tras completar una simulación individual, se abre automáticamente un gráfico del instrumento que muestra las transacciones completas y los indicadores usados en el EA. Esto ayuda a comprobar visualmente los puntos de entrada y salida, y compararlos con los valores de los indicadores.  

Nota: los indicadores mostrados en el gráfico que se abre automáticamente tras completarse la simulación se calculan de nuevo después de que se complete la simulación. Aún si estos indicadores se usaran en el EA simulado.

Pero en algunos casos, el programador puede querer ocultar la información sobre qué indicadores estuvieron involucrados en los algoritmos de trading. Por ejemplo, el código del EA se alquila o vende como archivo ejecutable, sin la provisión del código fuente. Por esta razón, la función IndicatorRelease() es apropiada.

Si el terminal configura una plantilla con el nombre tester.tpl en el directorio directory/profiles/templates del terminal de cliente, entonces se aplicará al gráfico abierto. En su ausencia, se aplica la plantilla por defecto. (default.tpl).

La función IndicatorRelease() tiene por objetivo original liberar la porción de cálculo del indicador, si ya no se necesita. Esto le permite guardar ambos, la memoria y los recursos CPU, porque cada tick llama a un cálculo del indicador. Su segundo propósito es la prohibición de mostrar un indicador en el gráfico de la simulación tras una ejecución de prueba individual.

Para prohibir mostrar el indicador en el gráfico tras la simulación, llame a la función IndicatorRelease() con el controlador del indicador OnDeinit(). La función OnDeinit() siempre se llama después de completar y antes de mostrar el gráfico de la simulación.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   bool hidden=IndicatorRelease(handle_ind);
   if(hidden) Print("IndicatorRelease() successfully completed");
   else Print("IndicatorRelease() returned false. Error code ",GetLastError());
  }
Para prohibir mostrar el indicador en el gráfico tras completar una simulación, use a la función IndicatorRelease() en el controlador OnDeinit().


Gestión de Eventos en el Probador

La presencia del controlador OnTick() en el EA no es obligatoria para que este sea sujeto de una simulación con datos del historial en el Probador del MetaTrader 5. Es suficiente con que el EA contenga al menos uno de los siguientes controladores de funciones:

Al hacer una simulación en el EA, podemos controlar eventos personalizados usando la función OnChartEvent(), pero en los indicadores, esta función no se puede llamar en el Probador. Aún si el indicador tiene el controlador de eventos OnChartEvent() y este indicador se usa en el EA simulado, el indicador en sí no recibirá ningún evento personalizado.

Durante la simulación, un indicador puede generar eventos personalizados usando la función EventChartCustom(), y el EA puede procesar este evento en el OnChartEvent().


Agentes de Simulación

La simulación en el terminal de cliente MetaTrader 5 se lleva a cabo usando agentes de simulación. Los agentes locales se crean y activan automáticamente. El número por defecto de objetos locales se corresponde con el número de núcleos en un ordenador.

Cada agente de simulación tiene su propia copia de las variables globales, que no están relacionadas con el terminal de cliente. El terminal en sí mismo es el despachador que distribuye la tarea a los agentes locales y remotos. Tras ejecutar una tarea en la simulación de un EA con los parámetros dados, el agente devuelve los resultados al terminal. En una simulación se usa solo un agente.

El agente guarda el historial recibido del terminal en carpetas separadas por nombre del instrumento, para que el historial para EURUSD se guarde en una carpeta llamada EURUSD. Además, el historial de los instrumentos está separado por sus fuentes. La estructura para guardar el historial tiene el siguiente aspecto:

tester_catalog\Agent-IPaddress-Port\bases\name_source\history\symbol_name

Por ejemplo, el historial para EURUSD del servidor MetaQuotes-Demo se puede guardar en la carpeta tester_catalog\Agent-127.0.0.1-3000\bases\MetaQuotes-Demo\EURUSD.

Un agente local, tras completarse la simulación, pasa a un modo de espera, aguardando la siguiente tarea, durante 5 minutos. Así no perderá tiempo al lanzarse a la siguiente llamada. Solo después de que el tiempo de espera llegue a su fin, el agente local se apaga y se descarga de la memoria CPU.

En caso de que la simulación termine de forma temprana por una acción del usuario (el botón de "Cancelar"), así como durante el cierre del terminal de cliente, todos los agentes locales detienen su trabajo inmediatamente y se descargan de la memoria.


El Intercambio de Datos entre el Terminal y el Agente

Cuando realiza una simulación, el terminal de cliente se prepara para enviar al agente un número de bloques de parámetro:

Para cada bloque de parámetros se crea una huella dactilar digital en forma de MD5-hash, que se envía al agente. La MD5-hash es única para cada conjunto, su volumen es mucho menor que la cantidad de información en la que se calcula.

El agente recibe un hash de bloques y los compara con los que ya tiene. Si la huella del bloque de parámetros dado no está presente en el agente o el hash recibido es diferente del que ya existe, el agente solicita este block de parámetros. Esto reduce el tráfico entre el terminal y el agente.

Tras la simulación, el agente devuelve al terminal todos los resultados de la ejecución, que se muestran en las pestañas "Resultados de la simulación" y "Resultados de la optimización": el beneficio recibido, el número de transacciones, el resultado de la función OnTester(), etc.

Durante la optimización, el terminal distribuye las tareas a los agentes en paquetes pequeños, y cada paquete contiene varias tareas (cada tarea significa una simulación individual con un conjunto de parámetros de entrada). Esto reduce el tiempo de intercambio entre el terminal y el agente.

Los agentes nunca graban los archivos EX5 obtenidos del terminal (EA, indicadores, bibliotecas, etc) en el disco duro por motivos de seguridad, para que un ordenador con un agente en funcionamiento no use los datos enviados. Todos los demás archivos, incluidos los DLL, se graban en el sandbox. En agentes remotos no podrá simular EAs usando DLL.

Los resultados de la simulación se añaden por el terminal en una caché especial de resultados (la caché de resultados) para un acceso rápido a ellos cuando se los necesita. Para cada conjunto de parámetros, el terminal busca en la caché resultados ya disponibles de ejecuciones anteriores para evitar simulaciones innecesarias. Si el resultado con tal conjunto de parámetros no se encuentra, el agente recibe la tarea de llevar a cabo la simulación.

Todo el tráfico entre el terminal y el agente está encriptado.


Usar la Carpeta Compartida para Todos los Terminales de Cliente

Todos los agentes de simulación están aislados los unos de los otros y del terminal de cliente: cada agente tiene su propia carpeta, donde graba sus diarios. Además, todas las operaciones de archivo durante la simulación del agente se dan en la carpeta agent_name/MQL5/Files. No obstante, podemos implementar la interacción entre los agentes locales y el terminal de cliente a través de una carpeta compartida para todos los terminales de cliente, si durante la apertura del archivo especifica FILE_COMMON:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- the shared folder for all of the client terminals
   common_folder=TerminalInfoString(TERMINAL_COMMONDATA_PATH);
//--- draw out the name of this folder
   PrintFormat("Open the file in the shared folder of the client terminals %s", common_folder);
//--- open a file in the shared folder (indicated by FILE_COMMON flag)
   handle=FileOpen(filename,FILE_WRITE|FILE_READ|FILE_COMMON);
   ... further actions
//---
   return(0);
  }


Usar DLLs

Para acelerar la optimización, podemos usar no solo agentes locales, sino también agentes remotos. En este caso, hay algunas limitaciones para agentes remotos. En primer lugar, los agentes remotos no muestran en sus diarios los resultados de la ejecución de la función Print(), es decir, mensajes sobre la apertura y cierre de posiciones. Se muestra un mínimo de información en el diario para prevenir que EAs escritos incorrectamente saturen con mensajes el ordenador en el que los agentes remotos están trabajando.

Una segunda limitación es la prohibición del uso de DLL al simular EAs. Las llamadas DLL están absolutamente prohibidas en agentes remotos por motivos de seguridad. En agentes locales, las llamadas DLL se permiten en EAs simulados solo con el permiso correspondiente "Permitir importar DLL".

Fig. 10. La opción "Permitir importar DLL" en programas mql5

Nota: al usar los datos recibidos de EAs (scripts, indicadores) que requieren el permiso para llamadas DLL, debe ser consciente de los riegos que asume al permitir esta opción en la configuración del terminal. Independientemente de cómo se use el EA - para simulaciones o para ejecutarlo en un gráfico.


Conclusión

Este artículo aporta conocimientos básicos que le ayudarán a controlar rápidamente las simulaciones de EAs en el terminal de cliente MetaTrader 5.

La tarea principal del Probador de Estrategias del terminal de cliente es asegurar la precisión de los datos requerida con el mínimo esfuerzo del programador de MQL5. Los desarrolladores han trabajado mucho para que usted no tenga que reescribir su código solo para simular su estrategia de trading en datos del historial. Basta con conocer lo básico sobre simulaciones para que un EA correctamente escrito funcione bien tanto en el Probador como en el modo online en el gráfico.