Timer event: OnTimer

The OnTimer event is one of the standard events supported by MQL5 programs (see section Overview of event handling functions). To receive timer events in the program code, you should describe a function with the following prototype.

void OnTimer(void)

The OnTimer event is periodically generated by the client terminal for an Expert Advisor or an indicator that has activated the timer using the EventSetTimer or EventSetMillisecondTimer functions (see the next section).

Attention! In dependent indicators created by calling iCustom or IndicatorCreate from other programs, the timer does not work, and the OnTimer event is not generated. This is an architectural limitation of MetaTrader 5.

It should be understood that the presence of an enabled timer and OnTimer handler does not make the MQL program multi-threaded. No more than one thread is allocated per MQL program (an indicator can even share a thread with other indicators on the same symbol), so the call of OnTimer and other handlers always happen sequentially, in agreement with the event queue. If one of the handlers, including OnTimer, will start lengthy calculations, this will suspend the execution of all other events and sections of the program code.

If you need to organize parallel data processing, you should run several MQL programs simultaneously (perhaps, instances of the same program on different charts or chart objects) and exchange commands and data between them using their own protocol, for example, using custom events.

As an example, let's create classes that can organize several logical timers in one program. The periods of all logical timers will be set as a multiplier of the base period, that is, the period of a single hardware timer supplying events to the standard handler OnTimer. In this handler, we must call a certain method of our new MultiTimer class which will manage all logical timers.

void OnTimer()
{
   // call the MultiTimer method to check and call dependent timers when needed
   MultiTimer::onTimer();
}

Class MultiTimer and related classes of individual timers will be combined in one file, MultiTimer.mqh.

The base class for working timers will be TimerNotification. Strictly speaking, this could be an interface, but it is convenient to output some details of the general implementation into it: in particular, store the reading of the counter chronometer, using which we will ensure that the timer fires with a certain multiplier of the relative period of the main timer, as well as a method for checking the moment when the timer should fire isTimeCome. That's why TimerNotification is an abstract class. It lacks implementations of two virtual methods: notify - for actions when the timer fires - and getInterval to obtain a multiplier that determines the period of a particular timer relative to the period of the main timer.

class TimerNotification
{
protected:
   int chronometer// counter of timer checks (isTimeCome calls)
public:
   TimerNotification(): chronometer(0)
   {
   }
   
   // timer work event
   // pure virtual method, it is required to be described in the heirs
   virtual void notify() = 0;
   // returns the period of the timer (it can be changed on the go)
   // pure virtual method, it is required to be described in the heirs
   virtual int getInterval() = 0;
   // check if it's time for the timer to fire, and if so, call notify
   virtual bool isTimeCome()
   {
      if(chronometer >= getInterval() - 1)
      {
         chronometer = 0// reset the counter
         notify();        // notify application code
         return true;
      }
      
      ++chronometer;
      return false;
   }
};

All logic is provided in the isTimeCome method. Each time it is called, the chronometer counter is incremented, and if it reaches the last iteration according to the getInterval method, the notify method is called to notify the application code.

For example, if the main timer is started with a period of 1 second (EventSetTimer(1)), then the child object TimerNotification, which will return 5 from getInterval, will receive calls to its notify method every 5 seconds.

As we have already said, such timer objects will be managed by the MultiTimer manager object. We need only one such object. Therefore, its constructor is declared protected, and a single instance is created statically within the class.

class MultiTimer
{
protected:
   static MultiTimer _mainTimer;
   
   MultiTimer()
   {
   }
   ...

Inside this class, we organize the storage of the TimerNotification array of objects (we will see how it is filled in a few paragraphs). Once we have the array, we can easily write the checkTimers method which loops through all logical timers. For external access, this method is duplicated by the public static method onTimer, which we have already seen in the global OnTimer handler. Since the only manager instance is created statically, we can access it from a static method.

   ...
   TimerNotification *subscribers[];
   
   void checkTimers()
   {
      int n = ArraySize(subscribers);
      for(int i = 0i < n; ++i)
      {
         if(CheckPointer(subscribers[i]) != POINTER_INVALID)
         {
            subscribers[i].isTimeCome();
         }
      }
   }
   
public:
   static void onTimer()
   {
      _mainTimer.checkTimers();
   }
   ...

The TimerNotification object is added into the subscribers array using the bind method.

