Русский 中文 Español Deutsch 日本語 Português
preview
An attempt at developing an EA constructor

An attempt at developing an EA constructor

MetaTrader 5Examples | 23 December 2021, 09:45
20 749 8
Vladimir Karputov
Vladimir Karputov

Contents


Introduction

From the very start, my objective was to use the Standard Library. My first task was to implement the simplest functionality: include the CTrade trading class and execute the Buy or Sell method. I have chosen the Standard Library because it yields a short and concise code. The short code below executed in the form of a script opens a BUY position with the volume of 1.0 lot:

//+------------------------------------------------------------------+
//|                                                     Open Buy.mq5 |
//|                         Copyright © 2018-2021, Vladimir Karputov |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2018-2021, Vladimir Karputov"
#property version   "1.001"
//---
#include <Trade\Trade.mqh>
CTrade         m_trade;                      // trading object
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   m_trade.Buy(1.0); // open Buy position, volume 1.0 lot
  }

Gradually, the requirements became more and more complicated and I came across trading errors almost every time when writing a new Expert Advisor (EA). So my desire to write correct code became more and more intense. Finally, a very important article The checks a trading robot must pass before publication in the Market came out. By the time this article was published, I was already aware of the need for reliable control functions over the execution of trading orders. From that moment on, I began to gradually acquire proven functions that can be easily added to the EA using copy->paste.

Since the work of EAs almost always involves using indicators, I started obtaining the functions for the correct indicator handle creation, as well as for receiving indicator data.

NB: MQL5 style implies that the indicator handle is created ONCE. As a rule, this is done in OnInit.

Since about version 2.XXX, I started maintaining two development branches the normal procedural code and the code in the form of a class (the main objective of the class is implementing multicurrency EAs).

During the course of my work, the constructor gradually received the most popular settings:

  • Stop Loss and Take Profit,
  • Trailing,
  • lot calculation as a risk percentage or as a constant/minimum lot,
  • control of the time interval, inside which the trading is carried out,
  • presence of only one position on the market,
  • reversing trading signals,
  • forced closing of positions in case an opposite signal appears...

Each input entailed creating code blocks and new functions.

For daily use, I decided to collect all the most popular functions and the full set of inputs into Trading engine 3.mq5 EA. In fact, this is a ready-made EA that frees us from a lot of routine work. All we have to do is add/remove functions or change the interaction between code blocks in each particular case.


1. EA functionality after the constructor

The EA created by the constructor immediately features multiple settings, which can be combined to create unique strategies. The version 4.XXX applies the following rules: 

  • the current symbol is used (a symbol of the chart the EA is launched on)
  • Take Profit, Stop Loss and Trailing are set in Points in the inputs. Points — current symbol point size in the quote currency, for example for 'EURSD' 1.00055-1.00045=10 points.

The 'points' can always be seen on the symbol chart by dragging the Crosshair tool:

points

Fig. 1. Points

