The Implementation of a Multi-currency Mode in MetaTrader 5

Konstantin Gruzdev | 18 February, 2011


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:

  1. 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.

  2. 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.

  3. 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:

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:

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:

 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:

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

After exploring the full code of the RSI indicator for the dollar index USDx, it will become more clear how this works.

Installation features:


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.