English Русский Deutsch 日本語
preview
Implementación de módulos prácticos de otros lenguajes en MQL5 (Parte 03): Módulo schedule de Python, una versión mejorada del evento OnTimer

Implementación de módulos prácticos de otros lenguajes en MQL5 (Parte 03): Módulo schedule de Python, una versión mejorada del evento OnTimer

MetaTrader 5Probador |
17 1
Omega J Msigwa
Omega J Msigwa

Contenido


Introducción

La programación tiene como objetivo facilitarnos la vida, permitiéndonos automatizar muchas de las tareas cruciales y, a veces, aburridas o repetitivas que solemos querer que realicen los ordenadores sin ninguna interacción humana. Un buen ejemplo es la función de guardado automático que vemos en muchos editores de texto. En lugar de tener que preocuparse por guardar los documentos cada vez que se escribe una palabra nueva, los editores de texto se encargan del proceso de guardado automáticamente para que uno pueda concentrarse en escribir sin preocuparse por perder el trabajo cuando ocurre algo inesperado.

En el ámbito del trading, esto no es diferente, ya que numerosas actividades y tareas repetitivas facilitan las operaciones y queremos automatizarlas mediante unas pocas líneas de código.

Fuente: unsplash.com

En el lenguaje de programación MQL5, contamos con la famosa función OnTimer, que permite ejecutar determinadas funciones y líneas de código tras un intervalo de tiempo específico establecido por el programador.