Below are the inputs of the EA that is created by using the constructor:

  • Trading settings
    • Working timeframe. A working timeframe may differ from the chart timeframe the EA is launched on. This is a timeframe the indicator is created on (if another timeframe is not explicitly specified in the indicator). It is also used to track the moment of a new bar creation (in case a trading signal should be detected only when a new bar appears or if Trailing is to be launched only when a new bar appears).
    • Stop Loss (0 – disabled).
    • Take Profit  (0 – disabled).
    • Trailing on ...  check the trailing ability at each tick (bar #0 (at every tick)) or only when a new bar appears (bar #1 (on a new bar)).
    • Search signals on ...  search for a trading signal at each tick (bar #0 (at every tick)) or only when a new bar appears (bar #1 (on a new bar)).
    • Trailing Stop (min distance from price to Stop Loss)  minimal distance between the price and the position Stop Loss. Trailing is activated only if the position is profitable and the price moves away from the open price by Trailing Stop + Trailing Step. The trailing operation is displayed in the TrailingStop code images.
    • Trailing Step
  • Position size management (lot calculation).
    • Money management lot: Lot OR Risk  lot calculation system. The lot can be constant ( Money management= Constant lot, the lot size is set in The value for "Money management") or dynamic - risk % per deal ( Money management= Risk in percent for a deal, the risk % is set in The value for "Money management"). You can also set a constant lot equal to the minimum one ( Money management= Lots Min).
    • The value for "Money management"  
  • Trade mode
    • Trade mode: Allowed only BUY positions, Allowed only SELL positions  and Allowed BUY and SELL positions
  • DEMA custom indicator parameters. This is where you eventually set your indicator and its parameters
    • DEMA: averaging period
    • DEMA: horizontal shift
    • DEMA: type of price
  • Time control working time period. The time period, within which it is allowed to search for trading signals
    • Use time control  flag, enable/disable Time control
    • Start Hour  period start hours
    • Start Minute  period start minutes
    • End Hour  period end hours
    • End Minute  period end minutes
  • Pending Order Parameters  parameters related to pending orders
    • Pending: Expiration, in minutes (0 -> OFF)  pending order lifetime (0 disabled). 
    • Pending: Indent pending order indent from the current price (when the pending order price is not set explicitly)
    • Pending: Maximum spread (0 -> OFF)  maximum spread (0 disabled). If the current spread exceeds the specified one, the pending order is not set (the EA waits for the spread to decrease)
    • Pending: Only one pending  enabled/disabled flag. Only one pending order is allowed in the market
    • Pending: Reverse pending type  enabled/disabled flag. Pending order reverse
    • Pending: New pending -> delete previous ones  if a pending order is to be set, then all other pending orders are preliminarily deleted
  • Additional features
    • Positions: Only one  enabled/disabled flag. Only one position is allowed in the market
    • Positions: Reverse  enabled/disabled flag. Trading order reverse
    • Positions: Close opposite  enabled/disabled flag. If there is a trading order, all positions are preliminarily closed so that the order can be executed
    • Print log  enabled/disabled flag. Display extended information on operations and errors
    • Coefficient (if Freeze==0 Or StopsLevels==0)  ratio considering the Stop Level 
    • Deviation  specified slippage
    • Magic number  EA unique ID

2. Constructor general algorithm

The SPosition array is declared at the global program level (in the EA header). It consists of STRUCT_POSITION structures. During the launch, the array has a zero size. After handling a trading signal, the array also returns to zero.

From OnTick, the SearchTradingSignals function is called. If the signal is present (the market has no open positions), the function forms a trading order (a single STRUCT_POSITION structure is created for each trading order in the array). The presence of a trading order is checked in OnTick — the SPosition array size is checked: if it exceeds zero, there is a trading order, which is sent for execution to OpenBuy or OpenSell. Control over the trading request execution is done in OnTradeTransaction:

general algorithm (simple)

Fig. 2. General algorithm (simple)

It is always assumed that the EA works on the current symbol — the one whose chart the EA is placed on. Example: If the EA is placed on USDPLN, it works on USDPLN.

2.1. STRUCT_POSITION structure

This structure is the heart of the EA. It performs two roles at once: the structure features the fields the trading order is set in (setting is performed in SearchTradingSignals). The structure also features the fields for managing the execution of a trading order (control is done in OnTradeTransaction). 

//+------------------------------------------------------------------+
//| Structure Positions                                              |
//+------------------------------------------------------------------+
struct STRUCT_POSITION
  {
   ENUM_POSITION_TYPE pos_type;              // position type
   double            volume;                 // position volume (if "0.0" -> the lot is "Money management")
   double            lot_coefficient;        // lot coefficient
   bool              waiting_transaction;    // waiting transaction, "true" -> it's forbidden to trade, we expect a transaction
   ulong             waiting_order_ticket;   // waiting order ticket, ticket of the expected order
   bool              transaction_confirmed;  // transaction confirmed, "true" -> transaction confirmed
   //--- Constructor
                     STRUCT_POSITION()
     {
      pos_type                   = WRONG_VALUE;
      volume                     = 0.0;
      lot_coefficient            = 0.0;
      waiting_transaction        = false;
      waiting_order_ticket       = 0;
      transaction_confirmed      = false;
     }
  };

Some fields are responsible for the trading order itself, while others handle its execution. The structure contains the constructor  the STRUCT_POSITION() special function. It is called when creating the structure object and used to initialize the structure elements.

Trading order fields:

  • pos_type position type to be opened (can be POSITION_TYPE_BUY or POSITION_TYPE_SELL)
  • volume position volume. If the volume is 0.0, it will be taken from the 'Position size management (lot calculation)' group of inputs
  • lot_coefficient  if the ratio exceeds 0.0, the position volume is multiplied by the ratio

Fields of the trading order execution control

  • waiting_transaction  flag indicating that a trading order has ben executed successfully and it is necessary to wait for confirmation
  • waiting_order_ticket index of the order obtained when executing a trading order
  • transaction_confirmed  flag indicating that a trading order execution has been confirmed 

After enabling the flag in transaction_confirmed, a trading order is removed from the structure. Thus, if an EA operation involves no trading order, the structure has a zero size. Find out more about the structure fields and control in the ' We catch the transaction (simplified code)' section.

Why exactly do I use such an algorithm?

At first, it may seem that it is enough to check the Buy method for true or false and, in case of true, assume that the trading order has been executed. In many cases, this approach actually works. But sometimes returning true does not guarantee the result. This is mentioned in the documentation about Buy and Sell methods:

Note

A successful completion of the method does not always mean a successful execution of a trading operation. It is necessary to check the result of trade request (trade server return code) using ResultRetcode() and value returned by ResultDeal().

The presence of the entry in the trading history may serve as the final and accurate confirmation of the trade operation execution. Therefore, the following algorithm has been selected: if the method is executed successfully, check ResultDeal (deal ticket), check ResultRetcode (request execution result code) and save ResultOrder (order ticket). The order ticket is found in OnTradeTransaction.


3. Adding the standard indicator — handling the Indicators Code.mq5 file

For more convenience, the ready-made code blocks (declaring the variables for storing handles, inputs, creating handles) are gathered in the Indicators Code.mq5 EA. The indicator inputs and variables for storing handles are located in the EA "header", handle creation is set in OnInit. Keep in mind that the names of variables for storing handles are formed as follows: 'handle_' + 'indicator', for example 'handle_iStdDev'. The entire handling of Indicators Code.mq5 boils down to copy-paste operations. 

NB: MQL5 style implies that the indicator handle is created ONCE. As a rule, this is done in OnInit.

3.1. Example of adding iRVI (Relative Vigor Index, RVI) indicator

Create the Add indicator.mq5 EA. In MetaEditor, run MQL Wizard, for example by clicking  New and select Expert Advisor (template)

Expert Advisor (template)

Fig. 3. MQL Wizard -> Expert Advisor (template)

I highly recommend adding at least one input at the next step 

Expert Advisor (template)

Fig. 4. Expert Advisor (template) -> Add parameter

This allows to automatically add the strings for the input block to the code:

//--- input parameters
input int      Input1=9;

MQL Wizard has created an empty EA. Now let's add iRVI (Relative Vigor Index, RVI) indicator to it. In Indicators Code.mq5, search for handle_iRVI (via ctrl + F). The search yields the variable the handle is stored in:

iRVI

Fig. 5. handle RVI

Copy the newly found string and insert it into 'Add indicator' EA header:

//--- input parameters
input int      Input1=9;
//---
int      handle_iRVI;                           // variable for storing the handle of the iRVI indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()

Resume the search and find the handle creation block:

iRVI

Fig. 6. handle iRVI

Copy the newly found strings and insert them into 'Add indicator' EA in OnInit:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create handle of the indicator iRVI
   handle_iRVI=iRVI(m_symbol.Name(),Inp_RVI_period,Inp_RVI_ma_period);
//--- if the handle is not created
   if(handle_iRVI==INVALID_HANDLE)
     {
      //--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iRVI indicator for the symbol %s/%s, error code %d",
                  m_symbol.Name(),
                  EnumToString(Inp_RVI_period),
                  GetLastError());
      //--- the indicator is stopped early
      m_init_error=true;
      return(INIT_SUCCEEDED);
     }
//---
   return(INIT_SUCCEEDED);
  }

