Desarrollamos un asesor experto multidivisas (Parte 29): Mejoras en el proceso de optimización automatizada
Introducción
En las tres partes anteriores, nos hemos desviado ligeramente de la línea principal de desarrollo y hemos centrado nuestros esfuerzos en crear herramientas auxiliares que nos resultarán útiles de una forma u otra en el futuro.
Por ejemplo, en la parte 28, hemos ampliado las funciones de gestión de capital de nuestro EA multidivisa mediante el desarrollo y la implementación de un nuevo módulo de software: el gestor de cierre. Permitía realizar un seguimiento de las ganancias o pérdidas totales en relación con un saldo base actualizado dinámicamente y reiniciar todas las estrategias de negociación cuando se alcanzaban los valores establecidos. Entre los planes de futuro figuraba ampliar su funcionalidad añadiendo la posibilidad de aplicar un seguimiento dinámico al beneficio total y llevar todo el conjunto de posiciones abiertas al punto de equilibrio.
En la parte 27, creamos otro componente: un cuadro de diálogo que ocupa toda el área del gráfico del terminal y permite mostrar texto de varias líneas con opciones flexibles de configuración de la fuente y compatibilidad con el desplazamiento. Esta herramienta hizo que la visualización de la información resultara más práctica y clara. Hemos añadido este componente a la biblioteca Adwizard como herramienta para mostrar distintos tipos de información durante la ejecución del EA.
En general, hemos consolidado el enfoque adoptado anteriormente, que consiste en una clara división de todo el código del programa en una parte de biblioteca (repositorio Adwizard) y una o varias partes del proyecto (repositorios SimpleCandles y SymbolsInformer).
Ahora volvamos a los resultados obtenidos en la parte 25. Allí alcanzamos un hito importante en el desarrollo de nuestro EA multidivisa, tras haber completado la creación de un sistema universal de optimización automática y haber integrado con éxito en él una nueva estrategia de trading denominada SimpleCandles. Todo el proceso —desde la creación de un proyecto de optimización en la base de datos y la puesta en marcha de una optimización en varias etapas en un clúster de agentes hasta la generación de un EA final listo para su uso— estuvo totalmente automatizado.
Sin embargo, desde la publicación de ese artículo, hemos encontrado una serie de dificultades o inconvenientes a la hora de utilizar este sistema. Esto no es de extrañar, ya que se trataba únicamente de la primera iteración del desarrollo del sistema en su conjunto. Veamos, pues, algunas mejoras que nos permitirán utilizar la optimización automática de forma más eficaz.
Planificación del proceso
Recordemos en qué consiste la cadena propuesta para desarrollar el asesor experto (EA) de trading definitivo. En la primera etapa, queremos llevar a cabo varios procesos de optimización de una misma estrategia de trading para diferentes símbolos, marcos temporales y otros parámetros en el probador de estrategias de MetaTrader 5. A partir de los resultados de la optimización, seleccionaremos un buen número de resultados prometedores para cada símbolo (si los hay, claro está). Llamémoslos instancias individuales de una estrategia de trading.
En la segunda etapa, llevaremos a cabo una optimización para identificar los mejores grupos formados por un pequeño número de instancias individuales de estrategias de trading. Es decir, de entre miles de instancias, seleccionaremos para cada símbolo un grupo de entre 8 y 16 instancias que hayan mostrado los mejores resultados al trabajar juntas. Durante la tercera etapa, combinaremos estos mejores grupos para cargarlos y utilizarlos en el EA definitivo.
Tras un largo proceso de desarrollo, todas las acciones mencionadas se han automatizado en la medida de lo posible. Ahora tenemos que especificar manualmente los parámetros para generar un proyecto de optimización; es decir, básicamente, un escenario general según el cual se llevará a cabo la optimización automática. Una vez finalizada la optimización, habrá que realizar varios pasos para poner en marcha el EA definitivo en la cuenta de trading. Sin embargo, el tiempo dedicado actualmente a las operaciones manuales (apenas unos minutos) es insignificante comparado con el tiempo durante el cual una cadena automatizada de optimización puede funcionar sin necesidad de intervenir en el proceso (horas, días o incluso semanas). Esta es la herramienta con la que empezamos a trabajar.
El primer inconveniente surgió cuando, tras terminar de probar la integración de una nueva estrategia, decidimos pasar a otra nueva. Resultó que, a pesar de la división declarada del código en dos partes independientes (la parte de biblioteca y la parte del proyecto), aún persistían algunas conexiones entre ellas. Veamos dónde se producen e intentemos eliminarlas.
Otra dificultad era que las tareas individuales ejecutadas dentro de la cadena de optimización del proyecto podían tardar bastante tiempo. Esta duración depende directamente de la extensión del intervalo histórico sobre el que se realizan todas las optimizaciones. Si queremos realizar una optimización sobre un periodo histórico de, por ejemplo, 5 años, el proceso llevará mucho más tiempo que una optimización sobre un periodo de 3 meses. Sin embargo, como ha demostrado la práctica, durante la optimización genética es posible encontrar buenas combinaciones de parámetros de la estrategia mucho antes del final previsto del proceso. Por lo tanto, es posible reducir el tiempo total de la cadena deteniendo los procesos de optimización tras un periodo de tiempo determinado. Añadamos a nuestro sistema la posibilidad de especificar un límite de tiempo para completar cada tarea de optimización.
Por último, vamos a facilitar un poco el seguimiento del proceso de optimización automática mostrando información más detallada sobre la tarea actual, en lugar de mostrar solo su ID.
Para mayor claridad, repasemos todo el proceso paso a paso hasta crear el EA definitivo, deteniéndonos para realizar las correcciones que consideremos oportunas.
Creación de una base de datos
En primer lugar, debemos clonar los dos repositorios siguientes en MQL5/Shared Projects: el repositorio de la biblioteca Adwizard y el repositorio del proyecto. En este artículo encontraremos información detallada sobre cómo clonar en nuestro ordenador los repositorios alojados en MQL5 Algo Forge.
También podemos crear nuestro propio repositorio para el proyecto utilizando el repositorio SimpleCandles como plantilla. Hemos hablado de cómo hacerlo en los artículos dedicados a la transición a una nueva estrategia. Esto será necesario si queremos implementar alguna de nuestras propias estrategias de trading. Seguiremos utilizando la estrategia SimpleCandles y el repositorio del proyecto del mismo nombre. Contiene un proyecto para crear un asesor experto (EA) multidivisa final utilizando esta estrategia de trading.
Cuando ambos repositorios se encuentren en la carpeta de trabajo del terminal, en MQL5/Shared Projects, compilaremos el archivo del EA para crear un proyecto de optimización automática, SimpleCandles/Optimization/CreateProject.mq5, y arrastraremos la versión compilada que aparece en el Navegador del terminal a cualquier gráfico. En el cuadro de diálogo de parámetros de entrada, iremos a la pestaña «Parámetros de entrada» y veremos lo siguiente:

