English Deutsch
preview
MQL5で他の言語の実用的なモジュールを実装する(第3回):Pythonのscheduleモジュール、強化版OnTimerイベント

MQL5で他の言語の実用的なモジュールを実装する(第3回):Pythonのscheduleモジュール、強化版OnTimerイベント

MetaTrader 5テスター |
86 0
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

この関数は基本的には機能しますが、複数の異なるスケジュールを同時に運用する柔軟性には欠けます。 

OnTimerイベントハンドラをエキスパートアドバイザー(EA)やインジケーターで設定すると、1つの「タイマースケジュール」にしか依存できません。これは、プログラム内で異なるタスクを異なる時間間隔で実行したい場合には制約となります。

たとえば、ユーザーに日次、週次、月次の取引レポートを出力する場合などです。

PythonにはOnTimer関数に似たモジュールがありますが、関数を正確なタイミングで実行する能力がはるかに優れています。本記事ではそれを紹介し、MQL5で類似モジュールを実装します。


Pythonが提供するScheduleモジュールとは

Python job scheduling for humans(人間のためのPythonジョブスケジューリング)

特定の時間や曜日にタスクを実行するための、人間にやさしいPythonモジュールです。簡単で軽量なので、Python開発者は知っておくべきモジュールです。

MQL5のOnTimer関数とは異なり、このscheduleモジュールは単に特定の時間間隔でタスクを実行するだけでなく、特定のタスク(関数)をいつ、どのように実行するかをより詳細に指定する柔軟性があります。

以下はこのモジュールが提供する関数の一部です。

インポート

import schedule

関数 説明
schedule.every(10).minutes.do(job)
OnTimerイベントと同様、10分ごとに job関数を実行
schedule.every().hour.do(job)
スクリプトの開始から1時間ごとにjob関数を実行
schedule.every().day.at("10:30").do(job)
毎日特定のローカル時間10:30(24時間制)にjob関数を実行
schedule.every().monday.do(job)
毎週月曜日に、スクリプト実行時刻に基づきjob関数を実行
schedule.every().wednesday.at("13:15").do(job)
毎週水曜日の13:15にjob関数を実行 
schedule.every().day.at("12:42", "Europe/Amsterdam").do(job)
毎日12:42(ヨーロッパ/アムステルダム時間)にjob関数を実行 
schedule.every().minute.at(":17").do(job) 
毎分17秒にjob関数を実行 

これらはモジュールの基本的かつ重要な機能です。次に、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という関数で指定された特定の関数を実行する」という意味になります。

この関数の核心部分では、与えられた期間(period)をm_periodという変数に代入し、クラス内部で保持します。

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

また、seconds、minutes、hoursなどの関数は、列挙型 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という構造体に格納され、最後に実行された時刻や次回実行予定時刻など、他のジョブのプロパティと一緒に管理されます。

そして、この構造体の情報はすべて、jobs_struct型の配列であるm_jobsに格納されます。

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秒ごとに呼び出します。スクリプトを停止するまで実行を続けるため、無限ループを使用します。

以下は、デバッグモードでスクリプトを実行した際のログ出力例です。

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!

これら4つの関数を、それぞれ4回目の時間枠間隔後に実行するよう設定していたにもかかわらず、すべてが同じ現在時刻に実行されてしまいました。


スケジュールの管理

時間の経過とともに不要になるスケジュールもあるため、プログラムから異なるスケジュールにアクセスしてキャンセルする方法が必要です。

すべてのジョブを取得する

関数 説明
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]


取引アプリでの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()
                   ); 
//---
 }

以下は、ストラテジーテスターの出力です。

すべてのスケジュールは、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の比較

この2つの方法を直接比較するのはやや不公平です。どちらもそれぞれのやり方で正しく動作します。しかし、両者の違いを理解し、どの状況で使うべきか、どの状況で使わないほうがよいかを知っておくことは有益です。

