English Русский Español Deutsch 日本語
preview
在 MQL5 中实现来自其他语言的实用模块(第 03 部分):移植 Python 的 Schedule 模块,打造增强版 OnTimer

在 MQL5 中实现来自其他语言的实用模块(第 03 部分):移植 Python 的 Schedule 模块,打造增强版 OnTimer

MetaTrader 5测试者 |
9 1
Omega J Msigwa
Omega J Msigwa

目录


概述

编程旨在使我们的生活更加便捷,它让我们能够自动化许多关键且有时枯燥/重复的任务,这些任务通常都希望由计算机在无需人工参与的情况下自动完成。一个很好的例子就是我们在许多文本编辑器中看到的自动保存功能。有了这个功能,你无需每次写一个新单词时都担心保存文档,因为文本编辑器会自动处理保存过程,这样你就可以专注于写作,而不必担心在发生意外时丢失工作成果。

交易领域也是如此,其中有许多重复性的操作和任务,我们希望通过编写几行代码将其自动化。

图片来源:unsplash.com

在 MQL5 编程语言中,我们有一个众所周知的 OnTimer 函数,它有助于在程序员设定的特定时间间隔后运行某些函数和代码行。

下面是一个简单的例子 — 每隔 10 秒运行一次 OnTimer 函数。

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

这个函数还算不错,但不够精细,也不够灵活,无法在同一程序中同时运行多个/不同的计划。 

一旦你在 EA 或指标中设置了 OnTimer 事件处理程序,你就只能依赖那个单一的“定时计划”。这非常具有局限性,因为我们经常希望在程序中的不同时间(间隔)执行不同的任务。

例如,向用户打印或发送每日、每周或每月交易报告。

在 Python 编程语言中,有一个模块类似于 OnTimer 函数,但它在定时运行函数方面要强得多。在本文中,我们将讨论这个问题,并在 MQL5 编程语言中实现一个类似的模块。


Python 中提供的 Schedule 模块是什么?

被誉为 — 面向人类的 Python 作业调度工具

这是一个对人类友好的 Python 模块,可帮助我们把特定任务安排在一天、一周等周期中的特定时间执行。该模块使用简单且轻便,因此是每位 Python 开发者都值得了解的一个实用模块。

与 MQL5 中的 OnTimer 函数不同,schedule 模块不仅允许我们安排任务在特定时间间隔后运行,而且还让我们能够更灵活地指定特定任务(函数)应该何时以及如何运行。

以下是该模块提供的一些功能。

导入

import schedule

函数 描述
schedule.every(10).minutes.do(job)
与 OnTimer 事件类似,每隔 10 分钟,名为 job 的函数就会运行。
schedule.every().hour.do(job)
名为 job 的函数将从脚本开始每小时运行一次。
schedule.every().day.at("10:30").do(job)
名为 job 的函数每天会在 24 小时制的当地时间 10:30 运行。
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 中实现一个形式相近的类。


MQL5 中的 Schedule 类

Python 中的 schedule 模块旨在为要在特定时间间隔内安排的每个任务提供单独的函数。名为 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);
 }

这种语法使我们能够拥有类似于 Python 的 schedule 模块所提供的流畅接口。

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

名为 every 的函数对于设置特定时间范围内的间隔至关重要。例如:

   schedule.every(10).minutes()

也就是说,每隔 10 分钟,名为 dO 的函数接收到的特定函数应该被触发。

该函数的核心逻辑是接收一个给定的间隔值,并将该变量赋值给一个名为 m_period 的变量 — 该变量存储在类中。

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

函数:秒、分、小时 等,根据由名为 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());  
 }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

我们希望每隔 10 秒执行一次 runthis 函数。使用无限 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” 类(所有以 “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()); 
 }


为任务添加标识信息

如前一个示例输出日志所示,识别和跟踪作业进度颇为困难,尤其是在同时运行多个计划函数的情况下。为了解决这个问题,我们需要在名为 dO 的函数中添加一个名为 jobs_name 的可选变量该变量有助于标记所有已安排的任务。

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 的函数。 类似于 Python 中 schedule 模块提供的功能。

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

下面介绍“时间源枚举”及其对应的函数。

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]


交易应用程序中日程模块的应用

我们已经了解了如何在简单函数中使用此模块,并通过示例展示了如何按时触发函数执行。下面给出几个在交易应用程序中使用该库的实际示例。

更高效的 NewBar 事件处理

编写一个能够有效检测新 K 线开启的函数并不总是那么容易,因为 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()
                   ); 
//---
 }

策略测试器中的输出。

我们必须像 EventSetTimer 一样,在 OnInit 函数中显式地设置所有内容 — 使用 dO 函数。

由于 schedule_pending 函数负责持续监控这些调度任务,因此它应该在 EA 的 OnTick 函数中运行,在指标的 OnCalculate 函数中运行,以及在 MQL5 脚本的无限循环中运行。

发送每日交易报告

通过跟踪市场收盘前几分钟或几秒钟(例如,00:00 前 5 分钟),我们可以打印或向用户发送每日交易报告。

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 与 OnTimer

虽然直接比较这两种方法有失公允,因为它们各自都有其优点,但了解它们之间的差异以及何时使用哪种方法会更为有益。

OnTimer Schedule
由于它只允许一个定时器事件(一个计划),因此当您想安排单个任务时,这种内置功能非常有用。 它允许多个任务,并为每个任务设置不同的间隔时间。当您希望同时执行多个计划时,这个自定义库非常有用。
它快速且有效   不如内置的 OnTimer 快;其效果尚待探索。
仅限于 EA 和指标。  它适用于所有 MQL5 程序;EA、指标和脚本。 

它全天候 24 小时运行(始终可靠) 

它依赖于交易功能(OnTick 和 OnCalculate),这些功能只有在市场开放时才会触发。
除非在脚本的无限循环中使用,否则,监控函数 run_pending 的工作时段并非全天候,而是受市场开市时间限制。

要解决使用此库时出现的可靠性问题(如比较表最后一行所述),您必须在 OnTimer 函数中运行 CSchedule 类。

#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 程序中设置重复事件的简便方法,与 Python 中的 schedule 模块所提供的方法类似。

虽然 OnTimer 函数不错,但它缺少一些设置人性化时间表的关键方法。因此,在 OnTimer 不够用的场景中,可以考虑使用这个库。

再见。


附件表

文件名 描述与用途
Experts\Schedule testing EA.mq5 用于安排交易操作的 EA。
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 3月 2026 在 10:57
好文章!我认为这是一种行为管理器,有助于规范不同组件之间的交互。

交易中的神经网络:摆脱特定数据依赖的时间序列泛化(结论) 交易中的神经网络:摆脱特定数据依赖的时间序列泛化(结论)
本文将向您展示Mamba4Cast如何把理论转化为可运行的交易算法,并为您开展自主实验奠定基础。不要错过这次机会,获取用于自研策略的全面的知识与策略开发灵感。
从基础到进阶:图表对象(I) 从基础到进阶:图表对象(I)
在本文中,我们将开始讲解如何在图表上直接操作图形对象。相关示例使用专为演示编写的代码实现。图形对象编程十分有趣,能给人带来许多快乐。由于这是我们初次接触该主题,将从非常基础的内容入手。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
科学家群体优化(CoSO):理论 科学家群体优化(CoSO):理论
元启发式方法中交易策略有效优化的秘诀。科学家群体优化算法是一种新型的基于种群的算法,其灵感来源于科学界的运作机制。与传统的自然启发式隐喻不同,CoSO 模型展现了人类科学活动的独特方面:在期刊上发表成果、竞争资助以及组建研究团队。