En el parámetro del archivo de base de datos de optimización, hemos especificado el nombre de la base de datos que utilizamos en artículos anteriores. Dado que tenemos previsto realizar algunos cambios, entre ellos algunos que afectan a la estructura de la base de datos de optimización, no podemos seguir utilizando la base de datos antigua. Es necesario crear una nueva.
Es muy sencillo: solo tenemos que especificar otro nombre para la base de datos en este parámetro de entrada. Si no existe ningún archivo con este nombre, el EA de creación de proyectos creará automáticamente una base de datos vacía con este nombre y la estructura de tablas necesaria. Para evitar tener que cambiar el valor predeterminado del nombre la próxima vez que ejecutemos este EA, cambiémoslo por uno nuevo en el archivo fuente del EA:
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ sinput group "::: Database" sinput string fileName_ = "article.17607.db.sqlite"; // - Optimization database file sinput group "::: Project parameters - Basic" sinput string projectName_ = "SimpleCandles"; // - Name sinput string projectVersion_ = "1.00"; // - Version sinput string symbols_ = "GBPUSD,EURUSD,EURGBP"; // - Symbols sinput string timeframes_ = "H1,M30"; // - Timeframes ...
Además, resulta más cómodo modificar los valores de otros parámetros en el código fuente de este EA que en el cuadro de diálogo de configuración de parámetros al iniciar el EA. Esta es una etapa crucial en la que debemos analizar detenidamente el escenario general de la cadena automatizada de optimización, registrarlo en los parámetros de entrada del EA de creación del proyecto y, a continuación, ejecutarlo una vez. En la práctica, lo ejecutamos más de una vez, ya que resulta muy difícil determinar todos los parámetros de una sola vez. Por lo tanto, procedemos por iteraciones: fijamos los valores de los parámetros en el código fuente, creamos un proyecto, ejecutamos la optimización automática y, si vemos que hay que cambiar algo, detenemos el proceso y volvemos al principio.
¿Qué podría salir mal? Por ejemplo, hemos elegido un intervalo temporal demasiado largo para las pruebas y, según unas estimaciones aproximadas, todo el proceso durará varias semanas. En este caso, si no queremos esperar tanto tiempo, podemos intentar acortar el periodo de prueba o, por ejemplo, reducir el número de símbolos diferentes para los que se repetirán las pasadas de optimización. Para ello, modificamos los parámetros de entrada y volvemos a crear el proyecto. Incluso podemos eliminar previamente el archivo de la base de datos para que el proyecto recién creado sea el único que figure en la nueva base de datos y no se añada a una ya existente.
En general, modificar los valores predeterminados de estos parámetros en la parte del proyecto es aceptable y está bastante justificado: cada proyecto requiere una configuración específica, que debe establecerse en el propio proyecto.
El resultado de este paso es la creación de un archivo de base de datos con el nombre especificado en la carpeta de datos común de los terminales. La base de datos creada contiene información sobre la configuración del proyecto de optimización automática (en la tabla «projects»), que consta de un conjunto de etapas (tabla «stages»). Las etapas se componen de trabajos (tabla «jobs»), y dentro de un trabajo puede haber una o varias tareas de optimización (tabla «tasks»). Estas relaciones se representan en la figura mediante flechas:

Todas las tareas del proyecto se encuentran en el estado «Queued» (en cola), lo que significa que están pendientes de ejecución. Ahora podemos pasar al siguiente paso: poner en marcha la cadena automatizada de optimización, en la que todas las tareas creadas se convertirán en ejecuciones independientes de optimización de los EA de cada etapa en el probador de estrategias de MetaTrader 5.
Puesta en marcha de la cadena de optimización
Para ello, compilaremos el archivo del EA de optimización automática SimpleCandles/Optimization/Optimization.mq5 y arrastraremos la versión compilada que aparece en el Navegador del terminal a cualquier gráfico. En el cuadro de diálogo de parámetros de entrada, en la pestaña «Parámetros de entrada», veremos lo siguiente:

