English Deutsch 日本語
preview
Внедрение в MQL5 практических модулей из других языков (Часть 03): Модуль schedule из Python — расширенные возможности OnTimer

Внедрение в MQL5 практических модулей из других языков (Часть 03): Модуль schedule из Python — расширенные возможности OnTimer

MetaTrader 5Тестер |
74 1
Omega J Msigwa
Omega J Msigwa

Разделы


Введение

Программирование призвано облегчить нам жизнь, позволяя автоматизировать многие важные, а иногда и скучные или повторяющиеся задачи, которые мы часто хотим поручить компьютерам выполнить без участия человека. Хороший пример — функция автосохранения, которую можно увидеть во многих текстовых редакторах. Вместо того чтобы беспокоиться о сохранении документа каждый раз, когда вы пишете новое слово, текстовые редакторы обрабатывают процесс сохранения автоматически, так что вы можете сосредоточиться на создании контента и не беспокоиться о потере своего труда в случае непредвиденных обстоятельств.

В сфере торговли ситуация ничем не отличается: многочисленные повторяющиеся действия и задачи, необходимые для совершения сделок, мы хотим автоматизировать с помощью нескольких строк кода.

source: unsplash.com

В языке программирования MQL5 существует хорошо известная функция OnTimer, которая помогает запускать определенные функции и строки кода через заданный программистом интервал времени.

Ниже приведен простой пример — запуск функции OnTimer через каждые 10 секунд.

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

Результаты.

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

Эта функция вполне добротна, но примитивна и недостаточно гибка, чтобы позволить одновременно запускать несколько различных расписаний в одной и той же программе. 

После того как вы настроите обработчик событий OnTimer в своем советнике или индикаторе, вы можете полагаться только на это единственное «расписание таймера». Это очень ограничивает возможности, поскольку в наших программах часто необходимо выполнять разные задачи в разное время (с разными интервалами).

Например, печать или отправка пользователям ежедневных, еженедельных и ежемесячных отчетов о торговых операциях.

В языке программирования Python существует модуль, похожий на функцию OnTimer, но он гораздо лучше подходит для своевременного выполнения функций. В этой статье мы рассмотрим его и реализуем аналогичный модуль на языке программирования MQL5.


Что представляет собой модуль schedule в Python?

Dubbed as — планирование задач в Python для человека

Это удобный для пользователя модуль в языке Python, который помогает планировать выполнение конкретных задач в определенное время дня, недели и т. д. Этот модуль прост в использовании и имеет небольшой вес, что делает его необходимым компонентом, о котором должен знать каждый разработчик на Python.

В отличие от функции OnTimer, которая есть в MQL5, модуль schedule не только позволяет планировать выполнение задач через определенный промежуток времени, но и обеспечивает гибкость, позволяя более точно указывать, когда и как должна выполняться та или иная задача (функция).

Ниже перечислены некоторые функции, которые предлагает этот модуль.

Объем импорта

расписание import

Функция Описание
schedule.every(10).minutes.do(job)
Аналогично событию OnTimer, каждые 10 минут будет запускаться функция с именем job.
schedule.every().hour.do(job)
Функция с именем job будет запускаться каждый час с момента начала выполнения скрипта.
schedule.every().day.at("10:30").do(job)
Функция с именем job будет запускаться ежедневно в определенное местное время — в 10:30 в 24-часовом формате.
schedule.every().monday.do(job)
Функция с именем job будет вводиться в действие каждый понедельник в точное время, когда скрипт был запущен в первый раз.
schedule.every().wednesday.at("13:15").do(job)
Функция с именем job будет вводиться в действие каждую среду в 13:15. 
schedule.every().day.at("12:42", "Europe/Amsterdam").do(job)
Функция с именем job будет вызываться ежедневно в 12:42 по европейскому/амстердамскому времени. 
schedule.every().minute.at(":17").do(job) 
Функция с именем job будет вызываться каждую минуту на 17-й секунде. 

Это лишь некоторые из важнейших функций, предлагаемых в этом модуле. Реализуем хорошо знакомый нам класс на языке MQL5.


Класс schedule в MQL5

Модуль schedule в языке Python предназначен для использования отдельных функций при выполнении каждой задачи, которую вы хотите запланировать на определенный интервал времени. Функция с именем doявляется конечной точкой всей цепочки функций из класса schedule.

schedule.every(10).minutes.do

Для реализации аналогичного синтаксиса в MQL5 нам необходимо заставить несколько функций в классе CSchedule вернуть экземпляр всего класса, за исключением функции с имени do, которая является конечной точкой.

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

Этот синтаксис позволяет иметь интерфейс, аналогичный тому, который предлагает модуль schedule из языка Python.

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

Функция с именем every имеет решающее значение для установки интервала конкретного таймфрейма. Для примера:

   schedule.every(10).minutes()

Это означает, что каждые 10 минут должна срабатывать какая-то отдельная функция, полученная функцией с именем do.

По своей сути, эта функция принимает заданное значение period и присваивает эту переменную переменной с именемm_period, которая хранится внутри класса.

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

Функции: seconds, minutes, hours и т. д., присваивает переменную timeframe в соответствии со всеми доступными вариантами таймфреймов, заданными перечислителем под именем  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); 
  }

Прежде чем мы разберемся с функцией, названной dO, которая является конечной точкой всех функций планирования, постараемся понять, как обрабатывается и хранится каждая задача (функция) в файле с именем 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;
  }

Поскольку в классе CSchedule есть функции, которые ссылаются на него же, обработка каждого объекта задания внутри этого класса становится сложной или запутанной и чреватой ошибками. 

Наличие глобального массива с именем m_jobs предоставляет универсальный способ хранения и обработки всех задач, используемых внутри класса. Мы обсудим это чуть позже.

Функция с именем dO получает функцию, которая должна выполняться многократно в соответствии с полученным «расписанием». Она вычисляет время последнего выполнения функции и время следующего ожидаемого ее выполнения.

Полученная функция сохраняется в структуре с именем jobs_struct наряду со свойствами других задач, такими как время последнего выполнения и время следующего выполнения задания.

Все эти значения затем сохраняются в массиве с именем m_jobs, который представляет собой массив типа 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
 }

После того как задача сохранена в соответствующем массиве, нам необходима универсальная функция для постоянного мониторинга этого массива и запуска его функции при наступлении соответствующего времени по расписанию.

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

Составим расписание для самой первой нашей задачи с помощью этого класса.

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

Мы хотим вызывать функцию runthis каждые 10 секунд. Бесконечный цикл while используется просто для того, чтобы скрипт продолжал работать, пока его не остановят.

Ниже приведен результат, записанный в лог при выполнении скрипта в режиме отладки.

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

Мы можем составить несколько расписаний для одного и того же класса.

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

Результаты.

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


Запуск задач (функций) в определенное время

Нам часто требуется запускать наши функции в строго определенное время. Например, нужно запустить функцию, отвечающую за открытие сделки в определенное время в соответствии с торговой сессией, предположим, открытие сделки в 19:00 по местному времени.

В классе CSchedule все функции, названные at, несут ответственность за это, если им задано конкретное «законное время».

Например:

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

Это задает для функции с именем job расписание, в соответствии с которым она запускается ежедневно в 19:10.

Реализация этой функции в MQL5 представляет собой сложную задачу, поскольку нам необходимо, чтобы отдельные классы возвращались для каждой «привязанной ко времени» функции, то есть функции секунды, минуты, часа, дня, недели.

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

Внутри каждого «строительного» класса (все классы, заканчивающиеся на словоBuilder) у нас есть функция с именем at, которая отвечает за установку определенного временного интервала.

Кроме того, у нас есть функция с именем dO, которая наследует одноименную функцию от класса CSchedule.

Например, класс 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);
                       }  
  };

Все функции с именем at принимают заданное время, переданное в качестве аргумента, — время до первого выполнения функции, — и присваивают значение времени в секундах переменной с именем m_fixed_time.

Внутри функции с именем dO вводим условие для проверки, является полученное значение времени фиксированным значением времени (например, 19:00) или запланированным количеством секунд, минут и т. д. для следующего запуска функции, поскольку для решения обеих проблем требуется несколько иной подход.

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
 }

Ниже описано, как настроить несколько расписаний для многократного выполнения в определенное время.

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


Передача аргументов в задачу

Как видно из приведенных ранее примеров логов с результатами, сложно идентифицировать и отслеживать ход выполнения задачи, особенно когда одновременно выполняется несколько запланированных функций. Чтобы это исправить, нам нужна необязательная переменная с именем jobs_name в функции с именем dO, причем эта переменная помогает при маркировке всех запланированных задач.

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
 }

Теперь мы можем более эффективно отслеживать ход выполнения каждой задачи.

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

Результаты.

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]


Выполнение задачи до определенного времени

Иногда у нас есть запланированные задачи, но мы не хотим, чтобы они выполнялись бесконечно. В данном случае помогло бы установление крайнего срока для выполнения этих работ.

Представим функцию с именем until. Функция, аналогичная той, что доступна в модуле schedule в языке Python.

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

Внутри функции с именемdO мы берем дату истечения срока действия из нашего класса (полученную из функции с именем until) и присваиваем ее структуре задачи.

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
 }      

