English 日本語
preview
Implementierung von praktischen Modulen aus anderen Sprachen in MQL5 (Teil 03): Zeitplan-Modul von Python, das OnTimer-Ereignis auf Steroiden

Implementierung von praktischen Modulen aus anderen Sprachen in MQL5 (Teil 03): Zeitplan-Modul von Python, das OnTimer-Ereignis auf Steroiden

MetaTrader 5Tester |
90 0
Omega J Msigwa
Omega J Msigwa

Inhalt


Einführung

Das Programmieren soll unser Leben erleichtern, indem es uns ermöglicht, viele der wichtigen und manchmal langweiligen/wiederkehrenden Aufgaben zu automatisieren, die wir oft von Computern ohne menschliche Interaktion erledigen lassen wollen. Ein gutes Beispiel ist die automatische Speicherfunktion, die in vielen Texteditoren zu finden ist. Anstatt sich jedes Mal, wenn Sie ein neues Wort schreiben, um das Speichern von Dokumenten kümmern zu müssen, erledigen Texteditoren den Speichervorgang automatisch, sodass Sie sich auf das Schreiben konzentrieren können und sich keine Sorgen machen müssen, dass Ihre Arbeit verloren geht, wenn etwas Unerwartetes passiert.

Das ist im Handelsbereich nicht anders, wo zahlreiche sich wiederholende Tätigkeiten und Aufgaben beim Handel helfen, die wir mit einigen Zeilen Code automatisieren wollen.

Quelle: unsplash.com

In der Programmiersprache MQL5 gibt es die weit bekannte Funktion OnTimer, die dabei hilft, bestimmte Funktionen und Codezeilen nach einem bestimmten, vom Programmierer festgelegten Zeitintervall auszuführen.

Nachfolgend finden Sie ein einfaches Beispiel, bei dem die Funktion OnTimer alle 10 Sekunden ausgeführt wird.

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

Ausgabe:

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

Diese Funktion ist gut, aber sie ist grob und nicht flexibel genug, um mehrere/unterschiedliche Zeitpläne gleichzeitig im selben Programm zu ermöglichen. 

Sobald Sie die Ereignisbehandlung von OnTimer in Ihrem Expert Advisor oder Indikator eingestellt haben, brauchen Sie sich nur noch auf diesen einen „Zeitplan“ verlassen. Dies ist sehr einschränkend, da wir in unseren Programmen oft verschiedene Aufgaben haben, die zu unterschiedlichen Zeiten (Intervallen) ausgeführt werden sollen.

Zum Beispiel das Drucken oder Versenden von täglichen, wöchentlichen oder monatlichen Handelsberichten an die Nutzer.

In der Programmiersprache Python gibt es ein Modul, das der Funktion OnTimer ähnelt, das aber viel besser in der Lage ist, Funktionen zeitgerecht auszuführen. In diesem Artikel werden wir es diskutieren und ein ähnliches Modul in der Programmiersprache MQL5 implementieren.


Was bietet das Modul Schedule von Python?

Bezeichnet als – Auftragsplanung in Python für Menschen

Dies ist ein menschenfreundliches Python-Modul, das bei der Planung bestimmter Aufgaben hilft, die nach einer bestimmten Zeit des Tages, der Woche usw. ausgeführt werden sollen. Dieses Modul ist einfach zu nutzen und leichtgewichtig, was es zu einem notwendigen Modul macht, das jeder Python-Entwickler kennen sollte.

Im Gegensatz zu OnTimer von MQL5 können wir mit dem Schedule-Modul nicht nur Aufgaben so planen, dass sie nach einem bestimmten Zeitintervall ausgeführt werden, sondern es gibt uns auch die Flexibilität, genauer festzulegen, wann und wie eine bestimmte Aufgabe (Funktion) ausgeführt werden soll.

Im Folgenden finden Sie einige der Funktionen, die dieses Modul bietet.

Imports

import schedule

Funktion Beschreibung
schedule.every(10).minutes.do(job)
Ähnlich wie beim OnTimer-Ereignis wird nach jeweils 10 Minuten die Funktion namens job ausgeführt.
schedule.every().hour.do(job)
Die Funktion namens job wird ab dem Start des Skripts jede Stunde ausgeführt.
schedule.every().day.at("10:30").do(job)
Die Funktion mit dem Namen job wird jeden Tag zu einer bestimmten Ortszeit um 10:30 Uhr in 24-Stunden-Zeit ausgeführt.
schedule.every().monday.do(job)
Die Funktion mit dem Namen job wird jeden Montag genau zu dem Zeitpunkt aufgerufen, zu dem das Skript ursprünglich ausgeführt wurde.
schedule.every().wednesday.at("13:15").do(job)
Die Funktion namens job wird jeden Mittwoch um 13:15 Uhr aufgerufen. 
schedule.every().day.at("12:42", "Europe/Amsterdam").do(job)
Die Funktion mit dem Namen job wird jeden Tag um 12:42 Uhr europäischer/Amsterdamer Zeit aufgerufen. 
schedule.every().minute.at(":17").do(job) 
Die Funktion mit dem Namen job wird jede Minute zur 17. Sekunde aufgerufen. 

Dies sind nur einige wenige, aber entscheidende Funktionen, die dieses Modul bietet. Lassen Sie uns eine bekannte Klasse in MQL5 implementieren.


Die Schedule-Klasse in MQL5

Das Schedule-Modul in Python ist so aufgebaut, dass es für jede Aufgabe, die Sie in einem bestimmten Intervall planen wollen, eine eigene Funktion bereitstellt. Die Funktion do ist der Endpunkt der gesamten Funktionsverkettung der Klasse schedule.

schedule.every(10).minutes.do

Um eine ähnliche Syntax in MQL5 zu erreichen, müssen wir einige Funktionen in der Klasse CSchedule dazu bringen, eine Instanz der gesamten Klasse zurückzugeben, mit Ausnahme der Funktion dO, die der Endpunkt ist.

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

Diese Syntax ermöglicht uns eine ähnlich flüssige Schnittstelle wie die des Schedule-Moduls von Python.

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

Die Funktion mit dem Namen every ist entscheidend für die Festlegung eines Intervalls für einen bestimmten Zeitrahmen. Zum Beispiel:

   schedule.every(10).minutes()

Das bedeutet, dass alle 10 Minuten eine bestimmte Funktion, die von der Funktion mit dem Namen dO empfangen wird, ausgelöst werden soll.

Im Kern nimmt diese Funktion eine gegebene Periode und ordnet diese Variable einer Variablen namens m_period zu, die innerhalb der Klasse gespeichert ist.

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

Die Funktionen: seconds, minutes, hours usw. ordnen die Zeitrahmenvariable allen verfügbaren Zeitintervalloptionen zu, die durch den Enumerator time_intervals_enum angegeben sind.

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

Bevor wir die Funktion namens dO verstehen, die der Endpunkt aller Planungsfunktionen ist. Wir wollen verstehen, wie jeder Auftrag (Funktion) gehandhabt und in der Datei schedule.mqh gespeichert wird.

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

Da die Klasse CSchedule über Funktionen verfügt, die auf sich selbst verweisen, wird die Behandlung der einzelnen Auftragsobjekte innerhalb dieser Klasse kompliziert/verwirrend und fehleranfällig. 

Ein globales Array mit dem Namen m_jobs bietet eine universelle Möglichkeit, alle innerhalb der Klasse verwendeten Jobs zu speichern und zu verwalten. Wir werden gleich darüber sprechen.

Die Funktion mit dem Namen dO empfängt eine Funktion, die nach einem empfangenen „Zeitplan“ wiederholt ausgeführt werden soll. Es wird berechnet, wann eine Funktion zuletzt ausgeführt wurde und wann sie voraussichtlich das nächste Mal ausgeführt wird.

Eine empfangene Funktion wird in der Struktur jobs_struct zusammen mit den Eigenschaften anderer Aufträge gespeichert, z. B. wann der Auftrag das letzte Mal ausgeführt wurde und wann er das nächste Mal ausgeführt werden soll.

Alle diese Werte werden dann in einem Array namens m_jobs gespeichert, das ein Array vom Typ jobs_struct ist.

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
 }