A continuación se muestra un ejemplo sencillo: ejecutar la función OnTimer cada 10 segundos.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {   
   
   EventSetTimer(10); //Creates a timer with 10 seconds period    
    
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
    EventKillTimer(); //Destroy the timer after completing the work
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
    
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnTimer(void)
  {
     Print("Ontimer called: ",TimeLocal());   //This line of code will be run after every 10 seconds
  }

Resultados.

MN      0       10:16:39.455    Schedule test (XAUUSD,D1)       Ontimer called: 2025.07.21 10:16:39
CD      0       10:16:49.459    Schedule test (XAUUSD,D1)       Ontimer called: 2025.07.21 10:16:49

Esta función cumple su función, pero sigue siendo bastante básica y no lo suficientemente flexible como para permitir que varios horarios diferentes se ejecuten simultáneamente en el mismo programa. 

Una vez que configure el controlador de eventos OnTimer en su Asesor Experto o Indicador, solo podrá depender de esa única programación del temporizador. Esto resulta muy limitante porque a menudo tenemos diferentes tareas que queremos que se ejecuten en diferentes momentos (intervalos) en nuestros programas.

Por ejemplo, imprimir o enviar informes de operaciones diarios, semanales o mensuales a los usuarios.

En el lenguaje de programación Python, existe un módulo similar a la función OnTimer, pero es mucho mejor para ejecutar funciones de manera oportuna. En este artículo, lo analizaremos e implementaremos un módulo similar en el lenguaje de programación MQL5.


¿Qué es el módulo Schedule que ofrece Python?

Conocido como: «Python job scheduling for humans» (Programación de tareas en Python para personas)

Este es un módulo de Python fácil de usar que ayuda a programar tareas específicas para que se ejecuten después de una hora determinada del día, de la semana, etc. Este módulo es sencillo de usar y ligero, lo que lo convierte en un módulo necesario que todo desarrollador de Python debe conocer.

A diferencia de la función OnTimer que tenemos en MQL5, el módulo schedule no solo nos permite programar tareas para que se ejecuten tras un intervalo de tiempo específico, sino que también nos ofrece la flexibilidad necesaria para definir con mayor precisión cuándo y cómo debe ejecutarse una tarea (función) concreta.

A continuación se enumeran algunas de las funciones que ofrece este módulo.

Importaciones

import schedule

Función Descripción
schedule.every(10).minutes.do(job)
Al igual que en el evento OnTimer, cada 10 minutos se ejecutará la función denominada job.
schedule.every().hour.do(job)
La función denominada job se ejecutará cada hora desde el inicio del script.
schedule.every().day.at("10:30").do(job)
La función denominada job se ejecutará todos los días a las 10:30, hora local, en formato de 24 horas.
schedule.every().monday.do(job)
La función denominada job se ejecutará todos los lunes a la misma hora exacta a la que se ejecutó inicialmente el script.
schedule.every().wednesday.at("13:15").do(job)
La función denominada job se ejecutará todos los miércoles a las 13:15. 
schedule.every().day.at("12:42", "Europe/Amsterdam").do(job)
La función denominada job se ejecutará todos los días a las 12:42, según la zona horaria Europa/Amsterdam. 
schedule.every().minute.at(":17").do(job) 
La función denominada job se ejecutará cada minuto, en el segundo 17. 

Estas son solo algunas de las funciones, aunque fundamentales, que ofrece este módulo. Implementemos una clase que nos resulte familiar en MQL5.


La clase Schedule en MQL5

El módulo schedule en Python está diseñado para utilizar funciones separadas para cada tarea que se desee programar en un intervalo específico. La función denominada do es el punto final de toda la cadena de funciones de la clase «schedule».

schedule.every(10).minutes.do

Para conseguir una sintaxis similar en MQL5, debemos hacer que algunas funciones de la clase CSchedule devuelvan una instancia de toda la clase, excepto la función denominada dO, que es el punto final.

class CSchedule
  {
private:

   int               m_period; //the number of seconds, minutes, etc to use
   time_intervals_enum m_unit; //time interval: minutes, hours, etc
   int               m_time_seconds; //datetime in seconds 
   JobFunction       m_func; //The function to run for the current schedule

public:

                     int  m_fixed_time;  // time from midnight in seconds

                     CSchedule(void);
                    ~CSchedule(void);
                                      
                     CSchedule*  every(int period = 1);
                     CSchedule*  seconds();
                     CSchedule*  minutes();
                     CSchedule*  hours();
                     CSchedule*  days();
                     CSchedule*  weeks();
                     CSchedule*  months();
                     CSchedule*  years();

                     void  dO(JobFunction func);
 }

Esta sintaxis nos permite disponer de una interfaz fluida similar a la que ofrece el módulo schedule de Python.

#include <schedule.mqh>
CSchedule schedule;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {   
   schedule.every().minutes().dO(runthis);
  }

La función denominada every es fundamental para establecer un intervalo específico. Por ejemplo:

   schedule.every(10).minutes()

Esto significa que, cada 10 minutos, debe activarse una función específica recibida por la función denominada dO.

En esencia, esta función toma un period dado y asigna esa variable a una variable llamada m_periodalmacenada dentro de la clase.

CSchedule* CSchedule::every(int period = 1)
  {
   m_period = period;
   return GetPointer(this);
  }

Las funciones: segundos, minutos, horas, etc., asignan la variable de intervalo de tiempo según todas las opciones de intervalos de tiempo disponibles que proporciona el enumerador denominado time_intervals_enum.

enum time_intervals_enum 
 {
   SECONDS,
   MINUTES,
   HOURS,
   DAYS,
   WEEKS,
   MONTHS,
   YEARS
};
CSchedule* CSchedule::seconds() 
 { 
   m_unit = SECONDS; 
   return GetPointer(this); 
 }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSchedule* CSchedule::minutes() 
 { 
   m_unit = MINUTES; 
   return GetPointer(this); 
 }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSchedule* CSchedule::hours() 
 { 
   m_unit = HOURS; 
   return GetPointer(this); 
 }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSchedule* CSchedule::days() 
  { 
   m_unit = DAYS; 
   return GetPointer(this); 
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSchedule* CSchedule::weeks(void)
  { 
   m_unit = WEEKS; 
   return GetPointer(this); 
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSchedule* CSchedule::months(void)
  { 
   m_unit = MONTHS; 
   return GetPointer(this); 
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSchedule* CSchedule::years(void)
  { 
   m_unit = YEARS; 
   return GetPointer(this); 
  }

Antes de abordar la función denominada dO, que es el punto final de todas las funciones de programación. Veamos cómo se gestiona y se almacena cada tarea (función) dentro del archivo denominado schedule.mqh.

//+------------------------------------------------------------------+
//| Handling and storing every job (task) used in the class CSchedule|
//+------------------------------------------------------------------+
typedef void (*JobFunction)();  // For global functions

struct jobs_struct
{
    int prev_run;
    int next_run;
    int interval;
    
    JobFunction func;  // Store the function pointer
};

jobs_struct m_jobs[];  // Global job list

void jobs_add(jobs_struct &jobs_array[], const jobs_struct &job)
  {
   uint size = ArraySize(jobs_array);
   ArrayResize(jobs_array, size + 1);
   jobs_array[size] = job;
  }

Dado que la clase CSchedule tiene funciones que hacen referencia a sí misma, gestionar cada objeto de tarea dentro de esta clase resulta complicado o confuso, y propicio a errores. 

Disponer de una matriz global denominada m_jobs ofrece una forma universal de almacenar y gestionar todos los trabajos utilizados dentro de la clase. Lo comentaremos dentro de un momento.

La función denominada dO recibe una función que se ejecuta repetidamente según un «schedule» recibido. Calcula cuándo se ejecutó por última vez una función y cuándo se prevé que se ejecute la próxima vez.

Una función recibida se almacena en la estructura denominada jobs_struct, junto con otras propiedades de los trabajos, como la última vez que se ejecutó y la próxima vez que se ejecutará.

A continuación, todos estos valores se almacenan en un array denominado m_jobs, que es un array de tipo jobs_struct.

jobs_struct m_jobs[];  // Global job list
void CSchedule::dO(JobFunction func)
 {
      m_func = func;
   
      jobs_struct job;
      job.func = m_func;
   
      datetime now = TimeLocal();
      job.prev_run = (int)now;
      job.interval = timedelta(m_period, m_unit); //Get configs from the every() method and above
      job.next_run = job.prev_run + timedelta(m_period, m_unit);
      
    if (MQLInfoInteger(MQL_DEBUG))
      Print("The first function run is schedule at: ", TimeToString((datetime)job.next_run, TIME_DATE | TIME_SECONDS));      
    
//---
      jobs_add(m_jobs, job); //store the job object to the list of jobs
 }

Una vez que una tarea se ha almacenado en la matriz correspondiente, necesitamos una función universal que la supervise constantemente y ejecute su función cuando llegue el momento programado.

void CSchedule::run_pending()
  {   
   int now = (int)TimeLocal();
   for(int i = 0; i < ArraySize(m_jobs); i++)
     {
      if(now >= m_jobs[i].next_run)
        {
         if(m_jobs[i].func != NULL)
            m_jobs[i].func();

         m_jobs[i].prev_run = m_jobs[i].next_run;
         // Recalculate next_run
         m_jobs[i].next_run += m_jobs[i].interval;
         
         if (MQLInfoInteger(MQL_DEBUG))
            printf("Prev run: %s Next run: %s", TimeToString((datetime)m_jobs[i].prev_run, TIME_DATE|TIME_SECONDS), TimeToString((datetime)m_jobs[i].next_run, TIME_DATE|TIME_SECONDS));
        }
     }
  }

Programemos nuestra primera tarea utilizando esta clase.

#include <schedule.mqh>
CSchedule schedule;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   
   schedule.every(10).seconds().dO(runthis);
   
   while (true)
    {
      schedule.run_pending();
    }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void runthis()
 {
   Print(__FUNCTION__," called at: ",TimeLocal());  
 }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

Queremos llamar a la función runthis cada 10 segundos. El bucle «while» infinito se utiliza únicamente para que el script siga ejecutándose hasta que se detenga.

A continuación se muestra la salida registrada cuando se ejecutó el script en modo de depuración.

NO      0       14:51:56.301    schedule test (XAUUSD,D1)       The first function run is schedule at: 2025.07.21 14:52:06
GS      0       14:52:06.000    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 14:52:06
PJ      0       14:52:06.000    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 14:52:06 Next run: 2025.07.21 14:52:16
QH      0       14:52:16.000    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 14:52:16
GR      0       14:52:16.000    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 14:52:16 Next run: 2025.07.21 14:52:26
KP      0       14:52:26.000    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 14:52:26
FJ      0       14:52:26.000    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 14:52:26 Next run: 2025.07.21 14:52:36

Podemos tener varios horarios que utilicen la misma clase.

void OnStart()
  {
//---
      
   schedule.every(10).seconds().dO(runthis); //run after every 10 seconds
   schedule.every().minute().dO(runthis2); //run on every minute
   
   while (true)
    {
      schedule.run_pending();
    }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void runthis()
 {
   Print(__FUNCTION__," called at: ",TimeLocal());  
 }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void runthis2()
 {
   Print("Hello world!, This function is called after a minute has passed");  
 }

Resultados.

FK      0       15:00:55.079    schedule test (XAUUSD,D1)       The first function run is schedule at: 2025.07.21 15:01:05
IL      0       15:00:55.079    schedule test (XAUUSD,D1)       The first function run is schedule at: 2025.07.21 15:01:55
ER      0       15:01:05.000    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 15:01:05
RK      0       15:01:05.000    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 15:01:05 Next run: 2025.07.21 15:01:15
OK      0       15:01:15.000    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 15:01:15
ES      0       15:01:15.000    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 15:01:15 Next run: 2025.07.21 15:01:25
IS      0       15:01:25.000    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 15:01:25
LJ      0       15:01:25.000    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 15:01:25 Next run: 2025.07.21 15:01:35
CK      0       15:01:35.000    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 15:01:35
KR      0       15:01:35.000    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 15:01:35 Next run: 2025.07.21 15:01:45
MP      0       15:01:45.000    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 15:01:45
FJ      0       15:01:45.000    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 15:01:45 Next run: 2025.07.21 15:01:55
GH      0       15:01:55.000    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 15:01:55
NM      0       15:01:55.000    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 15:01:55 Next run: 2025.07.21 15:02:05
KR      0       15:01:55.000    schedule test (XAUUSD,D1)       Hello world!, This function is called after a minute has passed
MH      0       15:01:55.000    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 15:01:55 Next run: 2025.07.21 15:02:55
NJ      0       15:02:05.001    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 15:02:05
HP      0       15:02:05.001    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 15:02:05 Next run: 2025.07.21 15:02:15
GR      0       15:02:15.000    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 15:02:15
LK      0       15:02:15.000    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 15:02:15 Next run: 2025.07.21 15:02:25
RK      0       15:02:25.001    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 15:02:25
FS      0       15:02:25.001    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 15:02:25 Next run: 2025.07.21 15:02:35
OS      0       15:02:35.004    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 15:02:35
JK      0       15:02:35.004    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 15:02:35 Next run: 2025.07.21 15:02:45
EK      0       15:02:45.000    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 15:02:45
KR      0       15:02:45.000    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 15:02:45 Next run: 2025.07.21 15:02:55
OP      0       15:02:55.000    schedule test (XAUUSD,D1)       runthis called at: 2025.07.21 15:02:55
EJ      0       15:02:55.000    schedule test (XAUUSD,D1)       Prev run: 2025.07.21 15:02:55 Next run: 2025.07.21 15:03:05
FK      0       15:02:55.000    schedule test (XAUUSD,D1)       Hello world!, This function is called after a minute has passed


Ejecutar tareas (funciones) a una hora concreta

A menudo queremos ejecutar nuestras funciones en un momento muy concreto. Por ejemplo, ejecutar una función encargada de abrir una operación a una hora determinada según una sesión de trading; por ejemplo, abrir una operación a las 19:00, hora local.

En la clase CSchedule, todas las funciones denominadas at se encargan de realizar esta tarea cuando se les proporciona «una hora válida» concreta.

Por ejemplo.

   schedule.every().day().at(19, 10).dO(job);

Esto programa la ejecución de la función denominada job todos los días a las 19:10 horas.

Implementar esta función en MQL5 resulta complicado, ya que necesitamos que se devuelvan clases distintas para cada función asociada al tiempo, es decir, segundo, minuto, hora, día y semana.

class CSchedule
  {
private:

   int               m_period; //the number of seconds, minutes, etc to use
   time_intervals_enum m_unit; //time interval: minutes, hours, etc
   int               m_time_seconds; //datetime in seconds 
   JobFunction       m_func;   //The function to run for the current schedule
   
   bool has_fixed_time() const { return m_fixed_time > 0; }
   datetime TodaysDate(datetime dt)
   {
      // Extract year, month, day — and build a new datetime at 00:00:00
      MqlDateTime tm;
      TimeToStruct(dt, tm);
      tm.hour = 0;
      tm.min = 0;
      tm.sec = 0;
      return StructToTime(tm);
   }

public:

                     int  m_fixed_time;  // time from midnight in seconds

                     CSchedule(void);
                    ~CSchedule(void);
                                      
                     CSchedule*  every(int period = 1);
                     CSchedule*  seconds();
                     CSchedule*  minutes();
                     CSchedule*  hours();
                     CSchedule*  days();
                     CSchedule*  weeks();
                     
                     MinuteScheduleBuilder* minute();
                     HourScheduleBuilder* hour();
                     DayScheduleBuilder* day();
                     WeekScheduleBuilder* week();

Dentro de cada clase «Builder» (todas las clases que terminan con la palabra Builder), hay una función llamada at, que se encarga de establecer un intervalo de tiempo específico.

También disponemos de la función denominada dO,, que hereda una función homónima de la clase CSchedule.

Por ejemplo, la clase WeekScheduleBuilder.

class CSchedule; //forward declaration | VERY IMPORTANT

class WeekScheduleBuilder
  {
protected:
   
   CSchedule *m_schedule;
   
public:
                     
                     WeekScheduleBuilder(CSchedule *schedule_) { m_schedule = schedule_; }
                    ~WeekScheduleBuilder(void) {};
                    
                     CSchedule* at(ENUM_DAY_OF_WEEK dayofweek, uint hour=0, uint minutes = 0, uint seconds = 0)
                        {
                           if (CheckPointer(m_schedule) == POINTER_INVALID || m_schedule == NULL)
                              return NULL;
                        
                           datetime now = TimeLocal();
                           MqlDateTime tm;
                           TimeToStruct(now, tm);
                        
                           int today_dow = tm.day_of_week;
                        
                           //--- Compute days until target day (next week if it's the same day or already passed)
                           
                           int days_ahead = (int)dayofweek - today_dow;
                           if (days_ahead < 0) 
                              days_ahead += 7;  // ensure it's next week
                        
                           datetime next_target_date = now + timedelta(days_ahead, DAYS);
                           MqlDateTime target_tm;
                           TimeToStruct(next_target_date, target_tm);
                        
                           //--- setting the correct time
                           
                           target_tm.hour = (int)hour;
                           target_tm.min = (int)minutes;
                           target_tm.sec = (int)seconds;
                        
                           m_schedule.m_fixed_time = (int)StructToTime(target_tm);
                           return m_schedule;
                        }
                       
                     void dO(JobFunction func)
                       {
                          m_schedule.dO(func);
                       }  
  };

Todas las funciones denominadas at reciben una hora concreta como argumento, calculan cuánto falta hasta la primera ejecución y asignan ese valor en segundos a la variable m_fixed_time.

Dentro de la función denominada dO, introducimos una condición para comprobar si el valor de tiempo recibido es un valor de tiempo fijo (p. ej., 19:00) o un número determinado de segundos, minutos, etc., hasta la próxima ejecución de la función, ya que ambas condiciones requieren un enfoque ligeramente diferente.

void CSchedule::dO(JobFunction func)
 {
      m_func = func;
   
      jobs_struct job;
      job.func = m_func;
   
      datetime now = TimeLocal();
      job.prev_run = (int)now;
      job.interval = timedelta(m_period, m_unit); //Get configs from the every() method and above
      
      if (has_fixed_time())
         {
            datetime scheduled_time = (datetime)m_fixed_time; //we add today's date to the fixed_time calculated
            
            //Add interval repeatedly until scheduled_time >= now
            while (scheduled_time <= now)
               scheduled_time += job.interval; //Schedule for the next time if the current time has passed
         
            job.next_run = (int)scheduled_time;
         }
      else
        {
          job.next_run = job.prev_run + job.interval;
        }
    
    if (MQLInfoInteger(MQL_DEBUG))
      Print("The first function run is schedule at: ", TimeToString((datetime)job.next_run, TIME_DATE | TIME_SECONDS));      
    
//---
      jobs_add(m_jobs, job); //store the job object to the list of jobs
 }

A continuación se explica cómo configurar varias programaciones para que se ejecuten de forma periódica a una hora determinada.

void OnStart()
  {
//---
   
   schedule.every().minute().at(10).dO(job); //Runs on every minute at the 10th second  
   schedule.every().hour().at(10).dO(job); //runs on every hour at the 10th minute  
   schedule.every().day().at(19, 10).dO(job); //runs every day at 19:10 hours
   schedule.every().week().at(MONDAY).dO(job); //Runs every week on Monday at 00:00 (by default)
   
   while (true)
    {
      schedule.run_pending();
    }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void job()
 {
   Print(__FUNCTION__," run at: ",TimeLocal()); 
 }


Cómo pasar argumentos a una tarea (Job)

Como se ha podido observar en los registros de salida del ejemplo anterior, resulta difícil identificar y realizar un seguimiento del progreso de un trabajo, sobre todo cuando hay varias funciones programadas ejecutándose al mismo tiempo. Para solucionar esto, necesitamos una variable opcional llamada jobs_name en la función llamada dO,; esta variable sirve para etiquetar todas las tareas programadas.

void CSchedule::dO(JobFunction func, const string jobs_name="")
 {    
      jobs_struct job;
      
      job.func = func; //Assigns the function to job's sturucture
      job.name = jobs_name; //Assigns the name to job's structure
   
      datetime now = TimeLocal();
      job.prev_run = (int)now;
      job.interval = timedelta(m_period, m_unit); //Get configs from the every() method and above
      
      if (has_fixed_time())
         {
            datetime today_midnight = TodaysDate(now); //Get todays date at 00:00
            datetime scheduled_time = today_midnight + m_fixed_time; //we add today's date to the fixed_time calculated
            
            //Add interval repeatedly until scheduled_time >= now
            while (scheduled_time <= now)
               scheduled_time += job.interval; //Schedule for the next time
         
            job.next_run = (int)scheduled_time;
         }
      else
        {
          job.next_run = job.prev_run + job.interval;
        }
    
    if (MQLInfoInteger(MQL_DEBUG))
      printf("Job: %s -> first run schedule at: [%s]",job.name, TimeToString((datetime)job.next_run, TIME_DATE | TIME_SECONDS));      
    
//---
      jobs_add(m_jobs, job); //store the job object to the list of jobs
 }

Ahora podemos hacer un seguimiento del avance de cada trabajo de forma más eficaz.

void OnStart()
  {
//---
   
   schedule.every().minute().at(10).dO(Greet, "Jacob");   
   schedule.every().hour().at(10).dO(Greet, "Anne");   
   schedule.every().day().at(08, 10).dO(Greet, "Chriss");
   schedule.every().week().at(MONDAY).dO(Greet, "Nobody");
   
   while (true)
    {
      schedule.run_pending();
    }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void Greet()
 {
   Print("Hello there!");
 }

Resultados.

JI      0       06:57:54.817    schedule test (XAUUSD,D1)       Job: Jacob -> first run schedule at: [2025.07.22 06:58:10]
MH      0       06:57:54.817    schedule test (XAUUSD,D1)       Job: Anne -> first run schedule at: [2025.07.22 07:10:00]
FL      0       06:57:54.817    schedule test (XAUUSD,D1)       Job: Chriss -> first run schedule at: [2025.07.22 08:10:00]
JO      0       06:57:54.817    schedule test (XAUUSD,D1)       Job: Nobody -> first run schedule at: [2025.07.28 00:00:00]
GN      0       06:58:10.014    schedule test (XAUUSD,D1)       Hello there!
LF      0       06:58:10.014    schedule test (XAUUSD,D1)       Job: Jacob -> Prev run: [2025.07.22 06:58:10] Next run: [2025.07.22 06:59:10]


Ejecutar una tarea hasta cierta hora

A veces tenemos tareas programadas que no queremos que se ejecuten indefinidamente. En este caso, establecer una fecha límite para estos trabajos sería de gran ayuda.

Vamos a presentar la función denominada until. Una función similar a la que ofrece el módulo «schedule» de Python.

CSchedule* CSchedule::until(datetime expiry_date)
 {
   m_expiry_date = expiry_date;
   
   return GetPointer(this);
 }

Dentro de la función denominada dO, tomamos la fecha de vencimiento de nuestra clase (obtenida de la función denominada «until») y la asignamos a la estructura del trabajo.

void CSchedule::dO(JobFunction func, const string jobs_name="")
 {    
      jobs_struct job;
      
      job.func = func; //Assigns the function to job's sturucture
      job.name = jobs_name; //Assigns the name to job's structure
   
      datetime now = TimeLocal();
      job.prev_run = (int)now;
      job.interval = timedelta(m_period, m_unit); //Get configs from the every() method and above
      job.expiry_date = m_expiry_date;
      
      //Other lines of code
 }      

Antes de ejecutar un trabajo dentro de la función denominada run_pending, debemos comprobar que no haya caducado.

void CSchedule::run_pending()
  {   
   int now = (int)TimeLocal();
   for(int i = 0; i < ArraySize(m_jobs); i++)
     {
      if (now >= (int)m_jobs[i].expiry_date && expiry_date != 0) //Check if the job hasn't expired
        {
          if (MQLInfoInteger(MQL_DEBUG))
            printf("Job: %s -> Expired",m_jobs[i].name);
            
          continue; //skip all expired jobs
        }

    //... other checks
  }

Por último, ejecutamos una tarea y fijamos la fecha de caducidad en 5 minutos a partir de la hora actual.

#include <schedule.mqh>
CSchedule schedule;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   
   schedule.every(1).minutes().until(D'22.7.2025 10:15').dO(Greet, "Greet"); //The current time was 10:10, in the same date
   
   while (true)
    {
      schedule.run_pending();
      Sleep(1000);
    }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void Greet()
 {
   Print("Hello there!");
 }

Resultados.

EM      0       10:10:02.849    schedule test (XAUUSD,D1)       Job: Greet -> first run schedule at: [2025.07.22 10:11:02]
CI      0       10:11:02.864    schedule test (XAUUSD,D1)       Hello there!
NS      0       10:11:02.864    schedule test (XAUUSD,D1)       Job: Greet -> Prev run: [2025.07.22 10:11:02] Next run: [2025.07.22 10:12:02]
FR      0       10:12:02.873    schedule test (XAUUSD,D1)       Hello there!
EJ      0       10:12:02.873    schedule test (XAUUSD,D1)       Job: Greet -> Prev run: [2025.07.22 10:12:02] Next run: [2025.07.22 10:13:02]
ND      0       10:13:02.861    schedule test (XAUUSD,D1)       Hello there!
OL      0       10:13:02.861    schedule test (XAUUSD,D1)       Job: Greet -> Prev run: [2025.07.22 10:13:02] Next run: [2025.07.22 10:14:02]
GM      0       10:14:02.922    schedule test (XAUUSD,D1)       Hello there!
PG      0       10:14:02.922    schedule test (XAUUSD,D1)       Job: Greet -> Prev run: [2025.07.22 10:14:02] Next run: [2025.07.22 10:15:02]
LH      0       10:15:00.945    schedule test (XAUUSD,D1)       Job: Greet -> Expired


Ejecutar todas las tareas independientemente de su programación

A veces puede que tengas que ejecutar todas las funciones de forma inmediata, independientemente de sus horarios. Normalmente, para realizar pruebas, y a veces simplemente queremos forzar que todas las operaciones programadas se ejecuten a la vez, por ejemplo, durante el inicio del programa.

En estas situaciones, resulta muy útil la función denominada run_all.

void CSchedule::run_all(void)
  {   
   datetime now = TimeLocal();
   for(int i = 0; i < ArraySize(m_jobs); i++)
     {
         if(m_jobs[i].func != NULL)
            m_jobs[i].func();
   
         m_jobs[i].prev_run = m_jobs[i].next_run;
         // Recalculate next_run
         m_jobs[i].next_run += m_jobs[i].interval;
         
         if (MQLInfoInteger(MQL_DEBUG))
            printf("%s run at: %s",m_jobs[i].name, TimeToString(now, TIME_DATE|TIME_SECONDS));
     }
     
    if (MQLInfoInteger(MQL_DEBUG)) 
      printf("%s -> All %I64u Jobs have been executed!",__FUNCTION__, m_jobs.Size());
  }

Ejemplo de uso.

#include <schedule.mqh>
CSchedule schedule;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   
   schedule.every(4).minutes().dO(Greet, "Greet every minute");
   schedule.every(4).hours().dO(Greet, "Greet hourly");
   schedule.every(4).days().dO(Greet, "Greet daily");
   schedule.every(4).weeks().dO(Greet, "Greet weekly");
   
   schedule.run_all();
 }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void Greet()
 {
   Print("Hello there!");
 }

Resultados.

CM      0       11:04:06.695    schedule test (XAUUSD,D1)       Job: Greet every minute -> first run schedule at: [2025.07.22 11:08:06]
RR      0       11:04:06.695    schedule test (XAUUSD,D1)       Job: Greet hourly -> first run schedule at: [2025.07.22 15:04:06]
HR      0       11:04:06.695    schedule test (XAUUSD,D1)       Job: Greet daily -> first run schedule at: [2025.07.26 11:04:06]
MN      0       11:04:06.695    schedule test (XAUUSD,D1)       Job: Greet weekly -> first run schedule at: [2025.08.19 11:04:06]
NI      0       11:04:06.695    schedule test (XAUUSD,D1)       Hello there!
QL      0       11:04:06.695    schedule test (XAUUSD,D1)       Greet every minute run at: 2025.07.22 11:04:06
RN      0       11:04:06.695    schedule test (XAUUSD,D1)       Hello there!
GF      0       11:04:06.695    schedule test (XAUUSD,D1)       Greet hourly run at: 2025.07.22 11:04:06
RL      0       11:04:06.695    schedule test (XAUUSD,D1)       Hello there!
KJ      0       11:04:06.695    schedule test (XAUUSD,D1)       Greet daily run at: 2025.07.22 11:04:06
DR      0       11:04:06.695    schedule test (XAUUSD,D1)       Hello there!
QK      0       11:04:06.695    schedule test (XAUUSD,D1)       Greet weekly run at: 2025.07.22 11:04:06
FL      0       11:04:06.695    schedule test (XAUUSD,D1)       CSchedule::run_all -> All 4 Jobs have been executed!

A pesar de haber configurado estas cuatro funciones para que se ejecutaran después de su cuarto intervalo de tiempo, todas se ejecutaron al mismo tiempo.


Gestión de horarios

Necesitamos diferentes maneras de acceder y cancelar diferentes programaciones mediante programación, ya que algunas programaciones podrían quedar obsoletas con el paso del tiempo.

Obtener todos los "jobs"

Función Descripción
void get_jobs(jobs_struct &jobs_struct_array[]) 
  { 
    ArrayResize(jobs_struct_array, m_jobs.Size());
    for (uint i=0; i<m_jobs.Size(); i++)
      jobs_struct_array[i] = m_jobs[i];
  }
Esta función proporciona un argumento que devuelve, por referencia, un array que contiene una estructura con todas las propiedades de todas las tareas.
uint get_jobs() { return m_jobs.Size(); }
Devuelve el número de Jobs programados.

Cancelación de Jobs programados

Función Descripción
bool Cancel(const string jobs_name);
Esto cancela una tarea programada utilizando su nombre.
bool Cancel(const uint jobs_index);
Cancela una tarea programada utilizando su número de índice (de 0 a +infinito), es decir, si el trabajo fue el primero en programarse, su número de índice es 0.
bool Clear() { return ArrayResize(m_jobs, 0)==-1?false:true; } 
Esto borra (elimina) todas las tareas programadas de la memoria. No se ejecutará ningún trabajo ni tarea tras la llamada a esta función.

Ejemplo de uso.

#include <schedule.mqh>
CSchedule schedule;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   
   schedule.every().minute().at(0).dO(Greet, "EveryMin Greetings"); //Job is set at index 0
   schedule.every().hour().at(20,10).dO(Greet, "Hourly Greetings"); //Job is set at index 1
   schedule.every().day().at(13,20,10).dO(Greet, "Daily Greetings"); //JOb is set at index 2
   schedule.every().week().at(MONDAY, 13, 56).dO(Greet, "Weekly Greetings"); //Job is set at index 3
   
   schedule.Cancel(0); //Cancel the job at index 0, the first one
   Print("Jobs remaining: ",schedule.get_jobs());
   
   schedule.Cancel("Hourly Greetings"); //Cancel the job with this name
   Print("Jobs remaining: ",schedule.get_jobs());
   
   schedule.Clear(); //Clear all schedules
   Print("Jobs remaining: ",schedule.get_jobs());
   
   while (true)
    {
      schedule.run_pending();
      Sleep(1000);
    }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void Greet()
 {
   Print("Hello there!");
 }

Resultados.

LK      0       16:01:02.017    schedule test (XAUUSD,D1)       Job: EveryMin Greetings -> first run schedule at: [2025.07.22 16:02:00]
HH      0       16:01:02.017    schedule test (XAUUSD,D1)       Job: Hourly Greetings -> first run schedule at: [2025.07.22 17:20:10]
EH      0       16:01:02.017    schedule test (XAUUSD,D1)       Job: Daily Greetings -> first run schedule at: [2025.07.23 13:20:10]
PE      0       16:01:02.017    schedule test (XAUUSD,D1)       Job: Weekly Greetings -> first run schedule at: [2025.07.28 13:56:00]
OS      0       16:01:02.017    schedule test (XAUUSD,D1)       CSchedule::Cancel Job at index [0] removed
HS      0       16:01:02.017    schedule test (XAUUSD,D1)       Jobs remaining: 3
RG      0       16:01:02.018    schedule test (XAUUSD,D1)       CSchedule::Cancel Job 'Hourly Greetings' removed
DL      0       16:01:02.018    schedule test (XAUUSD,D1)       Jobs remaining: 2
DD      0       16:01:02.018    schedule test (XAUUSD,D1)       Jobs remaining: 0


Cómo gestionar las zonas horarias

En todos los ejemplos e implementaciones de código que hemos comentado anteriormente en nuestra biblioteca, hemos utilizado la hora local. Sin embargo, esto resulta muy limitante si tenemos en cuenta que disponemos de múltiples opciones de tiempo que los desarrolladores pueden utilizar en el lenguaje de programación MQL5. Por ejemplo, es posible que desee programar una operación comercial para una hora determinada según la hora del servidor del bróker o la hora UTC.

Dentro del constructor de la clase CSchedule, añadimos una variable opcional que permite a los desarrolladores seleccionar el tipo de tiempo que se utilizará para todas las operaciones de programación.

CSchedule::CSchedule(TIME_SOURCE_ENUM time_source=TIME_SOURCE_LOCAL):
 m_time_source(time_source)
 {
   if (MQLInfoInteger(MQL_DEBUG)) 
     printf("Schedule class initialized using %s, current time -> %s",EnumToString(time_source), (string)GetTime(m_time_source));
 }

A continuación se muestra la «Enumeración de fuentes de tiempo» y su función correspondiente.

enum TIME_SOURCE_ENUM
  {
   TIME_SOURCE_LOCAL,        // TimeLocal()
   TIME_SOURCE_CURRENT,      // TimeCurrent()
   TIME_SOURCE_TRADE_SERVER, // TimeTradeServer()
   TIME_SOURCE_GMT           // TimeGMT()
  };

datetime GetTime(TIME_SOURCE_ENUM source)
  {
   switch(source)
     {
      case TIME_SOURCE_LOCAL:
         return TimeLocal();
         
      case TIME_SOURCE_CURRENT:
         return TimeCurrent();
         
      case TIME_SOURCE_TRADE_SERVER:
         return TimeTradeServer();
         
      case TIME_SOURCE_GMT:
         return TimeGMT();
         
      default:
         return TimeLocal(); // Fallback
     }
  }

Ejemplo de uso.

#include <schedule.mqh>
CSchedule schedule(TIME_SOURCE_GMT); //Using GMT 
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   
   schedule.every().minute().at(0).dO(Greet, "EveryMin Greetings"); //Job is set at index 0
   schedule.every().hour().at(20,10).dO(Greet, "Hourly Greetings"); //Job is set at index 1
   schedule.every().day().at(13,20,10).dO(Greet, "Daily Greetings"); //JOb is set at index 2
   schedule.every().week().at(MONDAY, 13, 56).dO(Greet, "Weekly Greetings"); //Job is set at index 3
   
   while (true)
    {
      schedule.run_pending();
      Sleep(1000);
    }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void Greet()
 {
   Print("Hello there!");
 }

Resultados.

LP      0       16:57:34.961    schedule test (XAUUSD,D1)       Schedule class initialized using TIME_SOURCE_GMT, current time -> 2025.07.22 13:57:34
QL      0       16:57:34.964    schedule test (XAUUSD,D1)       Job: EveryMin Greetings -> first run schedule at: [2025.07.22 13:58:00]
RM      0       16:57:34.964    schedule test (XAUUSD,D1)       Job: Hourly Greetings -> first run schedule at: [2025.07.22 14:20:10]
RE      0       16:57:34.964    schedule test (XAUUSD,D1)       Job: Daily Greetings -> first run schedule at: [2025.07.23 13:20:10]
KK      0       16:57:34.964    schedule test (XAUUSD,D1)       Job: Weekly Greetings -> first run schedule at: [2025.07.28 13:56:00]
HK      0       16:58:00.161    schedule test (XAUUSD,D1)       Hello there!
KO      0       16:58:00.161    schedule test (XAUUSD,D1)       Job: EveryMin Greetings -> Prev run: [2025.07.22 13:58:00] Next run: [2025.07.22 13:59:00]


Aplicaciones del módulo «Schedule» en tus aplicaciones de trading

Hemos visto cómo se puede utilizar este módulo en funciones sencillas y en ejemplos que muestran la ejecución programada de funciones. A continuación se muestran algunos ejemplos prácticos sobre cómo puedes utilizar esta biblioteca en tus aplicaciones de trading.

Una detección más eficaz del evento NewBar

No siempre es fácil escribir una función que detecte eficazmente la apertura de un nuevo bar, ya que la clase CSchedule tiene diferentes maneras de programar una tarea en un momento muy específico, podemos usarla para realizar ciertas acciones al comienzo de un segundo, minuto, hora, día, etc.

Dentro del archivo Schedule Testing EA.mq5.

#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>

CTrade m_trade;
CSymbolInfo m_symbol;
CPositionInfo m_position;

//---

#include <schedule.mqh>
CSchedule schedule(TIME_SOURCE_CURRENT); //Use the current broker's time
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

input int magic_number = 22072025;
input uint slippage = 100;
input uint stoploss = 500;
input uint takeprofit = 700;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
   m_trade.SetExpertMagicNumber(magic_number);
   m_trade.SetTypeFillingBySymbol(Symbol());
   m_trade.SetDeviationInPoints(slippage);
   
   if (!m_symbol.Name(Symbol()))
      {
         printf("%s -> Failed to select a symbol '%s'. Error = %d", __FUNCTION__,Symbol(),GetLastError());
         return INIT_FAILED;
      }
   
//--- Schedule
   
   schedule.every().hour().at(0,0).dO(MainTradingFunction); //every hour when the minute == 0 and second == 0

//---

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
    
    schedule.run_pending(); //Constanly monitor all the scheduled tasks
    
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool PosExists(ENUM_POSITION_TYPE type)
 {
    for (int i=PositionsTotal()-1; i>=0; i--)
      if (m_position.SelectByIndex(i))
         if (m_position.Symbol()==Symbol() && m_position.Magic() == magic_number && m_position.PositionType()==type)
            return (true);
            
    return (false);
 }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CloseAllTrades()
 {
   for (int i = PositionsTotal() - 1; i >= 0; i--)
      if (m_position.SelectByIndex(i))
         if (m_position.Magic() == magic_number && m_position.Symbol() == Symbol())
             m_trade.PositionClose(m_position.Ticket(), slippage);
 }
//+------------------------------------------------------------------+
//|      The main function for opening trades and performing other   |
//|      trading related tasks                                       |
//+------------------------------------------------------------------+
void MainTradingFunction()
 {
   printf("New bar detected!"); 
//---
   
   if (!m_symbol.RefreshRates())
      return;
      
    if (!PosExists(POSITION_TYPE_BUY))
      m_trade.Buy(m_symbol.LotsMin(), 
                  Symbol(), 
                  m_symbol.Ask(), 
                  m_symbol.Ask()-stoploss*m_symbol.Point(),
                  m_symbol.Ask()+takeprofit*m_symbol.Point()
                 );
                  
    if (!PosExists(POSITION_TYPE_SELL))
      m_trade.Sell(m_symbol.LotsMin(), 
                   Symbol(), 
                   m_symbol.Bid(),
                   m_symbol.Bid()+stoploss*m_symbol.Point(),
                   m_symbol.Bid()-takeprofit*m_symbol.Point()
                   ); 
//---
 }

Resultados en el Probador de estrategias.

Debemos configurar explícitamente las programaciones dentro de la función OnInit, de forma similar a EventSetTimer, utilizando la función dO .

Dado que la función schedule_pending se encarga de supervisar constantemente las programaciones, debe ejecutarse dentro de la función OnTick en los Expert Advisors, dentro de la función OnCalculate en los indicadores y dentro de un bucle infinito en un script MQL5.

Envío de informes diarios de operaciones

Al realizar un seguimiento durante unos minutos o segundos antes del cierre del mercado (por ejemplo, 5 minutos antes de las 00:00 horas), podemos imprimir o enviar informes diarios de operaciones a los usuarios.

int OnInit()
  {
//... other lines of code
   
//--- Schedule
   
   schedule.every().hour().at(0,0).dO(MainTradingFunction);
   schedule.every().day().at(23, 55).dO(SendDailyTradingReport); //every day 5 minutes before market closing

//---

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
    
    schedule.run_pending();
    
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void SendDailyTradingReport()
 {
   string sdate = TimeToString (TimeCurrent(), TIME_DATE);
   datetime start = StringToTime(sdate);

   if (!HistorySelect(start, TimeCurrent()))
     {
       printf("%s, line %d failed to obtain closed deals from history error =%d",__FUNCTION__,__LINE__,GetLastError());
       return;
     }
   
   Comment("");
   
//---
   
   double pl = 0.0;
   
   int trades_count=0;
   string report_body = "";
   for(int i = 0; i < HistoryDealsTotal(); i++)
     {
      if (m_deal.SelectByIndex(i))   
        if (m_deal.Entry() == DEAL_ENTRY_OUT && m_deal.Magic() == magic_number)
          {
            pl += m_deal.Profit();
            trades_count++;
            
            report_body += StringFormat("Trade[%d] -> | ticket: %I64u | type: %s | entry: %.5f | volume: %.3f | commision: %.3f\n",
                                          trades_count, 
                                          m_deal.Ticket(),
                                          EnumToString(m_deal.DealType()),
                                          m_deal.Entry(),
                                          m_deal.Volume(),
                                          m_deal.Commission()
                                        ); 
          }
     }
    string report_header = StringFormat("<<< Daily Trading Report >>> \r\n\r\nAC Balance: %.3f\r\nAC Equity: %.3f\r\nPL: %.3f\r\nTotal Trades: %d \r\n\r\n",
                                          m_account.Balance(),
                                          m_account.Equity(),
                                          pl,
                                          trades_count
                                        );   
   
//--- You might choose to send the reports instead of printing

   Comment(report_header+report_body); 
   Print(report_header+report_body);
 }

Resultados en el Probador de estrategias.

CS      0       11:33:47.902    Schedule testing EA (EURUSD,H1) 2025.03.13 23:55:00   <<< Daily Trading Report >>> 
CS      0       11:33:47.902    Schedule testing EA (EURUSD,H1) 2025.03.13 23:55:00   
CS      0       11:33:47.902    Schedule testing EA (EURUSD,H1) 2025.03.13 23:55:00   AC Balance: 2983.830
CS      0       11:33:47.902    Schedule testing EA (EURUSD,H1) 2025.03.13 23:55:00   AC Equity: 2983.200
CS      0       11:33:47.902    Schedule testing EA (EURUSD,H1) 2025.03.13 23:55:00   PL: -2.960
CS      0       11:33:47.902    Schedule testing EA (EURUSD,H1) 2025.03.13 23:55:00   Total Trades: 3 
CS      0       11:33:47.902    Schedule testing EA (EURUSD,H1) 2025.03.13 23:55:00   
CS      0       11:33:47.902    Schedule testing EA (EURUSD,H1) 2025.03.13 23:55:00   Trade[1] -> | ticket: 166 | type: DEAL_TYPE_SELL | entry: 1.00000 | volume: 0.010 | commision: 0.000
CS      0       11:33:47.902    Schedule testing EA (EURUSD,H1) 2025.03.13 23:55:00   Trade[2] -> | ticket: 168 | type: DEAL_TYPE_BUY | entry: 1.00000 | volume: 0.010 | commision: 0.000
CS      0       11:33:47.902    Schedule testing EA (EURUSD,H1) 2025.03.13 23:55:00   Trade[3] -> | ticket: 169 | type: DEAL_TYPE_SELL | entry: 1.00000 | volume: 0.010 | commision: 0.000
CS      0       11:33:47.902    Schedule testing EA (EURUSD,H1) 2025.03.13 23:55:00   

La misma idea puede extenderse a la generación de informes semanales todos los viernes, justo antes del cierre del mercado, y a la generación de informes mensuales.


Schedule vs. OnTimer

Si bien sería injusto comparar directamente estos dos métodos, ya que ambos funcionan bien a su manera, es bueno conocer sus diferencias y cuándo usar uno u otro y cuándo no.

OnTimer Schedule
Dado que permite un único evento de temporizador (una sola programación), esta funcionalidad integrada resulta útil cuando se desea programar una sola tarea. Permite realizar múltiples tareas y establecer diferentes intervalos para cada una. Esta biblioteca personalizada resulta útil cuando se tienen varios horarios que se desean ejecutar simultáneamente.
Es rápido y eficaz.   No es tan rápido como el OnTimer integrado; su eficacia aún está por comprobarse.
Está limitado únicamente a Asesores Expertos e Indicadores.  Funciona en todos los programas MQL5: Asesores Expertos (EA), Indicadores y Scripts. 

Funciona las 24 horas del día, los 7 días de la semana (siempre fiable). 

Se basa en las funciones de negociación (OnTick y OnCalculate), que solo se activan cuando el mercado está abierto.
A menos que se utilice dentro de un bucle infinito en un script, la función de monitorización denominada run_pending funciona durante menos de 24 (horas)/5 (días de la semana), dependiendo del mercado.

Para solucionar el problema de fiabilidad que surge al utilizar esta biblioteca, tal y como se describe en la última fila de la tabla comparativa, debes ejecutar la clase CSchedule dentro de la función OnTimer.

#include <schedule.mqh>
CSchedule schedule(TIME_SOURCE_CURRENT); //Use the current broker's time
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//... other functions & lines of code


//--- Schedule
   
   schedule.every().hour().at(0,0).dO(MainTradingFunction);
   schedule.every().day().at(23, 55).dO(SendDailyTradingReport); //every day 5 minutes before market closing

//--- Ontimer
   
   EventSetTimer(1); //Run the Ontimer function after every 1 second (pretty much always)

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
    EventKillTimer(); //Delete the timer
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
    
    //schedule.run_pending(); //❎
    
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnTimer(void)
  {
    schedule.run_pending(); //✅   
  }


Conclusión

Para los operadores algorítmicos, es fundamental poder establecer horarios específicos y ejecutar tareas a horas concretas. No solo muchas estrategias de negociación dependen de momentos concretos del día, sino que también hay numerosas actividades que a menudo queremos automatizar para que se ejecuten de forma repetida, como el envío de informes diarios de operaciones, actualizaciones mensuales y otras tareas similares.

La clase implementada ofrece una forma sencilla de configurar eventos recurrentes en tus programas MQL5, de forma similar a la que ofrece el módulo de programación de Python.

Aunque la función OnTimer es bastante buena, carece de algunas de las opciones fundamentales para configurar horarios fáciles de definir y entender por las personas. Así pues, no dudes en utilizar esta biblioteca en aquellos casos en los que OnTimer no sea suficiente.

Un saludo.


Tabla de archivos adjuntos

Nombre del archivo Descripción y uso
Experts\Schedule testing EA.mq5 Un Asesor Experto (EA) para programar operaciones de trading.
Include\schedule.mqh Contiene la clase CSchedule, útil para programar la ejecución de funciones en momentos e intervalos específicos.
Scripts\schedule test.mq5 Un script para probar la clase CSchedule.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18913

Archivos adjuntos |
Attachments.zip (7.22 KB)
Alexandru Gisca
Alexandru Gisca | 26 mar 2026 en 10:57
¡Un artículo estupendo! Lo veo como una especie de gestor de comportamientos que ayuda a estructurar la interacción entre los distintos componentes.

Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 33): Herramienta de teoría del rango de velas Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 33): Herramienta de teoría del rango de velas
Mejora tu análisis del mercado con el paquete «Candle-Range Theory» para MetaTrader 5, una solución totalmente nativa de MQL5 que convierte barras de precio en bruto en información de volatilidad en tiempo real. La biblioteca CRangePattern, de tamaño reducido, compara el rango real de cada vela con un ATR adaptativo y la clasifica en el instante en que cierra; a continuación, el indicador CRT muestra esas clasificaciones en el gráfico mediante rectángulos nítidos y flechas codificadas por color que revelan consolidaciones cada vez más estrechas, rupturas explosivas y envolvimientos completos del rango en el momento en que se producen.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Análisis de la dinámica horaria de los símbolos de trading y de sus spreads en MetaTrader 5 Análisis de la dinámica horaria de los símbolos de trading y de sus spreads en MetaTrader 5
El indicador de índice de estacionalidad ProSpread con media móvil, como herramienta de análisis técnico que identifica patrones estacionales en el movimiento del precio, analiza el comportamiento del precio en determinadas horas de negociación, puede usarse tanto con un único instrumento como con el spread entre dos activos, y visualiza la probabilidad estadística de movimientos direccionales.