Projects assist in creating profitable trading robots! Or at least, so it seems

MetaQuotes | 12 June, 2020

Creation of a trading robot always starts with a small file, which then grows in size as you implement more additional functions and custom objects. Most of MQL5 programmers utilize include files (MQH) to handle this problem. However, there is a better solution: start developing any trading application in a project. There are so many reasons to do so.


Project benefits

A project is a separate file with the MQPROJ extension, which stores program settings, compilation parameters and information about all files used in the project. A separate tab in the Navigator is provided for a convenient work with the project. All files, such as include, resource, header and other files are arranged into categories in this tab.

Project example

You see, the project is not just a set of files and folders arranged under a separate directory. It allows breaking down a complex program into elements arranged into a well-balanced structure. All the required information is at hand:

All connections between program parts are clearly visible in the project, so you can easily navigate between all used files. Furthermore, multiple programmers can collaborate on a project via the built-in MQL5 Storage.


Creating a Project

A new project is created using the MQL5 Wizard as an ordinary MQL5 program. Click "New project" and go through the required steps: set the program name, add input parameters and specify utilized event handlers. Upon the completion of the MQL5 Wizard, an MQPROJ file will be opened. This file allows managing project properties.

Project Properties>


Here you can specify the version, set a program description, add an icon and manage additional options:

  1. Maximum optimization — optimization of the EX5 executable file for maximum performance. If the option is disabled, compilation of the source code can be completed faster, but the resulting EX5 file can run much slower.
  2. Check floating point dividers — check whether real numbers of double and float types are not equal to zero in division operations. The operation speed can be higher if the option is disabled. However, you should be totally confident in your code.
  3. Use tester optimization cache — the tester is enabled by default, and thus the tester saves all results of completed passes to the optimization cache. The data can further be used in re-calculations. The cache can be disabled using the tester_no_cache property. Optionally, you can uncheck the relevant option in the project.

If the project file is closed, it can be reopened using the appropriate command of the Properties context menu. For a deeper understanding of the MQPROJ file contents, you can open it in the text format using the Open command. Thus, you can view the internal structure of projects.