Nachdem ein Auftrag in einem entsprechenden Array gespeichert wurde, benötigen wir eine universelle Funktion, um ihn ständig zu überwachen und seine Funktion auszuführen, wenn der geplante Zeitpunkt erreicht ist.

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

Lassen Sie uns unseren ersten Auftrag mit dieser Klasse planen.

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

Wir wollen die Funktion runthis nach jeweils 10 Sekunden aufrufen. Eine unendliche while-Schleife dient nur dazu, das Skript so lange laufen zu lassen, bis es angehalten wird.

Nachfolgend sehen Sie die protokollierte Ausgabe, wenn das Skript im Debug-Modus ausgeführt wurde.

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

Wir können mehrere Zeitpläne haben, die dieselbe Klasse verwenden.

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

Ausgabe:

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


Aufträge (Funktionen) zu einer bestimmten Zeit ausführen

Oft wollen wir unsere Funktionen zu einer ganz bestimmten Zeit ausführen. Zum Beispiel die Ausführung einer Funktion, die für die Eröffnung eines Geschäfts zu einem bestimmten Zeitpunkt entsprechend einer Handelssitzung verantwortlich ist, z.B. die Eröffnung eines Geschäfts um 19:00 Uhr Ortszeit.

In der Klasse CSchedule sind alle Funktionen mit dem Namen at dafür verantwortlich, dies zu tun, wenn eine bestimmte „legitime Zeit“ angegeben wird.

Zum Beispiel.

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

Damit wird festgelegt, dass die Funktion namens Job jeden Tag um 19:10 Uhr ausgeführt wird.

Die Implementierung dieser Funktion in MQL5 ist knifflig, da wir für jede „zeitliche“ Funktion, d.h. Sekunde, Minute, Stunde, Tag, Woche, separate Klassen benötigen, die zurückgegeben werden.

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

In jeder „Builder“-Klasse (alle Klassen, die mit dem Wort Builder enden) gibt es eine Funktion namens at, die für die Einstellung eines bestimmten Zeitintervalls verantwortlich ist.

Wir haben auch eine Funktion namens dO, die eine namensgleiche Funktion von der Klasse CSchedule erbt.

Zum Beispiel die Klasse 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);
                       }  
  };

Alle Funktionen mit dem Namen at nehmen eine bestimmte Zeit, die ihren Argumenten übergeben wird, die Zeit vor dem ersten Funktionsdurchlauf, und weisen den Zeitwert in Sekunden einer Variablen namens m_fixed_time zu.

Innerhalb der Funktion mit dem Namen dO wird eine Bedingung eingeführt, um zu prüfen, ob der empfangene Zeitwert ein fester Zeitwert ist (z. B.. 19:00) oder eine geplante Anzahl von Sekunden, Minuten usw. für den nächsten Funktionslauf, da beide Bedingungen eine etwas andere Vorgehensweise erfordern.

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
 }

Im Folgenden wird beschrieben, wie Sie mehrere Zeitpläne so einstellen, dass sie wiederholt zu einer bestimmten Zeit ausgeführt werden.

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


Übergabe von Argumenten an eine Task (Job)

Wie in den vorherigen Beispiel-Ausgabeprotokollen zu sehen ist, ist es schwierig, den Fortschritt eines Auftrags zu erkennen und zu verfolgen, insbesondere wenn mehrere geplante Funktionen gleichzeitig laufen. Um dies zu beheben, benötigen wir eine optionale Variable namens jobs_name in der Funktion dO. Diese Variable hilft bei der Kennzeichnung aller geplanten Aufgaben.

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
 }

Jetzt können wir die Fortschritte der einzelnen Aufträge besser verfolgen.

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

Ausgabe:

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]


Ausführen einer Aufgabe bis zu einer bestimmten Zeit

Manchmal haben wir geplante Aufgaben, die wir nicht ewig laufen lassen wollen. In diesem Fall wäre es hilfreich, eine Frist für diese Aufträge zu setzen.

Lassen Sie uns die Funktion until vorstellen. Eine Funktion, die derjenigen des Schedule-Moduls in Python ähnelt.

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