Now let's add the indicator inputs. In Indicators Code.mq5, middle-click on, say, Inp_RVI_period to get to the input block right away:

iRVI

Fig. 7. handle iRVI

Copy the strings and insert them to the inputs:

#property version   "1.00"
//--- input parameters
input group             "RVI"
input ENUM_TIMEFRAMES      Inp_RVI_period                = PERIOD_D1;      // RVI: timeframe
input int                  Inp_RVI_ma_period             = 15;             // RVI: averaging period
//---
int      handle_iRVI;                           // variable for storing the handle of the iRVI indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

Compiling yields the following errors: m_symbol and m_init_error. This is an expected behavior since these variables are present in the code obtained after the constructor operation, while 'Add indicator' EA has been created solely to demonstrate handling the Indicators Code.mq5 file.


4. Adding a custom indicator

Let's add MA on DeMarker custom indicator. First, it is a custom indicator, second, it uses group. Similar to the previous section, create the 'Add custom indicator' EA. After that, copy the inputs from the custom indicator and insert them to the EA:

#property version   "1.00"
//--- input parameters
input group             "DeMarker"
input int                  Inp_DeM_ma_period    = 14;             // DeM: averaging period
input double               Inp_DeM_LevelUP      = 0.7;            // DeM: Level UP
input double               Inp_DeM_LevelDOWN    = 0.3;            // DeM: Level DOWN
input group             "MA"
input int                  Inp_MA_ma_period     = 6;              // MA: averaging period
input ENUM_MA_METHOD       Inp_MA_ma_method     = MODE_EMA;       // MA: smoothing type
//---

In Indicators Code.mq5, find the handle_iCustom variable for storing the custom indicator handle and insert it to the EA:

//+------------------------------------------------------------------+
//|                                         Add custom indicator.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "1.00"
//--- input parameters
input group             "DeMarker"
input int                  Inp_DeM_ma_period    = 14;             // DeM: averaging period
input double               Inp_DeM_LevelUP      = 0.7;            // DeM: Level UP
input double               Inp_DeM_LevelDOWN    = 0.3;            // DeM: Level DOWN
input group             "MA"
input int                  Inp_MA_ma_period     = 6;              // MA: averaging period
input ENUM_MA_METHOD       Inp_MA_ma_method     = MODE_EMA;       // MA: smoothing type
//---
int      handle_iCustom;                        // variable for storing the handle of the iCustom indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()

Find the custom indicator handle creation block in OnInit() of Indicators Code.mq5  and insert it to the EA:  

int      handle_iCustom;                        // variable for storing the handle of the iCustom indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create handle of the indicator iCustom
   handle_iCustom=iCustom(m_symbol.Name(),Inp_DEMA_period,"Examples\\DEMA",
                          Inp_DEMA_ma_period,
                          Inp_DEMA_ma_shift,
                          Inp_DEMA_applied_price);
//--- if the handle is not created
   if(handle_iCustom==INVALID_HANDLE)
     {
      //--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iCustom indicator for the symbol %s/%s, error code %d",
                  m_symbol.Name(),
                  EnumToString(Inp_DEMA_period),
                  GetLastError());
      //--- the indicator is stopped early
      m_init_error=true;
      return(INIT_SUCCEEDED);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |

Here we have some work to do. We need to set the timeframe (InpWorkingPeriod), the path to the indicator (we assume that it is stored in the root of the Indicators folder) and the inputs:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create handle of the indicator iCustom
   handle_iCustom=iCustom(m_symbol.Name(),InpWorkingPeriod,"MA on DeMarker",
                          "DeMarker",
                          Inp_DeM_ma_period,
                          Inp_DeM_LevelUP,
                          Inp_DeM_LevelDOWN,
                          "MA",
                          Inp_MA_ma_period,
                          Inp_MA_ma_method);
