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:
- TradeSizeOptimized() – Calculate optimal lot size;
- CheckForOpen() – Check for open position conditions;
- CheckForClose() – Check for close position conditions;
- OnInit() – Expert initialization function;
- OnTick() – Expert tick function;
- 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.
//+------------------------------------------------------------------+ //| 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).
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
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