In der Funktion mit dem Namen dO übernehmen wir das Ablaufdatum aus unserer Klasse (das wir von der Funktion mit dem Namen until erhalten haben) und weisen es der Struktur des Auftrags zu.

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
 }      

Bevor wir einen Auftrag in der Funktion run_pending ausführen, müssen wir prüfen, ob er noch nicht abgelaufen ist.

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
  }

Schließlich führen wir eine Aufgabe aus und setzen das Ablaufdatum auf 5 Minuten ab der aktuellen Uhrzeit.

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

Ausgabe:

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


Alle Aufträge unabhängig von ihren Zeitplänen ausführen

Es kann vorkommen, dass Sie alle Funktionen trotz ihrer Zeitpläne sofort ausführen müssen. Normalerweise zu Testzwecken, manchmal aber auch, um alle geplanten Vorgänge auf einmal auszuführen, z. B. beim Programmstart.

In solchen Situationen ist die Funktion run_all sehr nützlich.

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

Beispiel für die Verwendung:

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

Ausgabe:

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!

Obwohl diese vier Funktionen so eingestellt sind, dass sie nach dem vierten Zeitrahmenintervall ausgeführt werden, wurden sie alle zum gleichen Zeitpunkt ausgeführt.


Zeitpläne verwalten

Wir brauchen verschiedene Möglichkeiten, um auf verschiedene Zeitpläne programmatisch zuzugreifen und sie zu stornieren, da einige Zeitpläne im Laufe der Zeit veraltet sein könnten.

Alle Jobs bekommen

Funktion Beschreibung
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];
  }
Diese Funktion liefert ein Argument, das als Referenz ein Array mit einer Struktur mit allen Eigenschaften für alle Aufträge/Aufgaben zurückgibt.
uint get_jobs() { return m_jobs.Size(); }
Sie gibt die Anzahl der geplanten Aufträge zurück.

Geplante Jobs stornieren

Funktion Beschreibung
bool Cancel(const string jobs_name);
Hiermit wird ein geplanter Auftrag unter seinem Namen abgebrochen.
bool Cancel(const uint jobs_index);
Er bricht einen geplanten Auftrag unter Verwendung seiner Indexnummer (von 0 bis +unendlich) ab, d.h., wenn der Auftrag der erste war, der geplant wurde, ist seine Indexnummer 0.
bool Clear() { return ArrayResize(m_jobs, 0)==-1?false:true; } 
Dadurch werden alle geplanten Aufträge aus dem Speicher gelöscht (entfernt). Nach dem Aufruf dieser Funktion wird kein Auftrag/keine Aufgabe ausgeführt.

Beispiel für die Verwendung:

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

Ausgabe:

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


Umgang mit Zeitzonen

In allen bisher besprochenen Beispielen und Code-Implementierungen in unserer Bibliothek haben wir die lokale Zeit verwendet. Dies ist jedoch sehr einschränkend, wenn man bedenkt, dass wir mehrere Zeitoptionen haben, die Entwickler in der Programmiersprache MQL5 verwenden können. Sie könnten zum Beispiel einen Handelsvorgang zu einer bestimmten Zeit entsprechend der Serverzeit des Brokers oder der UTC-Zeit einplanen wollen.

Im Konstruktor der Klasse CSchedule fügen wir eine optionale Variable hinzu, die es den Entwicklern ermöglicht, die Art der Zeit auszuwählen, die für alle Zeitplanungsvorgänge verwendet werden soll.

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

Nachstehend finden Sie die Enumeration der Zeitquellen und die entsprechende Funktion.

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

Beispiel für die Verwendung:

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

Ausgabe:

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]


Anwendungen des Schedule-Moduls in Ihren Handelsanwendungen

Wir haben gesehen, wie Sie dieses Modul in einfachen Funktionen und Beispielen verwenden können, die zeitnahe Funktionsabläufe zeigen. Im Folgenden finden Sie einige Beispiele aus der Praxis, wie Sie diese Bibliothek in Ihren Handelsanwendungen einsetzen können.

Eine effektivere Ereignisbehandlung von NewBar

