Download MetaTrader 5

MQL5 Cookbook: Developing a Framework for a Trading System Based on the Triple Screen Strategy

29 May 2013, 15:13
Anatoli Kazharski
2
5 983

Introduction

When searching for or developing trading systems, many traders must have heard about the Triple Screen strategy introduced by Dr. Alexander Elder. There are lots of people in the Internet whose judgment on that strategy is negative. However, many people believe that it can help one to profit. You do not have to trust either of the two opinions. Everything should always be checked first-hand. If you study programming, it's all in your hands as you can check the trading strategy performance using back-testing.

In this article, we will develop a framework for a trading system based on the Triple Screen strategy in MQL5. The Expert Advisor will not be developed from scratch. Instead, we will simply modify the program from the previous article "MQL5 Cookbook: Using Indicators to Set Trading Conditions in Expert Advisors". So the article will also demonstrate how you can easily modify patterns of ready-made programs.

The Expert Advisor from the previous article already features the possibility to enable/disable the Stop Loss/Take Profit and Trailing Stop levels, position volume increase and position reversal on the opposite signal. All the necessary functions have been set in place. So our task is centered around changing the list of external parameters by adding additional options and modifying some existing functions.

For illustrative purposes, we will arrange for signals on three time frames to be generated using the Moving Average indicator. Later on, in continuing to experiment on the developed framework, you will be able to employ any other indicator by slightly modifying the code. We will also implement the opportunity to set time frames for each screen. If the parameter responsible for the indicator period has zero value, this will denote that the corresponding screen is not used. In other words, the system can be set up to have one or two time frames.

Before you start, make a copy of the folder with the files of the Expert Advisor from the previous article and rename it.

 

Expert Advisor Development

Let's start with the external parameters. Below is the code of the updated list. New lines are singled out. Time frames are declared with the ENUM_TIMEFRAMES enumeration type. You will be able to select any time frame from the drop-down list.

//--- External parameters of the Expert Advisor
sinput   long              MagicNumber=777;        // Magic number
sinput   int               Deviation=10;           // Slippage
//---
input    ENUM_TIMEFRAMES   Screen01TimeFrame=PERIOD_W1;  // Time frame of the first screen
input    int               Screen01IndicatorPeriod=14;   // Indicator period of the first screen
//---
input    ENUM_TIMEFRAMES   Screen02TimeFrame=PERIOD_D1;  // Time frame of the second screen
input    int               Screen02IndicatorPeriod=24;   // Indicator period of the second screen
//---
input    ENUM_TIMEFRAMES   Screen03TimeFrame=PERIOD_H4;  // Time frame of the third screen
input    int               Screen03IndicatorPeriod=44;   // Indicator period of the third screen
//---
input    double            Lot=0.1;                      // Lot
input    double            VolumeIncrease=0.1;           // Position volume increase
input    double            VolumeIncreaseStep=10;        // Step for position volume increase
input    double            StopLoss=50;                  // Stop Loss
input    double            TakeProfit=100;               // Take Profit
input    double            TrailingStop=10;              // Trailing Stop
input    bool              Reverse=true;                 // Position reversal
sinput   bool              ShowInfoPanel=true;           // Display of the info panel

The IndicatorSegments parameter, as well as the AllowedNumberOfSegments variable and the CorrectInputParameters() function have been removed to simplify the example. Those of you who are interested in this condition can try to implement it on your own. You should also remove the enumeration of indicators in the Enums.mqh file as this Expert Advisor will only employ one indicator.

Since there will be a separate indicator on each time frame, we will need a separate variable to get a handle of each of the indicators:

//--- Indicator handles
int                  Screen01IndicatorHandle=INVALID_HANDLE;   // Indicator handle on the first screen
int                  Screen02IndicatorHandle=INVALID_HANDLE;   // Indicator handle on the second screen
int                  Screen03IndicatorHandle=INVALID_HANDLE;   // Indicator handle on the third screen

The new bar will be checked using the minimum time frame. When setting the minimum time frame in the external parameters, we do not have to follow a specific order, i.e. maximum, intermediate, minimum. The reverse order and basically any order would do. So we need a function that will identify the minimum time frame out of all time frames specified.

Since the Expert Advisor can be set to operate on three time frames, as well as on one or two, all options need to be considered when determining the minimum time frame. Below if the code of the GetMinimumTimeframe() function:

//+------------------------------------------------------------------+
//| Determining the minimum time frame for the new bar check         |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES GetMinimumTimeframe(ENUM_TIMEFRAMES timeframe1,int period1,
                                    ENUM_TIMEFRAMES timeframe2,int period2,
                                    ENUM_TIMEFRAMES timeframe3,int period3)
  {
//--- Default minimum time frame value
   ENUM_TIMEFRAMES timeframe_min=PERIOD_CURRENT;

//--- Convert time frame values to seconds for calculations
   int t1= PeriodSeconds(timeframe1);
   int t2= PeriodSeconds(timeframe2);
   int t3= PeriodSeconds(timeframe3);

//--- Check for incorrect period values
   if(period1<=0 && period2<=0 && period3<=0)
      return(timeframe_min);

//--- Conditions for a single time frame
   if(period1>0 && period2<=0 && period3<=0)
      return(timeframe1);
   if(period2>0 && period1<=0 && period3<=0)
      return(timeframe2);
   if(period3>0 && period1<=0 && period2<=0)
      return(timeframe3);

//--- Conditions for two time frames
   if(period1>0 && period2>0 && period3<=0)
     {
      timeframe_min=(MathMin(t1,t2)==t1) ? timeframe1 : timeframe2;
      return(timeframe_min);
     }
   if(period1>0 && period3>0 && period2<=0)
     {
      timeframe_min=(MathMin(t1,t3)==t1) ? timeframe1 : timeframe3;
      return(timeframe_min);
     }
   if(period2>0 && period3>0 && period1<=0)
     {
      timeframe_min=(MathMin(t2,t3)==t2) ? timeframe2 : timeframe3;
      return(timeframe_min);
     }

//--- Conditions for three time frames
   if(period1>0 && period2>0 && period3>0)
     {
      timeframe_min=(int)MathMin(t1,t2)==t1 ? timeframe1 : timeframe2;
      int t_min=PeriodSeconds(timeframe_min);
      timeframe_min=(int)MathMin(t_min,t3)==t_min ? timeframe_min : timeframe3;
      return(timeframe_min);
     }
   return(WRONG_VALUE);
  }

To save the minimum time frame value, we will create another global scope variable:

//--- Variable for determining the minimum time frame
ENUM_TIMEFRAMES  MinimumTimeframe=WRONG_VALUE;

The GetMinimumTimeframe() function will need to be called when initializing the Expert Advisor in the OnInit() function.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Determine the minimum time frame for the new bar check
   MinimumTimeframe=GetMinimumTimeframe(Screen01TimeFrame,Screen01IndicatorPeriod,
                                        Screen02TimeFrame,Screen02IndicatorPeriod,
                                        Screen03TimeFrame,Screen03IndicatorPeriod);
//--- Get indicator handles
   GetIndicatorHandles();
//--- Initialize the new bar
   CheckNewBar();
//--- Get the properties
   GetPositionProperties(P_ALL);
//--- Set the info panel
   SetInfoPanel();
//---
   return(0);
  }

The MinimumTimeframe variable value is then used in the CheckNewBar() and GetBarsData() functions.

The GetIndicatorHandle() function now looks as shown below. The period and time frame is specified for each indicator.

//+------------------------------------------------------------------+
//| Getting indicator handles                                        |
//+------------------------------------------------------------------+
void GetIndicatorHandles()
  {
//--- Get handles of the indicators specified in the parameters
   if(Screen01IndicatorPeriod>0)
     Screen01IndicatorHandle=iMA(_Symbol,Screen01TimeFrame,Screen01IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE);
   if(Screen02IndicatorPeriod>0)
     Screen02IndicatorHandle=iMA(_Symbol,Screen02TimeFrame,Screen02IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE);
   if(Screen03IndicatorPeriod>0)
     Screen03IndicatorHandle=iMA(_Symbol,Screen03TimeFrame,Screen03IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE);
     
//--- If the indicator handle for the first time frame could not be obtained
   if(Screen01IndicatorHandle==INVALID_HANDLE)
     Print("Failed to get the indicator handle for Screen 1!");
//--- If the indicator handle for the second time frame could not be obtained
   if(Screen01IndicatorHandle==INVALID_HANDLE)
     Print("Failed to get the indicator handle for Screen 2!");
//--- If the indicator handle for the third time frame could not be obtained
   if(Screen01IndicatorHandle==INVALID_HANDLE)
     Print("Failed to get the indicator handle for Screen 3!");
  }

Further, we need to add arrays for getting indicator values (separately for each time frame):

//--- Arrays for values of the indicators
double               indicator_buffer1[];
double               indicator_buffer2[];
double               indicator_buffer3[];

The GetIndicatorsData() function for getting indicator values now looks as shown below. The obtained handles are checked for accuracy and if everything is fine, the arrays are filled with indicator values.

//+------------------------------------------------------------------+
//| Getting indicator values                                         |
//+------------------------------------------------------------------+
bool GetIndicatorsData()
  {
//--- Number of indicator buffer values for determining the trading signal   
   int NumberOfValues=3;
//--- If indicator handles have not been obtained
   if((Screen01IndicatorPeriod>0 && Screen01IndicatorHandle==INVALID_HANDLE) ||
      (Screen02IndicatorPeriod>0 && Screen02IndicatorHandle==INVALID_HANDLE) ||
      (Screen03IndicatorPeriod>0 && Screen03IndicatorHandle==INVALID_HANDLE))
      //--- try to get them again
      GetIndicatorHandles();

//--- If the time frame of the first screen is used and the indicator handle has been obtained
   if(Screen01TimeFrame>0 && Screen01IndicatorHandle!=INVALID_HANDLE)
     {
      //--- Reverse the indexing order (... 3 2 1 0)
      ArraySetAsSeries(indicator_buffer1,true);
      //--- Get indicator values
      if(CopyBuffer(Screen01IndicatorHandle,0,0,NumberOfValues,indicator_buffer1)<NumberOfValues)
        {
         Print("Failed to copy the values ("+
                  _Symbol+"; "+TimeframeToString(Period())+") to the indicator_buffer1 array! Error ("+
                  IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError()));
         //---
         return(false);
        }
     }
//--- If the time frame of the second screen is used and the indicator handle has been obtained
   if(Screen02TimeFrame>0 && Screen02IndicatorHandle!=INVALID_HANDLE)
     {
      //--- Reverse the indexing order (... 3 2 1 0)
      ArraySetAsSeries(indicator_buffer2,true);
      //--- Get indicator values
      if(CopyBuffer(Screen02IndicatorHandle,0,0,NumberOfValues,indicator_buffer2)<NumberOfValues)
        {
         Print("Failed to copy the values ("+
                  _Symbol+"; "+TimeframeToString(Period())+") to the indicator_buffer2 array! Error ("+
                  IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError()));
         //---
         return(false);
        }
     }
//--- If the time frame of the third screen is used and the indicator handle has been obtained
   if(Screen03TimeFrame>0 && Screen03IndicatorHandle!=INVALID_HANDLE)
     {
      //--- Reverse the indexing order (... 3 2 1 0)
      ArraySetAsSeries(indicator_buffer3,true);
      //--- Get indicator values
      if(CopyBuffer(Screen03IndicatorHandle,0,0,NumberOfValues,indicator_buffer3)<NumberOfValues)
        {
         Print("Failed to copy the values ("+
                  _Symbol+"; "+TimeframeToString(Period())+") to the indicator_buffer3 array! Error ("+
                  IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError()));
         //---
         return(false);
        }
     }
//---
   return(true);
  }

The GetTradingSignal() and GetSignal() functions should be modified according to the task at hand. Below is the code of these functions for your consideration.

//+------------------------------------------------------------------+
//| Determining trading signals                                      |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE GetTradingSignal()
  {
//--- If there is no position
   if(!pos.exists)
     {
      //--- A Sell signal
      if(GetSignal()==ORDER_TYPE_SELL)
         return(ORDER_TYPE_SELL);
      //--- A Buy signal
      if(GetSignal()==ORDER_TYPE_BUY)
         return(ORDER_TYPE_BUY);
     }
//--- If the position exists
   if(pos.exists)
     {
      //--- Get the position type
      GetPositionProperties(P_TYPE);
      //--- Get the last deal price
      GetPositionProperties(P_PRICE_LAST_DEAL);

      //--- A Sell signal
      if(pos.type==POSITION_TYPE_BUY && 
         GetSignal()==ORDER_TYPE_SELL)
         return(ORDER_TYPE_SELL);
      if(pos.type==POSITION_TYPE_SELL && 
         GetSignal()==ORDER_TYPE_SELL && 
         close_price[1]<pos.last_deal_price-CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point))
         return(ORDER_TYPE_SELL);

      //--- A Buy signal
      if(pos.type==POSITION_TYPE_SELL && 
         GetSignal()==ORDER_TYPE_BUY)
         return(ORDER_TYPE_BUY);
      if(pos.type==POSITION_TYPE_BUY && 
         GetSignal()==ORDER_TYPE_BUY && 
         close_price[1]>pos.last_deal_price+CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point))
         return(ORDER_TYPE_BUY);

     }
