- Turning timer on and off
- Timer event: OnTimer
- High-precision timer: EventSetMillisecondTimer
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()
|
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
|
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
|
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.
...
|
The TimerNotification object is added into the subscribers array using the bind method.
void bind(TimerNotification &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)
|
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_DYNAMIC) delete 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 SingleTimer: public TimerNotification
|
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 CountableTimer: public MultiTimer::SingleTimer
|
In order to use CountableTimer, we have to describe the derived class in our program as follows.
// MultipleTimers.mq5
|
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
|
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
|
Then in the application code you can write like this:
// MultipleTimers.mq5
|
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
|
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>
|
The base timer is started in OnInit.
void OnInit()
|
Recall that the operation of all logical timers is ensured by the interception of the global OnTimer event.
void OnTimer()
|
In addition to the timer application class MyCountableTimer above, let's describe another class of the suspended timer MySuspendedTimer.
class MySuspendedTimer: public CountableTimer
|
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(1, 5);
|
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)
|
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
|
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.