Перед запуском задачи внутри функции с именем run_pending нам нужно проверить, не истек ли срок ее действия.

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
  }

Наконец, запускаем задачу и устанавливаем дату истечения срока действия через 5 минут после текущего момента.

#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!");
 }

Результаты.

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


Выполнение всех заданий независимо от их расписаний

Иногда может возникнуть необходимость запустить все функции мгновенно, несмотря на их расписание. Обычно это делается для тестирования, а иногда нам просто нужно принудительно запустить все запланированные операции одновременно, например, во время запуска программы.

В подобных ситуациях удобно использовать функцию с именем 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());
  }

Пример использования.

#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!");
 }

Результаты.

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!

Несмотря на то, что для этих четырех функций было задано время выполнения после четвертого временного интервала, все они были выполнены в одно и то же текущее время.


Управление расписаниями

Нам необходимы различные способы программного доступа к различным расписаниям и их отмены программным методом, поскольку некоторые расписания со временем могут устареть.

Получение всех задач

Функция Описание
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];
  }
Эта функция передает аргумент, который по ссылке возвращает массив, содержащий структуру со всеми свойствами для всех задач.
uint get_jobs() { return m_jobs.Size(); }
Она возвращает количество запланированных задач.

Отмена запланированных задач

Функция Описание
bool Cancel(const string jobs_name);
Это отменяет запланированную задачу с помощью ее имени.
bool Cancel(const uint jobs_index);
Она отменяет запланированную задачу, используя ее индексный номер (от 0 до +бесконечности), т.е., если задача была запланирована первой, ее порядковый номер равен 0.
bool Clear() { return ArrayResize(m_jobs, 0)==-1?false:true; } 
Это действие очищает (удаляет) все запланированные задачи из памяти. После вызова этой функции никакие задачи запускаться не будут.

Пример использования.

#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!");
 }

Результаты.

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


Работа с часовыми поясами

Во всех ранее рассмотренных примерах и реализациях кода в нашей библиотеке мы использовали местное время. Но это очень ограничивает возможности, учитывая, что у нас несколько временных опций, которые разработчики могут использовать в языке программирования MQL5. Например, вам может потребоваться запланировать торговую операцию на определенное время в соответствии со временем на серверах брокера или по времени UTC.

Внутри класса, конструктора CSchedule, добавляем необязательную переменную, которая позволяет разработчикам выбирать тип времени, используемого для всех операций планирования расписаний.

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

Ниже приведен элемент Time source enumeration («Перечисление источников времени») и соответствующая ей функция.

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

Пример использования.

#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!");
 }

Результаты.

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]


Применение модуля schedule в ваших торговых приложениях

Мы рассмотрели, как можно использовать этот модуль в простых функциях и на примерах, демонстрирующих своевременный запуск функций. Ниже приведены несколько реальных примеров того, как вы можете использовать эту библиотеку в своих торговых приложениях.

Более эффективная обработка событий в NewBar

Написать функцию, которая бы эффективно отслеживала открытие нового бара, не всегда легко, поскольку у класса CSchedule есть различные способы планирования задач на очень конкретное время, и мы можем использовать его для выполнения определенных действий в начале секунды, минуты, часа, дня и т. д.

Внутри файла 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()
                   ); 
//---
 }

Результаты в тестере стратегий.

Нам необходимо явно задать все параметры внутри функции OnInit аналогично EventSetTimer — с применением функции dO.

Поскольку функция schedule_pending отвечает за постоянный мониторинг расписаний, она должна запускаться внутри функции OnTick в советниках, внутри функции OnCalculate в индикаторах, а также в бесконечном цикле внутри скрипта MQL5.

Отправка ежедневных отчетов о торговых операциях

Отслеживая ситуацию за несколько минут или секунд до закрытия рынка (например, за 5 минут до 00:00), мы можем распечатывать или отправлять пользователям ежедневные отчеты о торговых операциях.

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

Результаты в тестере стратегий.

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   

Тот же принцип можно распространить и на составление еженедельных отчетов каждую пятницу, непосредственно перед закрытием рынка, а также на подготовку ежемесячных отчетов.


Модуль schedule vs событие OnTimer

Хотя прямое сравнение этих двух методов было бы необоснованным, поскольку оба, каждый по-своему, работают хорошо, полезно знать их различия и понимать, когда следует использовать тот или иной метод, а когда нет.