//--- No signal
   return(WRONG_VALUE);
  }

The GetSignal() function, just like in determining the minimum time frame, takes into account all possible variants of external parameter states relating to position opening conditions. The code of the function is provided below:

//+------------------------------------------------------------------+
//| Checking the condition and returning a signal                    |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE GetSignal()
  {
//--- A SELL SIGNAL: the current value of the indicators on completed bars is lower than on the previous bars
//--- Conditions for a single time frame
   if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod<=0)
     {
      if(indicator_buffer1[1]<indicator_buffer1[2])
         return(ORDER_TYPE_SELL);
     }
   if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0)
     {
      if(indicator_buffer2[1]<indicator_buffer2[2])
         return(ORDER_TYPE_SELL);
     }
//---
   if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0)
     {
      if(indicator_buffer3[1]<indicator_buffer3[2])
         return(ORDER_TYPE_SELL);
     }

//--- Conditions for two time frames
   if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0)
     {
      if(indicator_buffer1[1]<indicator_buffer1[2] && 
         indicator_buffer2[1]<indicator_buffer2[2])
         return(ORDER_TYPE_SELL);
     }
   if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0)
     {
      if(indicator_buffer2[1]<indicator_buffer2[2] && 
         indicator_buffer3[1]<indicator_buffer3[2])
         return(ORDER_TYPE_SELL);
     }
   if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0)
     {
      if(indicator_buffer1[1]<indicator_buffer1[2] && 
         indicator_buffer3[1]<indicator_buffer3[2])
         return(ORDER_TYPE_SELL);
     }

//--- Conditions for three time frames
   if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0)
     {
      if(indicator_buffer1[1]<indicator_buffer1[2] && 
         indicator_buffer2[1]<indicator_buffer2[2] && 
         indicator_buffer3[1]<indicator_buffer3[2]
         )
         return(ORDER_TYPE_SELL);
     }

//--- A BUY SIGNAL: the current value of the indicators on completed bars is higher than on the previous bars
//--- Conditions for a single time frame
   if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod<=0)
     {
      if(indicator_buffer1[1]>indicator_buffer1[2])
         return(ORDER_TYPE_BUY);
     }
   if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0)
     {
      if(indicator_buffer2[1]>indicator_buffer2[2])
         return(ORDER_TYPE_BUY);
     }
   if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0)
     {
      if(indicator_buffer3[1]>indicator_buffer3[2])
         return(ORDER_TYPE_BUY);
     }
     
//--- Conditions for two time frames
   if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod<=0)
     {
      if(indicator_buffer1[1]>indicator_buffer1[2] && 
         indicator_buffer2[1]>indicator_buffer2[2])
         return(ORDER_TYPE_BUY);
     }
   if(Screen01IndicatorPeriod<=0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0)
     {
      if(indicator_buffer2[1]>indicator_buffer2[2] && 
         indicator_buffer3[1]>indicator_buffer3[2])
         return(ORDER_TYPE_BUY);
     }
   if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod<=0 && Screen03IndicatorPeriod>0)
     {
      if(indicator_buffer1[1]>indicator_buffer1[2] && 
         indicator_buffer3[1]>indicator_buffer3[2])
         return(ORDER_TYPE_BUY);
     }

//--- Conditions for three time frames
   if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0)
     {
      if(indicator_buffer1[1]>indicator_buffer1[2] && 
         indicator_buffer2[1]>indicator_buffer2[2] && 
         indicator_buffer3[1]>indicator_buffer3[2]
         )
         return(ORDER_TYPE_BUY);
     }
     
//--- No signal
   return(WRONG_VALUE);
  }

Now, we only need to make small changes to the OnInit() and OnDeinit() functions. You can see the changes highlighted in the below code:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Determine the minimum time frame for the new bar check
   MinimumTimeframe=GetMinimumTimeframe(Screen01TimeFrame,Screen01IndicatorPeriod,
                                        Screen02TimeFrame,Screen02IndicatorPeriod,
                                        Screen03TimeFrame,Screen03IndicatorPeriod);
//--- Get indicator handles
   GetIndicatorHandles();
//--- Initialize the new bar
   CheckNewBar();
//--- Get the properties
   GetPositionProperties(P_ALL);
//--- Set the info panel
   SetInfoPanel();
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Print the deinitialization reason to the journal
   Print(GetDeinitReasonText(reason));
//--- When deleting from the chart
   if(reason==REASON_REMOVE)
     {
      //--- Delete all objects relating to the info panel from the chart
      DeleteInfoPanel();
      //--- Delete the indicator handles
      IndicatorRelease(Screen01IndicatorHandle);
      IndicatorRelease(Screen02IndicatorHandle);
      IndicatorRelease(Screen03IndicatorHandle);
     }
  } 

The framework for trading systems based on the Triple Screen strategy is ready. It can be modified at any time, by changing the indicators or adding some additional conditions, if necessary.

 

Optimizing Parameters and Testing Expert Advisor

Let's proceed to parameter optimization and check the results. The Strategy Tester is set as shown below (be sure to specify the lowest of the three time frames):

Fig. 1. Strategy Tester settings

Fig. 1. Strategy Tester settings.

The Expert Advisor parameters for optimization have been set as shown below. Time frames can be set for optimization but I prefer to set them manually.

Fig. 2. Settings of the Expert Advisor

Fig. 2. Settings of the Expert Advisor.

The optimization has been completed in about 30 minutes on a dual-core processor. The Optimization Graph is provided below:

Fig. 3. Optimization Graph

Fig. 3. Optimization Graph.

Maximum balance test results show less drawdown than the maximum recovery factor test results, which is why the maximum balance test results are used for demonstration purposes:

Fig. 4. Maximum balance test results

Fig. 4. Maximum balance test results.

Fig. 5. Maximum balance test graph

Fig. 5. Maximum balance test graph.

 

Conclusion

The article has demonstrated that the Expert Advisor can be fairly quickly modified, if the main functions are available. You can get a new trading system by only changing the signal block and indicators. Attached to the article is a downloadable archive containing the source codes of the Expert Advisor described herein above for your further self-study, as well as a set-file with the input parameter settings.

Translated from Russian by MetaQuotes Software Corp.
Original article: https://www.mql5.com/ru/articles/647

Attached files |
Last comments | Go to discussion (2)
Jaret
Jaret | 29 Sep 2014 at 13:52

Thanks for article!

  Is it possible to use this expert on account with 0.1 lot limitation? Not ECN account. I tryed on ECN -- at least testing looks just fine, as in the article.I understand, this was published long time ago, but amazingly, no discussion on the forum whatsoever!

 Testing works perfect on ECN account!
parasite
parasite | 14 Feb 2015 at 19:54

The article is interesting... but:

i) the strategy implemented isn't the Elder's Triple Screen. Elder proposal was based on the old Tide, Wave and Ripples principle: Find a tide, wait for a Wave and go with the Ripples. It isn't what you have implemented.

ii) it uses 3 simple MA as each timeframe indicator and only compares value to value, which is unrealistic.

Step on New Rails: Custom Indicators in MQL5 Step on New Rails: Custom Indicators in MQL5

I will not list all of the new possibilities and features of the new terminal and language. They are numerous, and some novelties are worth the discussion in a separate article. Also there is no code here, written with object-oriented programming, it is a too serous topic to be simply mentioned in a context as additional advantages for developers. In this article we will consider the indicators, their structure, drawing, types and their programming details, as compared to MQL4. I hope that this article will be useful both for beginners and experienced developers, maybe some of them will find something new.

Here Comes the New MetaTrader 5 and MQL5 Here Comes the New MetaTrader 5 and MQL5

This is just a brief review of MetaTrader 5. I can't describe all the system's new features for such a short time period - the testing started on 2009.09.09. This is a symbolical date, and I am sure it will be a lucky number. A few days have passed since I got the beta version of the MetaTrader 5 terminal and MQL5. I haven't managed to try all its features, but I am already impressed.

False trigger protection for Trading Robot False trigger protection for Trading Robot

Profitability of trading systems is defined not only by logic and precision of analyzing the financial instrument dynamics, but also by the quality of the performance algorithm of this logic. False trigger is typical for low quality performance of the main logic of a trading robot. Ways of solving the specified problem are considered in this article.

Using text files for storing input parameters of Expert Advisors, indicators and scripts Using text files for storing input parameters of Expert Advisors, indicators and scripts

The article describes the application of text files for storing dynamic objects, arrays and other variables used as properties of Expert Advisors, indicators and scripts. The files serve as a convenient addition to the functionality of standard tools offered by MQL languages.