English Русский 中文 Español Deutsch 日本語
preview
Implementação de módulos práticos de outras linguagens em MQL5 (Parte 03): módulo schedule do Python, recursos avançados do OnTimer

Implementação de módulos práticos de outras linguagens em MQL5 (Parte 03): módulo schedule do Python, recursos avançados do OnTimer

MetaTrader 5 — Testador |
20 1
Omega J Msigwa
Omega J Msigwa

Seções


Introdução

A programação existe para facilitar nossa vida, permitindo automatizar muitas tarefas importantes que, por vezes, são monótonas ou repetitivas e que preferimos deixar a cargo do computador, sem intervenção humana. Um bom exemplo é o recurso de salvamento automático disponível em muitos editores de texto. Em vez de precisar salvar o documento a cada nova palavra escrita, o editor cuida disso automaticamente. Assim, você pode se concentrar na criação do conteúdo sem se preocupar com a possibilidade de perder seu trabalho caso ocorra algum imprevisto.

No trading, não é diferente: queremos automatizar com poucas linhas de código as diversas rotinas repetitivas envolvidas na execução de operações.

Fonte: unsplash.com

O MQL5 conta com a conhecida função OnTimer, que permite executar funções ou trechos de código em intervalos de tempo definidos pelo programador.

Veja um exemplo simples de execução da função OnTimer a 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

Embora funcione bem, essa função é relativamente básica e não oferece flexibilidade suficiente para executar simultaneamente tarefas com diferentes agendamentos em um mesmo programa.

Depois de configurar o manipulador de eventos OnTimer em seu EA ou indicador, você fica restrito a um único agendamento do temporizador. Isso impõe uma limitação considerável, pois muitas vezes precisamos executar tarefas diferentes em horários distintos e com intervalos próprios.

Por exemplo, imprimir ou enviar aos usuários relatórios diários, semanais e mensais das operações de trading.

O Python dispõe de um módulo com finalidade semelhante à do OnTimer, mas muito mais adequado para agendar a execução de funções. Neste artigo, vamos conhecer esse módulo e implementar uma versão equivalente em MQL5.


O que é o módulo schedule do Python?

Conhecido como "agendamento de tarefas em Python para humanos".

Trata-se de um módulo Python intuitivo que permite agendar tarefas específicas para horários específicos do dia, determinados dias da semana e assim por diante. É simples de usar e leve, o que o torna uma ferramenta que todo desenvolvedor Python deveria conhecer.

Diferentemente da função OnTimer disponível no MQL5, o módulo schedule permite não apenas agendar tarefas em intervalos definidos, mas também especificar com mais precisão quando e como cada tarefa (função) deve ser executada.

Veja alguns dos recursos oferecidos pelo módulo.

Imports

расписание import

Função Descrição
schedule.every(10).minutes.do(job)
Assim como no evento OnTimer, a função job será executada a cada 10 minutos.
schedule.every().hour.do(job)
A função job será executada a cada hora, a partir do início da execução do script.
schedule.every().day.at("10:30").do(job)
A função job será executada diariamente às 10:30, no horário local (formato de 24 horas).
schedule.every().monday.do(job)
A função job será executada toda segunda-feira, no mesmo horário em que o script foi iniciado pela primeira vez.
schedule.every().wednesday.at("13:15").do(job)
A função job será executada toda quarta-feira às 13:15.
schedule.every().day.at("12:42", "Europe/Amsterdam").do(job)
A função job será executada diariamente às 12:42, no fuso horário Europe/Amsterdam.
schedule.every().minute.at(":17").do(job) 
A função job será executada a cada minuto, no segundo 17.

Esses são apenas alguns dos principais recursos oferecidos pelo módulo. Vamos implementar uma classe equivalente em MQL5.


Classe schedule em MQL5

No Python, o módulo schedule permite associar uma função a cada tarefa que você deseja agendar para determinado intervalo de tempo. O método "do" encerra a sequência de chamadas utilizada para configurar o agendamento.

schedule.every(10).minutes.do

Para reproduzir essa sintaxe em MQL5, precisamos fazer com que vários métodos da classe CSchedule retornem a própria instância da classe, com exceção do método do, que encerra a sequência de chamadas.

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);
 }

Dessa forma, conseguimos uma interface semelhante à oferecida pelo módulo schedule do Python.

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

O método every é responsável por definir o intervalo de execução correspondente à unidade de tempo escolhida. Por exemplo:

   schedule.every(10).minutes()

Isso significa que uma função específica, passada ao método dO, deverá ser executada a cada 10 minutos.

Na prática, o método recebe o valor do parâmetro period e o armazena na variável m_period da classe.

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