OnTimer Расписание
Поскольку эта встроенная функциональность позволяет запускать только одно событие таймера (одно расписание), она полезна, когда вам нужно запланировать выполнение одной задачи. Это позволяет выполнять несколько задач одновременно и устанавливать разные интервалы для каждой задачи. Такая пользовательская библиотека пригодится, когда у вас будет несколько задач, которые необходимо выполнить одновременно.
Это быстро и эффективно.   Она работает не так быстро, как встроенный OnTimer; ее эффективность еще предстоит изучить.
Ее использование ограничивается только советниками и индикаторами.  Она работает во всех программах MQL5: советниках, индикаторах и скриптах. 

Работает круглосуточно (и всегда надежно). 

Она основана на торговых функциях (OnTick и OnCalculate), которые срабатывают только при открытии рынка.
Если функция мониторинга с именем run_pending не используется внутри бесконечного цикла в скрипте, она работает менее чем 24 (часа) / 5 (дней недели) — в зависимости от рынка.

Для устранения проблемы с надежностью, возникающей при использовании этой библиотеки, как описано в последней строке сравнительной таблицы, необходимо запустить класс CSchedule внутри функции 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(); //✅   
  }


Заключение

Для алгоритмических трейдеров крайне важно умение устанавливать конкретные расписания и запускать задачи в точно заданное время. Многие торговые стратегии зависят не только от точного времени суток, но и от множества действий, которые мы часто хотим автоматизировать для многократного выполнения, таких как отправка ежедневных торговых отчетов, ежемесячных обновлений и многого другого.

Реализованный класс предоставляет простой способ установки повторяющихся событий в ваших программах на языке MQL5 аналогично тому, как это предлагается в модуле schedule языка Python.

Хотя функция OnTimer работает неплохо, ей не хватает некоторых важных способов настройки удобного для пользователя расписания. Поэтому смело используйте эту библиотеку в тех случаях, когда возможностей OnTimer недостаточно.

Всем добра.


Таблица вложений

Имя файла Описание и использование
Experts\Schedule testing EA.mq5 Советник для планирования торговых операций.
Include\schedule.mqh Содержит класс CSchedule, полезный для планирования запуска функций в определенное время и через заданные интервалы.
Scripts\schedule test.mq5 Скрипт для тестирования класса CSchedule.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18913

Прикрепленные файлы |
Attachments.zip (7.22 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Alexandru Gisca
Alexandru Gisca | 26 мар. 2026 в 10:57
Great article! I see this as a kind of behavior manager that helps structure the interaction between different components.

Машинное обучение и Data Science (Часть 42): Прогнозирование фондовых рынков с использованием N-BEATS в Python Машинное обучение и Data Science (Часть 42): Прогнозирование фондовых рынков с использованием N-BEATS в Python
N-BEATS — это революционная модель глубокого обучения, разработанная для прогнозирования временных рядов. Она была выпущена в попытке превзойти возможности классических моделей прогнозирования временных рядов, таких как ARIMA, PROPHET, VAR и др. Познакомимся с данной моделью и посмотрим на возможности ее применения для прогнозирования фондового рынка.
Неопределенность как модель (Часть 3): Математическая статистика — как извлекать знания из данных Неопределенность как модель (Часть 3): Математическая статистика — как извлекать знания из данных
В данной части цикла разбираются механизмы Закона больших чисел (ЗБЧ) и Центральной предельной теоремы (ЦПТ) как теоретической основы для понимания рыночных закономерностей. Описывается инструментарий описательной статистики и методы нахождения точечных и интервальных оценок параметров распределений. Особое внимание уделено методологии проверки статистических гипотез, позволяющей объективно отделять истинные рыночные аномалии от случайного шума. Каждое теоретическое построение сопровождено практическим примером в приложении, что позволяет закрепить материал на конкретных данных.
Нейросети в трейдинге: Адаптивное масштабирование представлений (Окончание) Нейросети в трейдинге: Адаптивное масштабирование представлений (Окончание)
В статье представлена интеграция ранее реализованных компонентов фреймворка ADS в прикладную торговую модель и их проверка на исторических данных. Показано, как построение объекта верхнего уровня позволяет встроить сложную архитектуру в существующие решения, сохранив управляемость и прозрачность модели. Проведенное тестирование раскрывает как потенциал подхода в генерации прибыли, так и его ограничения, формируя основу для дальнейшей оптимизации риск-менеджмента и повышения устойчивости системы.
Роевой оптимизатор с иерархией суброев — Flock by Leader Роевой оптимизатор с иерархией суброев — Flock by Leader
Мы строим и реализуем в MQL5 алгоритм Flock by Leader: суброи формируются по метрике ARF, лидер определяется по лучшему личному рекорду, а не по положению центроида. Приводим формулы обновления для ролей роя и механизм separation. Класс C_AO_FBL совместим с тестовым стендом и проверен на функциях Hilly, Forest и Megacity в размерностях 10–1000 координат, что упрощает воспроизведение и сравнение.