{
  "platform"    :"mt5",
  "program_type":"expert",
  "copyright"   :"Copyright 2019, MetaQuotes Software Corp.",
  "link"        :"https:\/\/www.mql5.com",
  "version"     :"1.00",
  "description" :"The mean reversion strategy: the price breaks the channel border outwards and reverts back towards the average. The channel is represented by Bollinger Bands. The Expert Advisor enters the market using limit orders, which can only be opened in the trend direction.",
  "icon"        :"Mean Reversion.ico",
  "optimize"    :"1",
  "fpzerocheck" :"1",
  "tester_no_cache":"0",
  "tester_everytick_calculate":"0",

  "files":
  [
    {
      "path":".\\Mean Reversion.mq5",
      "compile":"true",
      "relative_to_project":"true"
    },
    {
      "path":"MQL5\\Include\\Trade\\Trade.mqh",
      "compile":"false",
      "relative_to_project":"false"
    },
....


    Trading rules

    Let us apply classical rule: enter the market when the price touches a Bollinger Band. This is one of the trading strategies, expecting the price to return to its mean value.

    Market entry based on Bollinger Bands

    Only limit orders will be used for market entries. The additional rule will be as follows: trade only in the trend direction. Thus, a Buy Limit will be placed at the lower channel border in an uptrend. During a downtrend, a Sell Limit will be placed at the upper border.

    There are many ways to determine trend direction. Let's use the simplest one: the relative position of two moving averages. If the Fast EMA is above the Slow EMA, an uptrend is defined. The downtrend is recognized when the lines are arranged the opposite way.

    Determining trend using two moving averages

    This simple rule has one disadvantage: there will always be either an uptrend or a downtrend. Therefore, such a system can produce a lot of false entries during flat. To avoid this, add another rule which allows placing pending orders when the distance between the Bollinger bands is large enough. The optimal way is to measure the channel width in relative values rather than points. The width can be determined based on the ATR indicator that measures volatility in points.

    Here k is a certain coefficient which needs to be found.

    Calculating the Bollinger channel width with the ATR


    Thus, during project creation we need to specify 8 input parameters for determining trading signals. The Expert Advisor will trade a fixed lot which should be specified in the InpLot parameter. Another non-optimizable parameter is InpMagicNumber. By using it, we can instruct the EA to handle only its own orders and positions.

    //--- Channel parameters
    input int             InpBBPeriod   =20;           // Bollinger indicator period
    input double          InpBBDeviation=2.0;          // Deviation of Bollinger bands from the MA
    //-- EMA periods for trend calculation 
    input int             InpFastEMA    =12;           // Fast EMA period
    input int             InpSlowEMA    =26;           // Slow EMA period
    //-- ATR parameters
    input int             InpATRPeriod  =14;           // ATR period
    input double          InpATRCoeff   =1.0;          // ATR coefficient for determining the flat
    //--- Capital managements
    input double          InpLot        =0.1;          // Trading volume in lots
    //--- timeframe parameters
    input ENUM_TIMEFRAMES InpBBTF       =PERIOD_M15;   // the timeframe for Bollinger values calculation
    input ENUM_TIMEFRAMES InpMATF       =PERIOD_M15;   // the timeframe for trend determining
    //--- Expert Advisor identifier for trading transactions
    input long            InpMagicNumber=245600;       // Magic Number
    

    The InpBBTF and InpMATF parameters have been added to avoid the manual selection of timeframes for determining the trend and the channel width. In this case, optimal timeframe values can be found during optimization. The EA can run on the M1 timeframe, while using Bollinger Bands data from M15 and Moving Averages from M30. No input parameter is used for ATR, otherwise there would be to many parameters for this example.


    Writing functions

    After creating a project, we can proceed to developing the Expert Advisor. The below code shows the main three functions describing the rules.

    The calculation of the Bollinger channel width is simple: copy the values from the indicator buffers.

    //+------------------------------------------------------------------+
    //| Gets the values of the channel borders                           |
    //+------------------------------------------------------------------+
    bool ChannelBoundsCalculate(double &up, double &low)
      {
    //--- get the Bollinger Bands indicator values 
       double bbup_buffer[];
       double bblow_buffer[];
       if(CopyBuffer(ExtBBHandle, 1, 1, 1, bbup_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtBBHandle,0,1,2,bbup_buffer), code=%d", __FILE__, GetLastError());
          return(false);
         }
    
       if((CopyBuffer(ExtBBHandle, 2, 1, 1, bblow_buffer)==-1))
         {
          PrintFormat("%s: Failed CopyBuffer(ExtBBHandle,0,1,2,bblow_buffer), code=%d", __FILE__, GetLastError());
          return(false);
         }
       low=bblow_buffer[0];
       up =bbup_buffer[0];
    //--- successful
       return(true);
      }
    

    Flat determining method is also simple enough. First, get the values of the channel borders, then calculate the width and compare to the ATR value multiplied by the InpATRCoeff coefficient.

    //+------------------------------------------------------------------+
    //|  Returns true if the channel is too narrow (indication of flat)  |
    //+------------------------------------------------------------------+
    int IsRange()
      {
    //--- get the ATR value on the last completed bar
       double atr_buffer[];
       if(CopyBuffer(ExtATRHandle, 0, 1, 1, atr_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtATRHandle,0,1,2,atr_buffer), code=%d", __FILE__, GetLastError());
          return(NO_VALUE);
         }
       double atr=atr_buffer[0];
    //--- get the channel borders
       if(!ChannelBoundsCalculate(ExtUpChannel, ExtLowChannel))
          return(NO_VALUE);
       ExtChannelRange=ExtUpChannel-ExtLowChannel;
    //--- if the channel width is less than ATR*coefficients, this is a flat
       if(ExtChannelRange<InpATRCoeff*atr)
          return(true);
    //--- flat not detected
       return(false);
      }
    

    As can be seen from the code, the NO_VALUE macro code is returned sometimes, which means that calculation of a certain parameter failed.

    #define NO_VALUE      INT_MAX                      // invalid value when calculating Signal or Trend

    Trend determining function has the longest code.

    //+------------------------------------------------------------------+
    //| Returns 1 for UpTrend or -1 for DownTrend (0 = no trend)         |
    //+------------------------------------------------------------------+
    int TrendCalculate()
      {
    //--- first, check the flat 
       int is_range=IsRange();
    //--- check the result
       if(is_range==NO_VALUE)
         {
          //--- if the check failed, early termination with "no value" response
          return(NO_VALUE);
         }
    //--- do not determine direction during flat
       if(is_range==true) // narrow range, return "flat"
          return(0);
    //--- get the ATR value on the last completed bar
       double atr_buffer[];
       if(CopyBuffer(ExtBBHandle, 0, 1, 1, atr_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtATRHandle,0,1,2,atr_buffer), code=%d", __FILE__, GetLastError());
          return(NO_VALUE);
         }
    //--- get the Fast EMA value on the last completed bar
       double fastma_buffer[];
       if(CopyBuffer(ExtFastMAHandle, 0, 1, 1, fastma_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtFastMAHandle,0,1,2,fastma_buffer), code=%d", __FILE__, GetLastError());
          return(NO_VALUE);
         }
    //--- get the Slow EMA value on the last completed bar
       double slowma_buffer[];
       if(CopyBuffer(ExtSlowMAHandle, 0, 1, 1, slowma_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtSlowMAHandle,0,1,2,slowma_buffer), code=%d", __FILE__, GetLastError());
          return(NO_VALUE);
         }
    //--- trend is not defined by default
       int trend=0;
    //--- if fast EMA is above the slow one
       if(fastma_buffer[0]>slowma_buffer[0])
          trend=1;   // uptrend
    //--- if fast EMA is below the slow one
       if(fastma_buffer[0]<slowma_buffer[0])
          trend=-1;  // downtrend
    //--- return trend direction
       return(trend);
      }
    

    The last function of the trading algorithm is the determining of a new bar. According to the logic, the trend is determined when a new bar appears.

    //+------------------------------------------------------------------+
    //| Checks the emergence of a new bar on the current timeframe,      |
    //| also calculates the trend and the signal                         |
    //+------------------------------------------------------------------+
    bool IsNewBar(int &trend)
      {
    //--- permanently stores the current bar opening time between function calls
       static datetime timeopen=0;
    //--- get the current bar open time 
       datetime time=iTime(NULL, InpMATF, 0);
    //--- if the time has not changed, the bar is not new, so exit with the 'false' value
       if(time==timeopen)
          return(false);
    //--- the bar is new, and this trend direction should be calculated
       trend=TrendCalculate();
    //--- if trend direction could not be obtained, exit and try again during the next call
       if(trend==NO_VALUE)
          return(false);
    //--- all checks performed successfully: the bar is new and trend direction has been obtained
       timeopen=time; //remember current time open time for further calls.
    //---
       return(true);
      }
    

    The above logic allows organizing the EA operation so that all trading operations are performed only once during the entire bar. Therefore, testing results do not depend on the tick generation mode.

    The entire trading algorithm is presented in the OnTick() handler:

    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick()
      {
       static bool order_sent    =false;    // failed to place a limit order on the current bar
       static bool order_deleted =false;    // failed to delete a limit order on the current bar
       static bool order_modified=false;    // failed to modify a limit order on the current bar
    //--- if input parameters are invalid, stop testing at the first tick
       if(!ExtInputsValidated)
          TesterStop();
    //--- check the emergence of a new bar and the trend direction
       if(IsNewBar(ExtTrend))
         {
          //--- reset values of static variables to their original state
          order_sent    =false;
          order_deleted =false;
          order_modified=false;
         }
    //--- create auxiliary variables to make check calls only once, on the current bar
       bool order_exist   =OrderExist();
       bool trend_detected=TrendDetected(ExtTrend);
    //--- if there is no trend or there is an open position, delete pending orders
       if(!trend_detected || PositionExist())
          if(!order_deleted)
            {
             order_deleted=DeleteLimitOrders();
             //--- if the orders have been successfully deleted, no other operations are needed at this bar
             if(order_deleted)
               {
                //--- prohibit placing and modification of orders
                order_sent    =true;
                order_modified=true;
                return;
               }
            }
    
    //--- there is trend
       if(trend_detected)
         {
          //--- place an order at the channel border if no order is found
          if(!order_exist && !order_sent)
            {
             order_sent=SendLimitOrder(ExtTrend);
             if(order_sent)
                order_modified=true;
            }
          //--- try to move the order to the channel border if it has not been moved on the current bar
          if(order_exist && !order_modified)
             order_modified=ModifyLimitOrder(ExtTrend);
         }
    //---
      }
    

    Other trading functions of the Expert Advisor are standard. The source codes are available in the MetaTrader 5 terminal standard package. They are located under MQL5\Experts\Examples.

    MeanReversion project location in the Navigator


    Optimizing parameters and adding set files

    Now that the EA is ready, let's find optimal parameters in the strategy tester. Did you know that the tester provides options for easy copying of values from the "Settings" and "Inputs" tabs into the clipboard using the Ctr+C combination? Thus, you can provide your settings to another person, for example to a Customer via the Freelance chat, without having to save them to a set file. The customer can copy the data to the clipboard and paste into the Settings tab of the tester using Ctr+V.

    Saving to a set file is also a convenient solution. Many sellers in the Market provide such files, so that product buyers can instantly load the appropriate sets of parameters and test or optimize the EA on a required instrument. Separate set files need to be created for each of the traded instruments. The number of such files on the computer can be quite large, if many Expert Advisors exist in the platform. With projects, your customers can instantly access the required files without the need to search for them on the disk each time the symbol is changed.

    Here is an example of how projects can help to add appropriate parameter sets straight in the EA's EX5 file. Select the symbol for which the optimization will be performed. For example, EURUSD. Set Start, Step and Stop for the parameters you want to optimize and launch the optimization process. Once it is over, double click on the best pass in the Optimizations tab, and the values of input parameters from this pass will be inserted in the Parameters tab, as well as a single test will be run. The found parameters can be saved to a set file. However, there is no need to provide it separately. Save the set of parameters under a clear name, such as EURUSD.set. It means that the parameters should be applied for this pair and not GBPJPY.

    Saving inputs to a set file

    Repeat this operation for each symbol which your EA can trade. Thus, you have a number of ready set files, say 9. Add these files to your project. Create the appropriate folder "Settings and files\Set", to separate them from source files. With Projects, you can maintain order and the correct file structure.

    Adding set files to a project


    Now, compile the project and open the strategy tester with the MeanReversion EA. A new item "Load from EA" will appear in the context menu, on the Inputs tab. All available set files can be accessed from this menu.

    Loading input parameters from the EA

    Thus, the compiled EX5 file of the Expert Advisor is a fully completed product, with ready sets of parameters. The strategy can be instantly tested without having to set borders and steps for each of the desired symbols. Users and buyers of your trading robots will definitely appreciate this convenience.


    Strategy running on real data

    In September 2019, the MeanReversion Expert Advisor was launched on a demo account. The purpose was to find out programming and trading errors in real time. The EA was launched in a portfolio mode on multiple symbols (this was the initial idea during the optimization). A built-in VPS was rented for the EA, based on which a private signal Many MeanReversion Optimized was created for monitoring purposes. 

    Trade Results for 9 months

    The first month after the launch the EA showed positive results. This was followed by consecutive 5 losing months. The virtual hosting was rented with the automated renewal feature, and thus the EA was running in a fully autonomous mode. It kept trading towards a complete deposit loss. Then, in March, something changed in the forex market and the EA suddenly generated a record profit. During the next 2 months, the results were contradictory. The same growth can probably never be repeated again.

    The analysis of deals and results by symbols shows that loss was made by three yen pairs and AUDUSD. The Expert Adviser did not show impressive results. Nevertheless, even with such simple trading logic, it has been running for 9 months in a portfolio mode, due to which losses on some symbols are covered by profit on other pairs.

    Distribution by symbols

    The Expert Advisor parameters have never been modified since its launch, no additional migrations were performed during this time. The EA was compiled 9 months ago and was launched on eight charts, on a built-in VPS. It is still running without any human interference. We cannot even remember why only eight out of nine set files were launched. Moreover, we cannot remember the parameters used. Nevertheless, the MeanReversion Expert Advisor project created for educational purposes is still running and is showing profit as of June 10, 2020.


    Switch to Projects and enjoy the benefits

    Projects allow developers to create programs of any complexity level, as well as to collaborate during development. When working together with like-minded people, you can develop applications faster, exchange useful ideas and skills, as well as improve the quality of the code.

    The trading rules utilized within this Expert Advisor are very simple, but it can be used as a template for creating many other trading robots. Replace functions determining the trend direction, the flat state, or the entry levels and methods (for example, you may use market orders instead of limit ones). Perhaps, better results might be obtained if trading only during flat periods. Also, the EA lacks trailing stop, Stop Loss and TakeProfit settings. There is much more you can do to improve the EA.

    Using the MeanReversion EA from the MetaTrader 5 standard package, you can study and evaluate the advantages of projects. Create your own project or copy this one into a new folder and start experimenting. Start using Projects and evaluate the convenience for yourself!