MQL5 Cookbook: Multi-Currency Expert Advisor - Simple, Neat and Quick Approach

Anatoli Kazharski | 18 June, 2013

Introduction

This article will describe an implementation of a simple approach suitable for a multi-currency Expert Advisor. This means that you will be able to set up the Expert Advisor for testing/trading under identical conditions but with different parameters for each symbol. As an example, we will create a pattern for two symbols but in such a way so as to be able to add additional symbols, if necessary, by making small changes to the code.

A multi-currency pattern can be implemented in MQL5 in a number of ways:


Expert Advisor Development

The Expert Advisor featured in the article "MQL5 Cookbook: Using Indicators to Set Trading Conditions in Expert Advisors" will serve as a template. I have already deleted from it everything that had to do with the info panel and also simplified the position opening condition as implemented in the previous article entitled "MQL5 Cookbook: Developing a Framework for a Trading System Based on the Triple Screen Strategy". Since we intend to create an Expert Advisor for two symbols, each of them will need its own set of external parameters:

//--- External parameters of the Expert Advisor
sinput long   MagicNumber           = 777;      // Magic number
sinput int    Deviation             = 10;       // Slippage
//---
sinput string delimeter_00=""; // --------------------------------
sinput string Symbol_01             = "EURUSD"; // Symbol 1
input  int    IndicatorPeriod_01    = 5;        // |     Indicator period
input  double TakeProfit_01         = 100;      // |     Take Profit
input  double StopLoss_01           = 50;       // |     Stop Loss
input  double TrailingStop_01       = 10;       // |     Trailing Stop
input  bool   Reverse_01            = true;     // |     Position reversal
input  double Lot_01                = 0.1;      // |     Lot
input  double VolumeIncrease_01     = 0.1;      // |     Position volume increase
input  double VolumeIncreaseStep_01 = 10;       // |     Volume increase step
//---
sinput string delimeter_01=""; // --------------------------------
sinput string Symbol_02             = "NZDUSD"; // Symbol 2
input  int    IndicatorPeriod_02    = 5;        // |     Indicator period
input  double TakeProfit_02         = 100;      // |     Take Profit
input  double StopLoss_02           = 50;       // |     Stop Loss
input  double TrailingStop_02       = 10;       // |     Trailing Stop
input  bool   Reverse_02            = true;     // |     Position reversal
input  double Lot_02                = 0.1;      // |     Lot
input  double VolumeIncrease_02     = 0.1;      // |     Position volume increase
input  double VolumeIncreaseStep_02 = 10;       // |     Volume increase step

The external parameters will be placed into arrays whose sizes will depend on the number of symbols used. The number of symbols used in the Expert Advisor will be determined by the value of the NUMBER_OF_SYMBOLS constant that we need to create at the beginning of the file:

//--- Number of traded symbols
#define NUMBER_OF_SYMBOLS 2
//--- Name of the Expert Advisor
#define EXPERT_NAME MQL5InfoString(MQL5_PROGRAM_NAME)

Let's create the arrays that will be required to store the external parameters:

//--- Arrays for storing external parameters
string Symbols[NUMBER_OF_SYMBOLS];            // Symbol
int    IndicatorPeriod[NUMBER_OF_SYMBOLS];    // Indicator period
double TakeProfit[NUMBER_OF_SYMBOLS];         // Take Profit
double StopLoss[NUMBER_OF_SYMBOLS];           // Stop Loss
double TrailingStop[NUMBER_OF_SYMBOLS];       // Trailing Stop
bool   Reverse[NUMBER_OF_SYMBOLS];            // Position reversal
double Lot[NUMBER_OF_SYMBOLS];                // Lot
double VolumeIncrease[NUMBER_OF_SYMBOLS];     // Position volume increase
double VolumeIncreaseStep[NUMBER_OF_SYMBOLS]; // Volume increase step

Array initialization functions will be placed in the include InitArrays.mqh file. To initialize the Symbols[] array, we will create the GetSymbol() function. It will get the symbol name from the external parameters and if such symbol is available in the symbol list on the server, it will be selected in the Market Watch window. Or else, if the required symbol cannot be found on the server, the function will return an empty string and the Journal of Expert Advisors will be updated accordingly.

Below is the GetSymbol() function code:

//+------------------------------------------------------------------+
//| Adding the specified symbol to the Market Watch window           |
//+------------------------------------------------------------------+
string GetSymbolByName(string symbol)
  {
   string symbol_name="";   // Symbol name on the server
//--- If an empty string is passed, return the empty string
   if(symbol=="")
      return("");
//--- Iterate over the list of all symbols on the server
   for(int s=0; s<SymbolsTotal(false); s++)
     {
      //--- Get the symbol name
      symbol_name=SymbolName(s,false);
      //--- If the required symbol is available on the server
      if(symbol==symbol_name)
        {
         //--- Select it in the Market Watch window
         SymbolSelect(symbol,true);
         //--- Return the symbol name
         return(symbol);
        }
     }
//--- If the required symbol cannot be found, return the empty string
   Print("The "+symbol+" symbol could not be found on the server!");
   return("");
  }

The Symbols[] array will be initialized in the GetSymbols() function:

//+------------------------------------------------------------------+
//| Filling the array of symbols                                     |
//+------------------------------------------------------------------+
void GetSymbols()
  {
   Symbols[0]=GetSymbolByName(Symbol_01);
   Symbols[1]=GetSymbolByName(Symbol_02);
  }

Additionally, we will implement it in such a way that an empty value in the external parameters of a certain symbol will indicate that the corresponding block will not be involved in testing/trading. This is necessary in order to be able to optimize parameters for each symbol separately, while completely excluding the rest.

All the other arrays of external parameters are initialized in the same way. In other words, we need to create a separate function for each array. The codes of all these functions are provided below:

//+------------------------------------------------------------------+
//| Filling the indicator period array                               |
//+------------------------------------------------------------------+
void GetIndicatorPeriod()
  {
   IndicatorPeriod[0]=IndicatorPeriod_01;
   IndicatorPeriod[1]=IndicatorPeriod_02;
  }
//+------------------------------------------------------------------+
//| Filling the Take Profit array                                    |
//+------------------------------------------------------------------+
void GetTakeProfit()
  {
   TakeProfit[0]=TakeProfit_01;
   TakeProfit[1]=TakeProfit_02;
  }
//+------------------------------------------------------------------+
//| Filling the Stop Loss array                                      |
//+------------------------------------------------------------------+
void GetStopLoss()
  {
   StopLoss[0]=StopLoss_01;
   StopLoss[1]=StopLoss_02;
  }
//+------------------------------------------------------------------+
//| Filling the Trailing Stop array                                  |
//+------------------------------------------------------------------+
void GetTrailingStop()
  {
   TrailingStop[0]=TrailingStop_01;
   TrailingStop[1]=TrailingStop_02;
  }
//+------------------------------------------------------------------+
//| Filling the Reverse array                                        |
//+------------------------------------------------------------------+
void GetReverse()
  {
   Reverse[0]=Reverse_01;
   Reverse[1]=Reverse_02;
  }
//+------------------------------------------------------------------+
//| Filling the Lot array                                            |
//+------------------------------------------------------------------+
void GetLot()
  {
   Lot[0]=Lot_01;
   Lot[1]=Lot_02;
  }
//+------------------------------------------------------------------+
//| Filling the VolumeIncrease array                                 |
//+------------------------------------------------------------------+
void GetVolumeIncrease()
  {
   VolumeIncrease[0]=VolumeIncrease_01;
   VolumeIncrease[1]=VolumeIncrease_02;
  }
//+------------------------------------------------------------------+
//| Filling the VolumeIncreaseStep array                             |
//+------------------------------------------------------------------+
void GetVolumeIncreaseStep()
  {
   VolumeIncreaseStep[0]=VolumeIncreaseStep_01;
   VolumeIncreaseStep[1]=VolumeIncreaseStep_02;
  }

Let's now create a function that will help us to conveniently initialize all the external parameter arrays at once - the InitializeInputParameters() function:

//+------------------------------------------------------------------+
//| Initializing external parameter arrays                           |
//+------------------------------------------------------------------+
void InitializeInputParameters()
  {
   GetSymbols();
   GetIndicatorPeriod();
   GetTakeProfit();
   GetStopLoss();
   GetTrailingStop();
   GetReverse();
   GetLot();
   GetVolumeIncrease();
   GetVolumeIncreaseStep();
  }

Following the initialization of the external parameter arrays, we can proceed to the main part. Some procedures such as getting indicator handles, their values and price information, as well as checking for the new bar, etc. will be carried out in loops consecutively for each symbol. This is why external parameter values have been arranged in arrays. So it all will be done in the loops as follows:

//--- Iterate over all symbols
for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
  {
//--- If trading for this symbol is allowed
   if(Symbols[s]!="")
     {
      //--- The rest of the code
     }
  }

