Universal Expert Advisor: Integration with Standard MetaTrader Modules of Signals (Part 7)
Table of Contents
- Introduction
- An overview of classes used by the strategy generator
- An overview of the signal module, the concept of a pattern
- A module of signals. First use
- Writing the first strategy based on CSignalMACD
- An adapter of signals
- Combining multiple signals in a trading strategy code
- Conclusion
Introduction
In previous articles, we discussed mechanisms that would make the process of trading algorithm creation efficient and simple — in particular, we have created the CStartegy algorithm. The project is constantly evolving for more than six months. During this time, new modules have been added to CStrategy to make the trading process more efficient and secure in terms of technical execution of trading operations. However, the engine still lacked one important feature. Despite the fact that CStrategy is a strictly object-oriented application, it still remained a "thing in itself". The object-oriented approach postulates openness and modularity of the code. In fact, the code base should be based on common general classes. Especially it concerns the trading model and the signal formation model. The CStrategy trading logic is well based on the standard CTrade trading module, but things are not so good with the database of signals in CStrategy. Simply put, CStrategy did not contain any module responsible for generating trading signals. With the previous versions, any user had to re-write the Expert Advisor logic from scratch, even if the required signal were available in the standard MetaTrader 5 package. Therefore, it was decided to add to the new version of CStrategy mechanisms for working with the database of standard MetaTrader 5 signals. In this article, I will explain how to integrate CStrategy with one of the standard signal modules, and will show you how to create your own strategy using exclusively the ready-made algorithms.
An Overview of Classes Used by the Strategy Generator
A set of various classes included into the standard MetaTrader 5 package are used for generating automated strategies using the MQL Wizard. The classes are available as mqh files located in the appropriate subfolders of the MQL5\Include directory. These classes (or modules) can be conventionally divided into several categories. Here they are.
- Basic classes for organizing data (CObject, CArrayDouble, CArrayObj and others). All other modules for trading are constructed based on these classes.
- Classes for accessing indicator buffers (CDoubleBuffer, CIndicatorBuffer). Accordingly, they are used for working with indicators.
- Indicator classes and timeseries classes based on the common CSeries class.
- The basic expert class CBaseExpert and CExpert derived from it. All auxiliary modules are based on CBaseExpert — for example, the module for calculating capital and trailing stop control module. CExpert is the basis of all custom Expert Advisors.
- Signal modules based on CExpertSignal, which in turn is based on CEpertBase. The modules of signals generate trading Buy and Sell signals. They use the classes of indicators, on which the signals are based.
- The CExpertTrade trading module. It is based on the CTrade class, and it provides access to the execution of trade operations.
The below diagram shows the general scheme of vertical inheritance of classes used in the process of automatic generation of strategies:
Fig. 1. Inheritance of standard classes of the strategy generator
The figure only shows basic and some derived classes. The scheme does not feature all indicators inherited from CIndicators. Separate trailing, money management and signal modules are not included into the scheme. Instead, only the basic relationships are outlined. One of the featured groups is of interest to us: the signal classes CExpertSignal and its child classes. In Figure 1, the group is highlighted by a green dotted line.
In addition to vertical links, classes form a complex system of inclusion (horizontal links). For example, signal modules actively use indicator classes which, in turn, use indicator buffers. Various sets are part of each other. For example, money management modules are at the same time trading experts (at least at the level of CExpertBase), although it is not obvious whether these modules have anything to do with the Expert Advisor.
As a rule, complex initialization chains are required in order to create the objects of these classes. For example, in order to create a signal object, such as CSignalMacd, we should initialize the signal, initialize appropriate indicators on which it is based, as well as initialize required timeseries (child classes of CPriceSeries) required for signal operation. Since the object can be initialized by complex objects, they also require initialization (like timeseries). Thus, the question of initialization is one of the most complicated parts of the described library.
Let's analyze the following example. Suppose we need to initialize the CMacdSignal module inside its class at the time of creation of the corresponding object of this class. The signal module initialization code would be as follows:
//+------------------------------------------------------------------+ //| Initialization of the CSignalMacd signal module | //+------------------------------------------------------------------+ CStrategyMACD::CStrategyMACD(void) { CSymbolInfo* info = new CSymbolInfo(); // Creating an object that represents the trading symbol of the strategy info.Name(Symbol()); // Initializing the object that represents the trading symbol of the strategy m_signal_ma.Init(info, Period(), 10); // Initializing the signal module by the trading symbol and timeframe m_signal_ma.InitIndicators(GetPointer(m_indicators)); // Creating required indicators in the signal module based on the empty list of indicators m_indicators m_signal_ma.EveryTick(true); // Testing mode m_signal_ma.Magic(ExpertMagic()); // Magic number m_signal_ma.PatternsUsage(8); // Pattern mask m_open.Create(Symbol(), Period()); // Initializing the timeseries of Open prices m_high.Create(Symbol(), Period()); // Initializing the timeseries of High prices m_low.Create(Symbol(), Period()); // Initializing the timeseries of Low prices m_close.Create(Symbol(), Period()); // Initializing the timeseries of Close prices m_signal_ma.SetPriceSeries(GetPointer(m_open), // Initializing the signal module by timeseries objects GetPointer(m_high), GetPointer(m_low), GetPointer(m_close)); }
It should be noted that the initialization problems are not relevant for the users of the automated strategy generator. The entire chain of initializations is created automatically in the strategy generator, all the user needs to do is to start using the Expert Advisor. The situation is different for those who are going to use this set of classes to create their own solutions. In this case, it will be necessary to perform the entire initialization chain.
An Overview of the Signal Module, the Concept of a Pattern
As already mentioned, the signal modules are based on the common CExpertSignal class which, in turn, is based on CExpertBase. Each standard module of signals is in fact a class with functions that search for one or more patterns — special logical conditions that determine the moment for buying or selling. For example, if the fast Moving Average crosses the slow one from upside down, it forms a buying pattern. Each indicator can form a number of conditions for buying and selling. A simple example is the MACD indicator. Both the divergence of the indicator and a simple crossing of the signal line with the main indicator histogram can be interpreted as a signal. Both of these trading conditions represent different patterns. A market entry is possible upon the occurrence of one or more of these events. It is important to note that the patterns of buying and selling are different and usually represent opposite conditions. From the point of view of the automated strategy generator, a signal is a processor of one or more patterns having a common indicator. For example, a signal based on MACD can detect the presence of several patterns on the market. The standard module of MACD based signals contains five buying patterns and five selling patterns:
- Reversal — the oscillator turns up (buying) or down (selling)
- The intersection of the main and the signal lines
- Crossing the zero level
- Divergence
- Double divergence
The detailed description of the patterns is available in the terminal Help files, so we will not discuss the details here. Other signal modules contain different patterns, and the different numbers of them. Typically, every signal contains an average of three buying patterns and three selling ones. A signal can contain a maximum of 32 patterns in one direction and 32 in the opposite one (this is the length of a bit field of the integer variable that stores the mask of used patterns).
The signal can identify patterns, and it also can give some recommendation as an integer. It is expected that this number should show the signal strength: the larger the number, the stronger the signal. It is possible to set the signal strength for each of the patterns using the special group of methods Pattern_x(int value), where x is the index of the pattern. For example, the following will be written in the signal configuration code:
//+------------------------------------------------------------------+ //| Initialization of the CSignalMacd signal module | //+------------------------------------------------------------------+ CStrategyMACD::CStrategyMACD(void) { m_signal_ma.Pattern_0(0); m_signal_ma.Pattern_1(0); m_signal_ma.Pattern_2(0); m_signal_ma.Pattern_3(100); m_signal_ma.Pattern_4(0); m_signal_ma.Pattern_5(0); }
In this case the CSignalMacd signal module will only return a value when a Divergence is formed, i.e. the pattern of the MACD indicator, in which the first analyzed valley of the oscillator is shallower than the previous one, and the corresponding price valley is deeper than the previous one (the is a definition for buying). All other patterns will be skipped.
Trading recommendations are returned by two independent methods LongCondition and ShortCondition. The first one returns a recommendation to buy, the second — to sell. Let us have a look inside one of them in order to understand how they work. Here is LongCondition:
//+------------------------------------------------------------------+ //| An indication that the price will grow. | //+------------------------------------------------------------------+ int CSignalMACD::LongCondition(void) { int result=0; int idx =StartIndex(); //--- Checking the direction of the main line double diff = DiffMain(idx); if(diff>0.0) { //--- the main line is directed upwards, this is a confirmation that the price can rise if(IS_PATTERN_USAGE(0)) result=m_pattern_0; // signal confirmation number 0 //--- If pattern 1 is used, the reversal of the main line is searched if(IS_PATTERN_USAGE(1) && DiffMain(idx+1)<0.0) result=m_pattern_1; // signal number 1 //--- If pattern 2 is used, the crossover of the main line with the signal one is searched if(IS_PATTERN_USAGE(2) && State(idx)>0.0 && State(idx+1)<0.0) result=m_pattern_2; // signal No 2 //--- If pattern 3 is used, the crossover of the main line with the zero level is searched if(IS_PATTERN_USAGE(3) && Main(idx)>0.0 && Main(idx+1)<0.0) result=m_pattern_3; // Signal No 3 //--- If the patterns 4 and 5 are used and the main line is below the zero level and is rising, divergence is searched if((IS_PATTERN_USAGE(4) || IS_PATTERN_USAGE(5)) && Main(idx)<0.0) { //--- An extended analysis of the oscillator state ExtState(idx); //--- If pattern 4 is used, the Divergence signal is expected if(IS_PATTERN_USAGE(4) && CompareMaps(1,1)) // 0000 0001b result=m_pattern_4; // Signal No 4 //--- If pattern 5 is used, the Double Divergence signal is expected if(IS_PATTERN_USAGE(5) && CompareMaps(0x11,2)) // 0001 0001b return(m_pattern_5); //Signal No 5 } } //--- Returning the result return(result); }
The method works with the pattern detection macro IS_PATTERN_USAGE, which is based on a bit mask:
//--- Check if the market pattern is used #define IS_PATTERN_USAGE(p) ((m_patterns_usage&(((int)1)<<p))!=0)
If the pattern is used and the corresponding conditions are fulfilled, the recommendation result will be equal to the corresponding pattern weight, which, in turn, is defined by the user through the Pattern_x group methods. However, if you use multiple patterns, it is not possible to find out which of them has triggered, because the check does not have interrupt operators. The strength of recommendations in this case will be equal to the weight of the pattern that was determined last.
The number of the pattern that you want to use should be given in a special bit mask. For example, if you want to use pattern No 3, the fourth bit of the 32-bit variable should be equal to 1 (note that indexing of patterns starts with zero, so the fourth digit is used for the third pattern, while the first digit is used for the zero pattern). If we convert the number 1000 in binary to a decimal, we get the number 8. That is the number you need to pass to the PatternsUsage method. Patterns can be combined. For example, in order to use the pattern No 3 together with the pattern No 2, you must create a bit field, the fourth and the third digits of which would be equal to one: 1100. The same value in the decimal format is 12.
A Module of Signals. First Use
We now have learned enough to start using the modules of signals. Let us experiment on the basis of CStrategy. For this purpose, we create a special experimental class, the CSignalSamples strategy.
//+------------------------------------------------------------------+ //| EventListener.mqh | //| Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia | //| https://www.mql5.com/en/users/c-4 | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, Vasiliy Sokolov." #property link "https://www.mql5.com/en/users/c-4" #include <Strategy\Strategy.mqh> #include <Expert\Signal\SignalMACD.mqh> //+------------------------------------------------------------------+ //| The strategy receives events and displays them in terminal. | //+------------------------------------------------------------------+ class CSignalSamples : public CStrategy { private: CSignalMACD m_signal_macd; CSymbolInfo m_info; CiOpen m_open; CiHigh m_high; CiLow m_low; CiClose m_close; CIndicators m_indicators; public: CSignalSamples(void); virtual void OnEvent(const MarketEvent& event); }; //+------------------------------------------------------------------+ //| Initialization of the CSignalMacd signal module | //+------------------------------------------------------------------+ CSignalSamples::CSignalSamples(void) { m_signal_macd.Pattern_0(0); m_signal_macd.Pattern_1(0); m_signal_macd.Pattern_2(0); m_signal_macd.Pattern_3(100); m_signal_macd.Pattern_4(0); m_signal_macd.Pattern_5(0); m_info.Name(Symbol()); // Initializing the object that represents the trading symbol of the strategy m_signal_macd.Init(GetPointer(m_info), Period(), 10); // Initializing the signal module by the trading symbol and timeframe m_signal_macd.InitIndicators(GetPointer(m_indicators)); // creating required indicators in the signal module based on the empty list of indicators m_indicators m_signal_macd.EveryTick(true); // Testing mode m_signal_macd.Magic(ExpertMagic()); // Magic number m_signal_macd.PatternsUsage(8); // Pattern mask m_open.Create(Symbol(), Period()); // Initializing the timeseries of Open prices m_high.Create(Symbol(), Period()); // Initializing the timeseries of High prices m_low.Create(Symbol(), Period()); // Initializing the timeseries of Low prices m_close.Create(Symbol(), Period()); // Initializing the timeseries of Close prices m_signal_macd.SetPriceSeries(GetPointer(m_open), // Initializing the signal module by timeseries objects GetPointer(m_high), GetPointer(m_low), GetPointer(m_close)); } //+------------------------------------------------------------------+ //| Buying. | //+------------------------------------------------------------------+ void CSignalSamples::OnEvent(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN) return; m_indicators.Refresh(); m_signal_macd.SetDirection(); int power_sell = m_signal_macd.ShortCondition(); int power_buy = m_signal_macd.LongCondition(); if(power_buy != 0 || power_sell != 0) printf("PowerSell: " + (string)power_sell + " PowerBuy: " + (string)power_buy); } //+------------------------------------------------------------------+
The latest versions of CStrategy contain a new event OnEvent, which is represented by the OnEvent method, which is called whenever an event occurs. Unlike the more familiar methods BuyInit, SellInit, BuySupport and SellSupport, OnEvent is called regardless of the trading strategy mode and the trading schedule. Thus, OnEvent allows accessing the stream of events from the strategy, while maintaining a strict event model. OnEvent is very convenient to use for common calculations or actions, which are not related to a specific buy or sell direction.
Most of the code is devoted to the initialization of the trading signals module. As an example we used a module based on the MACD indicator: CSignalMACD. Only one pattern No 3 is used in the module. Weight 100 is assigned to this module, and an appropriate PatternsUsage method with the value of 8 is used. In order to run this strategy, it is necessary to prepare a strategy loader or an executable mq5 module. Its content:
//+------------------------------------------------------------------+ //| Agent.mq5 | //| Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia | //| https://www.mql5.com/en/users/c-4 | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, Vasiliy Sokolov." #property link "https://www.mql5.com/en/users/c-4" #property version "1.00" #include <Strategy\StrategiesList.mqh> #include <Strategy\Samples\SignalSamples.mqh> CStrategyList Manager; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { CSignalSamples* signal = new CSignalSamples(); signal.ExpertMagic(2918); signal.ExpertName("MQL Signal Samples"); signal.Timeframe(Period()); if(!Manager.AddStrategy(signal)) delete signal; return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { Manager.OnTick(); } //+------------------------------------------------------------------+ //| OnChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { Manager.OnChartEvent(id, lparam, dparam, sparam); }
It consists of the strategy initialization in the OnInit function, and handler of new ticks in OnTick. After running the resulting code in the Strategy Tester in the visualization mode, you will see notifications about received signals:
2016.06.20 16:34:31.697 tester agent shutdown finished 2016.06.20 16:34:31.642 shutdown tester machine 2016.06.20 16:34:31.599 tester agent shutdown started 2016.06.20 16:34:31.381 log file "Z:\MetaTrader 5\Tester\Agent-127.0.0.1-3000\logs\20160620.log" written 2016.06.20 16:34:31.381 325 Mb memory used including 28 Mb of history data, 64 Mb of tick data 2016.06.20 16:34:31.381 EURUSD,M1: 51350 ticks (12935 bars) generated in 0:00:00.780 (total bars in history 476937, total time 0:00:00.843) 2016.06.20 16:34:31.376 final balance 100000.00 USD 2016.06.20 16:34:31.373 2016.04.14 22:12:00 PowerSell: 100 PowerBuy: 0 2016.06.20 16:34:31.373 2016.04.14 22:01:00 PowerSell: 0 PowerBuy: 100 2016.06.20 16:34:31.373 2016.04.14 21:24:00 PowerSell: 100 PowerBuy: 0 2016.06.20 16:34:31.373 2016.04.14 20:54:00 PowerSell: 0 PowerBuy: 100 2016.06.20 16:34:31.373 2016.04.14 20:50:00 PowerSell: 100 PowerBuy: 0 2016.06.20 16:34:31.373 2016.04.14 20:18:00 PowerSell: 0 PowerBuy: 100 2016.06.20 16:34:31.373 2016.04.14 20:14:00 PowerSell: 100 PowerBuy: 0 2016.06.20 16:34:31.373 2016.04.14 20:13:00 PowerSell: 100 PowerBuy: 0 2016.06.20 16:34:31.373 2016.04.14 20:07:00 PowerSell: 0 PowerBuy: 100 2016.06.20 16:34:31.372 2016.04.14 19:48:00 PowerSell: 100 PowerBuy: 0 2016.06.20 16:34:31.372 2016.04.14 18:48:00 PowerSell: 0 PowerBuy: 100 ... ...
This notification indicates that the requested signals have been successfully received, and we can continue to integrate the signal module into our strategy.
Writing the First Strategy Based on CSignalMACD
Now it is time to write a fully functional strategy on the basis of the CSignalMACD signal module. The first pattern that we will use is the pattern of the signal line intersection with the oscillator. Let us use the documentation. Open MQL5 Reference -> Standard Library -> Trading Strategy Classes -> Modules of Trade Signals -> Signals of the Oscillator MACD. Find the second pattern "Crossover of the main and signal line". Here is its description:
- Buying: "Crossover of the main and signal line" — the main line is above the signal line at the analyzed bar and below the signal line at the previous one.
- Selling: "Crossover of the main and signal line" — the main line is below the signal line at the analyzed bar and above the signal line at the previous one.
Fig 2. The oscillator crosses the signal line from bottom to top
Fig. 3. The oscillator crosses the signal line from top to bottom
We need to define the number of the pattern that corresponds to this description. The header of the CSignalMACD class will help us:
//+------------------------------------------------------------------+ //| Class CSignalMACD. | //| Purpose: Class of generator of trade signals based on | //| the 'Moving Average Convergence/Divergence' oscillator. | //| Is derived from the CExpertSignal class. | //+------------------------------------------------------------------+ class CSignalMACD : public CExpertSignal { protected: CiMACD m_MACD; // Oscillator object //--- adjusted parameters int m_period_fast; // Oscillator parameter "fast EMA period" int m_period_slow; // Oscillator parameter "slow EMA period" int m_period_signal; // Oscillator parameter "difference averaging period" ENUM_APPLIED_PRICE m_applied; // Oscillator parameter "price series" //--- "weights" of market models (0-100) int m_pattern_0; // pattern 0 "oscillator moves in the required direction" int m_pattern_1; // pattern 1 "oscillator reverses towards the required direction" int m_pattern_2; // pattern 2 "crossover of the main and signal line" int m_pattern_3; // pattern 3 "crossover of the main line and the zero level" int m_pattern_4; // pattern 4 "divergence of the oscillator and the price" int m_pattern_5; // pattern 5 "double divergence of the oscillator and price" //--- variables double m_extr_osc[10]; // An array of extreme values of the oscillator double m_extr_pr[10]; // An array of values of appropriate extreme prices int m_extr_pos[10]; // Extremum shift array (in bars) uint m_extr_map; // Resulting bitmap of oscillator extremums and extreme prices ... }
From the comments in the code we can see that the pattern type is of No 2.
Now that we know the number of the pattern, we need to properly configure the signal. First of all, in order to avoid confusion, we will not use other patterns. For this purpose, we set the pattern mask equal to 4 (100 in a binary form). Since we are only going to use one pattern, we do not need to know the signal strength (not to configure the strength of the patterns) - either there is a signal, or there are no signals. We will check the signal at the opening of a new bar, so we should indicate this by calling the appropriate signal method EveryTick with the false flag. The configuration should be implemented in the strategy constructor. Then proceed to programming the trading logic. Let us override the methods InitBuy, SupportBuy, InitSell, SupportSell. Let us name the strategy COnSignalMACD: the On prefix indicates that the strategy is based on the standard module of signals. The strategy code is given below:
//+------------------------------------------------------------------+ //| EventListener.mqh | //| Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia | //| https://www.mql5.com/en/users/c-4 | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, Vasiliy Sokolov." #property link "https://www.mql5.com/en/users/c-4" #include <Strategy\Strategy.mqh> #include <Expert\Signal\SignalMACD.mqh> //+------------------------------------------------------------------+ //| The strategy receives events and displays them in terminal. | //+------------------------------------------------------------------+ class COnSignalMACD : public CStrategy { private: CSignalMACD m_signal_macd; CSymbolInfo m_info; CiOpen m_open; CiHigh m_high; CiLow m_low; CiClose m_close; CIndicators m_indicators; public: COnSignalMACD(void); virtual void InitBuy(const MarketEvent &event); virtual void InitSell(const MarketEvent &event); virtual void SupportBuy(const MarketEvent& event, CPosition* pos); virtual void SupportSell(const MarketEvent& event, CPosition* pos); }; //+------------------------------------------------------------------+ //| Initialization of the CSignalMacd signal module | //+------------------------------------------------------------------+ COnSignalMACD::COnSignalMACD(void) { m_info.Name(Symbol()); // Initializing the object that represents the trading symbol of the strategy m_signal_macd.Init(GetPointer(m_info), Period(), 10); // Initializing the signal module by the trading symbol and timeframe m_signal_macd.InitIndicators(GetPointer(m_indicators)); // reating required indicators in the signal module based on the empty list of indicators m_indicators m_signal_macd.EveryTick(false); // Testing mode m_signal_macd.Magic(ExpertMagic()); // Magic number m_signal_macd.PatternsUsage(4); // Pattern mask m_open.Create(Symbol(), Period()); // Initializing the timeseries of Open prices m_high.Create(Symbol(), Period()); // Initializing the timeseries of High prices m_low.Create(Symbol(), Period()); // Initializing the timeseries of Low prices m_close.Create(Symbol(), Period()); // Initializing the timeseries of Close prices m_signal_macd.SetPriceSeries(GetPointer(m_open), // Initializing the signal module by timeseries objects GetPointer(m_high), GetPointer(m_low), GetPointer(m_close)); } //+------------------------------------------------------------------+ //| Buying. | //+------------------------------------------------------------------+ void COnSignalMACD::InitBuy(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN) return; m_indicators.Refresh(); m_signal_macd.SetDirection(); int power_buy = m_signal_macd.LongCondition(); if(power_buy != 0) Trade.Buy(1.0); } //+------------------------------------------------------------------+ //| Closing buys | //+------------------------------------------------------------------+ void COnSignalMACD::SupportBuy(const MarketEvent &event, CPosition* pos) { if(event.type != MARKET_EVENT_BAR_OPEN) return; m_indicators.Refresh(); m_signal_macd.SetDirection(); int power_sell = m_signal_macd.ShortCondition(); //printf("Power sell: " + (string)power_sell); if(power_sell != 0) pos.CloseAtMarket(); } //+------------------------------------------------------------------+ //| Selling. | //+------------------------------------------------------------------+ void COnSignalMACD::InitSell(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN) return; m_indicators.Refresh(); m_signal_macd.SetDirection(); int power_sell = m_signal_macd.ShortCondition(); if(power_sell != 0) Trade.Sell(1.0); } //+------------------------------------------------------------------+ //| Closing buys | //+------------------------------------------------------------------+ void COnSignalMACD::SupportSell(const MarketEvent &event, CPosition* pos) { if(event.type != MARKET_EVENT_BAR_OPEN) return; m_indicators.Refresh(); m_signal_macd.SetDirection(); int power_buy = m_signal_macd.LongCondition(); if(power_buy != 0) pos.CloseAtMarket(); } //+------------------------------------------------------------------+
Long and short positions are opened based on the signals described in the reference. Existing positions are closed by opposite signals. Thus, if there is a condition to open a long position, the previously opened short position will be closed, and vice versa.
The trading result can be viewed in the strategy tester. Part of the testing history is featured in the below figure:
Fig. 4. Opening trades at the intersection of the MACD histogram and the signal line
According to the testing mode, trades are opened upon a signal received on the previous bar. The figure shows that on the bar following the crossing of the MACD histogram and the signal line, a long or a short position is opened, and the previous position is closed.
An Adapter of Signals
We have found out that before you start working with the signal, you need to configure it. Signals are configured using complex signals, which, in turn, also need to be configured before passing them to the signal. Different signals require different objects for operation. For example, some signals only require specification of basic timeseries, while for other signals you need to specify the container of indicators and additional price data, such as the tick or real volume. All this complicates the use of the signals at the user level, since the user needs to know the internal system of the signal, and what data is required for its proper operation.
To avoid these difficulties, a special adapter class was introduced. The class is called CSignalAdapter, and it is located in the common directory of the CStrategy project. The adapter has a simple interface. It allows creating a signal and receiving the flags of buy and sell pattern formation. To create a signal, we need to pass the signal parameters to a special method CSignalAdapter::CreateSignal. The signal parameters are contained in the special MqlSignalParams structure. Here is the definition of this structure:
//+--------------------------------------------------------------------+ //| Signal parameters | //+--------------------------------------------------------------------+ struct MqlSignalParams { public: string symbol; // Symbol ENUM_TIMEFRAMES period; // Chart period ENUM_SIGNAL_TYPE signal_type; // Signal type int index_pattern; // Patten index int magic; // EA's magic number double point; // Number of points bool every_tick; // "Every tick" testing mode void operator=(MqlSignalParams& params); }; //+--------------------------------------------------------------------+ //| The copy operator is used, since the strategy uses strings | //+--------------------------------------------------------------------+ void MqlSignalParams::operator=(MqlSignalParams& params) { symbol = params.symbol; period = params.period; signal_type = params.signal_type; usage_pattern = params.usage_pattern; magic = params.magic; point = params.point; every_tick = params.every_tick; }
The structure contains basic types that define the following signal characteristics:
- Symbol;
- Timeframe;
- Signal type;
- Expert Advisor's magic number;
- A flag indicating the "Every tick" testing mode;
- Price filter;
- Used signal pattern.
Here are a few more details about index_pattern. Unlike the module signals, it accepts not the pattern mask, but it only receives the index of one of them. Thus, every signal adapter can only use one pattern of the selected signal. The value of index_pattern should be within 1 to 31, and should be equal to the real number of patterns of the used signal.
In addition to basic parameters, the structure contains a copy operator, because it uses a string type. That is why it is not possible to automatically copy one structure to another. After determining the necessary parameters and filling the appropriate structure, the user can call the CSignalAdapter::CreateSignal method and receive an instance of the created signal from this method in response. The received instance can be further configured taking into account the specific features of the corresponding signal.
The listing below shows a method of configuring the CSignalMACD signal using the CSignalAdapter adapter:
//+------------------------------------------------------------------+ //| EventListener.mqh | //| Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia | //| https://www.mql5.com/en/users/c-4 | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, Vasiliy Sokolov." #property link "https://www.mql5.com/en/users/c-4" #include <Strategy\Strategy.mqh> #include <Strategy\SignalAdapter.mqh> //+------------------------------------------------------------------+ //| The strategy receives events and displays them in terminal. | //+------------------------------------------------------------------+ class CAdapterMACD : public CStrategy { private: CSignalAdapter m_signal; MqlSignalParams m_params; public: CAdapterMACD(void); virtual void InitBuy(const MarketEvent &event); virtual void InitSell(const MarketEvent &event); virtual void SupportBuy(const MarketEvent& event, CPosition* pos); virtual void SupportSell(const MarketEvent& event, CPosition* pos); }; //+------------------------------------------------------------------+ //| Configuring the adapter | //+------------------------------------------------------------------+ CAdapterMACD::CAdapterMACD(void) { m_params.symbol = Symbol(); m_params.period = Period(); m_params.every_tick = false; m_params.signal_type = SIGNAL_MACD; m_params.magic = 1234; m_params.point = 1.0; m_params.usage_pattern = 2; CSignalMACD* macd = m_signal.CreateSignal(m_params); macd.PeriodFast(15); macd.PeriodSlow(32); macd.PeriodSignal(6); }
Parameters should also be configured for the adapter. However, unlike the first version of the Expert Advisor, all parameters are trivial, i.e. of the basic types. In addition, there is no need to create or monitor other complex objects, such as timeseries and indicators. All this will be done by the adapter. That is why its use greatly simplifies work with signals.
Note that after creating the signal, we continued to configure it by setting our own period of the MACD indicator (15, 32, 6). This is easy to do, because the CreateSignal method has returned the corresponding object.
Once the signal is properly configured, you can start using it. Simple methods BuySignal and ShortSignal are used for this purpose. Here is the continuation of the strategy class:
//+------------------------------------------------------------------+ //| Buying. | //+------------------------------------------------------------------+ void CAdapterMACD::InitBuy(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(m_signal.LongSignal()) Trade.Buy(1.0); } //+------------------------------------------------------------------+ //| Closing buys | //+------------------------------------------------------------------+ void CAdapterMACD::SupportBuy(const MarketEvent &event, CPosition* pos) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(m_signal.ShortSignal()) pos.CloseAtMarket(); } //+------------------------------------------------------------------+ //| Selling | //+------------------------------------------------------------------+ void CAdapterMACD::InitSell(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(m_signal.ShortSignal()) Trade.Sell(1.0); } //+------------------------------------------------------------------+ //| Closing buys | //+------------------------------------------------------------------+ void CAdapterMACD::SupportSell(const MarketEvent &event, CPosition* pos) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(m_signal.LongSignal()) pos.CloseAtMarket(); }
The above logic does the same as the previous example: it opens long and short positions at the intersection of the MACD histogram and its signal line. However, as you can see, the code is even shorter now. Unlike the first version of the signal code, now there is no need to define the direction of the signal each time or to update the values of indicators. Nor you need to add auxiliary objects to the strategy code. All these actions are performed by the adapter.
Combining Multiple Signals in a Trading Strategy Code
Can we use different patterns and even different signals for market entry and exit? The answer is Yes. We have a full access to the signal system, so we can use more than one pattern. In order to avoid pattern confusion, the signal adapter allows setting only one pattern for use. But the number of such adapters is unlimited. In this case, each pattern is presented by a separate adapter and a signal, even if all patterns are based on one signal. Of course, in terms of resources, it is a slightly less efficient way than the one available in the standard library, but it has its benefits.
Let us write an example of a strategy that can receive different entry and exit signals. The strategy will use the patterns of the RSI indicator based on the overbought and oversold areas for entries. The second pattern - that of the Accelerator Oscillator (AC) proposed by Bill Williams will be used for exits. Here are the rules of the strategy in more detail.
Buying: Reverse behind the level of overselling — the oscillator turns upwards and its value at the analyzed bar is behind the level of overselling (default value is 30).
Fig. 5. Long position entry conditions
Selling: Reverse behind the level of overbuying — the oscillator turned downwards and its value at the analyzed bar is behind the level of overbuying (default value is 70).
Fig. 6. Short position entry conditions
Closing Buy: The value of the AC indicator is above 0, and it falls at the analyzed and at two previous bars:
Fig. 7. Long position exit conditions
Closing Sell: The value of the AC indicator is below 0, and it grows at the analyzed and at two previous bars:
Fig. 8. Short position exit conditions
Exit from a long position is performed based on the pattern of the AC signal which is used to enter a short position, and vice versa, you exit from a short position using the pattern of the AC signal which is used to enter a long position.
The Expert Advisor implementing this logic is available below:
//+------------------------------------------------------------------+ //| EventListener.mqh | //| Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia | //| https://www.mql5.com/en/users/c-4 | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, Vasiliy Sokolov." #property link "https://www.mql5.com/ru/users/c-4" #include <Strategy\Strategy.mqh> #include <Strategy\SignalAdapter.mqh> input int RSI_Period = 14; // RSI Period //+------------------------------------------------------------------+ //| The strategy receives events and displays them in terminal. | //+------------------------------------------------------------------+ class COnSignal_RSI_AC : public CStrategy { private: CSignalAdapter m_adapter_rsi; CSignalAdapter m_adapter_ac; public: COnSignal_RSI_AC(void); virtual void InitBuy(const MarketEvent &event); virtual void InitSell(const MarketEvent &event); virtual void SupportBuy(const MarketEvent& event, CPosition* pos); virtual void SupportSell(const MarketEvent& event, CPosition* pos); }; //+------------------------------------------------------------------+ //| Initialization of the CSignalMacd signal module | //+------------------------------------------------------------------+ COnSignal_RSI_AC::COnSignal_RSI_AC(void) { MqlSignalParams params; params.every_tick = false; params.magic = 32910; params.point = 10.0; params.symbol = Symbol(); params.period = Period(); params.usage_pattern = 2; params.signal_type = SIGNAL_AC; CSignalAC* ac = m_adapter_ac.CreateSignal(params); params.usage_pattern = 1; params.magic = 32911; params.signal_type = SIGNAL_RSI; CSignalRSI* rsi = m_adapter_rsi.CreateSignal(params); rsi.PeriodRSI(RSI_Period); } //+------------------------------------------------------------------+ //| Buying. | //+------------------------------------------------------------------+ void COnSignal_RSI_AC::InitBuy(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(positions.open_buy > 0) return; if(m_adapter_rsi.LongSignal()) Trade.Buy(1.0); } //+------------------------------------------------------------------+ //| Closing buys | //+------------------------------------------------------------------+ void COnSignal_RSI_AC::SupportBuy(const MarketEvent &event, CPosition* pos) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(m_adapter_ac.ShortSignal()) pos.CloseAtMarket(); } //+------------------------------------------------------------------+ //| Selling. | //+------------------------------------------------------------------+ void COnSignal_RSI_AC::InitSell(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(positions.open_sell > 0) return; if(m_adapter_rsi.ShortSignal()) Trade.Sell(1.0); } //+------------------------------------------------------------------+ //| Closing buys | //+------------------------------------------------------------------+ void COnSignal_RSI_AC::SupportSell(const MarketEvent &event, CPosition* pos) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(m_adapter_ac.LongSignal()) pos.CloseAtMarket(); } //+------------------------------------------------------------------+
Note that the Expert Advisor has an external parameter that allows you to specify the period of RSI. This is performed in the strategy constructor through a direct access to the signal.
The strategy operation result is shown in the below chart:
Fig. 8. The result of the strategy
As can be seen from the chart, the EA uses two indicators — RSI and AC. The EA enters the market when RSI starts rising or falling inside its overbought and oversold areas. These areas are circled in red. The EA exits when the AC indicator forms three lines of the same color. For a buy position exit, the lines must be red and must be above the zero level. For a sell position exit, the lines must be green and must be below the zero level. Such moments are shown in blue boxes.
The chart shows that the EA's logic is processed correctly. The trading rules of this Expert Advisor are non-trivial. However, the strategy itself is not long. These are the benefits of code reuse.
Conclusion
We have considered a method of integrating the standard library of signals into the CStrategy trading engine. Through this integration, CStrategy is a very handy tool for creating custom strategies based on the patterns of standard signals. In addition, any signal written for the automated MetaTrader strategy generator automatically becomes available for the CStrategy trading engine.
The possibility to use standard trading signals in the CStrategy can significantly save strategy development time. Now you do not need to write your own indicators or pattern detection algorithms, if they are already available in the standard module of signals. In addition, the new feature significantly reduces the complexity of the strategy development process, while the definition of complex patterns, such as the divergence or double divergence, is now performed by ready-made solutions.
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/2540
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
Thank Vasiliy for your contribution. I learned a lot. I downloaded all the code but it has a compiling error in file Panel.mqh:
'At' - object pointer expected Panel.mqh 210 39
'At' - object pointer expected Panel.mqh 228 37
Can you please check it?
Thank Vasiliy for your contribution. I learned a lot. I downloaded all the code but it has a compiling error in file Panel.mqh:
'At' - object pointer expected Panel.mqh 210 39
'At' - object pointer expected Panel.mqh 228 37
Can you please check it?
Hi Amy. I have just completed reading through Vasiliy Sokolov articles. If you are still interested in finding out the solution. Please post the error log here. I recall coming across that error and realised that there is an ".\Panel\Panel.mqh" in declarations in StrategiesList file. Panel.mqh does not exist. Instead, try going to https://www.mql5.com/en/articles/2411 and download the Panel File from there. I believe it has the Panel.mqh file.
This series by Vasiliy's is really good as a framework. I learn't a lot too,but when there are issues with a library, if the author does not offer support, you could be left stuck if you are not a good programmer. Which is the point Alain Verleyen made in your other discussions. But those spending time to share their knowledge and skill here are completely amazing. I am eternally grateful.
Hi, Vasily.
Thanks for all of your articles.
The Universal Expert Advisor is really impressive in terms of complexity and Software architecture.
For this particular version, I'd like to raise a point in this piece of code here:
Actually, the MacD parameters (15, 32 and 6) are taking no effect here, since CreateSignal() method initializes the MacD signal before the params are updated.
In this case, I'd suggest to split the CSignalAdapter::CreateSignal() method in two parts, where in the first one, the signal is in fact created and returned just like it is, and the second part would be the signal initialization, after all "Signal Dependant" parameters (in this case, PeriodFast, PeriodSlow and PeriodSignal) are set:
And of course, the newly created Init method needs to be called:
Thank you for the great work, and even more for sharing it, Vasily!
Cheers,
Rodrigo Haller
How it is possible to use class CSignalMACD in script?
I've tried to get signal result just inplace, but always got 0: