Moving Average – EA MetaTrader 5

The Moving Average EA is included in the standard pack of the MetaTrader 5 client terminal and is an example of the EA that trades using the Moving Average indicator.

The EA file Moving Average.mq5 is located in the folder “terminal_data_folder\MQL5\Experts\Examples\Moving Average\”. This EA is an example of use of technical indicators, trade history functions and trading classes of the Standard Library. In addition, the EA includes a money management system that is based on trade results.

Let’s consider the structure of the Expert Advisor and how it works.

1. EA Properties

//+------------------------------------------------------------------+
//|                                              Moving Averages.mq5 |
//|                   Copyright 2009-2013, MetaQuotes Software Corp. |
//|                                               |
//+------------------------------------------------------------------+
#property copyright "Copyright 2009-2013, MetaQuotes Software Corp."
#property link      ""
#property version   "1.00

First 5 rows contain a comment, the following three lines set the properties of the MQL5-program (copyright, link, version) using the preprocessor directives #property.

When you run the Expert Advisor they are displayed in the “Common” tab:


Figure 1. Common Parameters of the Moving Average EA

1.2. Include Files

Next, the #include directive tells the compiler to include the “Trade.mqh” file.

This file is part of the Standard Library, it contains the CTrade class for easy access to trading functions.

#include <Trade\Trade.mqh>

The name of the include file is shown in brackets “<>;”, so the path is set relative to the directory: “terminal_data_folder\Include\”.

1.3 Inputs

Then goes the type, name, default values and a comment. Their role is shown in fig. 2.

input double MaximumRisk        = 0.02;    // Maximum Risk in percentage
input double DecreaseFactor     = 3;       // Descrease factor
input int    MovingPeriod       = 12;      // Moving Average period
input int    MovingShift        = 6;       // Moving Average shift

The MaximumRisk and DecreaseFactor parameters will be used for money management, MovingPeriod anad MovingShift set the period and shift of the Moving Average technical indicator that will be used or checking trade conditions.

The text in the comment in the input parameter line, along with default values ​, are ​displayed in the “Options” tab instead of the name of the input parameter:


Fig. 2. Input Parameters of the Moving Average EA

1.4. Global Variables

Then the global variable ExtHandle is declared. It will be used for storing the handle of the Moving Average indicator.

//---
int   ExtHandle=0;

It is followed by 6 functions. The purpose of each of them is described in the comment before the function body:

  1. TradeSizeOptimized() – Calculate optimal lot size;
  2. CheckForOpen() – Check for open position conditions;
  3. CheckForClose() – Check for close position conditions;
  4. OnInit() – Expert initialization function;
  5. OnTick() – Expert tick function;
  6. OnDeinit() – Expert deinitialization function;

The last three functions are event handling functions; the first three service functions are called in their code.


2. Event Handling Functions

2.1. The OnInit() initialization function

The OnInit() function is called once during the first start of the Expert Advisor. Usually in the OnInit() event handler the EA is prepared for operation: input parameters are checked, indicators and parameters are initialized, etc. In the case of critical errors, when further work is meaningless, function is exited with a return code INIT_FAILED.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//---
   ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
   if(ExtHandle==INVALID_HANDLE)
     {
      printf("Error creating MA indicator");
      return(INIT_FAILED);
     }
//---
   return(INIT_SUCCEEDED);
  }

Since the EA trading is based on the indicator Moving Average, by calling iMA() the Moving Average indicator is created and its handle is saved in the global variable ExtHandle.

In case of an error, OnInit() is exited with a return code INIT_FAILED – it is a correct way to complete the EA/indicator operation in the case of an unsuccessful initialization.

2.2. The OnTick() function

The OnTick() function is called each time a new quote is received for the symbol of the chart, on which the EA runs.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//---
   if(PositionSelect(_Symbol))
      CheckForClose();
   else
      CheckForOpen();
//---
  }

The PositionSelect() function is used for defining if there is an open position for the current symbol.

If there are open positions, the CheckForClose() function is called, which analyzes the current state of the market and closes the open position, otherwise CheckForOpen() is called, which checks the conditions of market entry and opens a new position if such conditions occur.

2.3. The OnDeInit() deinitialization function

OnDeInit() is called when an EA is removed from the chart. If a program places graphical objects during operation, they can be removed from the chart.

Alternative:  Hidden Stop Loss and Take Profit - EA MetaTrader 4
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
  }
//+------------------------------------------------------------------+

In this case no actions are performed during Expert Advisor deinitialization.

3. Service Functions

3.1. Function TradeSizeOptimized()

This function calculates and returns the value of the optimal lot size for position opening with the specified risk level and trading results.

//+------------------------------------------------------------------+
//| Calculate optimal lot size                                       |
//+------------------------------------------------------------------+
double TradeSizeOptimized(void)
  {
   double price=0.0;
   double margin=0.0;
//--- Calculate the lot size
   if(!SymbolInfoDouble(_Symbol,SYMBOL_ASK,price))
      return(0.0);
   if(!OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,1.0,price,margin))
      return(0.0);
   if(margin<=0.0)
      return(0.0);

   double lot=NormalizeDouble(AccountInfoDouble(ACCOUNT_FREEMARGIN)*MaximumRisk/margin,2);
//--- calculate the length of the series of consecutive losing trades
   if(DecreaseFactor>0)
     {
      //--- request the entire trading history
      HistorySelect(0,TimeCurrent());
      //--
      int    orders=HistoryDealsTotal();  // the total number of deals
      int    losses=0;                    // the number of loss deals in the series

      for(int i=orders-1;i>=0;i--)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket==0)
           {
            Print("HistoryDealGetTicket failed, no trade history");
            break;
           }
         //--- checking the deal symbol
         if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=_Symbol)
            continue;
         //--- checking the profit
         double profit=HistoryDealGetDouble(ticket,DEAL_PROFIT);
         if(profit>0.0)
            break;
         if(profit<0.0)
            losses++;
        }
      //---
      if(losses>1)
         lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
     }
//--- normalizing and checking the allowed values of the trade volume
   double stepvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
   lot=stepvol*NormalizeDouble(lot/stepvol,0);

   double minvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   if(lot<minvol)
      lot=minvol;

   double maxvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
   if(lot>maxvol)
      lot=maxvol;
//--- return the value of the trade volume
   return(lot);
  }

The SymbolInfoDouble() function is used for checking the availability of prices for the current symbol, next the OrderCalcMargin() function is used for requesting the margin required to place an order (in this case a buy order). The initial lot size is determined from the value of the margin required for placing an order, the free margin of the account (AccountInfoDouble(ACCOUNT_FREEMARGIN)) and the maximum allowed value of risk specified in the input parameter MaximumRisk.

If the value of the input parameter DecreaseFactor is positive, deals in history are analyzed and the size of the lot is adjusted taking into account information about the maximal series of losing trades: the initial lot size is multiplied by the size (1-losses/DecreaseFactor).

Then the trade volume is “rounded” to the value that is multiple of the minimum allowable step of volume (stepvol) for the current symbol. Also the minimum (minvol) and the maximum possible values ​​(maxvol) of the trade volume are requested, and if the lot value exits the allowed limits, it is adjusted. As a result, the function returns the calculated value of the trading volume.

3.2. Function CheckForOpen()

CheckForOpen() is used for checking position opening conditions and opens it when trade conditions occur (in this case when the price crosses the moving average).

//+------------------------------------------------------------------+
//| Check for open position conditions                               |
//+------------------------------------------------------------------+
void CheckForOpen(void)
  {
   MqlRates rt[2];
//--- copy the price values
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("CopyRates of ",_Symbol," failed, no history");
      return;
     }
//--- Trade only on the first tick of the new bar
   if(rt[1].tick_volume>1)
      return;
//--- Get the current value of the Moving Average indicator 
   double   ma[1];
   if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }
//--- check the signals
   ENUM_ORDER_TYPE signal=WRONG_VALUE;

   if(rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=ORDER_TYPE_SELL;    // sell condition
   else
     {
      if(rt[0].open<ma[0] && rt[0].close>ma[0])
         signal=ORDER_TYPE_BUY;  // buy condition
     }
//--- additional checks
   if(signal!=WRONG_VALUE)
      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
         if(Bars(_Symbol,_Period)>100)
           {
            CTrade trade;
            trade.PositionOpen(_Symbol,signal,TradeSizeOptimized(),
                               SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK),
                               0,0);
           }
//---
  }

When trading using the moving, you need to check if price crosses the moving average. Using the CopyRates() function, two values of the current prices are copied in the array of structures rt[], rt[1] corresponds to the current bar, rt[0] – completed bar.

A new bar is started by checking the tick volume of the current bar if it is equal to 1, then a new bar has started. It should be noted that this method of detecting a new bar may fail in some cases (when quotes come in packs), so the fact of start of a new bar formation should be done by saving and comparing the time of the current quote (see IsNewBar).

Alternative:  NTK 07 - EA MetaTrader 5

The current value of the Moving Average indicator is requested using the CopyBuffer() function and is saved in the ma[] array that contains only one value. The program then checks if the price has crossed the moving average and makes additional checks (if trading using the EA is possible and the presence of bars in history). If successful, an appropriate position for the symbol is opened by calling the PositionOpen() method of the trade object (an instance of CTrade).

Position opening price is set using the SymbolInfoDouble() function that returns the Bid or Ask price depending on the value of the signal variable. The position volume is determined by calling TradeSizeOptimized() described above.

3.3. Function CheckForClose()

CheckForClose() checks conditions for position closing and closes it if conditions to close it occur.

//+------------------------------------------------------------------+
//| Check for close position conditions                              |
//+------------------------------------------------------------------+
void CheckForClose(void)
  {
   MqlRates rt[2];
//--- Copy price values
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("CopyRates of ",_Symbol," failed, no history");
      return;
     }
//--- Trade only on the first tick o the new bar
   if(rt[1].tick_volume>1)
      return;
//--- get the current value of the Moving Average indicator
   double   ma[1];
   if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }
//--- get the type of the position selected earlier using PositionSelect()
   bool signal=false;
   long type=PositionGetInteger(POSITION_TYPE);

   if(type==(long)POSITION_TYPE_BUY   && rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=true;
   if(type==(long)POSITION_TYPE_SELL  && rt[0].open<ma[0] && rt[0].close>ma[0])
      signal=true;
//--- additional checks
   if(signal)
      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
         if(Bars(_Symbol,_Period)>100)
           {
            CTrade trade;
            trade.PositionClose(_Symbol,3);
           }
//---
  }

The algorithm of the CheckForClose() function is similar to the algorithm of CheckForOpen(). Depending on the direction of the current open positions, conditions of its closure re checked (price crossing the MA downwards to buy or upwards to sell). An open position is closed by calling the PositionClose() method of the trade object (instance of CTrade).

4. Backtesting

The best values ​​of the parameters can be found using the Strategy Tester of the MetaTrader 5 terminal.

For example, when optimizing the MovingPeriod paramter in the interval 2012.01.01-2013.08.01, the best results are obtained with MovingPeriod=45:

Backtesting Results of the Moving Average Expert Advisor

Backtesting Results of the Moving Average Expert Advisor

Conclusions:

The Moving Average Expert Advisor included in the standard pack of the MetaTrader 5 terminal is an example of use of technical indicators, trading history functions and trade classes of the Standard Library. In addition, the EA includes a money management system that is based on trade results.


/ru/code/1921

📈 ROBOTFX MetaTrader Expert Advisors and Indicators to maximize profits and minimize the risks