But before we start modifying the existing functions and creating new ones, let's also create arrays that will be required in that pattern.

We will need two arrays for indicator handles:

//--- Array of indicator agent handles
int spy_indicator_handles[NUMBER_OF_SYMBOLS];
//--- Array of signal indicator handles
int signal_indicator_handles[NUMBER_OF_SYMBOLS];

These two arrays will first be initialized to invalid values:

//+------------------------------------------------------------------+
//| Initializing arrays of indicator handles                         |
//+------------------------------------------------------------------+
void InitializeArrayHandles()
  {
   ArrayInitialize(spy_indicator_handles,INVALID_HANDLE);
   ArrayInitialize(signal_indicator_handles,INVALID_HANDLE);
  }

Arrays of price data and indicator values will now be accessed using structures:

//--- Data arrays for checking trading conditions
struct PriceData
  {
   double            value[];
  };
PriceData open[NUMBER_OF_SYMBOLS];      // Opening price of the bar
PriceData high[NUMBER_OF_SYMBOLS];      // High price of the bar
PriceData low[NUMBER_OF_SYMBOLS];       // Low price of the bar
PriceData close[NUMBER_OF_SYMBOLS];     // Closing price of the bar
PriceData indicator[NUMBER_OF_SYMBOLS]; // Array of indicator values

Now, if you need to get the indicator value on the last completed bar of the first symbol in the list, you should write something like that:

double indicator_value=indicator[0].value[1];

We also need to create arrays instead of the variables that were previously used in the CheckNewBar() function:

//--- Arrays for getting the opening time of the current bar
struct Datetime
  {
   datetime          time[];
  };
Datetime lastbar_time[NUMBER_OF_SYMBOLS];
//--- Array for checking the new bar for each symbol
datetime new_bar[NUMBER_OF_SYMBOLS];

So we have arranged the arrays. Now we need to modify a number of functions according to the changes made above. Let's start with the GetIndicatorHandles() function:

//+------------------------------------------------------------------+
//| Getting indicator handles                                        |
//+------------------------------------------------------------------+
void GetIndicatorHandles()
  {
//--- Iterate over all symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the handle is yet to be obtained
         if(signal_indicator_handles[s]==INVALID_HANDLE)
           {
            //--- Get the indicator handle
            signal_indicator_handles[s]=iMA(Symbols[s],_Period,IndicatorPeriod[s],0,MODE_SMA,PRICE_CLOSE);
            //--- If the indicator handle could not be obtained
            if(signal_indicator_handles[s]==INVALID_HANDLE)
               Print("Failed to get the indicator handle for the symbol "+Symbols[s]+"!");
           }
        }
     }
  }

Now, regardless of the number of symbols used in testing/ trading, the code of the function will remain the same.

Similarly, we will create another function, GetSpyHandles(), for getting handles of indicator agents that will transmit ticks from other symbols. But before that, we will add one more enumeration of all events by symbol, ENUM_CHART_EVENT_SYMBOL, arranged as flags in the Enums.mqh file:

//+------------------------------------------------------------------+
//| New bar and tick events from all symbols and time frames         |
//+------------------------------------------------------------------+
enum ENUM_CHART_EVENT_SYMBOL
  {
   CHARTEVENT_NO         = 0,          // Events are disabled - 0
   CHARTEVENT_INIT       = 0,          // Initialization event - 0
   //---
   CHARTEVENT_NEWBAR_M1  = 0x00000001, // New bar event on a minute chart (1)
   CHARTEVENT_NEWBAR_M2  = 0x00000002, // New bar event on a 2-minute chart (2)
   CHARTEVENT_NEWBAR_M3  = 0x00000004, // New bar event on a 3-minute chart (4)
   CHARTEVENT_NEWBAR_M4  = 0x00000008, // New bar event on a 4-minute chart (8)
   //---
   CHARTEVENT_NEWBAR_M5  = 0x00000010, // New bar event on a 5-minute chart (16)
   CHARTEVENT_NEWBAR_M6  = 0x00000020, // New bar event on a 6-minute chart (32)
   CHARTEVENT_NEWBAR_M10 = 0x00000040, // New bar event on a 10-minute chart (64)
   CHARTEVENT_NEWBAR_M12 = 0x00000080, // New bar event on a 12-minute chart (128)
   //---
   CHARTEVENT_NEWBAR_M15 = 0x00000100, // New bar event on a 15-minute chart (256)
   CHARTEVENT_NEWBAR_M20 = 0x00000200, // New bar event on a 20-minute chart (512)
   CHARTEVENT_NEWBAR_M30 = 0x00000400, // New bar event on a 30-minute chart (1024)
   CHARTEVENT_NEWBAR_H1  = 0x00000800, // New bar event on an hour chart (2048)
   //---
   CHARTEVENT_NEWBAR_H2  = 0x00001000, // New bar event on a 2-hour chart (4096)
   CHARTEVENT_NEWBAR_H3  = 0x00002000, // New bar event on a 3-hour chart (8192)
   CHARTEVENT_NEWBAR_H4  = 0x00004000, // New bar event on a 4-hour chart (16384)
   CHARTEVENT_NEWBAR_H6  = 0x00008000, // New bar event on a 6-hour chart (32768)
   //---
   CHARTEVENT_NEWBAR_H8  = 0x00010000, // New bar event on a 8-hour chart (65536)
   CHARTEVENT_NEWBAR_H12 = 0x00020000, // New bar event on a 12-hour chart (131072)
   CHARTEVENT_NEWBAR_D1  = 0x00040000, // New bar event on a daily chart (262144)
   CHARTEVENT_NEWBAR_W1  = 0x00080000, // New bar event on a weekly chart (524288)
   //---
   CHARTEVENT_NEWBAR_MN1 = 0x00100000, // New bar event on a monthly chart (1048576)
   CHARTEVENT_TICK       = 0x00200000, // New tick event (2097152)
   //---
   CHARTEVENT_ALL        = 0xFFFFFFFF  // All events are enabled (-1)
  };

This enumeration is necessary for working with the custom indicator EventsSpy.mq5 (the file is attached to the article) in the GetSpyHandles() function whose code is provided below:

//+------------------------------------------------------------------+
//| Getting agent handles by the specified symbols                   |
//+------------------------------------------------------------------+
void GetSpyHandles()
  {
//--- Iterate over all symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the handle is yet to be obtained
         if(spy_indicator_handles[s]==INVALID_HANDLE)
           {
            //--- Get the indicator handle
            spy_indicator_handles[s]=iCustom(Symbols[s],_Period,"EventsSpy.ex5",ChartID(),0,CHARTEVENT_TICK);
            //--- If the indicator handle could not be obtained
            if(spy_indicator_handles[s]==INVALID_HANDLE)
               Print("Failed to install the agent on "+Symbols[s]+"");
           }
        }
     }
  }

Please note the last parameter in the iCustom() function: in this case, the CHARTEVENT_TICK identifier has been used to get tick events. But if it is necessary, it can be modified to get the new bar events. For example, if you use the line as shown below, the Expert Advisor will get new bar events on a minute (M1) and an hour (H1) time frames:

handle_event_indicator[s]=iCustom(Symbols[s],_Period,"EventsSpy.ex5",ChartID(),0,CHARTEVENT_NEWBAR_M1|CHARTEVENT_NEWBAR_H1);

To get all events (tick and bar events on all time frames), you need to specify the CHARTEVENT_ALL identifier.

All arrays are initialized in the OnInit() function:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
void OnInit()
  {
//--- Initialization of arrays of external parameters
   InitializeInputParameters();
//--- Initialization of arrays of indicator handles
   InitializeArrayHandles();
//--- Get agent handles
   GetSpyHandles();
//--- Get indicator handles
   GetIndicatorHandles();
//--- Initialize the new bar
   InitializeArrayNewBar();
  }

As already mentioned at the beginning of the article, events from the indicator agents are received in the OnChartEvent() function. Below is the code that will be used in this function:

//+------------------------------------------------------------------+
//| Chart events handler                                             |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // Event identifier
                  const long &lparam,   // Long type event parameter
                  const double &dparam, // Double type event parameter
                  const string &sparam) // String type event parameter
  {
//--- If this is a custom event
   if(id>=CHARTEVENT_CUSTOM)
     {
      //--- Exit if trading is not allowed
      if(CheckTradingPermission()>0)
         return;
      //--- If there was a tick event
      if(lparam==CHARTEVENT_TICK)
        {
         //--- Check signals and trade on them
         CheckSignalsAndTrade();
         return;
        }
     }
  }

In the CheckSignalAndTrade() function (the highlighted line in the code above), we will have a loop where all the symbols will alternately be checked for the new bar event and trading signals as implemented before in the OnTick() function:

//+------------------------------------------------------------------+
//| Checking signals and trading based on the new bar event          |
//+------------------------------------------------------------------+
void CheckSignalsAndTrade()
  {
//--- Iterate over all specified symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the bar is not new, proceed to the next symbol
         if(!CheckNewBar(s))
            continue;
         //--- If there is a new bar
         else
           {
            //--- Get indicator data. If there is no data, proceed to the next symbol
            if(!GetIndicatorsData(s))
               continue;
            //--- Get bar data               
            GetBarsData(s);
            //--- Check the conditions and trade
            TradingBlock(s);
            //--- Trailing Stop
            ModifyTrailingStop(s);
           }
        }
     }
  }

All the functions that used the external parameters, as well as symbol and indicator data, need to be modified in accordance with all the above changes. For this purpose, we should add the symbol number as the first parameter and replace all variables and arrays inside the function with the new arrays described above.

For illustration, the revised codes of the CheckNewBar(), TradingBlock() and OpenPosition() functions are provided below.

The CheckNewBar() function code:

//+------------------------------------------------------------------+
//| Checking for the new bar                                         |
//+------------------------------------------------------------------+
bool CheckNewBar(int number_symbol)
  {
//--- Get the opening time of the current bar
//    If an error occurred when getting the time, print the relevant message
   if(CopyTime(Symbols[number_symbol],Period(),0,1,lastbar_time[number_symbol].time)==-1)
      Print(__FUNCTION__,": Error copying the opening time of the bar: "+IntegerToString(GetLastError()));
//--- If this is a first function call
   if(new_bar[number_symbol]==NULL)
     {
      //--- Set the time
      new_bar[number_symbol]=lastbar_time[number_symbol].time[0];
      Print(__FUNCTION__,": Initialization ["+Symbols[number_symbol]+"][TF: "+TimeframeToString(Period())+"]["
            +TimeToString(lastbar_time[number_symbol].time[0],TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"]");
      return(false);
     }
//--- If the time is different
   if(new_bar[number_symbol]!=lastbar_time[number_symbol].time[0])
     {
      //--- Set the time and exit
      new_bar[number_symbol]=lastbar_time[number_symbol].time[0];
      return(true);
     }
//--- If we have reached this line, then the bar is not new, so return false
   return(false);
  }

The TradingBlock() function code:

//+------------------------------------------------------------------+
//| Trading block                                                    |
//+------------------------------------------------------------------+
void TradingBlock(int symbol_number)
  {
   ENUM_ORDER_TYPE      signal=WRONG_VALUE;                 // Variable for getting a signal
   string               comment="hello :)";                 // Position comment
   double               tp=0.0;                             // Take Profit
   double               sl=0.0;                             // Stop Loss
   double               lot=0.0;                            // Volume for position calculation in case of position reversal
   double               position_open_price=0.0;            // Position opening price
   ENUM_ORDER_TYPE      order_type=WRONG_VALUE;             // Order type for opening a position
   ENUM_POSITION_TYPE   opposite_position_type=WRONG_VALUE; // Opposite position type
//--- Find out if there is a position
   pos.exists=PositionSelect(Symbols[symbol_number]);
//--- Get the signal
   signal=GetTradingSignal(symbol_number);
//--- If there is no signal, exit
   if(signal==WRONG_VALUE)
      return;
//--- Get symbol properties
   GetSymbolProperties(symbol_number,S_ALL);
//--- Determine values for trade variables
   switch(signal)
     {
      //--- Assign values to variables for a BUY
      case ORDER_TYPE_BUY  :
         position_open_price=symb.ask;
         order_type=ORDER_TYPE_BUY;
         opposite_position_type=POSITION_TYPE_SELL;
         break;
         //--- Assign values to variables for a SELL
      case ORDER_TYPE_SELL :
         position_open_price=symb.bid;
         order_type=ORDER_TYPE_SELL;
         opposite_position_type=POSITION_TYPE_BUY;
         break;
     }
//--- Get the Take Profit and Stop Loss levels
   sl=CalculateStopLoss(symbol_number,order_type);
   tp=CalculateTakeProfit(symbol_number,order_type);
//--- If there is no position
   if(!pos.exists)
     {
      //--- Adjust the volume
      lot=CalculateLot(symbol_number,Lot[symbol_number]);
      //--- Open a position
      OpenPosition(symbol_number,lot,order_type,position_open_price,sl,tp,comment);
     }
//--- If the position exists
   else
     {
      //--- Get the position type
      GetPositionProperties(symbol_number,P_TYPE);
      //--- If the position is opposite to the signal and the position reversal is enabled
      if(pos.type==opposite_position_type && Reverse[symbol_number])
        {
         //--- Get the position volume
         GetPositionProperties(symbol_number,P_VOLUME);
         //--- Adjust the volume
         lot=pos.volume+CalculateLot(symbol_number,Lot[symbol_number]);
         //--- Reverse the position
         OpenPosition(symbol_number,lot,order_type,position_open_price,sl,tp,comment);
         return;
        }
      //--- If the signal is in the direction of the position and the volume increase is enabled, increase the position volume
      if(!(pos.type==opposite_position_type) && VolumeIncrease[symbol_number]>0)
        {
         //--- Get the Stop Loss of the current position
         GetPositionProperties(symbol_number,P_SL);
         //--- Get the Take Profit of the current position
         GetPositionProperties(symbol_number,P_TP);
         //--- Adjust the volume
         lot=CalculateLot(symbol_number,VolumeIncrease[symbol_number]);
         //--- Increase the position volume
         OpenPosition(symbol_number,lot,order_type,position_open_price,pos.sl,pos.tp,comment);
         return;
        }
     }
  }

The OpenPosition() function code:

//+------------------------------------------------------------------+
//| Opening a position                                               |
//+------------------------------------------------------------------+
void OpenPosition(int symbol_number,
                  double lot,
                  ENUM_ORDER_TYPE order_type,
                  double price,
                  double sl,
                  double tp,
                  string comment)
  {
//--- Set the magic number in the trading structure
   trade.SetExpertMagicNumber(MagicNumber);
//--- Set the slippage in points
   trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation));