Es ist nicht immer einfach, eine Funktion zu schreiben, die das Öffnen eines neuen Balkens effektiv erkennt. Da die Klasse CSchedule über verschiedene Möglichkeiten verfügt, eine Aufgabe zu einem ganz bestimmten Zeitpunkt zu planen, können wir sie bei der Durchführung bestimmter Aktionen zum Zeitpunkt des Öffnens einer Sekunde, Minute, Stunde, eines Tages usw. verwenden.

In der Datei 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()
                   ); 
//---
 }

Ausgaben im Strategie-Tester.

Wir müssen alles explizit in der Funktion OnInit setzen, ähnlich wie bei EventSetTimer – unter Verwendung der Funktion dO.

Da die Funktion schedule_pending für die ständige Überwachung der Zeitpläne verantwortlich ist, sollte sie innerhalb von OnTick im Expert Advisor, innerhalb der Funktion OnCalculate in Indikatoren und unter einer Endlosschleife innerhalb eines MQL5-Skripts ausgeführt werden.

Versenden von täglichen Handelsberichten

Indem wir einige Minuten oder Sekunden vor Börsenschluss (z.B. 5 Minuten vor 00:00 Uhr) verfolgen, können wir tägliche Handelsberichte drucken oder an die Nutzer senden.

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

Ausgaben auf dem Strategieprüfgerät.

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   

Dieselbe Idee lässt sich auf die Erstellung von Wochenberichten an jedem Freitag kurz vor Börsenschluss und auf die Erstellung von Monatsberichten übertragen.


Zeitplan vs. OnTimer

Ein direkter Vergleich zwischen diesen beiden Methoden wäre zwar unfair, da beide auf ihre Weise gut funktionieren, aber es ist gut, die Unterschiede zu kennen und zu wissen, wann man die eine oder die andere Methode anwenden sollte und wann nicht.

OnTimer Schedule
Da es nur ein Timer-Ereignis (einen Zeitplan) zulässt, ist diese eingebaute Funktion nützlich, wenn Sie eine einzelne Aufgabe planen möchten. Es erlaubt mehrere Aufgaben und unterschiedliche Intervalle für jede Aufgabe. Diese nutzerdefinierte Bibliothek ist nützlich, wenn Sie mehrere Zeitpläne haben, die Sie gleichzeitig ausführen möchten.
Es ist schnell und effektiv   Nicht so schnell wie der integrierte OnTimer; seine Wirksamkeit muss noch erforscht werden.
Sie ist nur auf Expert Advisors und Indikatoren beschränkt.  Es funktioniert in allen MQL5-Programmen, EAs, Indikatoren und Skripten. 

Es funktioniert 24/7 (immer zuverlässig) 

Es stützt sich auf die Handelsfunktionen (OnTick und OnCalculate), die nur ausgelöst werden, wenn der Markt geöffnet wird.
Sofern sie nicht in einer Endlosschleife in einem Skript verwendet wird, arbeitet die Überwachungsfunktion mit dem Namen run_pending in weniger als 24 (Stunden)/5 (Wochentagen) – je nach Markt.

Um das Zuverlässigkeitsproblem zu beheben, das bei der Verwendung dieser Bibliothek auftritt, wie in der letzten Zeile der Vergleichstabelle beschrieben, müssen Sie die Klasse CSchedule innerhalb der Funktion OnTimer ausführen.

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


Schlussfolgerung

Für algorithmische Händler ist es von entscheidender Bedeutung, dass sie bestimmte Zeitpläne festlegen und Aufgaben zu genauen Zeiten ausführen können. Viele Handelsstrategien hängen nicht nur von genauen Tageszeiten ab, sondern es gibt auch zahlreiche Aktivitäten, die wir für eine wiederholte Ausführung automatisieren möchten, wie z. B. das Versenden von täglichen Handelsberichten, monatlichen Aktualisierungen und mehr.

Die implementierte Klasse bietet eine einfache Möglichkeit, wiederkehrende Ereignisse in Ihren MQL5-Programmen zu setzen, ähnlich wie es das Schedule-Modul in Python bietet.

Die OnTimer-Funktion ist zwar anständig, aber es fehlen einige der wichtigsten Möglichkeiten, um menschenfreundliche Zeitpläne zu erstellen. Sie können diese Bibliothek also gerne in Bereichen verwenden, in denen OnTimer nicht ausreicht.

