- Generar ticks en el probador
- Gestión del tiempo en el comprobador: temporizador, Sleep, GMT
- Pruebas de visualización: gráfico, objetos, indicadores
- Pruebas multidivisa
- Criterios de optimización
- Obtener estadísticas financieras de prueba: TesterStatistics
- Evento OnTester
- Sintonización automática: ParameterGetRange y ParameterSetRange
- Grupo de eventos OnTester para el control de la optimización
- Enviar marcos de datos de los agentes al terminal
- Obtener marcos de datos en terminal
- Directivas del preprocesador para el probador
- Gestionar la visibilidad de los indicadores: TesterHideIndicators
- Emulación de operaciones de depósito y retirada
- Parada forzada de la prueba: TesterStop
- Ejemplo de Gran Asesor Experto
- Cálculos matemáticos
- Depuración y creación de perfiles
- Limitaciones de las funciones del probador
Obtener frames de datos en terminal
Los frames enviados desde los agentes de simulación por la función FrameAdd se entregan en el terminal y se escriben en el orden de recepción en un archivo mqd con el nombre del Asesor Experto en la carpeta terminal_directory/MQL5/Files/Tester. La llegada de uno o varios frames a la vez genera el evento OnTesterPass.
La API de MQL5 proporciona 4 funciones para analizar y leer frames: FrameFirst, FrameFilter, FrameNext y FrameInputs. Todas las funciones devuelven un valor booleano con una indicación de éxito (true) o error (false).
Para acceder a los frames existentes, el núcleo mantiene la metáfora de un puntero interno al frame actual. El puntero avanza automáticamente cuando se lee el siguiente frame mediante la función FrameNext, pero puede volver al principio de todos los frames con FrameFirst o FrameFilter. Así, un programa MQL puede organizar la iteración de frames en un bucle hasta que haya revisado todos los frames. Este proceso puede repetirse si es necesario, por ejemplo, aplicando distintos filtros en OnTesterDeinit.
bool FrameFirst()
La función FrameFirst establece el puntero de lectura de frame interno al principio y restablece el filtro (si se estableció previamente utilizando la función FrameFilter).
En teoría, para una única recepción y procesamiento de todos los frames, no es necesario llamar a FrameFirst, puesto que el puntero ya está al principio cuando comienza la optimización.
bool FrameFilter(const string name, ulong id)
Establece el filtro de lectura de frames y fija el puntero interno del frame al principio. El filtro afectará a los frames que se incluyan en las siguientes llamadas a FrameNext.
Si se pasa una cadena vacía como primer parámetro, el filtro sólo funcionará mediante un parámetro numérico, es decir, todos los frames con el id especificado. Si el valor del segundo parámetro es igual a ULONG_MAX, sólo funciona el filtro de texto.
Llamar a FrameFilter("", ULONG_MAX) equivale a llamar a FrameFirst(), lo que equivale a la ausencia de filtro.
Si llama a FrameFirst o FrameFilter en OnTesterPass, asegúrese de que es realmente lo que necesita: probablemente el código contenga un error lógico, ya que es posible que se produzca un bucle, que se lea el mismo frame o que aumente exponencialmente la carga computacional.
bool FrameNext(ulong &pass, string &name, ulong &id, double &value)
bool FrameNext(ulong &pass, string &name, ulong &id, double &value, void &data[])
La función FrameNext lee un frame y mueve el puntero al siguiente. El parámetro pass tendrá registrado el número de pase de optimización. Los parámetros name, id y value recibirán los valores pasados en los parámetros correspondientes de la función FrameAdd.
Es importante tener en cuenta que la función puede devolver false mientras opera normalmente cuando no hay más frames para leer. En este caso, la variable integrada _LastError contiene el valor 4000 (no tiene notación integrada).
Independientemente de la forma de la función FrameAdd que se haya utilizado para enviar los datos, el contenido del archivo o del array se colocará en el array data receptor. El tipo del array receptor debe coincidir con el tipo del array enviado, y hay ciertos matices en el caso del envío de un archivo.
Un archivo binario (FILE_BIN) debe aceptarse preferiblemente en un array de bytes uchar para garantizar la compatibilidad con cualquier tamaño (porque otros tipos más grandes pueden no ser múltiplos del tamaño del archivo). Si el tamaño del archivo (de hecho, el tamaño del bloque de datos en el frame recibido) no es múltiplo del tamaño del tipo de array receptor, la función FrameNext no leerá los datos y devolverá un error INVALID_ARRAY (4006).
Un archivo de texto Unicode (FILE_TXT o FILE_CSV sin modificador FILE_ANSI) debe aceptarse en un array de tipo ushort y convertirse después en una cadena llamando a ShortArrayToString. Un archivo de texto ANSI debe recibirse en un array uchar y convertirse utilizando CharArrayToString.
bool FrameInputs(ulong pass, string ¶meters[], uint &count)
La función FrameInputs le permite obtener descripciones y valores de los parámetros del Asesor Experto input sobre los que se forma el pase con el número de pase especificado. El array de cadenas parameters se llenará con líneas como «ParameterNameN=ValueParameterN». El parámetro count se rellenará con el número de elementos del array parameters.
Las llamadas a estas cuatro funciones sólo están permitidas dentro de los manejadores OnTesterPass y OnTesterDeinit.
Los frames pueden llegar a la terminal en lotes, en cuyo caso se tarda tiempo en entregarlos. Por lo tanto, no es necesario que todos ellos tengan tiempo de generar el evento OnTesterPass y se procesarán hasta el final de la optimización. En este sentido, para garantizar la recepción de todos los frames tardíos, es necesario colocar un bloque de código con su procesamiento (utilizando la función FrameNext) en OnTesterDeinit.
Veamos un ejemplo sencillo de FrameTransfer.mq5.
El Asesor Experto tiene cuatro parámetros de prueba. Todos ellos, excepto la última cadena, pueden incluirse en la optimización.
input bool Parameter0;
|
No obstante, para simplificar el ejemplo, el número de pasos para los parámetros Parameter1 y Parameter2 se limita a 10 (para cada uno). Así, si no utiliza Parameter0, el número máximo de pasadas es 121. Parameter3 es un ejemplo de parámetro que no puede incluirse en la optimización.
El Asesor Experto no negocia, sino que genera datos aleatorios que imitan los datos arbitrarios de la aplicación. No utilice la aleatoriedad de este modo en sus proyectos de trabajo: sólo es adecuada para demostraciones.
ulong startup; // track the time of one run (just like demo data)
|
Los datos se envían en dos tipos de frames: desde un archivo y desde un array. Cada tipo tiene su propio identificador.
#define MY_FILE_ID 100
|
El archivo se escribe como binario, con cadenas simples. El resultado (criterio) de OnTester es una expresión aritmética simple en la que intervienen Parameter1 y Parameter2.
En el lado receptor, en la instancia del Asesor Experto que se ejecuta en el modo de servicio en el gráfico terminal, recogemos los datos de todos los frames con archivos y los ponemos en un archivo CSV común. El archivo se abre en el manejador OnTesterInit.
int handle; // file for collecting applied results
|
Como ya se ha mencionado, es posible que no dé tiempo a que todos los frames entren en el manejador OnTesterPass, por lo que es necesario comprobarlos adicionalmente en OnTesterDeinit. Por lo tanto, hemos implementado una función de ayuda ProcessFileFrames, que llamaremos desde OnTesterPass, y desde OnTesterDeinit.
Dentro de ProcessFileFrames guardamos nuestro contador interno de frames procesados, framecount. Utilizándolo como ejemplo, nos aseguraremos de que el orden de llegada de los frames y la numeración de las pasadas de prueba no suelan coincidir.
void ProcessFileFrames()
|
Para recibir frames en la función se describen las variables necesarias según el prototipo FrameNext. El array de datos receptor se describe aquí como uchar. Si escribiéramos algunas estructuras en nuestro archivo binario, podríamos llevarlas directamente a un array de estructuras del mismo tipo.
ulong pass;
|
A continuación se describen las variables para obtener las entradas del Asesor Experto para el pase actual al que pertenece el frame.
string params[];
|
A continuación, leemos los frames en un bucle con FrameNext. Recuerde que varios frames pueden entrar en el manejador a la vez, por lo que se necesita un bucle. Para cada frame enviamos al registro del terminal el número de pase, el nombre del frame y el valor double resultante. Omitimos los frames con un ID distinto de MY_FILE_ID y las procesaremos más tarde.
ResetLastError();
|
Para los frames con MY_FILE_ID, hacemos lo siguiente: consultamos las variables de entrada, averiguamos cuáles están incluidas en la optimización y guardamos sus valores en un archivo CSV común junto con la información del frame. Cuando el recuento de frames es 0, formamos el encabezado del archivo CSV en la variable header. En todos los frames, el registro actual (nuevo) del archivo CSV se forma en la variable record.
void ProcessFileFrames()
|
La llamada a ParameterGetRange también podría hacerse de forma más eficiente, sólo que con un valor cero de framecount: pruebe a hacerlo.
En el manejador OnTesterPass, simplemente llamamos a ProcessFileFrames.
void OnTesterPass()
|
Además, llamamos a la misma función desde OnTesterDeinit y cerramos el archivo CSV.
void OnTesterDeinit()
|
En OnTesterDeinit, procesamos frames con MY_TIME_ID. La duración de las pasadas de prueba se entrega en estos frames, y aquí se calcula la duración media de una pasada. En teoría, tiene sentido hacer esto sólo para el análisis en su programa, ya que para el usuario la duración de los pases la muestra ya el probador en el registro.
void OnTesterDeinit()
|
El Asesor Experto está listo. Vamos a activar la optimización completa para ello (porque el número total de opciones está limitado artificialmente y es demasiado pequeño para el algoritmo genético). Sólo podemos elegir precios abiertos ya que el Asesor Experto no negocia. Por ello, debe elegir un criterio personalizado (todos los demás criterios darán 0). Por ejemplo, fijemos el rango Parameter1 de 1 a 10 en pasos simples, y Parameter2 se fija de -0.5 a +0.5 en pasos de 0.1.
Vamos a ejecutar la optimización. En el registro de expertos del terminal, veremos entradas sobre los frames recibidos del formulario:
Pass: 0 Frame: binfile Value:5105.000000
|
En el archivo output.csv aparecerán las líneas correspondientes con los números de pase, los valores de los parámetros y el contenido de los frames:
Counter,Pass ID,Parameter1,Parameter2,Value,File Content
|
Obviamente, nuestra numeración interna (columna Count) va en orden, y los números de pase Pass ID pueden mezclarse (esto depende de muchos factores del procesamiento paralelo de los lotes de trabajos por parte de los agentes). En concreto, el lote de tareas puede ser el primero en terminar el agente al que se asignaron las tareas con números de secuencia superiores: en este caso, la numeración en el archivo comenzará a partir de los pases superiores.
En el registro del comprobador, puede consultar las estadísticas de servicio por frames.
242 frames (42.78 Kb total, 181 bytes per frame) received
|
Es importante señalar que durante la optimización genética, los números de ejecución se presentan en el informe de optimización como un par (generation number, copy number), mientras que el número de paso obtenido en la función FrameNext es ulong. De hecho, este es el número de paso en los trabajos por lotes en el contexto de la ejecución de optimización actual. MQL5 no proporciona ningún medio para hacer coincidir la numeración de pases con un informe genético. Para ello, deben calcularse las sumas de comprobación de los parámetros de entrada de cada pasada. Los archivos Opt con una caché de optimización contienen ya un campo de este tipo con un hash MD5.