Os métodos seconds, minutes, hours etc. definem o valor da variável timeframe de acordo com as unidades de tempo disponíveis na enumeração 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 analisar o método dO, que encerra a sequência de chamadas de agendamento, vamos entender como cada tarefa (função) é tratada e armazenada no arquivo 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;
  }

Como alguns métodos da classe CSchedule fazem referência à própria classe, o tratamento de cada objeto de tarefa dentro dela pode se tornar complexo, confuso e propenso a erros.

O array global m_jobs oferece uma solução geral para armazenar e tratar todas as tarefas utilizadas pela classe. Voltaremos a esse assunto mais adiante.

O método "dO" recebe a função que deverá ser executada repetidamente conforme o agendamento configurado. Ele calcula o horário da última execução da função e o horário previsto para a próxima.

A função recebida é armazenada na estrutura jobs_struct, junto com as demais propriedades da tarefa, como os horários da última execução e da próxima execução.

Em seguida, todos esses valores são armazenados no array m_jobs, cujos elementos são do 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
 }

Depois de armazenar a tarefa nesse array, precisamos de uma função genérica que monitore continuamente seus elementos e execute a função associada a cada tarefa quando chegar o horário 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));
        }
     }
  }

Vamos configurar o agendamento da nossa primeira tarefa usando essa classe.

#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 chamar a função runthis a cada 10 segundos. O laço infinito while serve apenas para manter o script em execução até que seja interrompido.

A seguir, vemos a saída registrada no log durante a execução do script em modo de depuração.

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 configurar vários agendamentos usando a mesma classe.

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


Execução de tarefas (funções) em horários específicos

Frequentemente, precisamos executar funções em horários exatos. Por exemplo, podemos precisar executar uma função responsável pela abertura de uma operação em um horário definido de acordo com a sessão de negociação, como às 19:00, no horário local.

Na classe CSchedule, os métodos "at" permitem configurar esse tipo de execução, desde que recebam um horário válido.

Por exemplo:

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

Isso agenda a execução da função job para todos os dias às 19:10.

A implementação desse recurso em MQL5 é mais complexa, pois cada método associado a uma unidade de tempo (segundos, minutos, horas, dias ou semanas) precisa retornar uma classe específica.

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();

Cada classe Builder (ou seja, cada classe cujo nome termina em Builder) contém um método "at", responsável por configurar um intervalo de tempo específico.

Além disso, cada uma dessas classes dispõe do método "dO", herdado da classe CSchedule.

Por exemplo, a classe 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);
                       }  
  };

Todos os métodos "at" recebem como argumento o tempo definido até a primeira execução da função e armazenam o valor correspondente, em segundos, na variável m_fixed_time.

No método "dO", incluímos uma condição para distinguir se o valor recebido representa um horário fixo (por exemplo, 19:00) ou um intervalo definido em segundos, minutos etc. até a próxima execução da função. Essa distinção é necessária porque os dois casos exigem tratamentos diferentes.

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 seguir, mostramos como configurar vários agendamentos para executar tarefas repetidamente em horários específicos.

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()); 
 }


Passagem de argumentos para uma tarefa

Como mostram os exemplos anteriores de saída no log, identificar cada tarefa e acompanhar sua execução pode ser difícil, sobretudo quando várias funções agendadas são executadas simultaneamente. Para resolver isso, precisamos de um parâmetro opcional chamado jobs_name no método "dO", que permite identificar cada tarefa agendada.

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
 }

Agora podemos acompanhar a execução de cada tarefa com mais facilidade.

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]


Execução de uma tarefa até determinado horário

Às vezes, precisamos agendar tarefas, mas não queremos que continuem sendo executadas indefinidamente. Nesses casos, seria útil estabelecer um prazo limite para sua execução.

Para isso, vamos introduzir o método "until", semelhante ao disponível no módulo schedule do Python.

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

No método "dO", obtemos a data de expiração definida na classe por meio do método "until" e a armazenamos na estrutura da tarefa.

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 executar uma tarefa no método "run_pending", precisamos verificar se seu prazo de execução já expirou.

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 fim, executamos a tarefa e definimos sua expiração para cinco minutos após o momento atual.

#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


Execução de todas as tarefas, independentemente de seus agendamentos

Em algumas situações, pode ser necessário executar todas as funções imediatamente, independentemente dos agendamentos definidos. Isso costuma ser útil em testes, mas também pode ser necessário forçar a execução simultânea de todas as tarefas programadas, por exemplo, durante a inicialização do programa.

Nesses casos, podemos utilizar o método "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());
  }

Exemplo 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!