OnTimer Schedule
1つのタイマーイベント(1つのスケジュール)しか設定できません。この組み込み機能は、単一のタスクをスケジュールしたい場合に有用です。 複数のタスクをそれぞれ異なる間隔で設定可能です。このカスタムライブラリは、複数のスケジュールを同時に実行したい場合に便利です。
高速で効果的です。   組み込みのOnTimerほど高速ではありません。効果はこれから検証される余地があります。
EAとインジケーターでのみ利用可能です。  すべてのMQL5プログラム(EA、インジケーター、スクリプト)で動作します。 

24時間365日稼働します(常に信頼性あり)。 

市場が開かれているときにのみトリガーされる取引関数(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(); //✅   
  }


結論

特定のスケジュールを設定し、タスクを正確な時刻に実行できることは、アルゴリズムトレーダーにとって非常に重要です。多くの取引戦略は一日の正確な時刻に依存するだけでなく、日次取引レポートや月次更新など、繰り返し実行したい活動も数多く存在します。

今回実装したクラスは、Pythonのscheduleモジュールのように、MQL5プログラム内で繰り返しイベントを簡単に設定できる方法を提供します。

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回):定量的なアプローチ 取引システムの構築(第1回):定量的なアプローチ
多くのトレーダーは短期的なパフォーマンスに基づいて戦略を評価し、利益を生むシステムであっても早い段階で手放してしまうことがよくあります。しかし、長期的な収益性は、最適化された勝率とリスクリワードレシオ(RRR: Reward-to-Risk Ratio)によって形成されるポジティブな期待値、そして規律あるポジションサイジングに依存しています。これらの原則は、バックテストの結果をもとにPythonでモンテカルロシミュレーションをおこなうことで検証することができ、戦略が時間の経過とともに堅牢であるか、もしくは破綻する可能性が高いかを評価するうえで役立ちます。
初心者からエキスパートへ:MQL5を使用したアニメーションニュースヘッドライン(VII) - ニュース取引におけるポストインパクト戦略 初心者からエキスパートへ:MQL5を使用したアニメーションニュースヘッドライン(VII) - ニュース取引におけるポストインパクト戦略
高インパクトの経済ニュースが発表された直後の1分間は、ウィップソー(騙しの多い相場)リスクが非常に高い時間帯です。この短い瞬間、価格変動は不規則で、かつ極めてボラティリティが高く、両方向のペンディング注文が立て続けに発動されることも少なくありません。しかし、通常は1分以内には市場が次第に安定し、従来のトレンドへと戻ったり、修正の動きを見せたりしながら、より通常に近いボラティリティ水準に落ち着いていきます。このセクションでは、ニュース取引における代替アプローチを検討し、その有効性を検証し、トレーダーの戦略ツールキットにどのように加えられるかを探っていきます。詳細と洞察は、以下の項目で順を追って解説します。
知っておくべきMQL5ウィザードのテクニック(第77回):ゲーターオシレーターとA/Dオシレーターの使用 知っておくべきMQL5ウィザードのテクニック(第77回):ゲーターオシレーターとA/Dオシレーターの使用
ビル・ウィリアムズが開発したゲーターオシレーター(Gator Oscillator)とA/Dオシレーター(Accumulation/Distribution Oscillator)は、MQL5のエキスパートアドバイザー(EA)内で調和的に活用できるインジケーターペアの一例です。ゲーターオシレーターはトレンドを確認するために使用し、A/Dオシレーターは出来高を通じてそのトレンドを検証する補助指標として機能します。本記事では、これら2つのインジケーターの組み合わせについて、MQL5ウィザードを活用して構築およびテストをおこない、その有効性を検証します。
MQL5サービスからPythonアプリケーションへのMetaTraderティック情報アクセス(ソケット使用) MQL5サービスからPythonアプリケーションへのMetaTraderティック情報アクセス(ソケット使用)
場合によっては、MQL5言語だけではすべてをプログラムできないことがあります。また、既存の高度なライブラリをMQL5に移植することは可能であっても、非常に時間がかかります。本記事では、MetaTraderのティック情報(Bid、Ask、時刻など)をMetaTraderサービスを経由してPythonアプリケーションに送信し、Windows OSへの依存を回避する方法を紹介します。