Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
MQL5 Cookbook: Multi-Currency Expert Advisor - Simple, Neat and Quick Approach

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

MetaTrader 5Examples | 18 June 2013, 15:33
20 067 7
Anatoli Kazharski
Anatoli Kazharski

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:

  • We can use a pattern where an Expert Advisor is guided by time, being capable of performing more accurate checks at the time intervals specified in the OnTimer() function.

  • Alternatively, as in all the Expert Advisors introduced in the previous articles of the series, the check can be done in the OnTick() function in which case the Expert Advisor will depend on ticks for the current symbol it works on. So if there is a completed bar on another symbol, whereas there is yet no tick for the current symbol, the Expert Advisor will only perform a check once there is a new tick for the current symbol.

  • There is yet another interesting option suggested by its author Konstantin Gruzdev (Lizar). It employs an event model: using the OnChartEvent() function, an Expert Advisor gets events that are reproduced by indicator agents located on the symbol charts involved in testing/trading. Indicator agents can reproduce new bar and tick events of the symbols they are attached to. This kind of indicator (EventsSpy.mq5) can be downloaded at the end of the article. We will need it for the operation of the Expert Advisor.


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.

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/648

Last comments | Go to discussion (7)
Jose
Jose | 7 Oct 2013 at 08:16

The article is neat and easy to follow, and providing the settings in the .set files. I did have an issue with the symbol execution modes, specifically the condition checking whether it was instant or market, and only after that letting orders be opened, which I had to remove, but everything worked fine .

I do have a question. I have seen many different advanced approaches, such as this one, which are built from the ground neglecting the Expert, ExpertSignal, ExpertTrade…. structure provided, even though they spend a lot of code (and time, I suppose) replicating some of its features. Could anybody justify this to me, please?

Alain Verleyen
Alain Verleyen | 7 Oct 2013 at 10:08
jlwarrior:

The article is neat and easy to follow, and providing the settings in the .set files. I did have an issue with the symbol execution modes, specifically the condition checking whether it was instant or market, and only after that letting orders be opened, which I had to remove, but everything worked fine .

I do have a question. I have seen many different advanced approaches, such as this one, which are built from the ground neglecting the Expert, ExpertSignal, ExpertTrade…. structure provided, even though they spend a lot of code (and time, I suppose) replicating some of its features. Could anybody justify this to me, please?

Some people like programming, and not only use what already exist. ;-)
david mackay
david mackay | 25 Nov 2013 at 08:36

The author's series, 'CookBook' is most helpful, especially as he starts with simpler concepts and gradually adds more and more to the experts. I spend a lot of time working with these and  although progress has been slow, I am definitely getting some grasp as to how to use MT5 to advantage.


daveM

earmarques
earmarques | 6 Oct 2016 at 04:51
Jose:

The article is neat and easy to follow, and providing the settings in the .set files. I did have an issue with the symbol execution modes, specifically the condition checking whether it was instant or market, and only after that letting orders be opened, which I had to remove, but everything worked fine .

I do have a question. I have seen many different advanced approaches, such as this one, which are built from the ground neglecting the Expert, ExpertSignal, ExpertTrade…. structure provided, even though they spend a lot of code (and time, I suppose) replicating some of its features. Could anybody justify this to me, please?

Hi everyone!

I'm trying to test this EA but just got the following message in Strategy Tester: "Error opening the position: 4753 - 1 The position not found". I don't understand why. This happens when the "trade.PositionOpen" function is executed in the line 159 of file "TradeFunctions.mqh". Someone can give me a help please?

Jose Ma Gassin Perez Traverso
Jose Ma Gassin Perez Traverso | 25 Jan 2024 at 15:35
"TimeframeToString" does not exist and is not declared in the article, kindly rteplace by "EnumToString".
Mechanical Trading System "Chuvashov's Triangle" Mechanical Trading System "Chuvashov's Triangle"
Let me offer you an overview and the program code of the mechanical trading system based on ideas of Stanislav Chuvashov. Triangle's construction is based on the intersection of two trend lines built by the upper and lower fractals.
Three Aspects of Manual Trading Automation. Part 1: Trading Three Aspects of Manual Trading Automation. Part 1: Trading
This article is the first in a series of articles on manual trading automation in the МetaТrader 4 trading platform. Each of the articles will be devoted to one of the following aspects: automation of manual trading, current state of trade display automation and automation of reports on trade results. In this article, I will present an interesting approach to creating EA's controlled manually by a trader.
LibMatrix: Library of Matrix Algebra (Part One) LibMatrix: Library of Matrix Algebra (Part One)
The author familiarizes the readers with a simple library of matrix algebra and provides descriptions and peculiarities of the main functions.
The Random Sandbox The Random Sandbox
The article includes an interactive "sandbox" as an Excel file which simulates randomized Expert Advisor backtest data. Readers can use this to help explore and more deeply understand the EA performance metrics offered by default with MetaTrader. The text of the article is designed to guide the user through this experience.