//--- Instant Execution and Market Execution mode
//    *** Starting with build 803, Stop Loss and Take Profit ***
//    *** can be set upon opening a position in the SYMBOL_TRADE_EXECUTION_MARKET mode ***
   if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_INSTANT ||
      symb.execution_mode==SYMBOL_TRADE_EXECUTION_MARKET)
     {
      //--- If the position failed to open, print the relevant message
      if(!trade.PositionOpen(Symbols[symbol_number],order_type,lot,price,sl,tp,comment))
         Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
     }
  }

So, each function now receives the symbol number (symbol_number). Please also note the change introduced in build 803:

Starting with build 803, Stop Loss and Take Profit can be set upon opening a position in the SYMBOL_TRADE_EXECUTION_MARKET mode.

The revised codes of the other functions can be found in the attached files. All we need to do now is to optimize the parameters and perform testing.


Optimizing Parameters and Testing Expert Advisor

We will first optimize the parameters for the first symbol and then for the second one. Let's start with EURUSD.

Below are the settings of the Strategy Tester:

Fig. 1. Strategy Tester settings

Fig. 1. Strategy Tester settings.

The settings of the Expert Advisor need to be made as shown below (for convenience, the .set files containing settings for each symbol are attached to the article). To exclude a certain symbol from the optimization, you should simply leave the symbol name parameter field empty. Optimization of parameters performed for each symbol separately will also speed up the optimization process.

Fig. 2. Expert Advisor settings for parameter optimization: EURUSD

Fig. 2. Expert Advisor settings for parameter optimization: EURUSD.

The optimization will take about an hour on a dual-core processor. Maximum recovery factor test results are as shown below:

Fig. 3. Maximum recovery factor test results for EURUSD

Fig. 3. Maximum recovery factor test results for EURUSD.

Now set NZDUSD as the second symbol. For the optimization, leave the line with the symbol name for the first parameter block empty.

Alternatively, you can simply add a dash at the end of the symbol name. The Expert Advisor will not find the symbol with such name in the symbol list and will initialize the array index to an empty string.

Results for NZDUSD have appeared to be as follows:

Fig. 4. Maximum recovery factor test results for NZDUSD

Fig. 4. Maximum recovery factor test results for NZDUSD.

Now we can test two symbols together. In the Strategy Tester settings, you can set any symbol on which the Expert Advisor is launched since the results will be identical. It can even be a symbol that is not involved in trading/testing.

Below are the results for two symbols tested together:

Fig. 5. Test results for two symbols: EURUSD and NZDUSD

Fig. 5. Test results for two symbols: EURUSD and NZDUSD.


Conclusion

That's about it. The source codes are attached below and can be downloaded for a more detailed study of the above. For practice, try to select one or more symbols or change position opening conditions using other indicators.

After extracting files from the archive, place MultiSymbolExpert folder into MetaTrader 5\MQL5\Experts directory. Further, the EventsSpy.mq5 indicator must be placed into MetaTrader 5\MQL5\Indicators directory.