//--- if the handle is not created
   if(handle_iCustom==INVALID_HANDLE)
     {
      //--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iCustom indicator for the symbol %s/%s, error code %d",
                  m_symbol.Name(),
                  EnumToString(InpWorkingPeriod),
                  GetLastError());
      //--- the indicator is stopped early
      m_init_error=true;
      return(INIT_SUCCEEDED);
     }
//---
   return(INIT_SUCCEEDED);
  }

5. We catch the transaction (simplified code)

NB: This is a simplified version of the EA. Many functions have shorter code compared to a full-fledged constructor.

If there are no positions in the market opened by this EA, set the trading request to open BUY position. Confirmation of opening the position is printed from OnTradeTransaction and OnTick. Searching and writing a trade order in the SearchTradingSignals function:

//+------------------------------------------------------------------+
//| Search trading signals                                           |
//+------------------------------------------------------------------+
bool SearchTradingSignals(void)
  {
   if(IsPositionExists())
      return(true);
//---
   int size_need_position=ArraySize(SPosition);
   if(size_need_position>0)
      return(true);
   ArrayResize(SPosition,size_need_position+1);
   SPosition[size_need_position].pos_type=POSITION_TYPE_BUY;
   if(InpPrintLog)
      Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY");
   return(true);
  }

If the market has no positions opened by the EA and the SPosition array size is zero, increase the array size by one. Thus, we create a single STRUCT_POSITION structure object. This, in turn, call the STRUCT_POSITION() constructor. After calling the constructor, the structure elements are initialized (for example, volume  the position volume is set to 0.0, thus, it will be taken from the 'Position size management (lot calculation)' group of inputs). Now it only remains to set the trading order type into the structure. In this case, it can be interpreted as: "Open BUY position".

After setting the trading order, the SPosition array consists of a single structure and the structure elements have the following values:

Element Value Note 
 pos_type  POSITION_TYPE_BUY  set in SearchTradingSignals
 volume  0.0  initialized in the structure constructor
 lot_coefficient  0.0  initialized in the structure constructor
 waiting_transaction  false  initialized in the structure constructor
 waiting_order_ticket  0  initialized in the structure constructor
 transaction_confirmed  false  initialized in the structure constructor

5.1. We get to OnTick on a new tick

OnTick general principle:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int size_need_position=ArraySize(SPosition);
   if(size_need_position>0)
     {
      for(int i=size_need_position-1; i>=0; i--)
        {
         if(SPosition[i].waiting_transaction)
           {
            if(!SPosition[i].transaction_confirmed)
              {
               if(InpPrintLog)
                  Print(__FILE__," ",__FUNCTION__,", OK: ","transaction_confirmed: ",SPosition[i].transaction_confirmed);
               return;
              }
            else
               if(SPosition[i].transaction_confirmed)
                 {
                  ArrayRemove(SPosition,i,1);
                  return;
                 }
           }
         if(SPosition[i].pos_type==POSITION_TYPE_BUY)
           {
            SPosition[i].waiting_transaction=true;
            OpenPosition(i);
            return;
           }
         if(SPosition[i].pos_type==POSITION_TYPE_SELL)
           {
            SPosition[i].waiting_transaction=true;
            OpenPosition(i);
            return;
           }
        }
     }
//--- search for trading signals only at the time of the birth of new bar
   if(!RefreshRates())
      return;
//--- search for trading signals
   if(!SearchTradingSignals())
      return;
//---
  }

At the start of OnTick, check the SPosition array size (this is the STRUCT_POSITION structure array). If the array size exceeds zerostart traversing to zero with two scenarios available: 

  • if the waiting_transaction structure flag is true (trading order has been prepared, need to wait for confirmation), check the transaction_confirmed flag 
    • if false, the transaction has not been confirmed yet (for instance, this may happen if the trading order has been sent, a new tick has arrived, while OnTradeTransaction still has no confirmation). Print the appropriate message and exit by return  wait for a new tick in the hope that the information is updated on the new tick
    • if true, the transaction has been confirmed. Remove the structure from the array and exit by return
  • if the waiting_transaction structure flag is false (the trading order has just been specified and has not been executed yet), enable the waiting_transaction flag and redirect the order to OpenPosition. The code block can be simplified down to

             SPosition[i].waiting_transaction=true;
             OpenPosition(i);
             return;
    but I have left it this way for easier understanding of the full form of the EA constructor:
             if(SPosition[i].pos_type==POSITION_TYPE_BUY)
               {
                if(InpCloseOpposite)
                  {
                   if(count_sells>0)
                     {
                      ClosePositions(POSITION_TYPE_SELL);
                      return;
                     }
                  }
                if(InpOnlyOne)
                  {
                   if(count_buys+count_sells==0)
                     {
                      SPosition[i].waiting_transaction=true;
                      OpenPosition(i);
                      return;
                     }
                   else
                     {
                      ArrayRemove(SPosition,i,1);
                      return;
                     }
                  }
                SPosition[i].waiting_transaction=true;
                OpenPosition(i);
                return;
               }
             if(SPosition[i].pos_type==POSITION_TYPE_SELL)
               {
                if(InpCloseOpposite)
                  {
                   if(count_buys>0)
                     {
                      ClosePositions(POSITION_TYPE_BUY);
                      return;
                     }
                  }
                if(InpOnlyOne)
                  {
                   if(count_buys+count_sells==0)
                     {
                      SPosition[i].waiting_transaction=true;
                      OpenPosition(i);
                      return;
                     }
                   else
                     {
                      ArrayRemove(SPosition,i,1);
                      return;
                     }
                  }
                SPosition[i].waiting_transaction=true;
                OpenPosition(i);
                return;
               }