Embora as quatro funções estivessem programadas para execução após o quarto intervalo de tempo, todas foram executadas no mesmo instante.


Gerenciamento dos agendamentos

Precisamos de meios para consultar e cancelar os agendamentos por código, pois alguns deles podem deixar de ser necessários com o tempo.

Obtenção de todas as tarefas

Função Descrição
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];
  }
Esse método recebe um parâmetro por referência e o preenche com um array de estruturas que contém todas as propriedades das tarefas.
uint get_jobs() { return m_jobs.Size(); }
Retorna a quantidade de tarefas agendadas.

Cancelamento de tarefas agendadas

Função Descrição
bool Cancel(const string jobs_name);
Cancela uma tarefa agendada pelo nome.
bool Cancel(const uint jobs_index);
Cancela uma tarefa agendada pelo índice (a partir de 0). Assim, a primeira tarefa agendada tem índice 0.
bool Clear() { return ArrayResize(m_jobs, 0)==-1?false:true; } 
Remove da memória todas as tarefas agendadas. Após a chamada desse método, nenhuma tarefa será executada.

Exemplo 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


Uso de fusos horários

Em todos os exemplos e implementações apresentados até aqui, utilizamos o horário local. No entanto, essa abordagem é bastante limitada, já que o MQL5 oferece diferentes fontes de horário que os desenvolvedores podem utilizar. Por exemplo, pode ser necessário agendar uma operação de trading para um horário específico com base no horário do servidor da corretora ou no horário UTC.

Para isso, adicionamos ao construtor da classe CSchedule um parâmetro opcional que permite ao desenvolvedor escolher a fonte de horário utilizada em todas as operações de agendamento.

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 seguir, apresentamos a enumeração Time source enumeration ("Enumeração das fontes de horário") e a função correspondente.

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
     }
  }

Exemplo 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]


Aplicação do módulo schedule em seus programas de trading

Até aqui, vimos como utilizar o módulo em funções simples, com exemplos que demonstram a execução de tarefas nos momentos programados. Agora, vamos explorar algumas aplicações práticas dessa biblioteca em programas de trading.

Tratamento mais eficiente de eventos NewBar

Nem sempre é fácil criar uma função que detecte de forma eficiente a abertura de uma nova barra.

Como a classe CSchedule oferece diferentes maneiras de agendar tarefas para momentos específicos, podemos utilizá-la para executar determinadas ações no início de cada segundo, minuto, hora, dia etc.

#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 no testador de estratégias.

Assim como fazemos com EventSetTimer, precisamos configurar explicitamente todos os parâmetros na função OnInit, utilizando o método "dO".

Como a função schedule_pending é responsável por monitorar continuamente os agendamentos, ela deve ser chamada dentro da função OnTick nos EAs, da função OnCalculate nos indicadores ou de um laço infinito nos scripts MQL5.

Envio de relatórios diários das operações de trading

Ao acompanhar a situação do mercado nos minutos ou segundos que antecedem seu fechamento (por exemplo, cinco minutos antes das 00:00), podemos imprimir ou enviar aos usuários relatórios diários das operações de trading.

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 no testador de estratégias.

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   

O mesmo princípio pode ser aplicado à geração de relatórios semanais, todas as sextas-feiras, pouco antes do fechamento do mercado, e também à preparação de relatórios mensais.


Módulo schedule vs. evento OnTimer

Embora não seja adequado comparar diretamente essas duas soluções, já que ambas funcionam bem em suas respectivas aplicações, é importante conhecer suas diferenças para saber quando utilizar cada uma delas.

OnTimer schedule
Como esse recurso nativo permite apenas um evento de temporizador (um único agendamento), ele é útil quando precisamos agendar a execução de uma única tarefa. Permite executar várias tarefas com agendamentos independentes e definir intervalos diferentes para cada uma. Essa biblioteca personalizada é útil quando precisamos executar várias tarefas simultaneamente.
É rápido e eficiente. Não é tão rápida quanto o OnTimer nativo, e sua eficiência ainda precisa ser avaliada.
Seu uso está restrito a EAs e indicadores. Funciona em todos os tipos de programa MQL5: EAs, indicadores e scripts.

Funciona 24 horas por dia e é sempre confiável.

A biblioteca depende dos manipuladores de eventos OnTick e OnCalculate, que são acionadas apenas quando o mercado está aberto.
Se a função de monitoramento "run_pending" não for chamada em um laço infinito dentro de um script, a execução ficará limitada aos períodos de atividade do mercado, podendo funcionar por menos de 24 horas por dia ou menos de cinco dias por semana, dependendo do mercado.

