Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
The Implementation of a Multi-currency Mode in MetaTrader 5

The Implementation of a Multi-currency Mode in MetaTrader 5

MetaTrader 5Examples | 18 February 2011, 17:58
18 478 5
Konstantin Gruzdev
Konstantin Gruzdev

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:

  • 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:
    1. EURUSD
    2. USDJPY
    3. GBPUSD
    4. USDCAD
    5. USDSEK
    6. USDCHF
    This is needed because I did not place an appropriate check into the indicator, and this sequence is necessary for the correct calculation of the indicator.
  • 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

Attached files |
irsiusdx_en.zip (7.54 KB)
Last comments | Go to discussion (5)
Pawel Wojnarowski
Pawel Wojnarowski | 14 Mar 2011 at 22:16
Hats off, gentlemen :) A true masterpiece. Simple yet elegant and powerful solution. Thanks a million, Konstantin, and congratulations :-)
Andriy Moraru
Andriy Moraru | 7 Jul 2011 at 22:16
For some reason, I still get different results when testing multi-currency EA on different currency pairs, even using the method shown in this article.
Guillaume Perinet
Guillaume Perinet | 15 Oct 2020 at 04:29

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

algui91
algui91 | 3 May 2022 at 21:18

Thank you so much for this, it's so useful. Great job!

Jose Ma Gassin Perez Traverso
Jose Ma Gassin Perez Traverso | 15 Jul 2023 at 02:25
As mentioned, this doesn't works on backtest (sadly) hopefully we'll have a better method to get ticks from other symbols in a near future :)
Averaging Price Series for Intermediate Calculations Without Using Additional Buffers Averaging Price Series for Intermediate Calculations Without Using Additional Buffers
This article is about traditional and unusual algorithms of averaging packed in simplest and single-type classes. They are intended for universal usage in almost all developments of indicators. I hope that the suggested classes will be a good alternative to 'bulky' calls of custom and technical indicators.
Drawing Channels - Inside and Outside View Drawing Channels - Inside and Outside View
I guess it won't be an exaggeration, if I say the channels are the most popular tool for the analysis of market and making trade decisions after the moving averages. Without diving deeply into the mass of trade strategies that use channels and their components, we are going to discuss the mathematical basis and the practical implementation of an indicator, which draws a channel determined by three extremums on the screen of the client terminal.
Finding Errors and Logging Finding Errors and Logging
MetaEditor 5 has the debugging feature. But when you write your MQL5 programs, you often want to display not the individual values, but all messages that appear during testing and online work. When the log file contents have large size, it is obvious to automate quick and easy retrieval of required message. In this article we will consider ways of finding errors in MQL5 programs and methods of logging. Also we will simplify logging into files and will get to know a simple program LogMon for comfortable viewing of logs.
Charts and diagrams in HTML Charts and diagrams in HTML
Today it is difficult to find a computer that does not have an installed web-browser. For a long time browsers have been evolving and improving. This article discusses the simple and safe way to create of charts and diagrams, based on the the information, obtained from MetaTrader 5 client terminal for displaying them in the browser.