Let's continue the tracking. Let me remind you the code block:

         if(SPosition[i].pos_type==POSITION_TYPE_BUY)
           {
            SPosition[i].waiting_transaction=true;
            OpenPosition(i);
            return;
           }

The trading order has just been set into the structure (in the SearchTradingSignals function) and the waiting_transaction flag is set to falseThus, set the waiting_transaction flag to true and pass the i parameter to the OpenPosition function. This is the structure serial number in the OpenPosition array. Since the trading order type is POSITION_TYPE_BUY, pass the structure serial number to the OpenBuy function.

5.2. OpenBuy function

The function objective is to pass the preliminary tests, send a trading request for opening BUY and track the result right away.

The first check - SYMBOL_VOLUME_LIMIT

 SYMBOL_VOLUME_LIMIT The maximum allowed total volume of an open position and pending orders in one direction (either buy or sell) for this symbol. For example, if the limit is 5 lots, you may have an open position of 5 lots and place a Sell Limit order of 5 lots as well. But in this case you cannot place a pending Buy Limit order (since the total volume in one direction will exceed the limit) or place a Sell Limit order above 5 lots.

If the check is not passed, remove the structure element from the SPosition array.

The second check - margin

Get the free margin remaining after a trading operation (FreeMarginCheck) and the margin necessary for a trading operation (MarginCheck). After that, be sure to leave the sum at least equal to FreeMarginCheck for safety:

   if(free_margin_check>margin_check)

If the check is not passed, print the free_margin_check variable and remove the structure element from the SPosition array.

The third check - bool result of the Buy operation

If the Buy method returns false, print the error and set waiting_transaction to false (the most common reason is error 10004, requote). Thus, an attempt to re-open the BUY position will be made on a new ticket. If the result is true (below is the code block, in which the Buy method has returned true)

         if(m_trade.ResultDeal()==0)
           {
            if(m_trade.ResultRetcode()==10009) // trade order went to the exchange
              {
               SPosition[index].waiting_transaction=true;
               SPosition[index].waiting_order_ticket=m_trade.ResultOrder();
              }
            else
              {
               SPosition[index].waiting_transaction=false;
               if(InpPrintLog)
                  Print(__FILE__," ",__FUNCTION__,", ERROR: ","#1 Buy -> false. Result Retcode: ",m_trade.ResultRetcode(),
                        ", description of result: ",m_trade.ResultRetcodeDescription());
              }
            if(InpPrintLog)
               PrintResultTrade(m_trade,m_symbol);
           }
         else
           {
            if(m_trade.ResultRetcode()==10009)
              {
               SPosition[index].waiting_transaction=true;
               SPosition[index].waiting_order_ticket=m_trade.ResultOrder();
              }
            else
              {
               SPosition[index].waiting_transaction=false;
               if(InpPrintLog)
                  Print(__FILE__," ",__FUNCTION__,", OK: ","#2 Buy -> true. Result Retcode: ",m_trade.ResultRetcode(),
                        ", description of result: ",m_trade.ResultRetcodeDescription());
              }
            if(InpPrintLog)
               PrintResultTrade(m_trade,m_symbol);
           }

then check ResultDeal (deal ticket).

If the deal ticket is zerocheck ResultRetcode (request execution reslut code). The resulting return code is 10009 (for example, the trading order was sent to an external trading system, like exchange, therefore the deal ticket is zero). Set waiting_transaction to true, while waiting_order_ticket is to contain ResultOrder (order ticket). Otherwise (the return code is not 10009), waiting_transaction is set to false and the error message is printed.

If the deal ticket is not zero (for example, the execution is performed on the same trade server), perform the similar return code checks and write the values to waiting_transaction and waiting_order_ticket.

5.3. OnTradeTransaction

If the trading order has been successfully sent, wait for the confirmation that the deal has been performed and recorded in the trading history. In OnTradeTransaction, we work with the trans variable (the structure of the MqlTradeTransaction type). The structure has only two strings of interest — deal and type:

struct MqlTradeTransaction
  {
   ulong                         deal;             // Deal ticket
   ulong                         order;            // Order ticket
   string                        symbol;           // Symbol name
   ENUM_TRADE_TRANSACTION_TYPE   type;             // Trading transaction type
   ENUM_ORDER_TYPE               order_type;       // Order type
   ENUM_ORDER_STATE              order_state;      // Order state
   ENUM_DEAL_TYPE                deal_type;        // Deal type
   ENUM_ORDER_TYPE_TIME          time_type;        // Order type by lifetime
   datetime                      time_expiration;  // Order expiration time
   double                        price;            // Price 
   double                        price_trigger;    // Stop Limit order trigger price
   double                        price_sl;         // Stop Loss level
   double                        price_tp;         // Take Profit level
   double                        volume;           // Volume in lots
   ulong                         position;         // Position ticket
   ulong                         position_by;      // Opposite position ticket
  };