Peace out.


Tabelle der Anhänge

Dateiname Beschreibung und Verwendung
Experts\Schedule testing EA.mq5 Ein Expert Advisor (EA) für die Planung von Handelsgeschäften.
Include\schedule.mqh Enthält die Klasse CSchedule, die für die Planung von Funktionen zur Ausführung zu bestimmten Zeiten und in bestimmten Intervallen nützlich ist.
Scripts\schedule test.mq5 Ein Skript zum Testen der Klasse CSchedule.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/18913

Beigefügte Dateien |
Attachments.zip (7.22 KB)
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 77): Verwendung des Gator-Oszillators und des Akkumulations-/Distributions-Oszillators MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 77): Verwendung des Gator-Oszillators und des Akkumulations-/Distributions-Oszillators
Der Gator Oscillator von Bill Williams und der Accumulation/Distribution Oscillator sind ein weiteres Indikatorpaar, das harmonisch in einem MQL5 Expert Advisor verwendet werden kann. Wir verwenden den Gator-Oszillator, weil er in der Lage ist, Trends zu bestätigen, während der A/D-Oszillator verwendet wird, um die Trends durch die Überprüfung des Volumens zu bestätigen. Bei der Erkundung dieser Indikatorenkombination verwenden wir wie immer den MQL5-Assistenten, um ihr Potenzial zu ermitteln und zu testen.
Vom Neuling zum Experten: Reporting EA – Einrichten des Arbeitsablaufs Vom Neuling zum Experten: Reporting EA – Einrichten des Arbeitsablaufs
Makler stellen oft in regelmäßigen Abständen nach einem vordefinierten Zeitplan Berichte über Handelskonten zur Verfügung. Diese Firmen haben über ihre API-Technologien Zugang zu Ihren Kontoaktivitäten und Ihrer Handelshistorie, sodass sie in Ihrem Namen Performanceberichte erstellen können. Ebenso speichert das MetaTrader 5-Terminal detaillierte Aufzeichnungen Ihrer Handelsaktivitäten, die mit MQL5 genutzt werden können, um vollständig angepasste Berichte zu erstellen und personalisierte Liefermethoden zu definieren.
Aufbau eines Handelssystems (Teil 1): Ein quantitativer Ansatz Aufbau eines Handelssystems (Teil 1): Ein quantitativer Ansatz
Viele Händler bewerten Strategien auf der Grundlage kurzfristiger Ergebnisse und geben profitable Systeme oft zu früh auf. Die langfristige Rentabilität hängt jedoch von einer positiven Erwartungshaltung durch eine optimierte Gewinnrate und ein optimiertes Risiko-Ertrags-Verhältnis ab, zusammen mit einer disziplinierten Positionsgröße. Diese Grundsätze können mit Hilfe von Monte-Carlo-Simulationen in Python mit bewährten Metriken validiert werden, um zu beurteilen, ob eine Strategie robust ist oder im Laufe der Zeit wahrscheinlich scheitern wird.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 76):  Verwendung von Mustern des Awesome Oszillators und der Envelope-Kanäle mit überwachtem Lernen MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 76): Verwendung von Mustern des Awesome Oszillators und der Envelope-Kanäle mit überwachtem Lernen
Wir knüpfen an unseren letzten Artikel an, in dem wir das Indikatorpaar des Awesome Oszillators und die Envelope-Kanäle vorstellten, indem wir uns ansehen, wie dieses Paar durch überwachtes Lernen verbessert werden kann. Der Awesome Oszillator und die Envelope-Kanäle sind eine Mischung aus Trendspotting und Unterstützung/Widerstand, die sich gegenseitig ergänzen. Unser überwachter Lernansatz ist ein CNN, der das Punktprodukt-Kernel mit Cross-Time-Attention einsetzt, um seine Kernel und Kanäle zu dimensionieren. Wie üblich erfolgt dies in einer nutzerdefinierten Signalklassendatei, die mit dem MQL5-Assistenten zur Zusammenstellung eines Expert Advisors arbeitet.