Aquí volvemos a ver que los valores predeterminados de los parámetros corresponden al nombre del archivo de base de datos anterior. Por supuesto, podemos sustituirlo ahora manualmente por un nuevo nombre, article.17607.db.sqlite, pero la experiencia nos ha demostrado que esto resulta poco práctico. El motivo es que, al configurar la cadena, lo más probable es que tengamos que realizar primero varias ejecuciones de prueba. Y en cada una de ellas tendremos que cambiar manualmente el nombre del archivo de la base de datos por el que corresponda en ese momento.
Sin embargo, si intentamos hacer lo descrito anteriormente, es decir, cambiar el valor del nombre en el código, veremos que estos cambios deben realizarse no en la parte del proyecto, sino en la parte de biblioteca. En la parte del proyecto, el archivo del EA de optimización (SimpleCandles/Optimization/Optimization.mq5) se limita a incluir el archivo de biblioteca, de modo que, al compilar, obtenemos un archivo ejecutable en la carpeta del proyecto:
#include "../Include/Adwizard/Experts/Optimization.mqh"
No contiene nada más, por lo que el valor utilizado de forma predeterminada se encuentra en el archivo de biblioteca incluido. Solucionemos esto de la siguiente manera: en el archivo del proyecto, antes de incluir el archivo de biblioteca, declaramos constantes con los valores predeterminados necesarios para los parámetros y, al mismo tiempo, corregimos la ruta al archivo incluido eliminando el nombre de la carpeta Include:
// Constants with default parameters for the project: // - File with the main database #define OPT_FILEMNAME "article.17607.db.sqlite" // - Path to the Python interpreter #define OPT_PYTHONPATH "C:\\Python\\Python312\\python.exe" #include "../../Adwizard/Experts/Optimization.mqh"
En la parte de biblioteca (Adwizard/Experts/Optimization.mqh), tendremos en cuenta que estas constantes podrían no existir. En ese caso, las declaramos asignándoles valores vacíos. A continuación, utilizamos sus valores en los parámetros de entrada:
// Create constants for default parameters, // if they are not defined in the project part #ifndef OPT_FILEMNAME #define OPT_FILEMNAME "" #endif #ifndef OPT_PYTHONPATH #define OPT_PYTHONPATH "" #endif sinput string fileName_ = OPT_FILEMNAME; // - File with the main database sinput string pythonPath_ = OPT_PYTHONPATH; // - Path to the Python interpreter
Ahora podemos especificar estos parámetros en el código de la parte del proyecto sin modificar la parte de biblioteca. Es decir, la parte de biblioteca será la misma para los distintos proyectos.
Limitación del tiempo de ejecución de las tareas
Como ya se ha mencionado, otra dificultad radicaba en que las tareas individuales ejecutadas dentro de la cadena de optimización del proyecto podían prolongarse bastante. Para establecer un límite de tiempo en la ejecución de una tarea de optimización concreta, tendremos que realizar cambios en varios lugares.
En primer lugar, debemos añadir parámetros de entrada al archivo del EA de creación del proyecto para poder especificar el tiempo máximo de ejecución deseado para las tareas. Estos cambios afectan a la parte del proyecto. En segundo lugar, en la tabla «tasks» de la base de datos, tenemos que añadir un campo para almacenar este valor. En tercer lugar, el código del EA de optimización debe permitir utilizar el valor del tiempo máximo de ejecución. Estos cambios se llevarán a cabo en la parte de biblioteca.
Añadamos dos parámetros al archivo SimpleCandles/Optimization/CreateProject.mq5 para especificar el tiempo máximo de ejecución de las tareas de optimización en la primera y la segunda etapa.
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ sinput group "::: Database" sinput string fileName_ = "article.17607.db.sqlite"; // - Optimization database file sinput group "::: Project parameters - Basic" sinput string projectName_ = "SimpleCandles"; // - Name sinput string projectVersion_ = "1.00"; // - Version sinput string symbols_ = "GBPUSD,EURUSD,EURGBP"; // - Symbols sinput string timeframes_ = "H1,M30"; // - Timeframes sinput group "::: Project parameters - Optimization interval" sinput datetime fromDate_ = D'2023-09-01'; // - Start date sinput datetime toDate_ = D'2024-01-01'; // - End date sinput group "::: Project parameters - Account" sinput string mainSymbol_ = "GBPUSD"; // - Main symbol sinput int deposit_ = 10000; // - Initial deposit sinput group "::: Stage 1. Search" sinput string stage1ExpertName_ = "Stage1.ex5"; // - Stage EA sinput string stage1Criterions_ = "6,6,6"; // - Optimization criteria for tasks sinput long stage1MaxDuration_ = 20; // - Max duration of tasks (с) sinput group "::: Stage 2. Grouping" sinput string stage2ExpertName_ = "Stage2.ex5"; // - Stage EA sinput string stage2Criterion_ = "6"; // - Optimization criterion for tasks sinput long stage2MaxDuration_ = 20; // - Max duration of tasks (с) //sinput bool stage2UseClusters_= false; // - Use clustering? sinput double stage2MinCustomOntester_ = 500; // - Min norm. profit sinput uint stage2MinTrades_ = 20; // - Min number of trades sinput double stage2MinSharpeRatio_ = 0.7; // - Min Sharpe coeff. sinput uint stage2Count_ = 8; // - Number of strategies in the group (1 - 16) sinput group "::: Stage 3. Final" sinput string stage3ExpertName_ = "Stage3.ex5"; // - Stage EA sinput ulong stage3Magic_ = 27183; // - Magic sinput bool stage3Tester_ = true; // - For the tester?
No podemos especificar una duración máxima para la tercera etapa, ya que esta utiliza una pasada única del probador de estrategias en lugar de una optimización. Solo una vez finalizada esta pasada se calculan los volúmenes normalizados de las posiciones abiertas y se genera la línea de inicialización del EA final. Si interrumpimos esta pasada antes de tiempo, no podremos obtener sus resultados. En la primera y la segunda etapa, detener el proceso de optimización antes de tiempo solo supondrá una reducción del número total de pasadas realizadas. Si el número de pasadas realizadas sigue siendo suficientemente alto, no hay de qué preocuparse.
Para añadir un nuevo campo a la estructura de la tabla «tasks», solo tenemos que añadir una consulta SQL al archivo de definición de la estructura de la base de datos de optimización db.opt.schema.sql:
-- Table: tasks DROP TABLE IF EXISTS tasks; CREATE TABLE tasks ( id_task INTEGER PRIMARY KEY AUTOINCREMENT, id_job INTEGER NOT NULL REFERENCES jobs (id_job) ON DELETE CASCADE ON UPDATE CASCADE, optimization_criterion INTEGER DEFAULT (7) NOT NULL, start_date DATETIME, finish_date DATETIME, max_duration INTEGER NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT Queued CHECK (status IN ('Queued', 'Process', 'Done') ) );
Ahora pasemos a editar la parte de biblioteca. Básicamente, tendremos que realizar cambios en dos archivos. En primer lugar, en el archivo de la clase de tareas de optimización Adwizard/Optimization/OptimizerTask.mqh, añadiremos otro campo para la duración máxima a la estructura destinada a leer los parámetros de la tarea desde la base de datos:
//+------------------------------------------------------------------+ //| Optimization task class | //+------------------------------------------------------------------+ class COptimizerTask { protected: // ... public: // Data structure for reading a single row of a query result struct params { string expert; int optimization; string from_date; string to_date; int forward_mode; string forward_date; double deposit; string symbol; string period; string tester_inputs; ulong id_task; int optimization_criterion; long max_duration; } m_params; // ... };
Lo añadiremos a la consulta para obtener la información de la tarea de la base de datos:
//+------------------------------------------------------------------+ //| Get the next optimization task from the queue | //+------------------------------------------------------------------+ void COptimizerTask::Load(ulong p_id) { // Save task ID m_id = p_id; // Request to get optimization task from queue by ID string query = StringFormat( "SELECT s.expert," " s.optimization," " s.from_date," " s.to_date," " s.forward_mode," " s.forward_date," " s.deposit," " j.symbol," " j.period," " j.tester_inputs," " t.id_task," " t.optimization_criterion," " t.max_duration" " FROM tasks t" " JOIN" " jobs j ON t.id_job = j.id_job" " JOIN" " stages s ON j.id_stage = s.id_stage" " WHERE t.id_task=%I64u;", m_id); // Open the database if(DB::Connect(m_fileName)) { // Execute the request int request = DatabasePrepare(DB::Id(), query); // ... } }
En el método para comprobar que la tarea ha finalizado, añadiremos un bloque de código que, si se especifica un valor distinto de cero para la duración máxima de la tarea, obtendrá de la base de datos el tiempo transcurrido desde el inicio de la tarea y lo comparará con el máximo especificado. Si la duración actual ya ha superado el máximo, se invoca el método de parada forzada de la tarea:
//+------------------------------------------------------------------+ //| Task completed? | //+------------------------------------------------------------------+ bool COptimizerTask::IsDone() { // If there is no current task, then everything is done if(m_id == 0) { return true; } // Result bool res = false; // If this is the EA optimization task if(m_type == TASK_TYPE_EX5) { // Check if the strategy tester has finished its work res |= MTTESTER::IsReady(); // If the tester is running and the maximum duration is specified, then if(!res && m_params.max_duration > 0) { // Request to get the elapsed execution time of the current task string query = StringFormat("SELECT unixepoch(datetime()) - unixepoch(start_date) AS duration" " FROM tasks" " WHERE id_task=%I64u;", m_id); // Get the execution time in seconds DB::Connect(m_fileName); long duration = StringToInteger(DB::GetValue(query)); DB::Close(); // If the execution time is greater than the maximum allowed, if(duration > m_params.max_duration) { // Stop the task Stop(); } } // If this is a task to run a Python program, then } else if(m_type == TASK_TYPE_PY) { // ... } } else { res = true; } return res; }
En Adwizard/Optimization/OptimizationProject.mqh, añadiremos el paso del parámetro de duración máxima a los métodos de creación de tareas:
//+------------------------------------------------------------------+ //| Optimization project class | //+------------------------------------------------------------------+ class COptimizationProject { public: // ... // Add new tasks to the database for the specified optimization criteria COptimizationProject* AddTasks(string p_criterions, long p_maxDuration = 0); COptimizationProject* AddTasks(string &p_criterions[], long p_maxDuration = 0); // ... }; //+------------------------------------------------------------------+ //| Add new tasks to the database for the specified | //| optimization criteria in one string | //+------------------------------------------------------------------+ COptimizationProject* COptimizationProject::AddTasks(string p_criterions, long p_maxDuration) { // Array for optimization criteria string criterions[]; StringReplace(p_criterions, ";", ","); StringSplit(p_criterions, ',', criterions); return AddTasks(criterions, p_maxDuration); } //+------------------------------------------------------------------+ //| Add new tasks to the database for the specified | //| optimization criteria in the array | //+------------------------------------------------------------------+ COptimizationProject* COptimizationProject::AddTasks(string &p_criterions[], long p_maxDuration) { // For each job of the current stage FOREACH_AS(m_stage.jobs, m_job) { // For each optimization criterion FOREACH(p_criterions) { // Create a new task object for the job m_task = new COptimizationTask(0, m_job, (int) p_criterions[i], p_maxDuration); // Insert it into the optimization database m_task.Insert(); // Add it to the array of all tasks APPEND(m_tasks, m_task); // Add it to the current job's tasks array APPEND(m_job.tasks, m_task); } } return &this; }
En el archivo Adwizard/Optimization/OptimizationTask.mqh, lo único que tenemos que hacer es añadir un campo adicional a la clase COptimizationTask y asegurarnos de que se inicialice en el constructor y en el método encargado de insertar datos en la tabla «tasks» de la base de datos:
//+------------------------------------------------------------------+ //| Optimization task class | //+------------------------------------------------------------------+ class COptimizationTask { public: ulong id_task; // task ID ulong id_job; // job ID int optimization; // Optimization criterion long maxDuration; // Max duration string status; // Task status COptimizationJob* job; // The job for the task will be launched for // Constructor COptimizationTask(ulong p_taskId = 0, COptimizationJob* p_job = NULL, int p_optimization = 6, long p_maxDuration = 0, string p_status = "Done"); // Create a task in the database void Insert(); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ COptimizationTask::COptimizationTask(ulong p_taskId = 0, COptimizationJob* p_job = NULL, int p_optimization = 6, long p_maxDuration = 0, string p_status = "Done") : id_task(p_taskId), job(p_job), id_job(!!p_job ? p_job.id_job : 0), optimization(p_optimization), maxDuration(p_maxDuration), status(p_status) {} //+------------------------------------------------------------------+ //| Create a task in the database | //+------------------------------------------------------------------+ void COptimizationTask::Insert() { string query = StringFormat("INSERT INTO tasks " " VALUES (NULL,%I64u,%d,NULL,NULL,%I64d,'%s');", id_job, optimization, maxDuration, status); id_task = DB::Insert(query); PrintFormat(__FUNCTION__" | %s -> %I64u", query, id_task); } //+------------------------------------------------------------------+
Ahora podemos compilar dos asesores expertos: uno para crear proyectos de optimización en la base de datos, especificando el tiempo máximo de ejecución de las tareas de optimización en las dos primeras etapas de la cadena (CreateProject.ex5), y otro para poner en marcha la cadena (Optimization.ex5).
Mostrar datos durante la optimización
Dado que antes nos resultaba más importante garantizar el correcto funcionamiento del EA de la cadena automatizada de optimización, la visualización de la información del proceso era una cuestión secundaria. Utilizábamos la función estándar Comment(), que permite mostrar un texto breve en la esquina superior izquierda del gráfico en el que se ejecuta el EA. Lo único que se mostraba era el ID de la tarea de optimización actual. Ahora que el funcionamiento principal del EA de optimización está más o menos definido, podemos pasar a cuestiones menos importantes. Además, ahora disponemos de un componente que permite mostrar texto con mayor flexibilidad en el gráfico del EA: la clase CConsoleDialog. Como recordaremos, un objeto de esta clase nos permite crear un cuadro de diálogo que se expande hasta cubrir todo el gráfico, con botones para contraerlo y cerrarlo, y que muestra texto de varias líneas con desplazamiento y escala ajustable. Vamos a utilizarlo.
Para ello, tenemos que añadir lo siguiente. En el archivo de biblioteca incluido en el EA de optimización (Adwizard/Experts/Optimization.mqh), debemos crear un puntero global a un objeto de esta clase y, en la función de inicialización, crear el propio objeto de diálogo y llamar a su método de ejecución:
CConsoleDialog *dialog; // Dialog for displaying text with data //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // If the database file is not specified, then exit if(fileName_ == "") { PrintFormat(__FUNCTION__" | ERROR: Set const OPT_FILEMNAME with filename of DB in project", 0); return INIT_FAILED; } // Create an optimizer optimizer = new COptimizer(fileName_, pythonPath_); // Create and launch a dialog to display information dialog = new CConsoleDialog(); dialog.Create(__FILE__); dialog.Run(); // Create the timer and start its handler EventSetTimer(2); OnTimer(); return(INIT_SUCCEEDED); }
En el controlador del temporizador, enviamos al objeto de diálogo el texto actualizado que proporciona el optimizador:
//+------------------------------------------------------------------+ //| Expert timer function | //+------------------------------------------------------------------+ void OnTimer() { // Start the optimizer handling optimizer.Process(); dialog.Text(optimizer.Text()); }
Añadamos una función de gestión de eventos del gráfico, OnChartEvent(), que pase los eventos al objeto de diálogo para garantizar la interacción con el usuario:
//+------------------------------------------------------------------+ //| Event handling | //+------------------------------------------------------------------+ void OnChartEvent(const int id, // event ID const long & lparam, // event parameter of the long type const double & dparam, // event parameter of the double type const string & sparam) { // event parameter of the string type if(!!dialog && !IsStopped()) { dialog.ChartEvent(id, lparam, dparam, sparam); } }
Asegurémonos de eliminar el objeto de diálogo creado cuando el EA termine de ejecutarse y de volver a dibujar el gráfico:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { PrintFormat(__FUNCTION__" | Reason: %d", reason); EventKillTimer(); // Remove the optimizer if(!!optimizer) { delete optimizer; } // Remove the dialog if(!!dialog) { dialog.Destroy(); delete dialog; ChartRedraw(); } }
Ahora tenemos que generar el texto deseado que se mostrará en pantalla durante la optimización. Implementemos esto en el método Text() del objeto optimizador en Adwizard/Optimization/Optimizer.mqh:
//+------------------------------------------------------------------+ //| Information about the current optimization state | //+------------------------------------------------------------------+ string COptimizer::Text(void) { string text = ""; // Get the number of projects with different statuses DB::Connect(m_fileName); int process_projects_count = (int) DB::GetValue("SELECT count(status) FROM projects WHERE status = 'Process'"); int queued_projects_count = (int) DB::GetValue("SELECT count(status) FROM projects WHERE status = 'Queued'"); int done_projects_count = (int) DB::GetValue("SELECT count(status) FROM projects WHERE status = 'Done'"); int total_projects_count = process_projects_count + queued_projects_count + done_projects_count; DB::Close(); // Add this to the message text text += StringFormat("DB: %s | %d Projects (Process: %d, Queued: %d, Done: %d)\n", m_fileName, total_projects_count, process_projects_count, queued_projects_count, done_projects_count ); // If there is an active project if(process_projects_count > 0) { // Add information about the current task to the message text text += m_task.Text(); // And the total number of tasks in the queue if(m_totalTasks) text += StringFormat( "Total tasks in queue: %d\n", m_totalTasks); } return text; }
Obtendremos información sobre la tarea actual llamando al método Text() de la clase COptimizerTask. Obtengamos la información sobre el proyecto, la etapa y el trabajo a los que pertenece la tarea de optimización actual en el archivo Adwizard/Optimization/OptimizerTask.mqh. También calcularemos el tiempo transcurrido desde que se inició la tarea y el tiempo restante hasta su finalización, si se especifica el tiempo máximo permitido para completar la tarea:
//+------------------------------------------------------------------+ //| Current task info | //+------------------------------------------------------------------+ string COptimizerTask::Text() { string text = ""; // If there is an active task if(m_params.id_task) { DB::Connect(m_fileName); // Add information about the project text += StringFormat("═════════════════════════════════════════════════════════════════════════\n" "PROJECT: %s v. %s\n%s\n\n", DB::GetValue("SELECT name FROM projects WHERE status = 'Process' LIMIT 1"), DB::GetValue("SELECT version FROM projects WHERE status = 'Process' LIMIT 1"), DB::GetValue("SELECT description FROM projects WHERE status = 'Process' LIMIT 1") ); // Request to get all information about the task string query = "SELECT s.name, s.expert, s.from_date, s.to_date, " " j.symbol, j.period, t.optimization_criterion, t.start_date, " " time(max_duration, 'unixepoch') AS max_duration," " time(unixepoch('now') - unixepoch(t.start_date), 'unixepoch') AS elapsed_time," " time(MAX(0, max_duration - (unixepoch('now') - unixepoch(t.start_date))), 'unixepoch') AS remaining_time" " FROM stages s" " JOIN" " projects p ON s.id_project = p.id_project AND" " p.status = 'Process' AND" " s.expert IS NOT NULL" " JOIN jobs j ON j.id_stage = s.id_stage" " JOIN tasks t ON t.id_job = j.id_job AND t.status = 'Process';"; // Execute the request int request = DatabasePrepare(DB::Id(), query); struct Row { string stage_name; string expert_name; string from_date; string to_date; string symbol; string timeframe; int optimization_criterion; string start_date; string max_duration; string elapsed_time; string remainig_time; } row; // If there is no error if(request != INVALID_HANDLE) { // Read data from the first line of the result and add it to the text if(DatabaseReadBind(request, row)) { text += StringFormat("TASK #%I64u:\n" " %10.10s │ %14.14s │ %-23s │ %6.6s │ %-3.3s │ %15.15s │ %-10.10s │ %-10.10s\n" "────────────┼────────────────┼─────────────────────────┼────────┼─────┼─────────────────┼────────────┼─────────────\n" " %10.10s │ %14.14s │ %s - %s │ %6.6s │ %-3.3s │ %15.15s │ %-10.10s │ %-10.10s \n\n" "═════════════════════════════════════════════════════════════════════════\n", m_id, "Stage", "Expert", "Testing period", "Symbol", "TF", "Criterion", "Max Durat.", "Remaining", row.stage_name, row.expert_name, row.from_date, row.to_date, row.symbol, row.timeframe, s_criterionNames[row.optimization_criterion], m_params.max_duration ? row.max_duration : "Unlimited", row.remainig_time ); } } DatabaseFinalize(request); DB::Close(); } return text; }
Organizaremos los datos obtenidos sobre la tarea en forma de tabla de texto. En este momento, ya se han completado las modificaciones necesarias para garantizar esta funcionalidad, por lo que podemos pasar a las pruebas.
Pruebas
Ejecutemos el EA de optimización SimpleCandles/Optimization/Optimization.ex5. Veremos algo como esto:

Para esta captura de pantalla, hemos establecido el tiempo máximo de ejecución permitido para una sola tarea de optimización en 3 minutos (180 segundos). Llevaremos a cabo optimizaciones en tres símbolos y dos marcos temporales sobre un periodo de cuatro meses.
En la primera etapa, para cada combinación de símbolo y marco temporal, ejecutaremos la optimización tres veces. Por lo tanto, en total tendremos que completar 2 símbolos * 3 marcos temporales * 3 optimizaciones = 18 tareas en la primera etapa.
En la segunda etapa, realizaremos una ejecución de optimización para seleccionar un buen grupo para cada combinación de símbolo y marco temporal. Por lo tanto, en la segunda etapa tendremos que completar 2 símbolos * 3 marcos temporales = 6 tareas.
En la tercera etapa se realiza una última tarea, por lo que el número total de tareas es 18 + 6 + 1 = 25. Establecemos un tiempo máximo para todas las tareas salvo la última. Si estimamos, a grandes rasgos, que el tiempo de ejecución de la última tarea es de 3 minutos, podemos calcular cuánto durará todo el proceso de la cadena automatizada de optimización: 25 * 3 = 75 minutos = 1 hora y 15 minutos. Esto es, por supuesto, mucho menos que días o semanas. Pero probemos con una opción aún más extrema.
Limitaremos el tiempo máximo de ejecución a solo 20 segundos. En este caso, la duración total del proceso de optimización automática se reducirá aún más y será de unos 25 × 20 segundos = 500 segundos = 8 minutos y 20 segundos.
Tras este breve periodo de tiempo, todas las tareas de optimización habrán finalizado. En el terminal MetaTrader 5, donde hemos ejecutado este proceso, podemos ver los resultados de la última tarea de la tercera etapa, que consistió en una pasada única del asesor experto (EA) que combina 2 símbolos * 3 marcos temporales * 8 instancias por grupo = 48 instancias individuales de la estrategia de trading SimpleCandles:


Estos resultados aún no se han normalizado, lo que significa que la reducción del capital durante estos cuatro meses fue inferior a 500 USD, frente a un máximo previsto de 1 000 USD (10 %). Por lo tanto, podemos aumentar el volumen de las posiciones abiertas aproximadamente al doble sin superar una reducción del capital del 10 % durante este periodo. Esta operación ya se ha completado al final de la tercera etapa, y la información sobre el tamaño de las posiciones, teniendo en cuenta esta normalización, ya se ha guardado en la base de datos independiente del EA final.
Además, en la tercera etapa no se utilizó ni un gestor de riesgos de cierre ni un gestor de cierre.
Según la convención adoptada anteriormente, el nombre del archivo de la base de datos del EA final se genera siguiendo un algoritmo estrictamente definido a partir del nombre del proyecto, el número mágico especificado en los parámetros del proyecto y el sufijo «test». En nuestro caso concreto, una vez finalizado el proceso de optimización automática, se creó un archivo llamado SimpleCandles-27183.test.db.sqlite en la carpeta de datos común de los terminales.
Echemos un vistazo al contenido de la base de datos del EA final. Contiene cuatro tablas, pero por ahora solo nos interesan las dos últimas:

La tabla strategy_groups contiene una entrada correspondiente al grupo de estrategias creado con el identificador id_group=1. Si volvemos a ejecutar la cadena automatizada de optimización para el mismo proyecto, se añadirán nuevas filas con ID diferentes a esta tabla. Necesitamos el valor del identificador del grupo de estrategias para especificarlo en los parámetros del EA final. En la tabla strategies, hemos añadido instancias individuales de estrategias de trading que pertenecen a un grupo de estrategias específico. Las tablas restantes serán utilizadas por el EA final cuando opere en la cuenta de trading.
Compilemos el archivo SimpleCandles/SimpleCandles.mq5 del EA final y ejecutemos las pruebas con el ID del grupo de estrategias existente, sin actualización automática, con el gestor de riesgos y el gestor de cierre desactivados, y con el número mágico 27183:

Obtendremos los siguientes resultados:


Ahora, la reducción del capital ya se acerca al valor máximo previsto especificado en la configuración del EA final y, en consecuencia, el beneficio ha aumentado aproximadamente al doble, en proporción al aumento del tamaño de las posiciones.
Conclusión
El trabajo realizado en este artículo ha mejorado considerablemente nuestra cadena automatizada de optimización y ha facilitado su uso. No hemos añadido nuevas funcionalidades a la lógica de negociación, sino que nos hemos centrado en resolver los problemas prácticos con los que nos hemos encontrado al utilizar el sistema.
Hemos seguido separando la parte de biblioteca de la parte del proyecto. Ahora, para crear un nuevo proyecto basado en una estrategia de negociación diferente, basta con describir sus parámetros en la parte del proyecto, sin necesidad de realizar cambios en el núcleo común. Este proceso aún no ha concluido y continuará en el futuro.
La implementación de un mecanismo para limitar el tiempo de ejecución de las tareas de optimización nos proporcionó una herramienta muy eficaz para controlar la duración de toda la cadena. Ahora podemos equilibrar de forma flexible la profundidad de la optimización y el tiempo dedicado, deteniendo los procesos en las dos primeras etapas una vez que se alcanza un límite razonable, lo cual es fundamental para la realización de pruebas rápidas y el desarrollo iterativo.
La integración del componente CConsoleDialog para mostrar información detallada sobre la optimización transformó la supervisión del proceso, pasando del seguimiento de los ID de las tareas a una supervisión cómoda y visual. Ahora podemos ver en tiempo real en qué etapa, con qué símbolo y en qué marco temporal está funcionando la cadena, cuánto tiempo queda para que finalice la tarea actual y cuál es el progreso general.
De este modo, se demostró con éxito en la práctica todo el ciclo: desde la creación de una base de datos del proyecto y la puesta en marcha de la cadena hasta la obtención de un EA final con parámetros normalizados, listo para las pruebas. Una ejecución extrema con límites de tiempo muy ajustados demostró claramente la viabilidad de este enfoque: incluso con duraciones muy limitadas para las tareas individuales, el sistema es capaz de completar un ciclo de optimización completo y ofrecer un resultado funcional. Esto abre posibilidades para crear prototipos rápidamente y probar nuevas estrategias de trading.
¡Gracias por su atención! ¡Hasta pronto!
Advertencia importante
Todos los resultados presentados en este artículo y en todos los artículos anteriores de la serie se basan únicamente en datos de pruebas históricas y no constituyen garantía alguna de obtener beneficios en el futuro. El trabajo realizado en el marco de este proyecto es de carácter investigativo. Cualquier persona puede utilizar todos los resultados publicados bajo su propia responsabilidad.
| # | Nombre | Versión | Descripción | Cambios recientes |
|---|---|---|---|---|
| SimpleCandles | Carpeta de trabajo del proyecto (dentro de MQL5/Shared Projects) | |||
| 1 | SimpleCandles.mq5 | 1.03 | Asesor experto (EA) final para el funcionamiento en paralelo de varios grupos de estrategias modelo. Los parámetros se obtendrán de la biblioteca de grupos integrada. | Parte 29 |
| └ Optimization | Carpeta de los asesores expertos (EA) de optimización del proyecto | |||
| 2 | CreateProject.mq5 | 1.05 | EA para crear un proyecto con etapas, trabajos y tareas de optimización. | Parte 29 |
| 3 | Optimization.mq5 | 1.03 | EA para la optimización automática de proyectos | Parte 29 |
| 4 | Stage1.mq5 | 1.02 | EA para la optimización de una instancia individual de una estrategia de trading (etapa 1) | Parte 25 |
| 5 | Stage2.mq5 | 1.01 | EA para la optimización de un grupo de instancias de estrategias de trading (etapa 2) | Parte 25 |
| 6 | Stage3.mq5 | 1.01 | El EA que guarda un grupo de estrategias generado y normalizado en una base de datos de EA con un nombre determinado. | Parte 25 |
| └ Strategies | Carpeta de estrategias del proyecto | Parte 25 | ||
| 7 | SimpleCandlesStrategy.mqh | 1.03 | Clase de la estrategia de trading SimpleCandles | Parte 29 |
| Adwizard | Carpeta de la biblioteca Adwizard (dentro de MQL5/Shared Projects) | |||
| └ Base | Clases base de las que heredan otras clases del proyecto | |||
| 8 | Advisor.mqh | 1.04 | Clase base del asesor experto (EA) | Parte 10 |
| 9 | Factorable.mqh | 1.06 | Clase base de los objetos creados a partir de una cadena | Parte 28 |
| 10 | FactorableCreator.mqh | 1.00 | Clase de creadores que asocian nombres y constructores estáticos de las clases descendientes de CFactorable | Parte 24 |
| 11 | Interface.mqh | 1.01 | Clase base para visualizar diversos objetos | Parte 4 |
| 12 | Receiver.mqh | 1.04 | Clase base para convertir volúmenes abiertos en posiciones de mercado | Parte 12 |
| 13 | Strategy.mqh | 1.04 | Clase base de las estrategias de trading | Parte 10 |
| └ Database | Archivos para gestionar todos los tipos de bases de datos utilizados por los asesores expertos (EA) del proyecto | |||
| 14 | Database.mqh | 1.13 | Clase para gestionar la base de datos | Parte 29 |
| 15 | db.adv.schema.sql | 1.00 | Estructura de la base de datos del EA final | Parte 22 |
| 16 | db.cut.schema.sql | 1.00 | Estructura de la base de datos de optimización truncada | Parte 22 |
| 17 | db.opt.schema.sql | 1.06 | Estructura de la base de datos de optimización | Parte 29 |
| 18 | Storage.mqh | 1.01 | Clase para gestionar el almacenamiento clave-valor del EA final en la base de datos del EA | Parte 23 |
| └ Experts | Archivos con partes comunes de los EA de distintos tipos utilizados | |||
| 19 | Expert.mqh | 1.24 | El archivo de biblioteca para el EA final. Los parámetros del grupo se pueden extraer de la base de datos del EA | Parte 28 |
| 20 | Optimization.mqh | 1.06 | Archivo de biblioteca para el EA que gestiona el lanzamiento de tareas de optimización | Parte 29 |
| 21 | Stage1.mqh | 1.19 | Archivo de biblioteca para el EA que optimiza una instancia individual de una estrategia de trading (etapa 1) | Parte 23 |
| 22 | Stage2.mqh | 1.04 | Archivo de biblioteca para el EA que optimiza un grupo de instancias de estrategias de trading (etapa 2) | Parte 23 |
| 23 | Stage3.mqh | 1.04 | Archivo de biblioteca para el EA que guarda un grupo de estrategias generado y normalizado en una base de datos de EA con un nombre determinado. | Parte 23 |
| └ Optimization | Clases encargadas de la optimización automática | |||
| 24 | OptimizationJob.mqh | 1.00 | Clase de trabajo de una etapa del proyecto de optimización | Parte 25 |
| 25 | OptimizationProject.mqh | 1.00 | Clase de proyecto de optimización | Parte 25 |
| 26 | OptimizationStage.mqh | 1.00 | Clase de etapa del proyecto de optimización | Parte 25 |
| 27 | OptimizationTask.mqh | 1.01 | Clase de tarea de optimización (creación) | Parte 29 |
| 28 | Optimizer.mqh | 1.04 | Clase para el gestor de optimización automática del proyecto | Parte 29 |
| 29 | OptimizerTask.mqh | 1.06 | Clase de tarea de optimización (cadena) | Parte 29 |
| └ Strategies | Ejemplos de estrategias de trading utilizadas para mostrar cómo funciona el proyecto | |||
| 24 | HistoryStrategy.mqh | 1.00 | Clase de estrategia de trading para reproducir el historial de operaciones | Parte 16 |
| 25 | SimpleVolumesStrategy.mqh | 1.11 | Clase de estrategia de trading que utiliza volúmenes de ticks | Parte 22 |
| └ Utils | Utilidades auxiliares, macros para reducir el código | |||
| 26 | ConsoleDialog.mqh | 1.01 | Clase para mostrar datos de texto en un gráfico | Parte 28 |
| 26 | ExpertHistory.mqh | 1.00 | Clase para exportar el historial de operaciones a un archivo | Parte 16 |
| 27 | Macros.mqh | 1.07 | Macros útiles para operaciones con matrices | Parte 26 |
| 28 | MTTester.mqh | — | Archivo de la biblioteca MultiTester para trabajar con el probador de estrategias | Parte 28 |
| 29 | NewBarEvent.mqh | 1.00 | Clase para detectar una nueva barra de un símbolo concreto | Parte 8 |
| 30 | SymbolsMonitor.mqh | 1.01 | Clase para obtener información sobre instrumentos de negociación (símbolos) | Parte 28 |
| └ Virtual | Clases para crear diversos objetos que tienen en común el uso de un sistema de órdenes y posiciones de negociación virtuales | |||
| 31 | Money.mqh | 1.01 | Clase básica de gestión monetaria | Parte 12 |
| 32 | TesterHandler.mqh | 1.07 | Clase para gestionar los eventos de optimización | Parte 23 |
| 33 | VirtualAdvisor.mqh | 1.12 | Clase del EA que gestiona posiciones virtuales (órdenes) | Parte 28 |
| 34 | VirtualChartOrder.mqh | 1.02 | Clase de posición virtual con representación gráfica | Parte 28 |
| 35 | VirtualCloseManager.mqh | 1.00 | Clase del gestor de cierre | Parte 28 |
| 36 | VirtualHistoryAdvisor.mqh | 1.00 | Clase del EA que reproduce el historial de operaciones | Parte 16 |
| 37 | VirtualInterface.mqh | 1.00 | Clase de la interfaz gráfica de usuario del EA | Parte 4 |
| 38 | VirtualOrder.mqh | 1.09 | Clase de órdenes y posiciones virtuales | Parte 22 |
| 39 | VirtualReceiver.mqh | 1.04 | Clase para convertir volúmenes abiertos en posiciones de mercado (receptor) | Parte 23 |
| 40 | VirtualRiskManager.mqh | 1.06 | Clase de gestión de riesgos (gestor de riesgos) | Parte 28 |
| 41 | VirtualStrategy.mqh | 1.09 | Clase de una estrategia de trading con posiciones virtuales | Parte 23 |
| 42 | VirtualStrategyGroup.mqh | 1.04 | Clase de grupos de estrategias de trading | Parte 28 |
| 43 | VirtualSymbolReceiver.mqh | 1.00 | Clase de receptor de símbolos | Parte 3 |
| Common/Files | Carpeta de datos común de los terminales de MetaTrader 5 | |||
| 44 | article.17607.db.sqlite | — | Base de datos de optimización | Parte 29 |
| 45 | SimpleCandles-27183.test.db.sqlite | — | Base de datos del EA final | Parte 29 |
El código fuente también está disponible en SimpleCandles y Adwizard
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/17607
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.