As soon as OnTradeTransaction gets TRADE_TRANSACTION_DEAL_ADD (adding a deal to history), perform the check: attempt to select a deal in history via HistoryDealSelect. If failed to select, print the error. If the deal exists in trading history, iterate over the SPosition array in a loop. In the loop, only have a look at the structures having the waiting_transaction set to true, while waiting_order_ticket is equal to the ticket of the order of a selected deal. If a match is found, set transaction_confirmed to true. This means the trading order has been executed and confirmed.

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
//--- get transaction type as enumeration value
   ENUM_TRADE_TRANSACTION_TYPE type=trans.type;
//--- if transaction is result of addition of the transaction in history
   if(type==TRADE_TRANSACTION_DEAL_ADD)
     {
      ResetLastError();
      if(HistoryDealSelect(trans.deal))
         m_deal.Ticket(trans.deal);
      else
        {
         Print(__FILE__," ",__FUNCTION__,", ERROR: ","HistoryDealSelect(",trans.deal,") error: ",GetLastError());
         return;
        }
      if(m_deal.Symbol()==m_symbol.Name() && m_deal.Magic()==InpMagic)
        {
         if(m_deal.DealType()==DEAL_TYPE_BUY || m_deal.DealType()==DEAL_TYPE_SELL)
           {
            int size_need_position=ArraySize(SPosition);
            if(size_need_position>0)
              {
               for(int i=0; i<size_need_position; i++)
                 {
                  if(SPosition[i].waiting_transaction)
                     if(SPosition[i].waiting_order_ticket==m_deal.Order())
                       {
                        Print(__FUNCTION__," Transaction confirmed");
                        SPosition[i].transaction_confirmed=true;
                        break;
                       }
                 }
              }
           }
        }
     }
  }

Get to OnTick on a new tick. The structure having true in transaction_confirmed is removed from the SPosition array. Thus, a trading order has been issued and tracked till it appears in the trading history.


6. Creating an EA (position opening signals) using the constructor

Before developing any EA, we should think about its trading strategy. Let's consider a simple strategy based on iDEMA (Double Exponential Moving Average, DEMA) indicator. This is a default strategy set in the constructor. An attempt to find an open signal is made only when a new bar appears, while the trading signal itself is an indicator going up or down sequentially:

DEMA Strategy

Fig. 8. DEMA Strategy

Keep in mind that any strategy can be greatly modified by adjusting the parameters. For example, we may disable Trailing while leaving Take Profit and Stop Loss intact. Or vice versa: disable Take Profit and Stop Loss, while Trailing remains. We may also restrict trading directions by allowing only BUY or SELL. It is also possible to enable Time control and limit trading at night or, on the contrary, set up trading at night time only. A trading system can also be drastically changed by adjusting parameters from the Additional features group.

In general, the backbone of a trading strategy is built in the SearchTradingSignals function, while all other parameters are meant for "probing" the market in search of optimal approaches.

So let's create a new file to be an EA blank (perform the steps indicated in Figures 3 and 4). At step 4, specify a unique name for your EA. Let it be iDEMA Full EA.mq5. As a result, we get the following workpiece:

//+------------------------------------------------------------------+
//|                                                iDEMA Full EA.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "1.00"
//--- input parameters
input int      Input1=9;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Now copy the entire code from Trading engine 3.mq5 and insert it instead of the strings. Now it is time to edit the "EA header". The resulting "header" looks as follows:

//+------------------------------------------------------------------+
//|                                                iDEMA Full EA.mq5 |
//+------------------------------------------------------------------+
//|                                             Trading engine 3.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "4.003"
#property description "barabashkakvn Trading engine 4.003"
#property description "Take Profit, Stop Loss and Trailing - in Points (1.00055-1.00045=10 points)"
/*
   barabashkakvn Trading engine 4.003
*/
#include <Trade\PositionInfo.mqh>

Make the "header" look as follows:

//+------------------------------------------------------------------+
//|                                                iDEMA Full EA.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "1.001"
#property description "iDEMA EA"
#property description "Take Profit, Stop Loss and Trailing - in Points (1.00055-1.00045=10 points)"
/*
   barabashkakvn Trading engine 4.003
*/
#include <Trade\PositionInfo.mqh>

Now if we compile this, there will be no errors. The obtained EA is even able to trade.

6.1. SearchTradingSignals function

This is the main function responsible for checking the availability of trading orders. Let's consider this function block by block.

No more than one position per bar:

   if(iTime(m_symbol.Name(),InpWorkingPeriod,0)==m_last_deal_in) // on one bar - only one deal
      return(true);

Checking the trading time range:

   if(!TimeControlHourMinute())
      return(true);

Receiving data from the indicator. The indicator data are passed to the dema array. With the help of ArraySetAsSeries, the reverse indexing order is set for it (the [0] array element is to correspond to the rightmost bar on the chart). The data is received via the iGetArray custom function:

   double dema[];
   ArraySetAsSeries(dema,true);
   int start_pos=0,count=6;
   if(!iGetArray(handle_iCustom,0,start_pos,count,dema))
     {
      return(false);
     }
   int size_need_position=ArraySize(SPosition);
   if(size_need_position>0)
      return(true);

