![MQL5 Cookbook: Multi-Currency Expert Advisor - Simple, Neat and Quick Approach](https://c.mql5.com/i/articles/overlay.png)
MQL5 Cookbook: Multi-Currency Expert Advisor - Simple, Neat and Quick Approach
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:
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.
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.
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.
Now set NZDUSD as the second symbol. For the optimization, leave the line with the symbol name for the first parameter block empty.
Results for NZDUSD have appeared to be as follows:
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.
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
![Mechanical Trading System "Chuvashov's Triangle"](https://c.mql5.com/2/17/985_56.png)
![Three Aspects of Manual Trading Automation. Part 1: Trading](https://c.mql5.com/2/13/1080_67.png)
![LibMatrix: Library of Matrix Algebra (Part One)](https://c.mql5.com/2/17/843_42.png)
![The Random Sandbox](https://c.mql5.com/2/13/1061_27.png)
![MQL5 - Language of trade strategies built-in the MetaTrader 5 client terminal](https://c.mql5.com/i/registerlandings/logo-2.png)
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
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?
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?
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
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?