
MQL5 Wizard: How to Create a Module of Trading Signals
Introduction
MetaTrader 5 provides a powerful tool for quick checking of trading ideas. This is the generator of trading strategies of the MQL5 Wizard. The use of the MQL5 Wizard for automatic creation of Expert Advisor codes is described in the article "MQL5 Wizard: Creating Expert Advisors without Programming". Openness of the code generation system allows you to add your own classes of trading signals, money management systems and trailing modules to the standard ones.
This article describes the principles of writing modules of trading signals to use them when creating Expert Advisors with the MQL5 Wizard.
The Expert Advisor created with MQL5 Wizard, is based on four pillars - four base classes:
Figure 1. The structure of the CExpert base class
The CExpert class (or its subclass) is the main "engine" of a trading robot. An instance of CExpert contains one copy of each class: CExpertSignal, CExpertMoney and CExpertTrailing (or their subclasses):
- CExpertSignal is the basis of the trading signals generator. An instance of the CExpertSignal derived class, included in CExpert, provides an Expert Advisor with information about the possibility of entering the market, levels of entry and placing of protective orders, based on built-in algorithms. The final decision on execution of trading operations is made by the EA.
- CExpertMoney is the basis of the money and risk management systems. An instance of CExpertMoney derived class calculates volumes for opening positions and placing pending orders. The final decision on the volume is made by the EA.
- CExpertTrailing - is the basis of the module of open positions support. An instance of the CExpertTrailing derived class informs an EA about the necessity to modify protective orders of a position. The final decision on the order modification is made by the EA.
In addition, the members of the CExpert class are instances of the following classes:
- CExpertTrade (for trading)
- CIndicators (for controlling indicators and timeseries involved in the work of the EA).
- CSymbolInfo (for getting information about the instrument)
- CAccountInfo (for obtaining information on the state of the trading account)
- CPositionInfo (for obtaining information about positions)
- COrderInfo (for obtaining information about pending orders)
Hereinafter, under "expert" we mean an instance of CExpert or its subclass.
More details of CExpert and work with it will be described in a separate article.
1. Base Class CExpertSignal
CExpertSignal is the basis of the trading signals generator. For communication with the "outside world", CExpertSignal has a set of public virtual method:
Initialization | Description |
virtual Init | Initialization of the class instance provides synchronization of the module data with the data of the EA |
virtual ValidationSettings | Validation of set parameters |
virtual InitIndicators | Creating and initializing all indicators and timeseries required for operation of the trading signals generator |
Signals of position opening/reversal/closing |
|
virtual CheckOpenLong | Generating the signal of long position opening, defining the levels of entry and placing of protective orders |
virtual CheckOpenShort | Generating the signal of a short position opening, defining the levels of entry and placing of protective orders |
virtual CheckCloseLong | Generating the signal of long position closing, defining the exit level |
virtual CheckCloseShort | Generating the signal of short position closing, defining the exit level |
virtual CheckReverseLong | Generating the signal of long position reversal, defining the levels of reversal and placing of protective orders |
virtual CheckReverseShort | Generating the signal of short position reversal, defining the levels of reversal and placing of protective orders |
Managing pending orders |
|
virtual CheckTrailingOrderLong | Generating the signal of modification of a pending Buy order, defining the new order price |
virtual CheckTrailingOrderShort | Generating the signal of modification of a pending Sell order, defining the new order price |
Description of Methods
1.1. Initialization methods:
The Init() method is called automatically right after a class instance is added to the expert. Method overriding is not required.
virtual bool Init(CSymbolInfo* symbol, ENUM_TIMEFRAMES period, double adjusted_point);
The ValidationSettings() method is called right from the expert after all the parameters are set. You must override the method if there are any setup parameters.
virtual bool ValidationSettings();
The overridden method must return true, if all options are valid (usable). If at least one of the parameters is incorrect, it must return false (further work is impossible).
Base class CExpertSignal has no adjustable parameters, therefore, the base class method always returns true without performing any checks.
The InitIndicators () method implements the creation and initialization of all necessary indicators and timeseries. It is called from the expert after all the parameters are set and their correctness is successful verified. The method should be overridden if the trading signal generator uses at least one indicator or timeseries.
virtual bool InitIndicators(CIndicators* indicators);
Indicators and/or timeseries should be used through the appropriate classes of the Standard Library. Pointers of all indicators and/or timeseries should be added to the collection of indicators of an expert (a pointer to which is passed as a parameter).
The overridden method must return true, if all manipulations with the indicators and/or timeseries were successful (they are suitable for use). If at least one operation with the indicators and/or timeseries failed, the method must return false (further work is impossible).
Base class CExpertSignal does not use indicators or timeseries, therefore, the base class method always returns true, without performing any action.
1.2. Methods of checking the signal of position opening:
The CheckOpenLong() method generates a signal of opening of a long position, defining the entry level and levels of protective orders placing. It is called by an expert to determine whether it is necessary to open a long position. The method must be overridden, if it is expected that a signal of a long position opening will be generated.
virtual bool CheckOpenLong(double& price, double& sl, double& tp, datetime& expiration);
The method should implement the algorithm of checking the condition of a long position opening. If the condition is met, the variables price, sl, tp, and expiration (references to which are passed as parameters) must be assigned appropriate values and the method should return true. If the condition is not fulfilled, the method must return false.
Base class CExpertSignal has no built-in algorithm for generating a signal of a long position opening, so the base class method always returns false.
The CheckOpenShort() method generates a signal of opening of a short position, defining the entry level and levels of protective orders placing. It is called by an expert to determine whether it is necessary to open a short position. The method must be overridden, if it is expected that a signal of a short position opening will be generated.
virtual bool CheckOpenShort(double& price, double& sl, double& tp, datetime& expiration);
The method must implement the algorithm for checking the condition to open a short position. If the condition is satisfied, the variables price, sl, tp, and expiration (references to which are passed as parameters) must be assigned appropriate values and the method should return true. If the condition is not fulfilled, the method must return false.
Base class CExpertSignal has no built-in algorithm for generating a signal of a short position opening, so the base class method always returns false.
1.3. Methods of checking the signal of position closing:
The CheckCloseLong() method generates a signal of closing of a long position, defining the exit level. It is called by an expert to determine whether it is necessary to close a long position. The method must be overridden, if it is expected that a signal of a long position closing will be generated.
virtual bool CheckCloseLong(double& price);
The method must implement the algorithm for checking the condition to close the long position. If the condition is satisfied, the variable price (the reference to which is passed as a parameter) must be assigned the appropriate value and the method should return true. If the condition is not fulfilled, the method must return false.
Base class CExpertSignal has no built-in algorithm for generating a signal of a long position closing, so the base class method always returns false.
The CheckCloseShort() method generates a signal of closing of a short position, defining the exit level. It is called by an expert to determine whether it is necessary to close a short position. The method must be overridden, if it is expected that a signal of a short position closing will be generated.
virtual bool CheckCloseShort(double& price);
The method must implement the algorithm for checking the condition to close a short position. If the condition is satisfied, the variable price (the reference to which is passed as a parameter) must be assigned the appropriate value and the method should return true. If the condition is not fulfilled, the method must return false.
Base class CExpertSignal has no built-in algorithm for generating a signal of a short position closing, so the base class method always returns false.
1.4. Methods of checking the signal of position reversal:
The CheckReverseLong method generates a signal of reversal of a long position, defining the reversal level and levels of protective orders placing. It is called by an expert to determine whether it is necessary to reverse a long position. The method must be overridden, if it is expected that a signal of a long position reversal will be generated.
virtual bool CheckReverseLong(double& price, double& sl, double& tp, datetime& expiration);
The method must implement the algorithm for checking the condition of long position reversal. If the condition is satisfied, the variables price, sl, tp, and expiration (references to which are passed as parameters) must be assigned appropriate values and the method should return true. If the condition is not fulfilled, the method must return false.
In the CExpertSignal base class, the following algorithm for generating a long position reversal signal is implemented:
- Checking for a signal to close a long position.
- Checking for a signal to open a short position.
- If both signals are active (the conditions are met) and the close and open prices match, the variables price, sl, tp, and expiration (references to which are passed as parameters) are assigned the appropriate values and the method returns true.
The CheckReverseShort method generates a signal of reversal of a short position, defining the reversal level and levels of protective orders placing. It is called by an expert to determine whether it is necessary to reverse a short position. The method must be overridden, if it is expected that a signal of a long position reversal will be generated according to the algorithm that differs from the one implemented in the base class.
virtual bool CheckReverseShort(double& price, double& sl, double& tp, datetime& expiration);
The method must implement the algorithm for checking the condition of short position reversal. If the condition is satisfied, the variables price, sl, tp, and expiration (references to which are passed as parameters) must be assigned appropriate values and the method should return true. If the condition is not fulfilled, the method must return false.
In the CExpertSignal base class, the following algorithm for generating a short position reversal signal is implemented:
- Checking for a signal to close a short position.
- Checking for a signal to open a long position.
- If both signals are active (the conditions are met) and the close and open prices match, the variables price, sl, tp, and expiration (references to which are passed as parameters) are assigned the appropriate values and the method returns true.
If the condition is not fulfilled, the method returns false.
1.5. Methods of checking the signal of pending order modification:
The CheckTrailingOrderLong() method generates the signal of modification of a pending Buy order, defining a new order price. It is called by an expert to determine whether it is necessary to modify a pending Buy order. The method must be overridden, if it is expected that a signal of modification of a pending Buy order will be generated.
virtual bool CheckTrailingOrderLong(COrderInfo* order, double& price)
The method must implement the algorithm for checking the condition of modification of a pending Buy order. If the condition is satisfied, the variable price (the reference to which is passed as a parameter) must be assigned the appropriate value and the method should return true. If the condition is not fulfilled, the method must return false.
Base class CExpertSignal has no built-in algorithm for generating a signal of modification of a pending Buy order, so the base class method always returns false.
The CheckTrailingOrderShort() method generates the signal of modification of a pending Sell order, defining a new order price. It is called by an expert to determine whether it is necessary to modify a pending Sell order. The method must be overridden, if it is expected that a signal of modification of a pending Sell order will be generated.
virtual bool CheckTrailingOrderShort(COrderInfo* order, double& price)
The method must implement the algorithm for checking the condition of modification of a pending Sell order. If the condition is satisfied, the variable price (the reference to which is passed as a parameter) must be assigned the appropriate value and the method should return true. If the condition is not fulfilled, the method must return false.
Base class CExpertSignal has no built-in algorithm for generating a signal of modification of a pending Sell order, so the base class method always returns false.
2. Develop Your Own Generator of Trading Signals
Now, after we have reviewed the structure of the CExpertSignal base class, you can start creating your own trading signals generator.
As mentioned above, the CExpertSignal class is a set of public virtual "ropes" - methods, using which the expert may know the opinion of the trading signals generator about entering the market in one direction or another.
Therefore, our primary goal is to create our own class of trading signals generator, deriving it from the CExpertSignal class and overriding the appropriate virtual methods, implementing the required algorithms.
Our second problem (which is not less important) - to make our class "visible" to MQL5 Wizard. But, first things first.
2.1. Creating the class of the trading signals generator
Let's begin.
First, we create (for example, using the same MQL5 Wizard) an include file with the mqh extension.
In the File menu select "Create" (or press Ctrl+N key combination) and indicate the creation of an included file:
Figure 2. Create an include file using MQL5 Wizard
It should be noted that in order for the file to be then "detected" by MQL5 Wizard as a signal generator, it should be created in the folder Include\Expert\Signal\.
In order not to trash in Standard Library, create our own folder Include\Expert\Signal\MySignals, in which we create file SampleSignal.mqh, specifying these parameters in MQL5 Wizard:
Figure 3. Setting the location of the include file
As a result of MQL5 Wizard operation we have the following pattern:
//+------------------------------------------------------------------+ //| SampleSignal.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| defines | //+------------------------------------------------------------------+ // #define MacrosHello "Hello, world!" // #define MacrosYear 2010 //+------------------------------------------------------------------+ //| DLL imports | //+------------------------------------------------------------------+ // #import "user32.dll" // int SendMessageA(int hWnd,int Msg,int wParam,int lParam); // #import "my_expert.dll" // int ExpertRecalculate(int wParam,int lParam); // #import //+------------------------------------------------------------------+ //| EX5 imports | //+------------------------------------------------------------------+ // #import "stdlib.ex5" // string ErrorDescription(int error_code); // #import //+------------------------------------------------------------------+
The following is only "manual" work. Remove the unnecessary parts and add what is required (include file ExpertSignal.mqh of the Standard Library and a class description which is now empty).
//+------------------------------------------------------------------+ //| SampleSignal.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| include files | //+------------------------------------------------------------------+ #include <Expert\ExpertSignal.mqh> //+------------------------------------------------------------------+ //| The CSampleSignal class. | //| Purpose: Class of trading signal generator. | //| It is derived from the CExpertSignal class. | //+------------------------------------------------------------------+ class CSampleSignal : public CExpertSignal { }; //+------------------------------------------------------------------+
Now, it is necessary to choose the algorithms.
As a basis for our trading signals generator, we take the widespread model "price crosses the moving average". But we make one more assumption: "After crossing the moving average, the price moves back, and only then goes in the right direction." Reflect this in our file.
Generally, when you are writing something, do not skimp on the comments. After some time, reading a carefully commented code will be so comfortable.
//+------------------------------------------------------------------+ //| SampleSignal.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| include files | //+------------------------------------------------------------------+ #include <Expert\ExpertSignal.mqh> //+------------------------------------------------------------------+ //| Class CSampleSignal. | //| Purpose: Class of trading signal generator when price | //| crosses moving average, | //| entering on the subsequent back movement. | //| It is derived from the CExpertSignal class. | //+------------------------------------------------------------------+ class CSampleSignal : public CExpertSignal { }; //+------------------------------------------------------------------+
Now let's define what data is needed for making decisions about the generation of trading signals. In our case, this is the open price and the close price of the previous bar, and the value of the moving average on the same previous bar.
To get access to these data, we use the standard library classes CiOpen, CiClose and CiMA. We'll discuss indicators and timeseries later.
In the meantime, let's define a list of settings for our generator. First, we need to set up the moving average. These parameters include the period, the shift along the time axis, the averaging method and the object of averaging. Secondly, we need to set up the entry level and the levels of placing of protective orders, and the lifetime of a pending order, because we are going to work with pending orders.
All settings of the generator will be stored in protected data members of the class. Access to the settings will be implemented through appropriate public methods.
Let's include these changes in our file:
//+------------------------------------------------------------------+ //| SampleSignal.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| include files | //+------------------------------------------------------------------+ #include <Expert\ExpertSignal.mqh> //+------------------------------------------------------------------+ //| The CSampleSignal class. | //| Purpose: Class of trading signal generator when price | //| crosses moving average, | //| entering on the subsequent back movement. | //| It is derived from the CExpertSignal class. | //+------------------------------------------------------------------+ class CSampleSignal : public CExpertSignal { protected: //--- Setup parameters int m_period_ma; // averaging period of the MA int m_shift_ma; // shift of the MA along the time axis ENUM_MA_METHOD m_method_ma; // averaging method of the MA ENUM_APPLIED_PRICE m_applied_ma; // averaging object of the MA double m_limit; // level to place a pending order relative to the MA double m_stop_loss; // level to place a stop loss order relative to the open price double m_take_profit; // level to place a take profit order relative to the open price int m_expiration; // lifetime of a pending order in bars public: //--- Methods to set the parameters void PeriodMA(int value) { m_period_ma=value; } void ShiftMA(int value) { m_shift_ma=value; } void MethodMA(ENUM_MA_METHOD value) { m_method_ma=value; } void AppliedMA(ENUM_APPLIED_PRICE value) { m_applied_ma=value; } void Limit(double value) { m_limit=value; } void StopLoss(double value) { m_stop_loss=value; } void TakeProfit(double value) { m_take_profit=value; } void Expiration(int value) { m_expiration=value; } }; //+------------------------------------------------------------------+
Since we are using protected data members, we need to add a class constructor, in which we will initialize these data by default values.
To check the parameters, let's override the virtual method ValidationSettings according to the description of the base class.
Description of the class:
class CSampleSignal : public CExpertSignal { protected: //--- Setup parameters int m_period_ma; // averaging period of the MA int m_shift_ma; // shift of the MA along the time axis ENUM_MA_METHOD m_method_ma; // averaging method of the MA ENUM_APPLIED_PRICE m_applied_ma; // averaging object of the MA double m_limit; // level to place a pending order relative to the MA double m_stop_loss; // level to place a stop loss order relative to the open price double m_take_profit; // level to place a take profit order relative to the open price int m_expiration; // lifetime of a pending order in bars public: CSampleSignal(); //--- Methods to set the parameters void PeriodMA(int value) { m_period_ma=value; } void ShiftMA(int value) { m_shift_ma=value; } void MethodMA(ENUM_MA_METHOD value) { m_method_ma=value; } void AppliedMA(ENUM_APPLIED_PRICE value) { m_applied_ma=value; } void Limit(double value) { m_limit=value; } void StopLoss(double value) { m_stop_loss=value; } void TakeProfit(double value) { m_take_profit=value; } void Expiration(int value) { m_expiration=value; } //--- Methods to validate the parameters virtual bool ValidationSettings(); };
Implementation of the ValidationSettings() method:
//+------------------------------------------------------------------+ //| Validation of the setup parameters. | //| INPUT: No. | //| OUTPUT: true if the settings are correct, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::ValidationSettings() { //--- Validation of parameters if(m_period_ma<=0) { printf(__FUNCTION__+": the MA period must be greater than zero"); return(false); } //--- ok return(true); } //+------------------------------------------------------------------+
Now, when we've finished the bulk of the preparatory work, we'll talk more about indicators and timeseries.
Indicators and timeseries are the main source of information for decision-making (you can certainly use the coin toss, or phases of the moon, but they are quite hard to formalize).
As we have already defined above, to make decisions, we need the following information: the open price of the previous bar, the close price of the previous bar, and the value of the moving average on the same previous bar.
To gain access to these data, we will use the following classes of the Standard Library:
- CiOpen - to access the open price of the previous bar,
- CiClose - to access the close price of the previous bar,
- CiMA - to access the value of the moving average on the previous bar.
You may ask: "Why use the indicator or timeseries, " wrapped " in a class, in order to get a single number?"
There is a hidden meaning, which we are going to reveal now.
How to use the data of an indicator or timeseries?
First, we need to create an indicator.
Second, we need to copy the necessary amount of data into an intermediate buffer.
Third, we need to check whether copying is complete.
Only after these steps, you can use the data.
Using the classes of the Standard Library, you avoid the necessity of creating an indicator, of caring about the availability of intermediate buffers and about data loading or release of a handle. The object of an appropriate class will do that for you. All the required indicators will be generated by our signal generator during the initialization stage, and all indicators will be provided with the necessary temporary buffer. And besides, once we add an indicator or timeseries object in the collection (the object of a special class), you can stop caring about the relevance of the data (the data will be updated automatically by the expert).
Let's override the virtual method InitIndicators (according to the description of the base class).
Description of the class:
class CSampleSignal : public CExpertSignal { protected: CiMA m_MA; // object to access the values om the moving average CiOpen m_open; // object to access the bar open prices CiClose m_close; // object to access the bar close prices //--- Setup parameters int m_period_ma; // averaging period of the MA int m_shift_ma; // shift of the MA along the time axis ENUM_MA_METHOD m_method_ma; // averaging method of the MA ENUM_APPLIED_PRICE m_applied_ma; // averaging object of the MA double m_limit; // level to place a pending order relative to the MA double m_stop_loss; // level to place a stop loss order relative to the open price double m_take_profit; // level to place a take profit order relative to the open price int m_expiration; // lifetime of a pending order in bars public: CSampleSignal(); //--- Methods to set the parameters void PeriodMA(int value) { m_period_ma=value; } void ShiftMA(int value) { m_shift_ma=value; } void MethodMA(ENUM_MA_METHOD value) { m_method_ma=value; } void AppliedMA(ENUM_APPLIED_PRICE value) { m_applied_ma=value; } void Limit(double value) { m_limit=value; } void StopLoss(double value) { m_stop_loss=value; } void TakeProfit(double value) { m_take_profit=value; } void Expiration(int value) { m_expiration=value; } //--- Method to validate the parameters virtual bool ValidationSettings(); //--- Method to validate the parameters virtual bool InitIndicators(CIndicators* indicators); protected: //--- Object initialization method bool InitMA(CIndicators* indicators); bool InitOpen(CIndicators* indicators); bool InitClose(CIndicators* indicators); //--- Methods to access object data double MA(int index) { return(m_MA.Main(index)); } double Open(int index) { return(m_open.GetData(index)); } double Close(int index) { return(m_close.GetData(index)); } };
Implementation of methods InitIndicators, InitMA, InitOpen, InitClose:
//+------------------------------------------------------------------+ //| Initialization of indicators and timeseries. | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitIndicators(CIndicators* indicators) { //--- Validation of the pointer if(indicators==NULL) return(false); //--- Initialization of the moving average if(!InitMA(indicators)) return(false); //--- Initialization of the timeseries of open prices if(!InitOpen(indicators)) return(false); //--- Initialization of the timeseries of close prices if(!InitClose(indicators)) return(false); //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Initialization of the moving average | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitMA(CIndicators* indicators) { //--- Initialization of the MA object if(!m_MA.Create(m_symbol.Name(),m_period,m_period_ma,m_shift_ma,m_method_ma,m_applied_ma)) { printf(__FUNCTION__+": object initialization error"); return(false); } m_MA.BufferResize(3+m_shift_ma); //--- Adding an object to the collection if(!indicators.Add(GetPointer(m_MA))) { printf(__FUNCTION__+": object adding error"); return(false); } //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Initialization of the timeseries of open prices. | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitOpen(CIndicators* indicators) { //--- Initialization of the timeseries object if(!m_open.Create(m_symbol.Name(),m_period)) { printf(__FUNCTION__+": object initialization error"); return(false); } //--- Adding an object to the collection if(!indicators.Add(GetPointer(m_open))) { printf(__FUNCTION__+": object adding error"); return(false); } //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Initialization of the timeseries of close prices. | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitClose(CIndicators* indicators) { //--- Initialization of the timeseries object if(!m_close.Create(m_symbol.Name(),m_period)) { printf(__FUNCTION__+": object initialization error"); return(false); } //--- Adding an object to the collection if(!indicators.Add(GetPointer(m_close))) { printf(__FUNCTION__+": object adding error"); return(false); } //--- Successful completion return(true); } //+------------------------------------------------------------------+
All the preparatory works are completed. As you can see, our class has grown significantly.
But now we are ready to generate trading signals.
Figure 4. Trading signals for the price crossing the moving average
Let's consider our algorithms again in more detail.
1. The signal to buy appears when the following conditions have been fulfilled on the previous bar:
- the bar open price is less than the value of the moving average,
- the bar close price is greater than the value of the moving average,
- the moving average is increasing.
In this case, we offer to place a pending Buy order with the parameters defined by the settings. For this purpose, we override the virtual method CheckOpenLong and fill it with the corresponding functional.
2. The signal to sell appears when the following conditions have been fulfilled on the previous bar:
- the bar open price is greater than the value of the moving average,
- the bar close price is less than the value of the moving average,
- the moving average is decreasing.
In this case, we offer to place a pending Sell order with the parameters defined by the settings. For this purpose, we override the virtual method CheckOpenShort and fill it with the corresponding functional.
3. We will not generate signals to close positions. Let the positions be closed by Stop Loss/Take Profit ордерам.
Accordingly, we will not override virtual methods CheckCloseLong and CheckCloseShort.
4. We will propose the modification of a pending order along the moving average at the "distance" specified by the settings.
For this purpose, we override the virtual methods CheckTrailingOrderLong and CheckTrailingOrderShort, filling them with corresponding functional.
Description of the class:
class CSampleSignal : public CExpertSignal { protected: CiMA m_MA; // object to access the values of the moving average CiOpen m_open; // object to access the bar open prices CiClose m_close; // object to access the bar close prices //--- Setup parameters int m_period_ma; // averaging period of the MA int m_shift_ma; // shift of the MA along the time axis ENUM_MA_METHOD m_method_ma; // averaging method of the MA ENUM_APPLIED_PRICE m_applied_ma; // averaging object of the MA double m_limit; // level to place a pending order relative to the MA double m_stop_loss; // level to place a stop loss order relative to the open price double m_take_profit; // level to place a take profit order relative to the open price int m_expiration; // lifetime of a pending order in bars public: CSampleSignal(); //--- Methods to set the parameters void PeriodMA(int value) { m_period_ma=value; } void ShiftMA(int value) { m_shift_ma=value; } void MethodMA(ENUM_MA_METHOD value) { m_method_ma=value; } void AppliedMA(ENUM_APPLIED_PRICE value) { m_applied_ma=value; } void Limit(double value) { m_limit=value; } void StopLoss(double value) { m_stop_loss=value; } void TakeProfit(double value) { m_take_profit=value; } void Expiration(int value) { m_expiration=value; } //--- Method to validate the parameters virtual bool ValidationSettings(); //--- Method to validate the parameters virtual bool InitIndicators(CIndicators* indicators); //--- Methods to generate signals to enter the market virtual bool CheckOpenLong(double& price,double& sl,double& tp,datetime& expiration); virtual bool CheckOpenShort(double& price,double& sl,double& tp,datetime& expiration); //--- Methods to generate signals of pending order modification virtual bool CheckTrailingOrderLong(COrderInfo* order,double& price); virtual bool CheckTrailingOrderShort(COrderInfo* order,double& price); protected: //--- Object initialization method bool InitMA(CIndicators* indicators); bool InitOpen(CIndicators* indicators); bool InitClose(CIndicators* indicators); //--- Methods to access object data double MA(int index) { return(m_MA.Main(index)); } double Open(int index) { return(m_open.GetData(index)); } double Close(int index) { return(m_close.GetData(index)); } };
Implementation of methods CheckOpenLong, CheckOpenShort, CheckTrailingOrderLong, CheckTrailingOrderShort:
//+------------------------------------------------------------------+ //| Check whether a Buy condition is fulfilled | //| INPUT: price - variable for open price | //| sl - variable for stop loss price, | //| tp - variable for take profit price | //| expiration - variable for expiration time. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckOpenLong(double& price,double& sl,double& tp,datetime& expiration) { //--- Preparing the data double spread=m_symbol.Ask()-m_symbol.Bid(); double ma =MA(1); double unit =PriceLevelUnit(); //--- Checking the condition if(Open(1)<ma && Close(1)>ma && ma>MA(2)) { price=m_symbol.NormalizePrice(ma-m_limit*unit+spread); sl =m_symbol.NormalizePrice(price-m_stop_loss*unit); tp =m_symbol.NormalizePrice(price+m_take_profit*unit); expiration+=m_expiration*PeriodSeconds(m_period); //--- Condition is fulfilled return(true); } //--- Condition is not fulfilled return(false); } //+------------------------------------------------------------------+ //| Check whether a Sell condition is fulfilled. | //| INPUT: price - variable for open price, | //| sl - variable for stop loss, | //| tp - variable for take profit | //| expiration - variable for expiration time. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckOpenShort(double& price,double& sl,double& tp,datetime& expiration) { //--- Preparing the data double ma =MA(1); double unit=PriceLevelUnit(); //--- Checking the condition if(Open(1)>ma && Close(1)<ma && ma<MA(2)) { price=m_symbol.NormalizePrice(ma+m_limit*unit); sl =m_symbol.NormalizePrice(price+m_stop_loss*unit); tp =m_symbol.NormalizePrice(price-m_take_profit*unit); expiration+=m_expiration*PeriodSeconds(m_period); //--- Condition is fulfilled return(true); } //--- Condition is not fulfilled return(false); } //+------------------------------------------------------------------+ //| Check whether the condition of modification | //| of a Buy order is fulfilled. | //| INPUT: order - pointer at the object-order, | //| price - a variable for the new open price. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckTrailingOrderLong(COrderInfo* order,double& price) { //--- Checking the pointer if(order==NULL) return(false); //--- Preparing the data double spread =m_symbol.Ask()-m_symbol.Bid(); double ma =MA(1); double unit =PriceLevelUnit(); double new_price=m_symbol.NormalizePrice(ma-m_limit*unit+spread); //--- Checking the condition if(order.PriceOpen()==new_price) return(false); price=new_price; //--- Condition is fulfilled return(true); } //+------------------------------------------------------------------+ //| Check whether the condition of modification | //| of a Sell order is fulfilled. | //| INPUT: order - pointer at the object-order, | //| price - a variable for the new open price. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckTrailingOrderShort(COrderInfo* order,double& price) { //--- Checking the pointer if(order==NULL) return(false); //--- Preparing the data double ma =MA(1); double unit=PriceLevelUnit(); double new_price=m_symbol.NormalizePrice(ma+m_limit*unit); //--- Checking the condition if(order.PriceOpen()==new_price) return(false); price=new_price; //--- Condition is fulfilled return(true); } //+------------------------------------------------------------------+
So we've solved the first problem. The above code is a source code of the class of trading signals generator that meets our main task.
2.2. Preparing a description of the created class of the trading signals for MQL5 Wizard
We now turn to solving the second problem. Our signal should be "recognized" by the generator of trading strategies MQL5 Wizard.
We've done the first necessary condition: we've placed the file where it will be "found" by the MQL5 Wizard. But this is not enough. The MQL5 Wizard must not only "find" the file, but also "recognize" it. To do this we must add to the original text the class descriptor for the MQL5 Wizard.
A class descriptor is a block of comments composed according to certain rules.
Let's consider these rules.
1. The block of comments should start with the following lines:
// wizard description start //+------------------------------------------------------------------+ //| Description of the class |
2. The next line is a text descriptor (what we will see in the MQL5 Wizard when choosing the signal) in the format "//| Title=<Text> |". If the text is too big for one line, you can add one more line (but not more) after it.
In our case, we have the following:
//| Title=Signal on the crossing of a price and the MA | //| entering on its back movement |
3. Then comes a line with the class type specified in the format "//| Type=<Type> |". The <Type> field must have the Signal value (in addition to signals, the MQL5 Wizard knows other types of classes).
Write:
//| Type=Signal |
4. The following line in the format "//| Name=<Name> |" is the short name of the signal (it is used by the MQL5 Wizard for generating the names of the global variables of the expert).
We get the following:
//| Name=Sample |
5. The name of a class is an important element of the description. In the line with the format "//| Class=<ClassNameа> |", the <ClassName> parameter must match with the name of our class:
//| Class=CSampleSignal |
6. We do not fill in this line, but it must be present (this is a link to the language reference section):
//| Page= |
7. Further, there are descriptions of the signal setup parameters.
This is a set of rows (the number of rows is equal to the number of parameters).
The format of each line is "//| Parameter=<NameOfMethod>,<TypeOfParameter>,<DefaultValue> |".
Here is our set of parameters:
//| Parameter=PeriodMA,int,12 | //| Parameter=ShiftMA,int,0 | //| Parameter=MethodMA,ENUM_MA_METHOD,MODE_EMA | //| Parameter=AppliedMA,ENUM_APPLIED_PRICE,PRICE_CLOSE | //| Parameter=Limit,double,0.0 | //| Parameter=StopLoss,double,50.0 | //| Parameter=TakeProfit,double,50.0 | //| Parameter=Expiration,int,10 |
8. The block of comment should end with the following lines:
//+------------------------------------------------------------------+ // wizard description end
Let's add the descriptor to the source code.
//+------------------------------------------------------------------+ //| SampleSignal.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| include files | //+------------------------------------------------------------------+ #include <Expert\ExpertSignal.mqh> // wizard description start //+------------------------------------------------------------------+ //| Description of the class | //| Title=Signal on crossing of the price and the MA | //| entering on the back movement | //| Type=Signal | //| Name=Sample | //| Class=CSampleSignal | //| Page= | //| Parameter=PeriodMA,int,12 | //| Parameter=ShiftMA,int,0 | //| Parameter=MethodMA,ENUM_MA_METHOD,MODE_EMA | //| Parameter=AppliedMA,ENUM_APPLIED_PRICE,PRICE_CLOSE | //| Parameter=Limit,double,0.0 | //| Parameter=StopLoss,double,50.0 | //| Parameter=TakeProfit,double,50.0 | //| Parameter=Expiration,int,10 | //+------------------------------------------------------------------+ // wizard description end //+------------------------------------------------------------------+ //| CSampleSignal class. | //| Purpose: Class of trading signal generator when price | //| crosses moving average, | //| entering on the subsequent back movement. | //| It is derived from the CExpertSignal class. | //+------------------------------------------------------------------+ class CSampleSignal : public CExpertSignal { protected: CiMA m_MA; // object to access the values of the moving average CiOpen m_open; // object to access the bar open prices CiClose m_close; // object to access the bar close prices //--- Setup parameters int m_period_ma; // averaging period of the MA int m_shift_ma; // shift of the MA along the time axis ENUM_MA_METHOD m_method_ma; // averaging method of the MA ENUM_APPLIED_PRICE m_applied_ma; // averaging object of the MA double m_limit; // level to place a pending order relative to the MA double m_stop_loss; // level to place a stop loss order relative to the open price double m_take_profit; // level to place a take profit order relative to the open price int m_expiration; // lifetime of a pending order in bars public: CSampleSignal(); //--- Methods to set the parameters void PeriodMA(int value) { m_period_ma=value; } void ShiftMA(int value) { m_shift_ma=value; } void MethodMA(ENUM_MA_METHOD value) { m_method_ma=value; } void AppliedMA(ENUM_APPLIED_PRICE value) { m_applied_ma=value; } void Limit(double value) { m_limit=value; } void StopLoss(double value) { m_stop_loss=value; } void TakeProfit(double value) { m_take_profit=value; } void Expiration(int value) { m_expiration=value; } //---Method to validate the parameters virtual bool ValidationSettings(); //--- Method to validate the parameters virtual bool InitIndicators(CIndicators* indicators); //--- Methods to generate signals to enter the market virtual bool CheckOpenLong(double& price,double& sl,double& tp,datetime& expiration); virtual bool CheckOpenShort(double& price,double& sl,double& tp,datetime& expiration); //--- Methods to generate signals of pending order modification virtual bool CheckTrailingOrderLong(COrderInfo* order,double& price); virtual bool CheckTrailingOrderShort(COrderInfo* order,double& price); protected: //--- Object initialization method bool InitMA(CIndicators* indicators); bool InitOpen(CIndicators* indicators); bool InitClose(CIndicators* indicators); //--- Methods to access object data double MA(int index) { return(m_MA.Main(index)); } double Open(int index) { return(m_open.GetData(index)); } double Close(int index) { return(m_close.GetData(index)); } }; //+------------------------------------------------------------------+ //| CSampleSignal Constructor. | //| INPUT: No. | //| OUTPUT: No. | //| REMARK: No. | //+------------------------------------------------------------------+ void CSampleSignal::CSampleSignal() { //--- Setting the default values m_period_ma =12; m_shift_ma =0; m_method_ma =MODE_EMA; m_applied_ma =PRICE_CLOSE; m_limit =0.0; m_stop_loss =50.0; m_take_profit=50.0; m_expiration =10; } //+------------------------------------------------------------------+ //| Validation of parameters. | //| INPUT: No. | //| OUTPUT: true if the settings are correct, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::ValidationSettings() { //--- Validation of parameters if(m_period_ma<=0) { printf(__FUNCTION__+": the MA period must be greater than zero"); return(false); } //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Initialization of indicators and timeseries. | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitIndicators(CIndicators* indicators) { //--- Validation of the pointer if(indicators==NULL) return(false); //--- Initialization of the moving average if(!InitMA(indicators)) return(false); //--- Initialization of the timeseries of open prices if(!InitOpen(indicators)) return(false); //--- Initialization of the timeseries of close prices if(!InitClose(indicators)) return(false); //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Initialization of the moving average | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitMA(CIndicators* indicators) { //--- Initialization of the MA object if(!m_MA.Create(m_symbol.Name(),m_period,m_period_ma,m_shift_ma,m_method_ma,m_applied_ma)) { printf(__FUNCTION__+": object initialization error"); return(false); } m_MA.BufferResize(3+m_shift_ma); //--- Adding an object to the collection if(!indicators.Add(GetPointer(m_MA))) { printf(__FUNCTION__+": object adding error"); return(false); } //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Initialization of the timeseries of open prices. | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitOpen(CIndicators* indicators) { //--- Initialization of the timeseries object if(!m_open.Create(m_symbol.Name(),m_period)) { printf(__FUNCTION__+": object initialization error"); return(false); } //--- Adding an object to the collection if(!indicators.Add(GetPointer(m_open))) { printf(__FUNCTION__+": object adding error"); return(false); } //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Initialization of the timeseries of close prices. | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitClose(CIndicators* indicators) { //--- Initialization of the timeseries object if(!m_close.Create(m_symbol.Name(),m_period)) { printf(__FUNCTION__+": object initialization error"); return(false); } //--- Adding an object to the collection if(!indicators.Add(GetPointer(m_close))) { printf(__FUNCTION__+": object adding error"); return(false); } //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Check whether a Buy condition is fulfilled | //| INPUT: price - variable for open price | //| sl - variable for stop loss price, | //| tp - variable for take profit price | //| expiration - variable for expiration time. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckOpenLong(double& price,double& sl,double& tp,datetime& expiration) { //--- Preparing the data double spread=m_symbol.Ask()-m_symbol.Bid(); double ma =MA(1); double unit =PriceLevelUnit(); //--- Checking the condition if(Open(1)<ma && Close(1)>ma && ma>MA(2)) { price=m_symbol.NormalizePrice(ma-m_limit*unit+spread); sl =m_symbol.NormalizePrice(price-m_stop_loss*unit); tp =m_symbol.NormalizePrice(price+m_take_profit*unit); expiration+=m_expiration*PeriodSeconds(m_period); //--- Condition is fulfilled return(true); } //--- Condition is not fulfilled return(false); } //+------------------------------------------------------------------+ //| Check whether a Sell condition is fulfilled. | //| INPUT: price - variable for open price, | //| sl - variable for stop loss, | //| tp - variable for take profit | //| expiration - variable for expiration time. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckOpenShort(double& price,double& sl,double& tp,datetime& expiration) { //--- Preparing the data double ma =MA(1); double unit=PriceLevelUnit(); //--- Checking the condition if(Open(1)>ma && Close(1)<ma && ma<MA(2)) { price=m_symbol.NormalizePrice(ma+m_limit*unit); sl =m_symbol.NormalizePrice(price+m_stop_loss*unit); tp =m_symbol.NormalizePrice(price-m_take_profit*unit); expiration+=m_expiration*PeriodSeconds(m_period); //--- Condition is fulfilled return(true); } //--- Condition is not fulfilled return(false); } //+------------------------------------------------------------------+ //| Check whether the condition of modification | //| of a Buy order is fulfilled. | //| INPUT: order - pointer at the object-order, | //| price - a variable for the new open price. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckTrailingOrderLong(COrderInfo* order,double& price) { //--- Checking the pointer if(order==NULL) return(false); //--- Preparing the data double spread =m_symbol.Ask()-m_symbol.Bid(); double ma =MA(1); double unit =PriceLevelUnit(); double new_price=m_symbol.NormalizePrice(ma-m_limit*unit+spread); //--- Checking the condition if(order.PriceOpen()==new_price) return(false); price=new_price; //--- Condition is fulfilled return(true); } //+------------------------------------------------------------------+ //| Check whether the condition of modification | //| of a Sell order is fulfilled. | //| INPUT: order - pointer at the object-order, | //| price - a variable for the new open price. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckTrailingOrderShort(COrderInfo* order,double& price) { //--- Checking the pointer if(order==NULL) return(false); //--- Preparing the data double ma =MA(1); double unit=PriceLevelUnit(); double new_price=m_symbol.NormalizePrice(ma+m_limit*unit); //--- Checking the condition if(order.PriceOpen()==new_price) return(false); price=new_price; //--- Condition is fulfilled return(true); } //+------------------------------------------------------------------+
Well, that's all. The signal is ready to use.
For the generator trading strategies MQL5 Wizard to be able to use our signal, we should restart MetaEditor (MQL5 Wizard scans the folder Include\Expert only at boot).
After restarting MetaEditor, the created module of trading signals can be used in the MQL5 Wizard:
Figure 5. The created generator of trading signals in the MQL5 Wizard
The input parameters specified in the section of description of the parameters of the trading signals generator are now available:
Figure 6. Input parameters of the created generator of trading signals in the MQL5 Wizard
The best values of the input parameters of the implemented trading strategy can be found using the Strategy Tester of the MetaTrader 5 terminal.
Conclusion
The generator of trading strategies of the MQL5 Wizard greatly simplifies the testing of trading ideas. The code of the generated expert is based on the classes of trading strategies of the Standard Library, which are used for creating certain implementations of trading signal classes, money and risk management classes and position support classes.
The article discusses how to write your own class of trading signals with the implementation of signals on the crossing of the price and the moving average, and how to include it to the generator of trading strategies of the MQL5 Wizard, as well as describes the structure and format of the description of the generated class for the MQL5 Wizard.
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/226





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
If it is possible to rewrite the exact, complete and executable code of this program and correct its bugs and put it here
Here you go!
Cheers, Zarik
Hi Trolley,
i have the same problem. Have you found any solution or idea on it?
thanks
The problem is caused by MetaTrader Wizard itself. Somehow it is not creating the signal object using the SampleSignal class but using the CExpertSignal! Look for the:
CExpertSignal *signal = new CExpertSignal;
and change it with:
CSampleSignal *signal = new CSampleSignal;
Cheers, Zarik
Hi,
When I compiled the code, I got three warnings
declaration of 'm_open' hides member samplesignal.mqh 42 23
declaration of 'm_close' hides member samplesignal.mqh 43 23
m_open and m_close were defined in ExpertBase.mqh but with different type.
m_expiratin was defined in ExpertSignal.mqh.
Comment out above three lines. Warnings are gone.
George
The reason for the warning is because those properties are already defined in the ExpertBase (m_open and m_close) and ExpertSignal (m_expiration). Rename the properties names to something different and the issue will be solved.
Cheers, Zarik
Important Note:
In order MetaEditor Wizard to be able to find the signal file (samplesignal.mqh file), The class discriptor should be as following:
// wizard description start
//+------------------------------------------------------------------+
//| Description of the class |
//| Title=Signal on crossing of the price and the MA |
//| entering on the back movement |
//| Type=SignalAdvanced |
//| Name=Sample |
//| Class=CSampleSignal |
//| Page= |
//| Parameter=PeriodMA,int,12 |
//| Parameter=ShiftMA,int,0 |
//| Parameter=MethodMA,ENUM_MA_METHOD,MODE_EMA |
//| Parameter=AppliedMA,ENUM_APPLIED_PRICE,PRICE_CLOSE |
//| Parameter=Limit,double,0.0 |
//| Parameter=StopLoss,double,50.0 |
//| Parameter=TakeProfit,double,50.0 |
//| Parameter=Expiration,int,10 |
//+------------------------------------------------------------------+
// wizard description end
//+------------------------------------------------------------------+
The Type should be SignalAdvanced (which is shown by red color), so change signal to SignalAdvanced in your source code and then MetaEditor Wizard will be able to find the signal file (samplesignal.mqh file).
and finlly metaquotes should edit this article.