BUY position open signal. If necessary (the InpReverse variable stores the Positions: Reverse input value), a trading signal can be reversed. If there is a limitation concerning trading direction (the InpTradeMode variable stores Trade mode: input value), it will be considered:

//--- BUY Signal
   if(dema[m_bar_current]>dema[m_bar_current+1] && dema[m_bar_current+1]>dema[m_bar_current+3])
     {
      if(!InpReverse)
        {
         if(InpTradeMode!=sell)
           {
            ArrayResize(SPosition,size_need_position+1);
            SPosition[size_need_position].pos_type=POSITION_TYPE_BUY;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY");
            return(true);
           }
        }
      else
        {
         if(InpTradeMode!=buy)
           {
            ArrayResize(SPosition,size_need_position+1);
            SPosition[size_need_position].pos_type=POSITION_TYPE_SELL;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL");
            return(true);
           }
        }
     }

The code block for a SELL signal is similar. 


7. Creating an EA (pending order signals) using the constructor

The EA's name will be iDEMA Full EA Pending.mq5. Open iDEMA Full EA.mq5 and save it under the new name.

A trading strategy is always developed first followed by the code. Let's slightly change the strategy used in the section 6. Create the EA (position open signals) using the constructor. BUY position open signal is replaced with Buy Stop pending order one, while SELL position open signal is replaced with Sell Stop pending order. The following parameters are used for the pending orders:

  • Pending: Expiration, in minutes (0 -> OFF)  -> 600
  • Pending: Indent  pending order indent from the current price (when the pending order price is not set explicitly) -> 50
  • Pending: Maximum spread (0 -> OFF). If the current spread exceeds the specified one, the pending order is not set (the EA waits for the spread to decrease) -> 12
  • Pending: Only one pending  enabled/disabled flag. Only one pending order is allowed in the market -> true
  • Pending: Reverse pending type  enabled/disabled flag. Pending order reverse -> false
  • Pending: New pending -> delete previous ones  if a pending order is to be set, then all other pending orders are preliminarily deleted -> true

The SearchTradingSignals function looks as follows:

//+------------------------------------------------------------------+
//| Search trading signals                                           |
//+------------------------------------------------------------------+
bool SearchTradingSignals(void)
  {
   if(iTime(m_symbol.Name(),InpWorkingPeriod,0)==m_last_deal_in) // on one bar - only one deal
      return(true);
   if(!TimeControlHourMinute())
      return(true);
   double dema[];
   ArraySetAsSeries(dema,true);
   int start_pos=0,count=6;
   if(!iGetArray(handle_iCustom,0,start_pos,count,dema))
     {
      return(false);
     }
   int size_need_pending=ArraySize(SPending);
   if(size_need_pending>0)
      return(true);
//---
   if(InpPendingOnlyOne)
      if(IsPendingOrdersExists())
         return(true);
   if(InpPendingClosePrevious)
      m_need_delete_all=true;
//--- BUY Signal
   if(dema[m_bar_current]>dema[m_bar_current+1] && dema[m_bar_current+1]>dema[m_bar_current+3])
     {
      if(!InpReverse)
        {
         if(InpTradeMode!=sell)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP");
            return(true);
           }
        }
      else
        {
         if(InpTradeMode!=buy)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL STOP");
            return(true);
           }
        }
     }
//--- SELL Signal
   if(dema[m_bar_current]<dema[m_bar_current+1] && dema[m_bar_current+1]<dema[m_bar_current+3])
     {
      if(!InpReverse)
        {
         if(InpTradeMode!=buy)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL STOP");
            return(true);
           }
        }
      else
        {
         if(InpTradeMode!=sell)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP");
            return(true);
           }
        }
     }
//---
   /*if(InpPendingOnlyOne)
      if(IsPendingOrdersExists())
         return(true);
   if(InpPendingClosePrevious)
      m_need_delete_all=true;
   int size_need_pending=ArraySize(SPending);
   ArrayResize(SPending,size_need_pending+1);
   if(!InpPendingReverse)
      SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP;
   else
      SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP;
   SPending[size_need_pending].indent=m_pending_indent;
   if(InpPendingExpiration>0)
      SPending[size_need_pending].expiration=(long)(InpPendingExpiration*60);
   if(InpPrintLog)
      Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP");*/
//---
   return(true);
  }

Note that we not set the pending order price to the SPending structure. This means the current price plus indent are to be used.

We received a trading signal, which triggered (a pending order was placed) only when the spread became lower than the specified one:

iDEMA Full EA Pending

Fig. 9. iDEMA Full EA Pending


Files attached to the article:

Name File type Description
Indicators Code EA Variables for storing handles, indicator inputs, indicator creation blocks
Add indicator.mq5 EA Example of working with Add indicator.mq5 adding a standard indicator
Add custom indicator.mq5 EA
Example of adding a custom indicator
Trading engine 4.mq5 EA Constructor
iDEMA Full EA.mq5
EA
EA created with the help of the constructor position open signals
iDEMA Full EA Pending.mq5
EA
EA created with the help of the constructor pending order placement signals

Conclusion

I hope that this set of trading functions will help you create more reliable Expert Advisors ready for the constantly changing market trading conditions. Never hesitate to experiment with the parameters. You are able to drastically change your strategy by enabling some parameters, while disabling others.


Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/9717