Para corrigir a limitação de confiabilidade descrita na última linha da tabela comparativa, é necessário executar a rotina de agendamento da classe CSchedule dentro do manipulador de eventos 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(); //✅   
  }


Conclusão

Para traders algorítmicos, é fundamental saber configurar agendamentos específicos e executar tarefas em horários exatos. Muitas estratégias de trading dependem não apenas de horários precisos, mas também de diversas tarefas que queremos automatizar para que sejam executadas repetidamente, como o envio de relatórios diários de operações, atualizações mensais e muitas outras atividades.

A classe que implementamos permite configurar eventos recorrentes de maneira simples em programas MQL5, assim como o módulo schedule do Python.

Embora a função OnTimer funcione bem, ela não oferece alguns recursos importantes para configurar agendamentos de forma prática e flexível. Por isso, não hesite em utilizar esta biblioteca sempre que os recursos do OnTimer não forem suficientes.

Tudo de bom a todos!


Tabela de arquivos anexos

Nome do arquivo Descrição e uso
Experts\Schedule testing EA.mq5 EA para agendar operações de trading.
Include\schedule.mqh Contém a classe CSchedule, útil para agendar a execução de funções em horários específicos ou a intervalos definidos.
Scripts\schedule test.mq5 Script para testar a classe CSchedule.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/18913

Arquivos anexados |
Attachments.zip (7.22 KB)
Últimos Comentários | Ir para discussão (1)
Alexandru Gisca
Alexandru Gisca | 26 mar. 2026 em 10:57
Ótimo artigo! Vejo isso como uma espécie de gerenciador de comportamento que ajuda a estruturar a interação entre diferentes componentes.

Desenvolvimento de um sistema de trading lucrativo (Parte 1): Abordagem quantitativa Desenvolvimento de um sistema de trading lucrativo (Parte 1): Abordagem quantitativa
Muitos traders avaliam suas estratégias com base em resultados de curto prazo e acabam abandonando sistemas lucrativos cedo demais. No entanto, a lucratividade no longo prazo depende de uma expectativa matemática positiva, sustentada por uma combinação adequada de Win Rate e relação risco-retorno (Risk-Reward), além de disciplina no dimensionamento das posições. Esses princípios podem ser avaliados por meio de simulações de Monte Carlo em Python, utilizando métricas obtidas em testes com dados históricos para determinar se uma estratégia é robusta ou se corre o risco de fracassar com o tempo.
Desenvolvimento de ferramentas para análise de Price Action (Parte 33): Ferramenta baseada na Teoria da Amplitude das Velas Desenvolvimento de ferramentas para análise de Price Action (Parte 33): Ferramenta baseada na Teoria da Amplitude das Velas
Aprofunde sua compreensão do mercado com o conjunto de ferramentas Candle-Range Theory para MetaTrader 5, uma solução totalmente nativa em MQL5, baseada na teoria da amplitude das velas, que transforma dados brutos das barras de preço em informações sobre a volatilidade em tempo real. A biblioteca leve CRangePattern compara a amplitude verdadeira de cada vela com um ATR adaptativo e a classifica no momento do fechamento. Em seguida, o CRT Indicator apresenta essas classificações no gráfico por meio de retângulos coloridos e setas, permitindo identificar rapidamente regiões de compressão, rompimentos bruscos e engolfamentos completos da amplitude anterior.
Recursos do Assistente MQL5 que você precisa conhecer (Parte 77): Uso dos indicadores Gator e A/D (Accumulation/Distribution) Recursos do Assistente MQL5 que você precisa conhecer (Parte 77): Uso dos indicadores Gator e A/D (Accumulation/Distribution)
O oscilador Gator, de Bill Williams, e o indicador de Acumulação/Distribuição formam mais uma combinação de indicadores que pode ser utilizada em um EA desenvolvido em MQL5. Usamos o oscilador Gator para confirmar a presença de uma tendência, enquanto o A/D fornece uma confirmação adicional com base no volume. Como nas análises anteriores, utilizaremos o Assistente MQL5 para criar e testar um EA que combine esses dois indicadores.
Ferramentas de trading em MQL5 (Parte 6): Painel holográfico dinâmico com animação pulsante e controles Ferramentas de trading em MQL5 (Parte 6): Painel holográfico dinâmico com animação pulsante e controles
Neste artigo, vamos criar um painel holográfico dinâmico em MQL5 para monitorar ativos e períodos gráficos por meio do RSI, com alertas de volatilidade e opções de ordenação. Também vamos adicionar animações pulsantes, botões interativos e efeitos holográficos para tornar a ferramenta visualmente atraente e responsiva.