   void bind(TimerNotification &tn)
   {
      int in = ArraySize(subscribers);
      for(i = 0i < n; ++i)
      {
         if(subscribers[i] == &tnreturn// there is already such an object
         if(subscribers[i] == NULLbreak// found an empty slot
      }
      if(i == n)
      {
         ArrayResize(subscribersn + 1);
      }
      else
      {
         n = i;
      }
      subscribers[n] = &tn;
   }

The method is protected from repeated addition of the object, and, if possible, the pointer is placed in an empty element of the array, if there is one, which eliminates the need to expand the array. Empty elements in an array may appear if any of the TimerNotification objects was removed using the unbind method (timers can be used occasionally).

   void unbind(TimerNotification &tn)
   {
      const int n = ArraySize(subscribers);
      for(int i = 0i < n; ++i)
      {
         if(subscribers[i] == &tn)
         {
            subscribers[i] = NULL;
            return;
         }
      }
   }

Note that the manager does not take ownership of the timer object and does not attempt to call delete. If you are going to register dynamically allocated timer objects in the manager, you can add the following code inside if before zeroing:

            if(CheckPointer(subscribers[i]) == POINTER_DYNAMICdelete subscribers[i];

Now it remains to understand how we can conveniently organize bind/unbind calls, so as not to load the application code with these utilitarian operations. If you do it "manually", then it's easy to forget to create or, on the contrary, delete the timer somewhere.

Let's develop the SingleTimer class derived from TimerNotification, in which we implement bind and unbind calls from the constructor and destructor, respectively. In addition, we describe in it the multiplier variable to store the timer period.

   class SingleTimerpublic TimerNotification
   {
   protected:
      int multiplier;
      MultiTimer *owner;
   
   public:
      // creating a timer with the specified base period multiplier, optionally paused
      // automatically register the object in the manager
      SingleTimer(const int mconst bool paused = false): multiplier(m)
      {
         owner = &MultiTimer::_mainTimer;
         if(!pausedowner.bind(this);
      }
   
      // automatically disconnect the object from the manager
      ~SingleTimer()
      {
         owner.unbind(this);
      }
   
      // return timer period
      virtual int getInterval() override 
      {
         return multiplier;
      }
   
      // pause this timer
      virtual void stop()
      {
         owner.unbind(this);
      }
   
      // resume this timer
      virtual void start()
      {
         owner.bind(this);
      }
   };

The second parameter of the constructor (paused) allows you to create an object, but not start the timer immediately. Such a delayed timer can then be activated using the start method.

The scheme of subscribing some objects to events in others is one of the popular design patterns in OOP and is called "publisher/subscriber".

It is important to note that this class is also abstract because it does not implement the notify method. Based on SingleTimer, let's describe the classes of timers with additional functionality.

Let's start with the class CountableTimer. It allows you to specify how many times it should trigger, after which it will be automatically stopped. With it, in particular, it is easy to organize a single delayed action. The CountableTimer constructor has parameters for setting the timer period, the pause flag, and the number of retries. By default, the number of repetitions is not limited, so this class will become the basis for most application timers.

class CountableTimerpublic MultiTimer::SingleTimer
{
protected:
   const uint repeat;
   uint count;
   
public:
   CountableTimer(const int mconst uint r = UINT_MAXconst bool paused = false):
      SingleTimer(mpaused), repeat(r), count(0) { }
   
   virtual bool isTimeCome() override
   {
      if(count >= repeat && repeat != UINT_MAX)
      {
         stop();
         return false;
      }
      // delegate the time check to the parent class,
      // increment our counter only if the timer fired (returned true)
      return SingleTimer::isTimeCome() && (bool)++count;
   }
   // reset our counter on stop
   virtual void stop() override
   {
      SingleTimer::stop();
      count = 0;
   }
 
   uint getCount() const
   {
      return count;
   }
   
   uint getRepeat() const
   {
      return repeat;
   }
};

In order to use CountableTimer, we have to describe the derived class in our program as follows.

// MultipleTimers.mq5 
class MyCountableTimerpublic CountableTimer
{
public:
   MyCountableTimer(const int sconst uint r = UINT_MAX):
      CountableTimer(sr) { }
   
   virtual void notify() override
   {
      Print(__FUNCSIG__multiplier" "count);
   }
};

In this implementation of the notify method, we just log the timer period and the number of times it triggered. By the way, this is a fragment of the MultipleTimers.mq5 indicator, which we will use as a working example.

Let's call the second class derived from SingleTimer FunctionalTimer. Its purpose is to provide a simple timer implementation for those who like the functional style of programming and don't feel like writing derived classes. The constructor of the FunctionalTimer class will take, in addition to the period, a pointer to a function of a special type, TimerHandler.

// MultiTimer.mqh
typedef bool (*TimerHandler)(void);
   
class FunctionalTimerpublic MultiTimer::SingleTimer
{
   TimerHandler func;
public:
   FunctionalTimer(const int mTimerHandler f):
      SingleTimer(m), func(f) { }
      
   virtual void notify() override
   {
      if(func != NULL)
      {
         if(!func())
         {
            stop();
         }
      }
   }
};

In this implementation of the notify method, the object calls the function by the pointer. With such a class, we can define a macro that, when placed before a block of statements in curly brackets, will "make" it the body of the timer function.

// MultiTimer.mqh
#define OnTimerCustom(POnTimer##P(); \
FunctionalTimer ft##P(POnTimer##P); \
bool OnTimer##P()

Then in the application code you can write like this:

// MultipleTimers.mq5
bool OnTimerCustom(3)
{
   Print(__FUNCSIG__);
   return true;        // continue the timer
}

This construct declares a timer with a period of 3 and a set of instructions inside parentheses (here, just printing to a log). If this function returns false, this timer will be stopped.

Let's consider the indicator MultipleTimers.mq5 more. Since it does not provide visualization, we will specify the number of diagrams equal to zero.

#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0

To use the classes of logical timers, we include the header file MultiTimer.mqh and add an input variable for the base (global) timer period.

#include <MQL5Book/MultiTimer.mqh>
   
input int BaseTimerPeriod = 1;

The base timer is started in OnInit.

void OnInit()
{
   Print(__FUNCSIG__" "BaseTimerPeriod" Seconds");
   EventSetTimer(BaseTimerPeriod);
}

Recall that the operation of all logical timers is ensured by the interception of the global OnTimer event.

void OnTimer()
{
   MultiTimer::onTimer();
}

In addition to the timer application class MyCountableTimer above, let's describe another class of the suspended timer MySuspendedTimer.

class MySuspendedTimerpublic CountableTimer
{
public:
   MySuspendedTimer(const int sconst uint r = UINT_MAX):
      CountableTimer(srtrue) { }
   virtual void notify() override
   {
      Print(__FUNCSIG__multiplier" "count);
      if(count == repeat - 1// execute last time
      {
         Print("Forcing all timers to stop");
         EventKillTimer();
      }
   }
};

A little lower we will see how it starts. It is also important to note here that after reaching the specified number of operations, this timer will turn off all timers by calling EventKillTimer.

Now let's show how (in the global context) the objects of different timers of these two classes are described.

MySuspendedTimer st(15);
MyCountableTimer t1(2);
MyCountableTimer t2(4);

The st timer of the MySuspendedTimer class has period 1 (1*BaseTimerPeriod) and should stop after 5 operations.

The t1 and t2 timers of the MyCountableTimer class have periods 2 (2 * BaseTimerPeriod) and 4 (4 * BaseTimerPeriod), respectively. With default value BaseTimerPeriod = 1 all periods represent seconds. These two timers are started immediately after the start of the program.

We will also create two timers in a functional style.

bool OnTimerCustom(5)
{
   Print(__FUNCSIG__);
   st.start();         // start delayed timer
   return false;       // and stop this timer object
}
   
bool OnTimerCustom(3)
{
   Print(__FUNCSIG__);
   return true;        // this timer keeps running
}

Please note that OnTimerCustom5 has only one task: 5 periods after the start of the program, it needs to start a delayed timer st and terminate its own execution. Considering that the delayed timer should deactivate all timers after 5 periods, we get 10 seconds of program activity at default settings.

The OnTimerCustom3 timer should trigger three times during this period.

So, we have 5 timers with different periods: 1, 2, 3, 4, 5 seconds.

Let's analyze an example of what is being output to the log (time stamps are schematically shown on the right).

                                                // time
17:08:45.174  void OnInit() 1 Seconds             |
17:08:47.202  void MyCountableTimer::notify()2 0    |
17:08:48.216  bool OnTimer3()                        |
17:08:49.230  void MyCountableTimer::notify()2 1      |
17:08:49.230  void MyCountableTimer::notify()4 0      |
17:08:50.244  bool OnTimer5()                          |
17:08:51.258  void MyCountableTimer::notify()2 2        |
17:08:51.258  bool OnTimer3()                           |
17:08:51.258  void MySuspendedTimer::notify()1 0        |
17:08:52.272  void MySuspendedTimer::notify()1 1         |
17:08:53.286  void MyCountableTimer::notify()2 3          |
17:08:53.286  void MyCountableTimer::notify()4 1          |
17:08:53.286  void MySuspendedTimer::notify()1 2          |
17:08:54.300  bool OnTimer3()                              |
17:08:54.300  void MySuspendedTimer::notify()1 3           |
17:08:55.314  void MyCountableTimer::notify()2 4            |
17:08:55.314  void MySuspendedTimer::notify()1 4            |
17:08:55.314  Forcing all timers to stop                    |

The first message from the two-second timer arrives, as expected, about 2 seconds after the start (we are saying "about" because the hardware timer has a limitation in accuracy and, in addition, other computer load affects the execution). One second later, the three-second timer triggers for the first time. The second hit of the two-second timer coincides with the first output from the four-second timer. After a single execution of the five-second timer, messages from the one-second timer begin to appear in the log regularly (its counter increases from 0 to 4). On its last iteration, it stops all timers.