Last comments | Go to discussion (8)
Ali irwan
Ali irwan | 5 Jan 2022 at 17:48
thank for shareing,good job
Mike Pascal Plavonil
Mike Pascal Plavonil | 31 Jan 2022 at 21:14

Dear Vladimir Karputov, 

A big thank for  your work and your involvement in the community.

There is an error in the code.
In the PendingOrder function, if a custom volume is set for a pending order the ea won't open it because the free_margin_check calculation is bad because check_order_type = -1

   ENUM_ORDER_TYPE check_order_type=-1;
   double check_price=0.0;
   if(SPending[index].volume>0.0)
      check_lot=SPending[index].volume;
   else
     {
      //--- check volume before OrderSend to avoid "not enough money" error (CTrade)
      switch(SPending[index].pending_type)
        {
         case  ORDER_TYPE_BUY:
            check_order_type=ORDER_TYPE_BUY;
            break;
         case ORDER_TYPE_SELL:
            check_order_type=ORDER_TYPE_SELL;
            break;
         case ORDER_TYPE_BUY_LIMIT:
            check_order_type=ORDER_TYPE_BUY;
            break;
         case ORDER_TYPE_SELL_LIMIT:
            check_order_type=ORDER_TYPE_SELL;
            break;
         case ORDER_TYPE_BUY_STOP:
            check_order_type=ORDER_TYPE_BUY;
            break;
         case ORDER_TYPE_SELL_STOP:
            check_order_type=ORDER_TYPE_SELL;
            break;
         default:
            return(false);
            break;
        }
      //--- 
      //...
     }
//...
//--- check volume before OrderSend to avoid "not enough money" error (CTrade)
   double free_margin_check=m_account.FreeMarginCheck(m_symbol.Name(),
                            check_order_type,check_lot,check_price);


The switch should be outside .

Anyway, thank for sharing this tool


Vladimir Karputov
Vladimir Karputov | 1 Feb 2022 at 04:41
Mike Pascal Plavonil # :

Dear Vladimir Karputov, 

A big thank for   your work and your involvement in the community.

There is an error in the code.
In the  PendingOrder function, if a custom volume is set for a pending order the ea won't open it because the free_margin_check calculation is bad because  check_order_type = -1


The switch should be outside .

Anyway, thank for sharing this tool


Thank you, changes have been made to the Trading engine 4 code (version "4.012").

Moon Domain - Unipessoal Lda
Dmitri Diall | 8 Jun 2022 at 22:09

Hello @Vladimir Karputov -- this is a really good repository of best practices to increase reliability for handling market and pending orders in an EA... It serves as a very good source of inspiration, and I will certainly steal some great ideas you implemented here!

Having said that, I find the codebase a bit too "monolithic" for my personal taste, as I usually prefer a more modular approaches for my own use. Regardless, your EA framework will certainly be very useful to quickly create prototypes for testing new strategy ideas without investing too much time with multiple files, etc... That's really awesome for increasing productivity.

Since about version 2.XXX, I started maintaining two development branches  the normal procedural code and the code in the form of a class (the main objective of the class is implementing multicurrency EAs).

You mention an class-based version of your framework, but I couldn't find it in the attached code -- am I missing something or where can I find that? When testing a new idea, I prefer to operate across multiple symbols simultaneously, so I'd be very grateful if you can point me in the right direction to download those files.

AK377MC
AK377MC | 2 Mar 2024 at 20:59

Hello Vladimir,


first of all thank You for this great work, which helped me a lot to learn and to understand "a little bit" coding MQL.

Now I've tested a few things and have discovered that when I exclude the buy or sale conditions  lines :1312, 1313 & 1338 the EA  still continue to buy!!

But only buys.

I found this out while testing different formulas, and the EA never did what I wanted :-(

Do you have any idea why this happens ?


Thank You in advance and for sharing this EA



MQL5 Cookbook – Economic Calendar MQL5 Cookbook – Economic Calendar
The article highlights the programming features of the Economic Calendar and considers creating a class for a simplified access to the calendar properties and receiving event values. Developing an indicator using CFTC non-commercial net positions serves as a practical example.
Graphics in DoEasy library (Part 88): Graphical object collection — two-dimensional dynamic array for storing dynamically changing object properties Graphics in DoEasy library (Part 88): Graphical object collection — two-dimensional dynamic array for storing dynamically changing object properties
In this article, I will create a dynamic multidimensional array class with the ability to change the amount of data in any dimension. Based on the created class, I will create a two-dimensional dynamic array to store some dynamically changed properties of graphical objects.
Manual charting and trading toolkit (Part III). Optimization and new tools Manual charting and trading toolkit (Part III). Optimization and new tools
In this article, we will further develop the idea of drawing graphical objects on charts using keyboard shortcuts. New tools have been added to the library, including a straight line plotted through arbitrary vertices, and a set of rectangles that enable the evaluation of the reversal time and level. Also, the article shows the possibility to optimize code for improved performance. The implementation example has been rewritten, allowing the use of Shortcuts alongside other trading programs. Required code knowledge level: slightly higher than a beginner.
Using AutoIt With MQL5 Using AutoIt With MQL5
Short description. In this article we will explore scripting the MetraTrader 5 terminal by integrating MQL5 with AutoIt. In it we will cover how to automate various tasks by manipulating the terminals' user interface and also present a class that uses the AutoItX library.