The Implementation of a Multi-currency Mode in MetaTrader 5
Introduction
Currently, there are a large number of developed multi-currency trading systems, indicators, and Expert Advisors. Nevertheless, the developers continue to face the specific problems of multi-currency systems development.
With the release of the MetaTrader 5 client terminal and the MQL5 programming language, we gained a new opportunity to implement a full fledged multi-currency mode and, consequently, more efficient multi-currency robots and indicators. These new opportunities will be the topic of this article.
Overview of the conventional approach
In our case the conventional approach is the attempt to implement a multi-currency system based on the standard OnTick() and OnCalculate() functions, which replaced the start() function of MQL4. Simply put, with the appearance of a new tick or bar on the current chart, all currency pairs (participating in the multi-currency system) are sequentially requested for subsequent analysis and decision making.
The problems of this approach are:
-
The dependency of the entire system on the incoming of ticks on a trading symbol of the current chart.
For a rapid market, when the ticks for the symbol of the current chart are frequent, there aren't really any problems. But with a slow market, for example, during night time, ticks may come very rarely: once every half a minute or even rarer. During the intervals between the incoming of rare ticks, the entire multi-currency system is "sleeping", although the changes occurring on the other symbols can be quite drastic.
This shortcoming is not very significant if the system is configured to work on large time-frames. But the smaller time-frame, the more effect it has. Every day the world increases the speed, computers are able to process more and more information per unit of time, and, consequently, more people are willing to work on small periods and even ticks.
-
The complexity of the historical data synchronization on all of the symbols used in a multi-currency system.
"In MetaTrader 4, only those bars are drawn, within which at least one price change took place. If no price change occurred within a minute, a one-bar gap will occur in the chart with one-minute period."- states the beginning of the "Free-of-Holes" Charts article.
Such an approach to building a chart is maintained in MetaTrader 5. That is, the same number of bars on the chart for each symbol does not mean that they are synchronized by time. For example, the one hundredth bar may have a different opening time for each symbol. Therefore, during the construction and recalculation of a multi-currency indicator, it is important to make sure that all of the bars are consistent with one another for each symbol.
This also is not very significant if the system is configured to work on a large time-frame, since with the increase of the period, the likelihood of missing a bar greatly decreases. Although, as they say, you can never be too careful. And you never can tell, only if the person does nothing.
We should separately note the time of synchronization of the current incomplete bar. When a new bar appears on the current chart, it does not mean that new bars have also formed on other symbols. Therefore, an attempt to inquire the price of a new bar through another symbol, using the functions CopyXXXX(), can lead to the fact that you will obtain the price of the previous bar for the symbol, or simply a copying error. By the way, a new bar on another symbol may form much earlier than on the current one. This may also affect the accuracy of the situation's assessment.
The article Creating a Multi-Currency Indicator Using a Number of Intermediate Indicator Buffers describes options that more or less solve the issue of historical data synchronization. -
Another important point, relevant to the synchronization of data: how do we find out that there has been a history update for some trading symbol?
For example, when we construct a single currency indicator - there are no problems. If the input variable prev_calculated of the function OnCalculate() zeroed, then we recalculate the indicator. But what do we do if there has been an update in the history for the symbol, and not on the current chart? This can occur at any time and it may be required to recalculate the multi-currency indicator. The answer to this question is quite relevant.
Without even going into the other features we can see that these three instances are sufficient enough to cause so many issues that the code of the multi-currency EA or indicator grow to be too large. But the full extent of the problem has not been solved ...
New hope with the OnTimer() function
The new capability of the MQL-program to generate the Timer event and the OnTimer() standard event handler, gave hope for the emergence of new types of multi-currency systems. This is due, on the one hand, to the fact that now the multi-currency Expert Advisor/indicator could become independent of the receipt of ticks by the symbol of the current chart, and on the other hand, by the fact that we could monitor the work of the EA by time. But ...
This partially solves the problems described in paragraph 1 of the previous section and, of course, gives some advantages. But just like when you receive the NewTick event, with the receipt of the Timer event it is necessary to sequentially inquire all of the currency pairs in an attempt to track the changes. Sometimes this has to be done quite often, which can significantly increase the use of resources of the MQL5-programs.
The issues identified in paragraphs 2 and 3, remain at the same solution level. In addition to this, it is necessary to solve the issues that are specific to the OnTimer() function. For example, the issues of initialization/deinitialization of the timer, its work during weekends, etc.
Without belittling the obvious advantages of the OnTimer() event handler, it must be noted that it still does not allow to implement a full multi-currency mode.
New possibilities with the OnChartEvent() function
The limitations of the above standard functions are due to their narrow specialization: they are intended to handle specific, predefined events, and not intended for a multi-currency systems.
A rescuing element for the developer of a multi-currency system may be the OnChartEvent() standard custom event handler. It allows the programmer to generate his own events at his discretion.
For example, nobody can prevent us from using this function for obtaining ticks for any symbol on the current chart. All that we need to do is to send a "spy" to the chart with the appropriate symbol.
By the funny word "spy", I mean the following indicator:
//+------------------------------------------------------------------+ //| iSpy.mq5 | //| Copyright 2010, Lizar | //| lizar-2010@mail.ru | //+------------------------------------------------------------------+ #define VERSION "1.00 Build 2 (26 Dec 2010)" #property copyright "Copyright 2010, Lizar" #property link "https://www.mql5.com/ru/users/Lizar" #property version VERSION #property description "iSpy agent-indicator. If you want to get ticks, attach it to the chart" #property indicator_chart_window input long chart_id=0; // chart id input ushort custom_event_id=0; // event id //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, // size of price[] array const int prev_calculated, // bars, calculated at the previous call const int begin, // starting index of data const double& price[] // array for the calculation ) { double price_current=price[rates_total-1]; //--- Initialization: if(prev_calculated==0) { // Generate and send "Initialization" event EventChartCustom(chart_id,0,(long)_Period,price_current,_Symbol); return(rates_total); } // When the new tick, let's generate the "New tick" custom event // that can be processed by Expert Advisor or indicator EventChartCustom(chart_id,custom_event_id+1,(long)_Period,price_current,_Symbol); //--- return value of prev_calculated for next call return(rates_total); }
We can launch this "spy" into the chart of the desired symbol, and then handle its messages in the EA or the indicator using the OnChartEvent() function. To decode the messages of "the spy" correctly, we must interpret the parameters of this function in the following way:
- id - event identifier. If the id-CHARTEVENT_CUSTOM = 0, our ""spy" reports that the variable prev_calculated has been zeroed, and appropriate actions should be taken;
- lparam - in this case it means the period of the chart, on which the "spy" is launched;
- dparam - tick price. By default, this is the last closing price. Although during the launch of the "spy", this can be set to any value from the enumeration ENUM_APPLIED_PRICE;
- sparam - the name of the trading symbol, on which the event was received.
To demonstrate the simultaneous work of several "spies", we'll write a simple EA exSpy.mq5 (full version is available in the archive):
//+------------------------------------------------------------------+ //| exSpy.mq5 | //| Copyright 2010, Lizar | //| https://www.mql5.com/ru/users/Lizar | //+------------------------------------------------------------------+ #define VERSION "1.00 Build 1 (26 Dec 2010)" #property copyright "Copyright 2010, Lizar" #property link "https://www.mql5.com/ru/users/Lizar" #property version VERSION #property description "The Expert Advisor shows the work of iSPY indicator" //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if(iCustom("GBPUSD",PERIOD_M1,"iSpy",ChartID(),0)==INVALID_HANDLE) { Print("Error in setting of spy on GBPUSD"); return(true);} if(iCustom("EURUSD",PERIOD_M1,"iSpy",ChartID(),1)==INVALID_HANDLE) { Print("Error in setting of spy on EURUSD"); return(true);} if(iCustom("USDJPY",PERIOD_M1,"iSpy",ChartID(),2)==INVALID_HANDLE) { Print("Error in setting of spy on USDJPY"); return(true);} Print("Spys ok, waiting for events..."); //--- return(0); } //+------------------------------------------------------------------+ //| The Standard event handler. | //| See MQL5 Reference for the details. | //| | //| In this case it is used for decoding of spy messages, sent by | //| iSPY spy indicator | //+------------------------------------------------------------------+ void OnChartEvent(const int id, // event id: // if id-CHARTEVENT_CUSTOM=0-"initialization" event const long& lparam, // chart period const double& dparam, // price const string& sparam // symbol ) { if(id>=CHARTEVENT_CUSTOM) { Print(TimeToString(TimeCurrent(),TIME_SECONDS)," -> id=", id-CHARTEVENT_CUSTOM,": ",sparam," ", EnumToString((ENUM_TIMEFRAMES)lparam)," price=",dparam); } } //+------------------------------------------------------------------+
Launch the Expert Advisor on any chart.
Here are the results:
As can be seen from the log, we obtain all of the ticks for the desired symbol, as well as the "initialization" event, which, in particular, will be received if there is an update or an upload of the history.
Let's sum up the intermediate results:
- We do not depend on ticks of the particular symbol, as we did when we used the standard OnTick() and OnCalculate() functions.
- We are not limited to strictly defined time intervals, as when using the OnTimer().
- There is no need to request all of the symbols in a sequence or in a loop to keep track their changes. Once changes on the symbol occurred, we obtain the corresponding event. We can identify the symbol, on which the event occurred, by the id event identifier or by the sparam parameter.
- We solved the issue of tracking the synchronization of historical data with the server for each symbol individually.
- A small portion of the work is given designated to additional programs. This is relevant to the parallelization of data processing.
- In the parameters of the OnChartEvent() function, we obtain additional information: the period, price, and name of the symbol. This can greatly simplify the code of the EA or indicator, as there is no need to additionally inquire for this data.
At this point we could basically wrap up this article, since we have successfully implemented a multi-currency mode by the means of the MetaTrader 5 terminal and the programming language of MQL5. But this is a "raw" implementation. Therefore, we will go on further.
The implementation of a multi-currency mode
If you use the above idea of implementing a multi-currency regime in its pure form, then at some point you will start to face difficulties. The problem is that such approach allows us to obtain all ticks for each trading symbol, on which the "spy" is running.
In one second, with a fast market, there may be a number of ticks on each symbol. This may lead to a "blockage" of the event order. Here's a warning from the Help section:
Client terminal adds appearing events to the events queue. So events are processed one after another in accordance to the order they were received. There is an exception for the NewTick event. If the queue already has such an event or this event is being processed, the new NewTick event is not enqueued.
Queue of events is limited in size. At queue overflow, old events are removed without being processed in order to allow the receipt of new events. Therefore, it is recommended to write efficient event handlers, and it is not recommended to use infinite loops (there is an exception of scripts, which handle the Start event only).
The overflow of the queue can lead to the loss of important events for the multi-currency indicator or EA. This is from one side. On the other hand, we do not always need ticks for all of the symbols. Sometimes we need to obtain only the "new bar" event on any time-frame. Or a number of "new bar" events for different time-frames. Basically, our "spy" is not suitable for such requirements and its use is not very convenient.
Let's make it universal, so that we never have to return to the question of how to obtain an event based on the symbol of a multi-currency EA or indicator. For this purpose, let's take as a sample, the ENUM_CHART_EVENT_SYMBOL events enumeration from the "MCM Control Panel" for Multicurrency Expert Advisors and Indicators description:
enum ENUM_CHART_EVENT_SYMBOL { CHARTEVENT_INIT =0, // "Initialization" event CHARTEVENT_NO =0, // No events CHARTEVENT_NEWBAR_M1 =0x00000001, // "New bar" event on M1 chart CHARTEVENT_NEWBAR_M2 =0x00000002, // "New bar" event on M2 chart CHARTEVENT_NEWBAR_M3 =0x00000004, // "New bar" event on M3 chart CHARTEVENT_NEWBAR_M4 =0x00000008, // "New bar" event on M4 chart CHARTEVENT_NEWBAR_M5 =0x00000010, // "New bar" event on M5 chart CHARTEVENT_NEWBAR_M6 =0x00000020, // "New bar" event on M6 chart CHARTEVENT_NEWBAR_M10=0x00000040, // "New bar" event on M10 chart CHARTEVENT_NEWBAR_M12=0x00000080, // "New bar" event on M12 chart CHARTEVENT_NEWBAR_M15=0x00000100, // "New bar" event on M15 chart CHARTEVENT_NEWBAR_M20=0x00000200, // "New bar" event on M20 chart CHARTEVENT_NEWBAR_M30=0x00000400, // "New bar" event on M30 chart CHARTEVENT_NEWBAR_H1 =0x00000800, // "New bar" event on H1 chart CHARTEVENT_NEWBAR_H2 =0x00001000, // "New bar" event on H2 chart CHARTEVENT_NEWBAR_H3 =0x00002000, // "New bar" event on H3 chart CHARTEVENT_NEWBAR_H4 =0x00004000, // "New bar" event on H4 chart CHARTEVENT_NEWBAR_H6 =0x00008000, // "New bar" event on H6 chart CHARTEVENT_NEWBAR_H8 =0x00010000, // "New bar" event on H8 chart CHARTEVENT_NEWBAR_H12=0x00020000, // "New bar" event on H12 chart CHARTEVENT_NEWBAR_D1 =0x00040000, // "New bar" event on D1 chart CHARTEVENT_NEWBAR_W1 =0x00080000, // "New bar" event on W1 chart CHARTEVENT_NEWBAR_MN1=0x00100000, // "New bar" event on MN1 chart CHARTEVENT_TICK =0x00200000, // "New tick" event CHARTEVENT_ALL =0xFFFFFFFF, // All events };
In fact, this enumeration is a flags of the custom chart events. It's the minimal set, which may be required for multi-currency mode. Of course, it can be complemented. The combination of flags will determine the events that we are going to send out from the "spy".
The flags can be combined using the bitwise operation "OR". For example, the CHARTEVENT_NEWBAR_M1 | CHARTEVENT_NEWBAR_H1 combination will mean that we are going to send the "new bar" events from the minute and hourly time-frame with the help of the "spy". These flags will be the input parameter of our spy-indicator. Further we will call it as the "agent-indicator".
The indicator itself, in accordance with the new ideas, looks like this:
//+------------------------------------------------------------------+ //| Spy Control panel MCM.mq5 | //| Copyright 2010, Lizar | //| https://www.mql5.com/en/users/Lizar | //+------------------------------------------------------------------+ #define VERSION "1.00 Build 3 (26 Dec 2010)" #property copyright "Copyright 2010, Lizar" #property link "https://www.mql5.com/en/users/Lizar" #property version VERSION #property description "This is the MCM Control Panel agent-indicator." #property description "Is launched on the required symbol on any time-frame" #property description "and generates the custom NewBar event and/or NewTick" #property description "for the chart which receives the event" #property indicator_chart_window input long chart_id; // identifier of the chart which receives the event input ushort custom_event_id; // event identifier input ENUM_CHART_EVENT_SYMBOL flag_event=CHARTEVENT_NO;// indicator, which determines the event type. MqlDateTime time, prev_time; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, // size of the price[] array const int prev_calculated, // bars processed at the previous call const int begin, // where the data begins const double& price[] // calculations array ) { double price_current=price[rates_total-1]; TimeCurrent(time); if(prev_calculated==0) { EventCustom(CHARTEVENT_INIT,price_current); prev_time=time; return(rates_total); } //--- new tick if((flag_event & CHARTEVENT_TICK)!=0) EventCustom(CHARTEVENT_TICK,price_current); //--- check change time if(time.min==prev_time.min && time.hour==prev_time.hour && time.day==prev_time.day && time.mon==prev_time.mon) return(rates_total); //--- new minute if((flag_event & CHARTEVENT_NEWBAR_M1)!=0) EventCustom(CHARTEVENT_NEWBAR_M1,price_current); if(time.min%2 ==0 && (flag_event & CHARTEVENT_NEWBAR_M2)!=0) EventCustom(CHARTEVENT_NEWBAR_M2,price_current); if(time.min%3 ==0 && (flag_event & CHARTEVENT_NEWBAR_M3)!=0) EventCustom(CHARTEVENT_NEWBAR_M3,price_current); if(time.min%4 ==0 && (flag_event & CHARTEVENT_NEWBAR_M4)!=0) EventCustom(CHARTEVENT_NEWBAR_M4,price_current); if(time.min%5 ==0 && (flag_event & CHARTEVENT_NEWBAR_M5)!=0) EventCustom(CHARTEVENT_NEWBAR_M5,price_current); if(time.min%6 ==0 && (flag_event & CHARTEVENT_NEWBAR_M6)!=0) EventCustom(CHARTEVENT_NEWBAR_M6,price_current); if(time.min%10==0 && (flag_event & CHARTEVENT_NEWBAR_M10)!=0) EventCustom(CHARTEVENT_NEWBAR_M10,price_current); if(time.min%12==0 && (flag_event & CHARTEVENT_NEWBAR_M12)!=0) EventCustom(CHARTEVENT_NEWBAR_M12,price_current); if(time.min%15==0 && (flag_event & CHARTEVENT_NEWBAR_M15)!=0) EventCustom(CHARTEVENT_NEWBAR_M15,price_current); if(time.min%20==0 && (flag_event & CHARTEVENT_NEWBAR_M20)!=0) EventCustom(CHARTEVENT_NEWBAR_M20,price_current); if(time.min%30==0 && (flag_event & CHARTEVENT_NEWBAR_M30)!=0) EventCustom(CHARTEVENT_NEWBAR_M30,price_current); if(time.min!=0) {prev_time=time; return(rates_total);} //--- new hour if((flag_event & CHARTEVENT_NEWBAR_H1)!=0) EventCustom(CHARTEVENT_NEWBAR_H1,price_current); if(time.hour%2 ==0 && (flag_event & CHARTEVENT_NEWBAR_H2)!=0) EventCustom(CHARTEVENT_NEWBAR_H2,price_current); if(time.hour%3 ==0 && (flag_event & CHARTEVENT_NEWBAR_H3)!=0) EventCustom(CHARTEVENT_NEWBAR_H3,price_current); if(time.hour%4 ==0 && (flag_event & CHARTEVENT_NEWBAR_H4)!=0) EventCustom(CHARTEVENT_NEWBAR_H4,price_current); if(time.hour%6 ==0 && (flag_event & CHARTEVENT_NEWBAR_H6)!=0) EventCustom(CHARTEVENT_NEWBAR_H6,price_current); if(time.hour%8 ==0 && (flag_event & CHARTEVENT_NEWBAR_H8)!=0) EventCustom(CHARTEVENT_NEWBAR_H8,price_current); if(time.hour%12==0 && (flag_event & CHARTEVENT_NEWBAR_H12)!=0) EventCustom(CHARTEVENT_NEWBAR_H12,price_current); if(time.hour!=0) {prev_time=time; return(rates_total);} //--- new day if((flag_event & CHARTEVENT_NEWBAR_D1)!=0) EventCustom(CHARTEVENT_NEWBAR_D1,price_current); //--- new week if(time.day_of_week==1 && (flag_event & CHARTEVENT_NEWBAR_W1)!=0) EventCustom(CHARTEVENT_NEWBAR_W1,price_current); //--- new month if(time.day==1 && (flag_event & CHARTEVENT_NEWBAR_MN1)!=0) EventCustom(CHARTEVENT_NEWBAR_MN1,price_current); prev_time=time; //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ void EventCustom(ENUM_CHART_EVENT_SYMBOL event,double price) { EventChartCustom(chart_id,custom_event_id,(long)event,price,_Symbol); return; }
This indicator is part of the MCM Control panel we did not rename it, the attachments contains simply an updated version of it (see "Spy Control panel MCM.mq5”). But this does not mean that it can not be used separately from the panel.
This agent-indicator generates custom user events, and transmits them to the chart-recipient for further processing of these events in the EA or the indicator, using the OnChartEvent() function. Now the input parameters of this function should be interpreted as follows:
- id - event identifier;
- lparam - indicator of the event, received from the panel agent. The indicators, corresponding to the ENUM_CHART_EVENT_SYMBOL enumeration;
- dparam - tick price or opening price for a new bar for a specific time-frame;
- sparam - the name of the trading symbol, on which the event occurred.
The demo EA looks no more complicated than the previous one (full version is available in the archive):
//+------------------------------------------------------------------+ //| exSpy Control panel MCM.mq5 | //| Copyright 2010, Lizar | //| https://www.mql5.com/en/users/Lizar | //+------------------------------------------------------------------+ #define VERSION "1.00 Build 1 (28 Dec 2010)" #property copyright "Copyright 2010, Lizar" #property link "https://www.mql5.com/en/users/Lizar" #property version VERSION #property description "The EA demonstrates the work of the MCM Spy Control panel" //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if(iCustom("GBPUSD",PERIOD_M1,"Spy Control panel MCM",ChartID(),0, CHARTEVENT_NEWBAR_M1|CHARTEVENT_NEWBAR_M5)==INVALID_HANDLE) { Print("Error in setting of spy on GBPUSD"); return(true);} if(iCustom("EURUSD",PERIOD_M1,"Spy Control panel MCM",ChartID(),1, CHARTEVENT_NEWBAR_M2)==INVALID_HANDLE) { Print("Error in setting of spy on EURUSD"); return(true);} if(iCustom("USDJPY",PERIOD_M1,"Spy Control panel MCM",ChartID(),2, CHARTEVENT_NEWBAR_M6)==INVALID_HANDLE) { Print("Error in setting of spy on USDJPY"); return(true);} Print("Spys ok, waiting for events..."); //--- return(0); } //+------------------------------------------------------------------+ //| The Standard event handler. | //| See MQL5 Reference for the details. | //| | //| In this case it is used for decoding of spy messages, sent by | //| iSPY Control panel MCM indicator. | //+------------------------------------------------------------------+ void OnChartEvent(const int id, // event identifier const long& lparam, // the event flag. // event flag is a bit mask of ENUM_CHART_EVENT_SYMBOL enumeration. const double& dparam, // price const string& sparam // symbol ) { if(id>=CHARTEVENT_CUSTOM) { Print(TimeToString(TimeCurrent(),TIME_SECONDS)," -> id=",id-CHARTEVENT_CUSTOM, ": ",sparam," ",EnumToString((ENUM_CHART_EVENT_SYMBOL)lparam)," price=",dparam); } }
The results of the work of exSpy Control panel MCM:
As you can see, we regularly receive all of the requested events.
Let us once more sum up the intermediate result:
- Thanks to the new version of the agent-indicator, we have retained all of our previous achievements.
- Now, from our multi-currency MQL-programs, we can specify at least 23 events, which include the events "new tick", "new bar" and "initialization".
- Even more work from the EA/indicator is given to the agents, in order to unload them.
Well, as it turned out, it isn't so hard to implement a full fledged multi-currency mode in MetaTrader 5.
In addition, I want to highlight a few nuances.
First. All events, which are generated by the "agents" for our multi-currency EA/indicator are external. In connection with this, the question arises: "Is it necessary to run the agents directly from the EA/indicator?". The answer is: "no."
Second. In the OnChartEvent() function, the event identifier id seems to be spare, since we can find out for which symbol the event was receives by the parameter sparam - the name of the trading symbol. Therefore, we can maybe use it for some other purpose? The answer is: "yes, we can."
Such arguments have led to the emergence of the "MCM Control Panel" for Multicurrency Expert Advisors and Indicators. It is a sort of "interlayer" between the terminal and an EA/indicator. This provided us with even more benefits and flexibility in configuring a multi-currency environment:
- The panel can be installed as a separate indicator on the chart, and then assign the multi-currency indicators, compatible with the panel.
- The panel may include indicators and EAs as component units. It will upload along with them.
- We can activate/deactivate the symbol from the "Market Watch" window for trading or analysis using the "Events" menu. In the OnChartEvent() function we can not find out the serial number of the symbol, by the event identifier id, in the window "Market Watch".
- We can set the trading mode by ticks or by the "new bar" event for any period, and for the symbol, selected in the "Market Watch". All this is done using the regular menu.
- We can change all of the above configurations, without unloading, stopping, or going into the properties window of the EA or indicator.
- All of this does not limit the creativity potential in creation of multi-currency indicators and EAs. Moreover, we now don't have to integrate into our code the outputs of this panel. Management of the agent-indicators is now implemented into the control panel.
- The structure of the multi-currency system is even simpler.
The multi-currency indicator RSI for the USDx dollar index
To experience all of the advantages of the above method, I propose to implement the multi-currency variant of the RSI indicator for the USDx dollar index using the MCM Control Panel.
To begin with, I'll note a few special features. Often, when trying to analyze the Dollar Index, we simply count the indicators of the index's readings. From my point of view, this is not entirely correct, since every symbol from the basket of currency pairs index, makes its own contributions. Therefore, for example, let's calculate the RSI for the dollar index by the formula, similar to the formula of index calculation:
That is, first we'll calculate the RSI for a particular currency pair, and then read the RSI for the index, taking into account the weight of the coefficients.
The reader may note that there is a problem with historical data synchronization all of the symbols, used in the multi-currency system. (see paragraph 2 of the section "Overview of the conventional approach").
This problem has been solved in the indicator, using the class functions for constructing the RSI synchronized buffers (SynchronizedBufferRSI.mqh file). There is no point of providing the entire code of the class, so there are only relevant moments presented below.
First, the indicator buffer is defined within the class with the public access modifier:
public: double buffer[]; // indicator buffer
Second, the indicator initialization is done using the class method:
//--- Initialization methods: bool Init(int n,string symbol,int rsi_count,int rsi_period);
And third, for each bar the value of the indicator buffer is synchronized with the current time-frame, using the refresh-method of class:
//+------------------------------------------------------------------+ //| The method of receiving/updating indicator data for one bar | //| of the indicator buffer. | //| INPUT: bar - bar number | //| OUTPUT: no. | //| REMARK: no. | //+------------------------------------------------------------------+ void CSynchronizedBufferRSI::Refresh(int bar=0) { buffer[bar]=EMPTY_VALUE; // Initialization of the bar of the indicator buffer. //--- Inquire the time of the bar for the current graph: datetime time[1]; if(CopyTime(_Symbol,_Period,bar,1,time)!=1) return; // In case of an error, we wait for the next tick/bar... //--- Request the value of the indicator for the symbol for the time, //--- consistent with that of the bar of the current graph: double value[1]; if(CopyBuffer(m_handle,0,time[0],time[0],value)!=1) return; // In case of an error, wait for the next tick/bar... buffer[bar]=value[0]; return; }
For the complete synchronization of all of the indicator buffers, we need to use an entire minute time-frame without "holes", as described in the following article. But for this method of synchronization of the indicator buffers, we specially selected the time-frame of the current graph, since the indicator display takes place on it.
Speaking from my own experience, I can say that for small periods, it makes sense to use such a method of synchronization, for any time-series or indicator buffer, if their symbol is different from the symbol on the current graph.
The graph clearly displays why this is worth doing:
For larger time-frames, this is not typically observed.
And last but not least. Below is the code of a standard user event handler, which is used in the indicator:
//+------------------------------------------------------------------+ //| The Standard event handler. | //| See MQL5 Reference for the details. | //+------------------------------------------------------------------+ void OnChartEvent(const int id, // event identifier or position symbol in the "Market Match"+CHARTEVENT_CUSTOM const long& lparam, // event indicator const double& dparam, // price const string& sparam // symbol ) { int custom_id=id-CHARTEVENT_CUSTOM-1; if(custom_id>=0) { if(lparam!=CHARTEVENT_NEWBAR_NO) { //--- Recalculation of the last uncompleted bar: if(EventToPeriod(lparam)==_Period && sparam==_Symbol) { // Recalculation of the indicator, if a new bar on the current chart iRSIUSDx_Ind[0]=EMPTY_VALUE; //--- Updating the value of the RSI for all of the currency pairs for the new bar for(int i=0;i<symbol_total;i++) buffer[i].Refresh(); iRSIUSDx(symbol_total); // calculation of the current incomplete bar RSI for the index return; } buffer[custom_id].Refresh(); // The value of RSI for the custom_id of the currency pair for the current bar iRSIUSDx(symbol_total); // calculation of the RSI for the current(uncompleted) bar RSIx return; } else { //--- Recalculation of the indicator for the "Initialization" event buffer[custom_id].RefreshBuffer(); // Update of the RSI buffer for the custom_id of the currency pair Init_iRSIUSDx(symbol_total,calculate); // Update of the RSI buffer for the index return; } } }
Special features of the code:
- The use of the event identifier id to refer to the array, containing pointers to class instances, which are intended to calculate the indicator buffers RSI, synchronized with the current time-frame. This approach greatly simplifies the structure of the code.
- The event "Initializing" is used to recalculate the indicator buffer RSI only for the currency pair, by which it was received, and not for all of the six symbols. As mentioned earlier, this allows you to synchronize the indicator, for example, when updating the history for one symbol.
- The event "new bar" is used to synchronize all of the indicator buffers RSI for a new bar on the current graph.
- The event "new tick" is used from all of the currency pairs to update the indicators on the last incomplete bar. Moreover, the recounting of the bar is done only for the pair, for which the "new tick" was received.
After exploring the full code of the RSI indicator for the dollar index USDx, it will become more clear how this works.
Installation features:
- Download the "MCM Control Panel" for Multicurrency Expert Advisors and Indicators and compile the "iControl panel MCM.mq5" and "Spy Control panel MCM.mq5" files.
- Specify the following order of symbols in the "Market Match" window:
- EURUSD
- USDJPY
- GBPUSD
- USDCAD
- USDSEK
- USDCHF
- Unpack the iRSIUSDx.zip archive into the /MQL5 folder. Attach the the iRSIUSDx.ex5 from the /MQL5/Indicators/iRSIUSDx/ folder to the EURUSD chart with the M1 period.
- Sequentially, for all of the six symbols in the "Event" menu of the "Control panel MCM" panel, set the event "New tick", as described here. You should get a picture, similar to that of the picture above.
- Additionally, for the EURUSD symbol, set the "new bar" event on the minute chart. In the indicator, this event is used for synchronization when the new bar on the current time-frame, which is equal to M1.
- If you would like a more visual example, set the dollar index as described here.
Conclusion
The discussed implementation of a full fledged multi-currency mode in MetaTrader 5 fully demonstrates the advantages of the platform and the MQL5 programming language in solving this problem. What previously caused the majority of difficulties, is now available.
Clearly, this is only the beginning of a movement in this direction. Surely, there will be more options found of an even better way of data synchronization, multi-currency modes management, etc. But I hope that now it's clear that we have have all of the necessary tools for it.
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/234
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
Thanks for this huge article. I haven't heard so far of EventChartCustom. I was trying other chart events but they only took into account events caused by human action. It solves many things.
By the way, I work on MQL4, it was at 98% the same thing.
Cheers
Thank you so much for this, it's so useful. Great job!